我正在尝试使用NAudio在给定的绝对时间之外计算事件的Midi音符的实时时间。我正在使用以下代码:
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;
}
,但是在速度事件后没有实时性。对于Midi文件中出现多个速度事件的情况,我可以用几秒而不是绝对时间的增量变化来获得实时的正确公式是什么?
答案 0 :(得分:0)
[由于进行过多的编辑,删除了先前的答案]
// 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。绝对时间表示刻度或脉冲的数量,因为这只会告诉我们可以用MBT显示的内容(小节,小节(可能是节拍)和刻度)。
例如0 = 001:01:000(M:B:T)或00:00:000,具体取决于您的偏好(我们从上述函数中删除了+1)。
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
例如:每四分音符(60000000.0 / 500000 = 120
为500000微秒
通常,我们只想保持一个速度状态或类似的状态即可包含(或初始化为)
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(如下),我们通常使用上述概念创建一个速度图,其中速度图基本上是一个列表
class NewTempo
{
public long PulseMin, PulseMax;
public double Seconds, Tempo;
public bool Match(MidiEvent pnote) {
return (pnote.AbsoluteTime >= PulseMin) && (pnote.AbsoluteTime < PulseMax);
}
}
我已经发布了example 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
...
除非我们正在研究MIDI格式2(也许是一种罕见的情况),否则,(应该)将速度存储在第一个(第0个)音轨中。
从我观察到的结果来看,要么有一个设置速度消息,要么有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));