6

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

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

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

我首先创建了一个枚举 EqualTemperamentTuning,其中包含所有 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、振幅和长度的 Note:

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 counter) 方法设置波形的频率。这应该(我还没有实际测试过!)只是根据频率和幅度(待添加)播放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 初学者,任何提示都会非常受欢迎!

编辑* *

感谢您的回答和建议,非常有帮助。考虑在这种情况下给出的答案让我重新考虑了我的一般音频实现,这现在非常不稳定。不确定我应该将谁标记为正确,因为我真的只是将所有建议都纳入其中,然后尝试从那里开始。

4

4 回答 4

4

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

在 Melody 中,我会将 Note 与 Measure 和 Beat 联系起来,并给 Melody 类添加一个 addNote(Note, int measure, int beatFraction),与删除 Note 相同。

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

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

于 2012-05-23T16:16:07.657 回答
3

想到的是实现一个简单的内部DSL,该 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)

Martin Fowler 写的一本关于 DSL 的好书

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

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

于 2012-05-23T16:50:01.610 回答
3

也许您可以将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;
    }

    ...
}

您可以将Hovercraft Full Of Eels中的提示合并到此模式中。

注意:方法NotePhraseBuilder.E_5()和其他类似的方法,以与其名称相同的方式调用NoteBuilder-constructor 。EqualTemperamentTuning写88个方法好像很浪费,这样可以做得更漂亮吗?

于 2012-05-23T16:50:42.010 回答
3

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

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

于 2012-05-23T21:08:47.853 回答