如何使用 Naudio 获取 MIDI 事件的实时时间
How to get the real time of a midi event using Naudio
我正在尝试阅读 midi 音符并使用 NAudio 库提取每个音符的实时时间
我写了这段代码,但它没有正确计算时间,我使用了一个我找到的公式 here
((note.AbsTime - lastTempoEvent.AbsTime) / midi.ticksPerQuarterNote) * tempo + lastTempoEvent.RealTime
代码:
var strictMode = false;
var mf = new MidiFile("Assets/Audios/Evangelion Midi.mid", strictMode);
mf.Events.MidiFileType = 0;
List<MidiEvent> midiNotes = new List<MidiEvent>();
List<TempoEvent> tempoEvents = new List<TempoEvent>();
for (int n = 0; n < mf.Tracks; n++)
{
foreach (var midiEvent in mf.Events[n])
{
if (!MidiEvent.IsNoteOff(midiEvent))
{
midiNotes.Add(midiEvent);
TempoEvent tempoE;
try { tempoE = (TempoEvent)midiEvent; tempoEvents.Add(tempoE);
Debug.Log("Absolute Time " + tempoE.AbsoluteTime);
}
catch { }
}
}
}
notesArray = midiNotes.ToArray();
tempoEventsArr = tempoEvents.ToArray();
eventsTimesArr = new float[notesArray.Length];
eventsTimesArr[0] = 0;
for (int i = 1; i < notesArray.Length; i++)
{
((notesArray[i].AbsoluteTime - tempoEventsArr[tempoEventsArr.Length - 1].AbsoluteTime) / mf.DeltaTicksPerQuarterNote)
* tempoEventsArr[tempoEventsArr.Length - 1].MicrosecondsPerQuarterNote + eventsTimesArr[i-1];
}
我得到这些 values 显然不正确
有没有人说我错了?
在这里看到有人进入MIDI真是太好了。
参考代码中的 note.AbsTime - lastTempoEvent.AbsTime
部分在您这边实施不正确。
此代码中的 lastTempoEvent
变量不能表示 MIDI 文件中的最后速度变化(因为您已经使用 notesArray[i].AbsoluteTime - tempoEventsArr[tempoEventsArr.Length - 1].AbsoluteTime
实现了它)。
引用的代码试图做的是在您的代码减去整个 MIDI 文件中最新速度变化的绝对时间。这是负数的根本原因(如果在当前音符之后有任何速度变化)。
旁注:我还建议保留音符关闭事件的时间。如果您不知道笔记何时发布,您如何关闭它?
试试这个。我测试了它并且它有效。请仔细阅读内联评论。
注意安全。
static void CalculateMidiRealTimes()
{
var strictMode = false;
var mf = new MidiFile("C:\Windows\Media\onestop.mid", strictMode);
mf.Events.MidiFileType = 0;
// Have just one collection for both non-note-off and tempo change events
List<MidiEvent> midiEvents = new List<MidiEvent>();
for (int n = 0; n < mf.Tracks; n++)
{
foreach (var midiEvent in mf.Events[n])
{
if (!MidiEvent.IsNoteOff(midiEvent))
{
midiEvents.Add(midiEvent);
// Instead of causing stack unwinding with try/catch,
// we just test if the event is of type TempoEvent
if (midiEvent is TempoEvent)
{
Debug.Write("Absolute Time " + (midiEvent as TempoEvent).AbsoluteTime);
}
}
}
}
// Now we have only one collection of both non-note-off and tempo events
// so we cannot be sure of the size of the time values array.
// Just employ a List<float>
List<float> eventsTimesArr = new List<float>();
// we introduce this variable to keep track of the tempo changes
// during play, which affects the timing of all the notes coming
// after it.
TempoEvent lastTempoChange = null;
for (int i = 0; i < midiEvents.Count; i++)
{
MidiEvent midiEvent = midiEvents[i];
TempoEvent tempoEvent = midiEvent as TempoEvent;
if (tempoEvent != null)
{
lastTempoChange = tempoEvent;
// Remove the tempo event to make events and timings match - index-wise
// Do not add to the eventTimes
midiEvents.RemoveAt(i);
i--;
continue;
}
if (lastTempoChange == null)
{
// If we haven't come accross a tempo change yet,
// set the time to zero.
eventsTimesArr.Add(0);
continue;
}
// This is the correct formula for calculating the real time of the event
// in microseconds:
var realTimeValue =
((midiEvent.AbsoluteTime - lastTempoChange.AbsoluteTime) / mf.DeltaTicksPerQuarterNote)
*
lastTempoChange.MicrosecondsPerQuarterNote + eventsTimesArr[eventsTimesArr.Count - 1];
// Add the time to the collection.
eventsTimesArr.Add(realTimeValue);
Debug.WriteLine("Time for {0} is: {1}", midiEvents.ToString(), realTimeValue);
}
}
编辑:
计算实时时的除法是 int/float,当事件之间的滴答声小于每个四分音符的增量滴答声时,结果为零。
下面是使用精度最好的数字类型 decimal 计算值的正确方法。
midi 歌曲 onestop.mid İS 4:08(248 秒)长,我们的最终事件实时时间是 247.3594906770833
static void CalculateMidiRealTimes()
{
var strictMode = false;
var mf = new MidiFile("C:\Windows\Media\onestop.mid", strictMode);
mf.Events.MidiFileType = 0;
// Have just one collection for both non-note-off and tempo change events
List<MidiEvent> midiEvents = new List<MidiEvent>();
for (int n = 0; n < mf.Tracks; n++)
{
foreach (var midiEvent in mf.Events[n])
{
if (!MidiEvent.IsNoteOff(midiEvent))
{
midiEvents.Add(midiEvent);
// Instead of causing stack unwinding with try/catch,
// we just test if the event is of type TempoEvent
if (midiEvent is TempoEvent)
{
Debug.Write("Absolute Time " + (midiEvent as TempoEvent).AbsoluteTime);
}
}
}
}
// Switch to decimal from float.
// decimal has 28-29 digits percision
// while float has only 6-9
// https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/floating-point-numeric-types
// Now we have only one collection of both non-note-off and tempo events
// so we cannot be sure of the size of the time values array.
// Just employ a List<float>
List<decimal> eventsTimesArr = new List<decimal>();
// Keep track of the last absolute time and last real time because
// tempo events also can occur "between" events
// which can cause incorrect times when calculated using AbsoluteTime
decimal lastRealTime = 0m;
decimal lastAbsoluteTime = 0m;
// instead of keeping the tempo event itself, and
// instead of multiplying every time, just keep
// the current value for microseconds per tick
decimal currentMicroSecondsPerTick = 0m;
for (int i = 0; i < midiEvents.Count; i++)
{
MidiEvent midiEvent = midiEvents[i];
TempoEvent tempoEvent = midiEvent as TempoEvent;
// Just append to last real time the microseconds passed
// since the last event (DeltaTime * MicroSecondsPerTick
if (midiEvent.AbsoluteTime > lastAbsoluteTime)
{
lastRealTime += ((decimal)midiEvent.AbsoluteTime - lastAbsoluteTime) * currentMicroSecondsPerTick;
}
lastAbsoluteTime = midiEvent.AbsoluteTime;
if (tempoEvent != null)
{
// Recalculate microseconds per tick
currentMicroSecondsPerTick = (decimal)tempoEvent.MicrosecondsPerQuarterNote / (decimal)mf.DeltaTicksPerQuarterNote;
// Remove the tempo event to make events and timings match - index-wise
// Do not add to the eventTimes
midiEvents.RemoveAt(i);
i--;
continue;
}
// Add the time to the collection.
eventsTimesArr.Add(lastRealTime);
Debug.WriteLine("Time for {0} is: {1}", midiEvent, lastRealTime / 1000000m);
}
}
我正在尝试阅读 midi 音符并使用 NAudio 库提取每个音符的实时时间
我写了这段代码,但它没有正确计算时间,我使用了一个我找到的公式 here
((note.AbsTime - lastTempoEvent.AbsTime) / midi.ticksPerQuarterNote) * tempo + lastTempoEvent.RealTime
代码:
var strictMode = false;
var mf = new MidiFile("Assets/Audios/Evangelion Midi.mid", strictMode);
mf.Events.MidiFileType = 0;
List<MidiEvent> midiNotes = new List<MidiEvent>();
List<TempoEvent> tempoEvents = new List<TempoEvent>();
for (int n = 0; n < mf.Tracks; n++)
{
foreach (var midiEvent in mf.Events[n])
{
if (!MidiEvent.IsNoteOff(midiEvent))
{
midiNotes.Add(midiEvent);
TempoEvent tempoE;
try { tempoE = (TempoEvent)midiEvent; tempoEvents.Add(tempoE);
Debug.Log("Absolute Time " + tempoE.AbsoluteTime);
}
catch { }
}
}
}
notesArray = midiNotes.ToArray();
tempoEventsArr = tempoEvents.ToArray();
eventsTimesArr = new float[notesArray.Length];
eventsTimesArr[0] = 0;
for (int i = 1; i < notesArray.Length; i++)
{
((notesArray[i].AbsoluteTime - tempoEventsArr[tempoEventsArr.Length - 1].AbsoluteTime) / mf.DeltaTicksPerQuarterNote)
* tempoEventsArr[tempoEventsArr.Length - 1].MicrosecondsPerQuarterNote + eventsTimesArr[i-1];
}
我得到这些 values 显然不正确
有没有人说我错了?
在这里看到有人进入MIDI真是太好了。
参考代码中的 note.AbsTime - lastTempoEvent.AbsTime
部分在您这边实施不正确。
此代码中的 lastTempoEvent
变量不能表示 MIDI 文件中的最后速度变化(因为您已经使用 notesArray[i].AbsoluteTime - tempoEventsArr[tempoEventsArr.Length - 1].AbsoluteTime
实现了它)。
引用的代码试图做的是在您的代码减去整个 MIDI 文件中最新速度变化的绝对时间。这是负数的根本原因(如果在当前音符之后有任何速度变化)。
旁注:我还建议保留音符关闭事件的时间。如果您不知道笔记何时发布,您如何关闭它? 试试这个。我测试了它并且它有效。请仔细阅读内联评论。
注意安全。
static void CalculateMidiRealTimes()
{
var strictMode = false;
var mf = new MidiFile("C:\Windows\Media\onestop.mid", strictMode);
mf.Events.MidiFileType = 0;
// Have just one collection for both non-note-off and tempo change events
List<MidiEvent> midiEvents = new List<MidiEvent>();
for (int n = 0; n < mf.Tracks; n++)
{
foreach (var midiEvent in mf.Events[n])
{
if (!MidiEvent.IsNoteOff(midiEvent))
{
midiEvents.Add(midiEvent);
// Instead of causing stack unwinding with try/catch,
// we just test if the event is of type TempoEvent
if (midiEvent is TempoEvent)
{
Debug.Write("Absolute Time " + (midiEvent as TempoEvent).AbsoluteTime);
}
}
}
}
// Now we have only one collection of both non-note-off and tempo events
// so we cannot be sure of the size of the time values array.
// Just employ a List<float>
List<float> eventsTimesArr = new List<float>();
// we introduce this variable to keep track of the tempo changes
// during play, which affects the timing of all the notes coming
// after it.
TempoEvent lastTempoChange = null;
for (int i = 0; i < midiEvents.Count; i++)
{
MidiEvent midiEvent = midiEvents[i];
TempoEvent tempoEvent = midiEvent as TempoEvent;
if (tempoEvent != null)
{
lastTempoChange = tempoEvent;
// Remove the tempo event to make events and timings match - index-wise
// Do not add to the eventTimes
midiEvents.RemoveAt(i);
i--;
continue;
}
if (lastTempoChange == null)
{
// If we haven't come accross a tempo change yet,
// set the time to zero.
eventsTimesArr.Add(0);
continue;
}
// This is the correct formula for calculating the real time of the event
// in microseconds:
var realTimeValue =
((midiEvent.AbsoluteTime - lastTempoChange.AbsoluteTime) / mf.DeltaTicksPerQuarterNote)
*
lastTempoChange.MicrosecondsPerQuarterNote + eventsTimesArr[eventsTimesArr.Count - 1];
// Add the time to the collection.
eventsTimesArr.Add(realTimeValue);
Debug.WriteLine("Time for {0} is: {1}", midiEvents.ToString(), realTimeValue);
}
}
编辑:
计算实时时的除法是 int/float,当事件之间的滴答声小于每个四分音符的增量滴答声时,结果为零。
下面是使用精度最好的数字类型 decimal 计算值的正确方法。
midi 歌曲 onestop.mid İS 4:08(248 秒)长,我们的最终事件实时时间是 247.3594906770833
static void CalculateMidiRealTimes()
{
var strictMode = false;
var mf = new MidiFile("C:\Windows\Media\onestop.mid", strictMode);
mf.Events.MidiFileType = 0;
// Have just one collection for both non-note-off and tempo change events
List<MidiEvent> midiEvents = new List<MidiEvent>();
for (int n = 0; n < mf.Tracks; n++)
{
foreach (var midiEvent in mf.Events[n])
{
if (!MidiEvent.IsNoteOff(midiEvent))
{
midiEvents.Add(midiEvent);
// Instead of causing stack unwinding with try/catch,
// we just test if the event is of type TempoEvent
if (midiEvent is TempoEvent)
{
Debug.Write("Absolute Time " + (midiEvent as TempoEvent).AbsoluteTime);
}
}
}
}
// Switch to decimal from float.
// decimal has 28-29 digits percision
// while float has only 6-9
// https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/floating-point-numeric-types
// Now we have only one collection of both non-note-off and tempo events
// so we cannot be sure of the size of the time values array.
// Just employ a List<float>
List<decimal> eventsTimesArr = new List<decimal>();
// Keep track of the last absolute time and last real time because
// tempo events also can occur "between" events
// which can cause incorrect times when calculated using AbsoluteTime
decimal lastRealTime = 0m;
decimal lastAbsoluteTime = 0m;
// instead of keeping the tempo event itself, and
// instead of multiplying every time, just keep
// the current value for microseconds per tick
decimal currentMicroSecondsPerTick = 0m;
for (int i = 0; i < midiEvents.Count; i++)
{
MidiEvent midiEvent = midiEvents[i];
TempoEvent tempoEvent = midiEvent as TempoEvent;
// Just append to last real time the microseconds passed
// since the last event (DeltaTime * MicroSecondsPerTick
if (midiEvent.AbsoluteTime > lastAbsoluteTime)
{
lastRealTime += ((decimal)midiEvent.AbsoluteTime - lastAbsoluteTime) * currentMicroSecondsPerTick;
}
lastAbsoluteTime = midiEvent.AbsoluteTime;
if (tempoEvent != null)
{
// Recalculate microseconds per tick
currentMicroSecondsPerTick = (decimal)tempoEvent.MicrosecondsPerQuarterNote / (decimal)mf.DeltaTicksPerQuarterNote;
// Remove the tempo event to make events and timings match - index-wise
// Do not add to the eventTimes
midiEvents.RemoveAt(i);
i--;
continue;
}
// Add the time to the collection.
eventsTimesArr.Add(lastRealTime);
Debug.WriteLine("Time for {0} is: {1}", midiEvent, lastRealTime / 1000000m);
}
}