Миди в режиме реального времени после события Tempo с использованием NAudio - PullRequest
0 голосов
/ 25 августа 2018

Я пытаюсь вычислить в реальном времени миди-ноту о событиях, используя NAudio, из заданного абсолютного времени. Я использую следующий код:

private static double CalcFactor(long noteAbsTime, long lastTempoAbsTime, int ticksPerQuarterNote, int tempo, double lastTempoRealTime)  //calculate the factor needed for presenting time in seconds
        {
            double currentTempoRealTime =
                ((double)((double)(noteAbsTime - lastTempoAbsTime) /
                          (double)ticksPerQuarterNote) * (double)tempo + lastTempoRealTime) / 1000000D;
            return currentTempoRealTime;
        }

но в реальном времени после события темпа нет преемственности. Какова правильная формула, с помощью которой я могу иметь реальное время в секундах вместо дельта-тиков абсолютного времени для случая нескольких событий темпа в миди-файле?

1 Ответ

0 голосов
/ 15 сентября 2018

[удаленный предыдущий ответ из-за слишком большого количества правок]

"MBT"

// Time-sig with 4/4 assumed (as opposed to perhaps 3/4)
internal static string GetMBT(long pulse, int division)
{
  double value = (double)pulse;
  var M = Convert.ToInt32(Math.Floor(value / (division * 4.0)) + 1);
  var B = Convert.ToInt32((Math.Floor(value / division) % 4) + 1);
  var T = pulse % division;
  return string.Format(fmt3, M, B, T);
}

MidiEvent. AbsoluteTime представляет количество тиков или импульсов, как этопросто сказал бы нам, что в противном случае может быть показано как MBT (измерение, гистограмма [возможно, удары] и тики).

например, 0 = 001: 01: 000 (M: B: T) или 00: 00: 000в зависимости от того, что вы предпочитаете (и мы удалили +1 из вышеуказанной функции).

Tempo Map (или State) Расчет (ы)

internal static double GetSeconds(int division, double tempo, long pulse, double sec = 0.0)
{
  return ((60.0 / tempo) * ((double)(pulse) / division)) + sec;
}
internal static string GetSSeconds(double seconds)
{
  var T = TimeSpan.FromSeconds(seconds);
  return string.Format("{0:00}:{1:00}:{2:00}.{3:00000}", T.Hours, T.Minutes, T.Seconds, T.Milliseconds);
}

Если событие темпа не установлено вMIDI-файл, 120BPM, предполагается
например: 500000 микросекунд на четвертную ноту (60000000.0 / 500000 = 120)

Как правило, мы просто хотим поддерживать состояние темпа или что-то подобное, которое будет содержать (или может бытьинициализируется как)

  • long mLastTempoPulses = 0
  • double mLastTempoSecond = 0.0
  • double mLastTempoValue = 120 или первое значение сообщения SET_TEMPO.

Когда мы сталкиваемся с изменением темпа с TempoEvent (nTempo в следующем фрагменте), мы можем сказать что-нибудьНапример, ...

// { some loop

double mNewSecond = GetSeconds(
  miditrack.DeltaTicksPerQuarterNote,
  mLastTempoValue,
  nTempo.AbsoluteTime - mLastTempoPulses,
  mLastTempoSecond);

// then we would continue to set the back-ref values
mLastTempoPulses = nTempo.AbsoluteTime;
mLastTempoValue  = nTempo.Tempo;
mLastTempoSecond = mNewSecond;

// ... }

Вышеприведенное предполагает, что мы выполняем итерацию по сообщениям TempoEvent, однако мы применили бы ту же общую концепцию к NoteEvent без необходимости хранения значений обратной ссылки.

Пример GIST

В GIST (ниже) мы создаем карту темпа, как правило, используя вышеуказанную концепцию, где карта темпа в основном представляет собой список

class NewTempo
{
  public long PulseMin, PulseMax;
  public double Seconds, Tempo;
  public bool Match(MidiEvent pnote) {
    return (pnote.AbsoluteTime >= PulseMin) && (pnote.AbsoluteTime < PulseMax);
  }
}

I 'Мы опубликовали пример gist программы командной строки, однако у нас нет программного обеспечения, которое доказывает или проверяет, что это действительно правильно на данный момент.

вы, вероятно, захотите вывести в текстфайл с ним, если вы скомпилируете его, так как его выход, вероятно, превысит размер буфера консоли.

app.exe file.mid > output.txt

