【问题标题】:How to create a simple but well structured musical notation class (musical score) in java?如何在java中创建一个简单但结构良好的乐谱类(乐谱)?
【发布时间】:2012-05-30 05:32:31
【问题描述】:

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

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

但我不确定如何实现。

我首先创建了一个枚举 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 初学者,任何提示都会非常受欢迎!

编辑**

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

【问题讨论】:

  • "这不太好,因为我必须为不同数量的音符创建一个新的构造函数。"你可以在这里使用可变参数
  • 谢谢,可变参数正是我所需要的。
  • 创建 Voice 和 Measure 类可能会有所帮助,因此您的 Melody 类不会尝试做不止一件事。

标签: java oop music-notation


【解决方案1】:

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

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

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

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

【讨论】:

  • 感谢您的提示。我认为我将 Melody 设为枚举,因为我认为 Melody 就像一首乐谱。它是固定的,只能在自身内部创建。我会根据您的建议修改代码。
  • @kirman:不客气,但不要将代码与数据混淆。我认为 Melody 是一个数据集合。枚举更适合用于原子构建块或状态常量。
【解决方案2】:

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

有一个good book on DSLs, written by Martin Fowler

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

附:如果您正在做一些严肃的事情,您应该尝试使用外部 DSL(即 midi 文件或类似文件)和某种排序库,并将音乐存储在应用程序的单独资源文件中。

【讨论】:

    【解决方案3】:

    也许您可以将Fluent interface pattern 与Builder pattern 结合使用。然后你可以写:

    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个方法好像很浪费,这样能不能做得更漂亮?

    【讨论】:

    • 类似于Boris Treukhov的做法。
    • 两个答案几乎同时发布 :-)
    • 这两个答案都比我的好。 1+ 到两者。
    【解决方案4】:

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

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

    【讨论】:

    • 是的,时间线很有意义。我想我还没有完全了解我正在制作的音频实现中我真正需要的东西,这使得设计变得困难! MIDI 看起来很复杂,但我想我可能更好地了解它,因为它与我正在做的事情非常相关......
    • MIDI 本质上是相当简单的——对于一个旋律,你有一个音符开和音符关事件,它有一个音高值和一个时间轴上的位置。播放旋律是在正确的时间将事件发送到声源的过程。您可能还想查看 mod 跟踪器。
    猜你喜欢
    • 2011-07-21
    • 2011-05-02
    • 1970-01-01
    • 1970-01-01
    • 2022-08-11
    • 1970-01-01
    • 2011-06-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多