I'm creating audio and effects in my game on the fly with very basic sound synthesis. Basically, I have some methods that can play a sound given a frequency & amplitude & duration.
For short phrases and melodies, I'd like to come up with a basic notation so that I can easily rewrite or add new melodies into the code (in the end maybe I could read from files, but that is probably overkill).
I'm not sure how to implement this however.
I've started by creating an enum EqualTemperamentTuning which contains all 88 basic piano notes with a MIDI # field and a frequency field. This at least means I can deal in note names and not frequencies.
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;
}
}
Then I started creating more objects, first a Note that holds an EqualTemperamentTuning, an amplitude and a length:
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;
}
}
Finally to define the melody I want to play, I created a NotePhrase class:
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;
}
}
Then in my audio generating class I have a loop (with counter) that generates samples from a wave generator. Every time I need a new sample to play, it sets the frequency of the wave according to the NotePhrase.getFrequency(int counter) method above. This should (I haven't actually tested yet!) just play the NotePhrase melody according to the frequency and amplitude (to be added).
Problem is, it doesn't seem very elegant and more specifically it's very difficult to "write" a melody in any kind of legible way. I have to code a whole bunch of new Note objects and then construct a NotePhrase object with an array of them... It's not obvious to me how I would hard code a bunch of these melodies and then easily switch between them later.
Really I would like to create an enum of Melodies or something like that, where I can easily hard code a human legible configuration for each different melody, and then when I want to use them, just pass the enum type to the audio player...
The best I've got is:
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;
}
}
Which I would then load into a NotePhrase object at runtime. This isn't much good because I have to create a new constructor for melodies with different amount of notes. If I do it the other way round and construct the enum with an array of notes, then I'm just "writing" the melody elsewhere and in two parts which seems even more confusing...
So I'm stuck as to how to structure this properly? ie what classes to create and what info they should hold... I want to get this "right" because, I might like to expand the notation in the future to include effects (echo etc) and I've already found with my very little experience, that the right classes, structure and relationships (even names) can make my programs very easy or difficult to understand.
Apologies for the essay, this might not be a very well phrased (ahem) question, but as a java & OOP beginner, any hints would be very welcome!
EDIT**
Thanks for the answers & suggestions, very helpful. Thinking about the answers given in this case triggered me to rethink my general audio implementation, which is pretty shaky right now. Not sure who I should mark as correct though, as I'm really just going to take all recommendations on board and try and go from there.
Musical notation can be thought of as a series of events, be they notes or key and tempo changes.
It might be more fruitful for you to think about a timeline with events and build from there. The MIDI protocol might give you some more ideas.