我正在用非常基本的声音合成在游戏中即时创建音频和效果。基本上,我有一些方法可以在给定频率、振幅和持续时间的情况下播放声音。
对于简短的短语和旋律,我想想出一个基本的符号,以便我可以轻松地重写或添加新的旋律到代码中(最后也许我可以从文件中读取,但这可能是矫枉过正(。
但是,我不确定如何实现这一点。
我首先创建了一个枚举 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 协议可能会给你更多的想法。