Это может быть сделано по-разному в зависимости от того, использует ли сценарий чтения входные данные из MidiFile или из входящих сообщенийв настоящее время.Пример GIST читает MIDI-файл и генерирует карту темпа, на которую можно сослаться, а затем просматривает заметки в данном треке (первый трек, содержащий ноты).

некоторые выходные данные из примера GIST ...

это может выглядеть как разрыв с первым изменением темпа, однако это правильно, поскольку в конкретном треке нет нот.

TEMPOEVENT (INPUT DATA)
=======================

 -> 001:01:000 => Time=        0, Tempo=123.999991733334
 -> 095:01:000 => Time=    45120, Tempo=122.999969250008
 -> 216:01:000 => Time=   103200, Tempo=122.999969250008
 -> 218:01:000 => Time=   104160, Tempo=122.999969250008

YIELD?
======

 -> Range={001:01:000 - 095:01:000}, BPM=124.0000, SS=00:03:01.00935
 -> Range={095:01:000 - 216:01:000}, BPM=123.0000, SS=00:06:58.00033
 -> Range={216:01:000 - 218:01:000}, BPM=123.0000, SS=00:07:01.00936

MIDI Format 1
Looking in track at index: 1
Processing 2694 events.
 -> 001:01:000 @00:03:01.00935 F#4  NoteOn 100
 -> 001:01:030 @00:03:02.00056 F#4 NoteOff 0
 -> 001:01:060 @00:03:02.00177 E4   NoteOn 100
 -> 001:01:090 @00:03:02.00298 E4  NoteOff 0
 -> 001:02:060 @00:03:02.00661 B3   NoteOn 100
 -> 001:02:090 @00:03:02.00782 B3  NoteOff 0
 -> 001:03:060 @00:03:03.00145 A3   NoteOn 100
 -> 001:03:090 @00:03:03.00266 A3  NoteOff 0
 -> 001:04:000 @00:03:03.00387 G3   NoteOn 100
 -> 001:04:060 @00:03:03.00629 G3  NoteOff 0

...

 -> 088:04:000 @00:05:51.00774 A3   NoteOn 100
 -> 088:04:060 @00:05:52.00016 A3  NoteOff 0
 -> 088:04:060 @00:05:52.00016 G3   NoteOn 100
 -> 089:01:000 @00:05:52.00258 G3  NoteOff 0
tempoIndex = 1
  => 45120 <= 49920 < 45120 bpm=122.999969250008
 -> 105:01:000 @00:07:17.00545 F#4  NoteOn 100
 -> 105:01:030 @00:07:17.00667 F#4 NoteOff 0
 -> 105:01:060 @00:07:17.00789 E4   NoteOn 100
 -> 105:01:090 @00:07:17.00911 E4  NoteOff 0
 -> 105:02:060 @00:07:18.00277 B3   NoteOn 100
 -> 105:02:090 @00:07:18.00399 B3  NoteOff 0
 -> 105:03:060 @00:07:18.00765 A3   NoteOn 100
 -> 105:03:090 @00:07:18.00887 A3  NoteOff 0

...

Некоторые ноты

Темпы (должны быть) сохранены в первой (0-й) дорожке, если только мы не смотрим на формат MIDI 2 (возможно, редкий случай).

Из того, что я заметил, естьэто либо одно сообщение с заданным темпом, либо 3 или более, где последнее событие темпа действует во многом подобно сообщению EOT (NAudio.Midi.MidiEvent.IsEndTrack(...)) - что, конечно, всегда есть EOT.

есть более полезные заметки.

Дополнительные

internal static IEnumerable<T> MidiEventT<T>(MidiFile midi, int tkid = 0, int max=-1)
  where T : MidiEvent
{
  int LIMIT = midi.Events[tkid].Count, counter=0;
  if ((max != -1) && (max < LIMIT)) LIMIT = max;
  for (int i = 0; i < midi.Events[tkid].Count; i++)
  {
    if (counter == LIMIT) break;
    T tmsg = midi.Events[tkid][i] as T;
    if (tmsg == null) continue;
    counter++;
    yield return tmsg;
  }
}

пример использования выше

// all tempo events
var tempos = new List<TempoEvent>(MidiEventT<TempoEvent>(midi));
// all note events (the gist has a better example)

// trackID is the track with events you want to grab, of course
var notes = new List<NoteEvent>(MidiEventT<NoteEvent>(midi, trackID));

// trackID is the track with events you want to grab, of course
var allMidiEventsInTrack = new List<MidiEvent>(MidiEventT<MidiEvent>(midi, trackID));
...