How to create a simple but well structured musical notation class (musical score) in java?

2.7k views Asked by At

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.

4

There are 4 answers

2
blank On

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.

2
Hovercraft Full Of Eels On

Don't use an enum for a Melody since the Melody doesn't represent a true constant, but rather a collection of Note data. I'd recommend not using arrays either but rather an ArrayList<Note> which is more flexible.

In Melody, I'd tie the Note to a Measure and a Beat, and would give the Melody class an addNote(Note, int measure, int beatFraction), same for remove Note.

Consider making your 12 core notes as enums, and then use them plus an octave int to create a Note object.

... there are many ways to play with this exercise. The "key" is to keep on experimenting.

0
Boris Treukhov On

What comes in mind is implementing a simple internal DSL which will be as close as possible to the notation you use to write down the music notes. In java it could look like

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

there is a good book on DSLs, written by Martin Fowler

This is just an example of how it could look like, I do not insist on one form or another - the basic idea is that your DSL must be readable both by the programmers and domain-experts(musicians). And the way the data is represented in your sequencer should not affect the way you write down the melodies i.e. to make it harder.

P.S. If you are doing something serious, you should try to use an external DSL a.k.a. a midi file or something like that and a sequencing library of some kind, and to store music in the separate resource files of your application.

3
Kasper van den Berg On

Perhaps you can use the Fluent interface pattern in combination with the Builder pattern. You could then write:

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

NotePhraseBuilder contains methods like beat(), measure(), E_5(), but no methods like 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;
    }

    ...
}

You can incorporate the tips from Hovercraft Full Of Eels within this pattern.

Note: The methods NotePhraseBuilder.E_5() and others like it, call the NoteBuilder-constructor with an EqualTemperamentTuning equal to their name. Writing 88 methods seems like a waste, can this be done more beautiful?