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

I create audio and effects in my game on the fly with a very simple sound synthesis. Basically, I have some methods that can reproduce sound with frequency, amplitude and duration.

For short phrases and melodies, I would like to get a basic notation so that I can easily rewrite or add new melodies to the code (in the end, maybe I could read from files, but this is probably too much).

I am not sure how to implement this.

I started by creating an enum EqualTemperamentTuning that contains all 88 basic piano notes with a MIDI # field and a frequency field. This, at least, means that I can deal with note names, 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 contains EqualTemperamentTuning, amplitude and 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 the 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 sound generation class, I have a loop (with a counter) that generates samples from the wave generator. Each time I need a new sample for playback, it sets the wave frequency according to the NotePhrase.getFrequency (int counter) method above. This should (I have not tested it yet!) Just play the NotePhrase melody according to the frequency and amplitude (to add).

The problem is that it does not seem very elegant and, more specifically, it is very difficult to "write" a melody in any understandable way. I have to encode a whole bunch of new Note objects and then build a NotePhrase object with an array of them ... It didn't seem to me that I would program a bunch of these melodies, and then easily switch between them later.

Indeed, I would like to create an enumeration of melodies or something like that, where I can easily hard-code the clear configuration for each melody, and then when I want to use them, just pass the enumeration type to the audio player ...

The best I have:

 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; } } 

What would I then load into the NotePhrase object at runtime. This is not very good, because I have to create a new constructor for melodies with a different number of notes. If I do it the other way around and build an enumeration with an array of notes, then I just β€œwrite” the melody in another place and in two parts, which seem even more confusing ...

So, I'm stuck on how to structure this correctly? those. what classes to create and what information they should keep ... I want to get this "right", because maybe I would like to expand the notation in the future to include effects (echo, etc.), and I already found with it is unlikely for me that the right classes, structure, and relationships (even names) can make my programs very easy or hard to understand.

Apologies for the essay, this may not be a very well-worded question (s), but as a Java and OOP beginner, any tips would be greatly appreciated!

EDIT **

Thanks for the answers and suggestions, very helpful. Thinking about the answers given in this case made me rethink my overall audio implementation, which is pretty shaky right now. Not sure who I should mark as correct, though, since I'm really going to take all the recommendations on board and try from there.

+6
source share
4 answers

Do not use the enumeration for a melody, since a Melody is not a true constant, but rather a Note dataset. I would recommend not using arrays, but rather an ArrayList<Note> , which is more flexible.

In Melody, I will bind Note to Measure and Beat and give the Melody class addNote (note, int measure, int beatFraction), the same for deleting Note.

Think about making your 12 main notes as enumerations, 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 experimenting.

+4
source

What comes to mind is the implementation of a simple internal DSL , which will be as close as possible to the notation that you use to record musical notes. In java, it may 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 about DSL written by Martin Fowler

This is just an example of how this might look, I do not insist on one form or another - the main idea is that your DSL should be readable by both programmers and domain experts (musicians). And the way the data is presented in your sequencer should not affect the way you record melodies, i.e. Make it harder.

PS If you are doing something serious, you should try to use an external DSL file, aka midi file or something like that, as well as a sequence library and save music in separate resource files of your application.

+3
source

Perhaps you can use the Fluent interface pattern in conjunction with the Figure Builder . Then you can write:

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

NotePhraseBuilder contains methods such as beat() , measure() , E_5() , but there are no methods such as 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; } ... } 

In this template, you can include tips from Hovercraft Full Of Eels .

Note. Methods NotePhraseBuilder.E_5() and others like it, call NoteBuilder -constructor with EqualTemperamentTuning equal to their name. Writing 88 methods seems like a waste, can this be prettier?

+3
source

A musical notation can be seen as a series of events, whether it's notes or changes in key and tempo.

It may be more useful for you to think of a timeline with events and build from there. The MIDI protocol can give you a few more ideas.

+3
source

Source: https://habr.com/ru/post/916473/


All Articles