3

Teaching myself Java by coding a MIDI handling program. One thing the program needs to be able to do is convert back and forth between MIDI note numbers and their corresponding compact string representations. I looked at using an enum setup, but due to naming constraints you can't do something like

c-1, c#-1, ...  g9;

because of the sharps and negatives (yes, I'm following the convention that makes you end up with a negative octave :P).

It seemed clunky to have to make a conversion between what's allowed and what I want.

CNEG1("c-1"),
CSNEG1("c#-1"),
DNEG1("d-1"),
...
G9("g9");

So I came up with the static imports scheme below, and it works fine. However, I want to learn more about how to use enums, and I have a hunch that they might actually be somehow better suited to the task - if only I understood the ins and outs better. So that's my question: can anyone come up with an elegant way to provide the same functionality using an enum scheme? Moreover, would there be a strong argument for doing so?

public abstract class MethodsAndConstants {

    public static final String TONICS[] = {"c","c#","d","d#","e","f","f#","g","g#","a","a#","b"};
    static final NoteMap notemap = new NoteMap();

    static class NoteMap{
        static String map[] = new String[128];

        NoteMap() {
            for (int i = 0; i < 128; i++){
                int octave = i/12 - 1;
                String tonic = MethodsAndConstants.TONICS[i%12];
                map[i] = tonic + octave;
            }
        }   
    }

    public static int convert_midi_note(String name){
        return indexOf(NoteMap.map, name);
    }

    public static String convert_midi_note(int note_num){   
        return NoteMap.map[note_num];
    }

    public static int indexOf(String[] a, String item){
        return java.util.Arrays.asList(a).indexOf(item);
    }       
}

EDIT ------------------------------------------

After heavy consideration I think in this particular situation enums might be overkill after all. I might end up just using this code down here, same sort of static import approach but no longer even requiring anything like the NoteMap business up above.

note_num -> name conversions are really straightforward, and the name -> note_num stuff is just good ol' string-parsing fun.

public abstract class MethodsAndConstants {
    public static final String[] TONICS = {"c","c#","d","d#","e","f","f#","g","g#","a","a#","b"};

    static String convert(int i) {
        String tonic = MethodsAndConstants.TONICS[i%12];
        int octave = (i / 12) - 1;
        return tonic + octave;
    }

    static int convert(String s) {
        int tonic = java.util.Arrays.asList(MethodsAndConstants.TONICS).indexOf(s.substring(0,1));
        if (s.contains("#")) tonic += 1;
        int octave = Integer.parseInt(s.substring(s.length()-1));
        if (s.contains("-")) octave -= 2;   // case octave = -1
        int note_num = ((octave + 1) * 12) + tonic;
        return note_num;
    }
}
TOZ
  • 49
  • 1
  • 7

2 Answers2

3

You could use an enum to represent the pitch, but I might try encapsulating a Pitch in a class

public class Pitch {

    private final int octave;
    private final Note note;

    public enum Note {
        C("C",4), CSHARP("C#/Db",5), DFLAT("C#/Db",5), //and so on

        private final String thePitch;
        private final int midiAdjust;

        private Note(final String thePitch, final int midiAdjust) {
            this.thePitch = thePitch;
            this.midiAdjust = midiAdjust;
        }

        String getThePitch() {
            return thePitch;
        }

        int getMidiAdjust() {
            return midiAdjust;
        }
    }

    public Pitch(Note note, int octave) {
        this.note = note;
        this.octave = octave;
    }

    public int getMidiNumber(){
        return 12*octave + note.getMidiAdjust();
    }

}

This would account for the fact that the note (C, C#, D, D#, E...) is going to be one of a repeating set, but you could have all kinds of octaves, in this case handled by an int. It would greatly reduce the size of your enum.

EDIT: I added a few lines in here as an idea. You could pass a second parameter into the constructor of the enum to allow you to return a MIDI number representing the pitch. In this one I assumed that the lowest number represented by MIDI is an A, but I may be wrong on that. Also the 12*octave is intended to add a whole octave of pitches for each increment. You will probably have to adjust this slightly, as I see you are using that one weird notation.

gobernador
  • 5,659
  • 3
  • 32
  • 51
1

Something like that:

public enum Note {
    CNEG1("c-1"), CSNEG1("c#-1"), DNEG1("d-1");

    private final String tonicOctave;

    private Note(final String tonicOctave) {
        this.tonicOctave = tonicOctave;
    }

    public String getTonicOctave() {
        return this.tonicOctave;
    }

    public static Note fromTonicOctave(final String val) {
        for (final Note note: Note.values()) {
            if (note.getTonicOctave().equals(val)) {
                return note;
            }
        }
        return null;
    }
}

Note, you can have as many parameters as you need in your enum, so if you need to separate tonic and octave, you can.

  • That looks interesting. Can only barely begin to parse it at this point, but I'll put in some study. EDIT *Can* only begin ... – TOZ May 06 '12 at 06:07
  • I think I know what you mean about having the ability to separate tonic and ocatve, btw. Along the lines of CNEG1("c",-1), etc. right? Could be useful somehow. – TOZ May 06 '12 at 06:09
  • @TOZ, yes, `CNEG1("c",-1)` is possible –  May 06 '12 at 06:20
  • OK, cool. After some struggling I've found that using your enum code above I can convert back and forth between number and name like so: int midi_note_number = Note.fromTonicOctave(my_note_name).ordinal() String my_note_name = Note.values()[midi_note_number].getTonicOctave() Nice. Now to look more closely @gobernador 's idea. – TOZ May 06 '12 at 08:03