如何在java中创建简单但结构良好的乐谱类(乐谱)



我正在用非常基本的声音合成在游戏中即时创建音频和效果。基本上,我有一些方法可以在给定频率、振幅和持续时间的情况下播放声音。

对于简短的短语和旋律,我想想出一个基本的符号,以便我可以轻松地重写或添加新的旋律到代码中(最后也许我可以从文件中读取,但这可能是矫枉过正(。

但是,我不确定如何实现这一点。

我首先创建了一个枚举 EqualTemperamentTune,其中包含所有 88 个基本钢琴音符,带有 MIDI # 字段和频率字段。这至少意味着我可以处理音符名称而不是频率。

public enum EqualTemperamentTuning {
    A_0         (1, 27.5),
    A_SHARP_0   (2, 29.1352),
        ...
    C_8         (88, 4186.01);
    private int num;
    private double frequency;
    EqualTemperamentTuning(int num, double frequency){
        this.num = num;
        this.frequency = frequency;
    }
    public double getFrequency(){
        return frequency;
    }
    public double getNum(){
        return num;
    }
}

然后我开始创建更多的对象,首先是一个包含 EqualTemperamentTuning、振幅和长度的音符:

public class Note {
    /** Note frequency */
    private double frequency;
    /** Note Amplitude */
    private double amplitude;
    /** Note length */
    private int length;     
    public Note(EqualTemperamentTuning tuning, double amplitude, int length){
        this.frequency = tuning.getFrequency();
        this.amplitude = amplitude;
        this.length = length;   
    }
    public double getFrequency(){
        return frequency;
    }
    public double getAmplitude(){
        return amplitude;
    }
    public int getLength(){
        return length;  
    }
}

最后,为了定义我想演奏的旋律,我创建了一个NotePhrase类:

public class NotePhrase {
    /** The arrayList of notes*/
    private Note[] notes;
    public NotePhrase(Note[] notes){
        this.notes = notes;
    }
    public double getFrequency(int counter){
        // for each note in the array
        for (int i = 0; i< notes.length; i++){
            // for each unit of length per note
            for (int j=0; j<notes[i].getLength(); j++){
                counter--;
                // return the frequency at this point
                if (counter <= 0) return notes[i].getFrequency();
            }
        }
        return -1;  
    }
}

然后在我的音频生成类中,我有一个循环(带计数器(,可以从波发生器生成样本。每次我需要一个新的样本来播放时,它都会根据上面的NotePhrase.getFrequency(int计数器(方法设置波的频率。这应该(我还没有实际测试过!(只是根据频率和振幅(待添加(播放NotePhrase旋律。

问题是,它看起来不是很优雅,更具体地说,很难以任何清晰的方式"写"旋律。我必须编写一大堆新的 Note 对象,然后用它们的数组构造一个 NotePhrase 对象......对我来说,如何硬编码一堆这些旋律,然后稍后轻松地在它们之间切换并不明显。

真的,我想

创建一个旋律枚举或类似的东西,在那里我可以轻松地为每个不同的旋律硬编码人类清晰的配置,然后当我想使用它们时,只需将枚举类型传递给音频播放器......

我得到的最好的是:

private static enum Melody {
    NOKIA_RINGTONE ( new Note(EqualTemperamentTuning.E_5, 0.5, 1), new Note(EqualTemperamentTuning.D_5, 0.5, 1))
    ;
    private Note[] notes = new Note[2];
    Melody (Note note1, Note note2){
        this.notes[0] = note1;
        this.notes[1] = note2;
    }
}

然后我会在运行时将其加载到 NotePhrase 对象中。这不是很好,因为我必须为具有不同音符数量的旋律创建一个新的构造函数。如果我反过来做,用一系列音符构建枚举,那么我只是在其他地方"写"旋律,分为两部分,这似乎更令人困惑......

所以我被困在如何正确构建它? 即要创建哪些类以及它们应该包含哪些信息......我想得到这个"正确",因为,我可能想在未来扩展符号以包括效果(回声等(,而且我已经发现,以我很少的经验,正确的类,结构和关系(甚至名称(可以使我的程序非常容易或难以理解。

抱歉,这篇文章

可能不是一个措辞很好(咳咳(的问题,但作为java和OOP初学者,任何提示都将非常受欢迎!

编辑**

感谢您的回答和建议,非常有帮助。想到在这种情况下给出的答案,促使我重新思考我的一般音频实现,现在它非常不稳定。不确定我应该将谁标记为正确,因为我真的只是要接受所有建议并尝试从那里开始。

不要对旋律使用枚举,因为旋律不表示真正的常量,而是音符数据的集合。我建议不要使用数组,而是使用更灵活的ArrayList<Note>

在Melody中,我会将音符与Measure和节拍联系起来,并给Melody类一个addNote(Note,int measure,int beatFraction(,与删除Note相同。

考虑将 12 个核心音符制作为枚举,然后使用它们加上八度整数来创建 Note 对象。

。有很多方法可以玩这个练习。"关键"是不断尝试。

想到的是实现一个简单的内部DSL,它将尽可能接近你用来写下音符的符号。在Java中,它可能看起来像

MelodyTrack  track1 = MelodyTrack().create().setTiming(TimeSignature.C)
.eighth(Notes.E5)
.bar().half(Notes.B5).quarter(Notes.A5).quarter(Notes.B5).bar().half(Notes.C6)

有一本关于DSL的好书,作者是马丁·福勒

这只是它的外观的一个例子,我不坚持一种或另一种形式 - 基本思想是你的DSL必须被程序员和领域专家(音乐家(读取。数据在音序器中的表示方式不应影响您写下旋律的方式,即使其更难。

附言如果你正在做一些严肃的事情,你应该尝试使用外部DSL,也就是midi文件或类似的东西,以及某种排序库,并将音乐存储在应用程序的单独资源文件中。

也许您可以将 Fluent 接口模式与 Builder 模式结合使用。 然后你可以写:

new NotePhraseBuilder()
     .beat(120)
     .measure(fourQuarters)
     .E_5()
     .quarter()
     .D_5()
     .half()
     ...;

NotePhraseBuilder 包含像 beat()measure()E_5() 这样的方法,但没有像 quarter()half() 这样的方法:

class NotePhraseBuilder {
    ...
    public NoteBuilder E_5() {
        return new NoteBuilder(this, E_5);
    }
    ...
}
class NoteBuilder {
    private final NotePhraseBuilder parent;
    private final EqualTemperamentTuning tuning;
    public NoteBuilder(NotePhraseBuilder parent_, EqualTemperamentTuning tuning_) {
        this.parent = parent_;
        this.tuning = tuning_;
    }
    public NotePhraseBuilder quarter() {
        parent.add(new Note(tuning_, parent.getAmplitude(), parent.getTempo() / 4));
        return parent;
    }
    ...
}

您可以将气垫船充满鳗鱼的提示合并到此模式中。

注意:NotePhraseBuilder.E_5() 和其他类似的方法调用 NoteBuilder -constructor,其EqualTemperamentTuning等于其名称。 写88种方法似乎很浪费,这能做得更漂亮吗?

乐谱可以被认为是一系列事件,无论是音符还是调和速度变化。

对于您来说,考虑包含事件的时间表并从那里构建可能会更有成效。MIDI 协议可能会给你更多的想法。

相关内容

  • 没有找到相关文章

最新更新