melanchall / drywetmidi

.NET library to read, write, process MIDI files and to work with MIDI devices
https://melanchall.github.io/drywetmidi
MIT License
545 stars 75 forks source link

Incorrect Parse #77

Closed sanbox-irl closed 4 years ago

sanbox-irl commented 4 years ago

Hello!

I am sure this is a user error, but I'm getting a problem with incorrect notes being parsed. I'm just using the library to take in a Midi file and parse the notes and timestamps. here's how i'm doing that (this is rough code, which has been given quite a lot of boolean flags):

        foreach (var chunk in midi_file.GetTrackChunks()) {
            foreach (var note in chunk.GetNotes()) {
                var time_on = TimeConverter.ConvertTo<MetricTimeSpan>(
                    note.GetTimedNoteOnEvent().Time, tempo_map
                );
                var time_off = TimeConverter.ConvertTo<MetricTimeSpan>(
                    note.GetTimedNoteOffEvent().Time, tempo_map
                );

                var musical_theory_note = note.GetMusicTheoryNote();

                Track data_track_type = Track.Down;
                RhythmBarType rhythm_bar_type = RhythmBarType.Normal;
                bool abort_this_operation = false;

                switch (musical_theory_note.NoteName) {
                    case NoteName.C:
                        switch (musical_theory_note.Octave) {
                            case 2:
                                data_track_type = Track.Down;
                                rhythm_bar_type = RhythmBarType.Hold;
                                break;
                            case 4:
                                data_track_type = Track.Up;
                                rhythm_bar_type = RhythmBarType.Mash;
                                break;

                            default:
                                abort_this_operation = true;
                                break;
                        }
                        break;
                    case NoteName.A:
                        switch (musical_theory_note.Octave) {
                            case 2:
                                data_track_type = Track.Up;
                                rhythm_bar_type = RhythmBarType.Normal;
                                break;
                            default:
                                abort_this_operation = true;
                                break;
                        }
                        break;
                    case NoteName.E:
                        switch (musical_theory_note.Octave) {
                            case 2:
                                data_track_type = Track.Down;
                                rhythm_bar_type = RhythmBarType.Normal;
                                break;
                            default:
                                abort_this_operation = true;
                                break;
                        }
                        break;
                    case NoteName.B:
                        switch (musical_theory_note.Octave) {
                            case 2:
                                data_track_type = Track.Up;
                                rhythm_bar_type = RhythmBarType.Hold;
                                break;
                            case 4:
                                data_track_type = Track.Down;
                                rhythm_bar_type = RhythmBarType.Mash;
                                break;
                            default:
                                abort_this_operation = true;
                                break;
                        }
                        break;
                    default:
                        abort_this_operation = true;
                        break;
                }

                if (abort_this_operation == false) {
                    RhythmData data = new RhythmData(data_track_type, rhythm_bar_type);
                    output.Add(new RhythmBar(
                        data,
                        time_on.TotalMicroseconds,
                        (time_off - time_on).TotalMicroseconds)
                    );
                    Debug.Log($"Bar Success..{musical_theory_note} and {musical_theory_note.NoteNumber}");
                } else {
                    Debug.LogError($"Bar Parsing FAILURE..{musical_theory_note} and {musical_theory_note.NoteNumber}");
                    abort_total_operation = true;
                }
            }
        }

I'm getting lots of these Defaults triggering on this Midi file (attached), as you can see from this picture:

image

I inspected it in a few different programs, the Midi file seems to only contain the notes as I've asked.

Any clue? Test_Midi_Riff.mid.zip

melanchall commented 4 years ago

Hi,

Just take a look at octave numbers. In your log you have a lot of records about A3, E3 and B3. But if you check your code for these notes (A, E, B), you'll notice that there is no processing of octave 3. So you get error which is correct according to logic of your code.

By the way, you can simplify calculating of start and end times of note:

var time_on = note.TimeAs<MetricTimeSpan>(tempo_map);
var time_off = note.EndTimeAs<MetricTimeSpan>(tempo_map);
sanbox-irl commented 4 years ago

Hi @melanchall, you're misunderstanding. There are not A3, E3, or B3s in this Midi file. There both shouldn't be, and in that specific Midi file, there are not any.

To check that, i loaded that midi file up in Cubase and in Garageband, and they both do not show any A3 e3s or b3s. Most of those 3rd octaves, they show as a 2nd octave program.

So the problem isn't my program (though I suspect it still is in some more subtle way), but that the library isn't giving me the correct parse of the midi file.

melanchall commented 4 years ago

Library gives you the correct notes. It's all about how different programs define middle C note.

DryWetMIDI uses Scientific Pitch Notation (SPN) which says that middle C is C of 4th octave. So you see B3 for 59 number (middle C is always note with number of 60). Cubase handles middle C differently, as C3. You can read interesting discussion about different notations here: MIDI Octave and Note Numbering Standard. If shortly, most developers choose between C3 and C4 which both are commonly used. I've decided to use SPN.

I'll write about this in the docs. Right now you can see mention of scientific pitch notation on the library docs, for example, here: Note.Get.

sanbox-irl commented 4 years ago

Thanks buddy -- does this sound correct that there is a linear difference between the two, ie for Cubase Note X, SPN will see that as Note X-1? a quick and dirty change like that would be fine for my use case here

melanchall commented 4 years ago

As you said, most of notes that in logs shown with 3d octave, in Cubase shown with 2nd one. So yes, difference is linear, but SPN_octave = Cubase_octave + 1.

sanbox-irl commented 4 years ago

Thanks buddy -- I went ahead and made those changes. Turns out the designer had some mistakes, which explains the Sharps which had floated in. Closing for now...