Чтение нот из MIDI-файла с помощью NAudio

Задача состоит в том, чтобы получить все ноты и их время из файла MIDI, используя библиотеку NAudio. Пока я получаю все заметки из файла, но не могу получить их время.

            Note noteOn = new Note(); //custom class Note
            MidiFile midi = new MidiFile(open.FileName);
            List<TempoEvent> tempo = new List<TempoEvent>();

            for (int i = 0; i < midi.Events.Count(); i++)
            {
                foreach (MidiEvent note in midi.Events[i])
                {
                    TempoEvent tempoE;

                    try { tempoE = (TempoEvent)note; tempo.Add(tempoE); }
                    catch { }

                    if (note.CommandCode == MidiCommandCode.NoteOn)
                    {
                        var t_note = ( NoteOnEvent)note;

                        var noteOffEvent = t_note.OffEvent;

                        noteOn.NoteName.Add(t_note.NoteName);
                        noteOn.NoteNumber.Add(t_note.NoteNumber);
                        noteOn.NoteVelocity.Add(t_note.Velocity);
                        noteOn.NoteLenght.Add(t_note.NoteLength);

                        double d = (t_note.AbsoluteTime / midi.DeltaTicksPerQuarterNote) * tempo[tempo.Count() - 1].Tempo;

                        noteOn.StartTime.Add(TimeSpan.FromSeconds(d));
                    }

                }
            }

Вопросы:

1) Чтобы получить только список заметок, я просто смотрю в NoteOnEvents или нет? Если я правильно понимаю, у каждой ноты есть «начало» и «конец», начало определяется NoteOnEvent, а «конец» определяется NoteOffEvent. Если я загляну в оба события (NoteOn и NoteOff), я получу повторяющиеся заметки. Я прав?

2) Как узнать время заметки? Согласно этому сообщению, я получаю некоторые значения, но кажется, что время первой заметки правильное, а другие нет. Также в этом посте есть комментарий, в котором говорится, что формула для расчета времени должна быть:

((note.AbsTime - lastTempoEvent.AbsTime) / midi.ticksPerQuarterNote) * tempo + lastTempoEvent.RealTime.

Параметры lastTempoEvent.RealTime и tempo мне неизвестны. Это темп последнего темпового события или?

3) Чтение MIDI-файла очень медленное, для небольших файлов нормально, а для больших нет. В этих маленьких файлах ~150 NoteOnEvents, а в больших файлах ~1250 NoteOnEvents, что не так уж «тяжело». Почему так медленно?


person skomi    schedule 27.05.2014    source источник


Ответы (2)


  1. В файлах MIDI нота имеет отдельные события включения и выключения ноты. NAudio уже ищет соответствующее событие отключения ноты и вычисляет для вас длину, поэтому вам не нужно самостоятельно обрабатывать события отключения ноты. (Однако темп может меняться между событиями включения и выключения ноты, поэтому вам придется вычислять два значения времени отдельно.)

  2. Это описания значений, а не фактические имена полей. tempo — это MicrosecondsPerQuarterNote значение последнего темпового события. lastTempoEvent.RealTime — это время (в микросекундах), которое вы вычислили для последнего события темпа.

    Последнее событие темпа — это событие темпа с наибольшим абсолютным временем, которое еще предшествует абсолютному времени этого события. Это событие темпа, вероятно, будет на другой дорожке, поэтому может быть хорошей идеей объединить все дорожки (установив midi.Events.MidiFileType на ноль) перед обработкой событий.

person CL.    schedule 27.05.2014

Вы можете взглянуть на другие библиотеки .NET, которые обеспечивают синтаксический анализ MIDI-файлов. Например, с помощью DryWetMIDI вы можете получить все ноты, содержащиеся в MIDI-файле, с помощью этого кода:

MidiFile file = MidiFile.Read("Great Song.mid");
IEnumerable<Note> notes = file.GetNotes();

Note имеет свойства Time и Length. Единицы значений, возвращаемые этими свойствами, определяются временным разделением MIDI-файла. Но вы можете получить время и длину ноты в более понятном формате. Для часов, минут, секунд вы можете написать:

TempoMap tempoMap = file.GetTempoMap();
MetricTimeSpan metricTime = note.TimeAs<MetricTimeSpan>(tempoMap);
MetricTimeSpan metricLength = note.LengthAs<MetricTimeSpan>(tempoMap);

При использовании методов TimeAs и LengthAs вам не нужно самостоятельно производить какие-либо расчеты. Экземпляр MetricTimeSpan может быть неявно приведен к TimeSpan.

person Maxim    schedule 09.07.2017