一段时间以来,我一直假设我正在进行的 MIDI 项目根本无法使用多轨 MIDI 文件,但是对数百个 MIDI 文件的广泛测试表明一些多轨文件确实可以工作。显然,这表明我的解析算法存在更根本的问题,我根本看不出问题出在哪里。我相信我已经充分考虑了运行状态、未知的元事件和其他可能的“MIDI 主义”,但显然我一直都错了。
偶尔,我的程序也会遇到它无法识别的通道语音事件(note-on,note-off,change instrument etc.),这应该是不可能的,因为我已经考虑了七种可能的事件 (详细在这里).
我认为这些问题是由某个地方出现的疏忽逻辑错误引起的,导致解析器“绊倒”一个字节,但是我对算法的跟踪没有突出显示这个或任何其他错误。
我的代码读取每个轨道,确定序列中的每个事件类型,并通过引用将 BinaryReader 对象传递给事件的构造函数,以便对每个事件数据的解释进行一些抽象:
For x As Integer = 0 To metadata.NumberTracks - 1
While Not (dataString.EndsWith("MTrk")) 'Advances to the start of the next track
dataString += Chr(reader.ReadByte)
End While
dataString = ""
Dim trk As New Track
Dim numberBytes As Integer = 0
Dim byteOffset As Integer = reader.BaseStream.Position
For z As Integer = 0 To 3
numberBytes = (256 * numberBytes) + reader.ReadByte
Next
Dim runningStatus As Byte
Do
trk.addEvent(GetNextEvent(reader, runningStatus))
Loop Until GetType(EndOfTrack) = trk.getLastEvent().GetType()
tracks.Add(trk)
Next
GetNextEvent()
函数如下:
Private Function GetNextEvent(ByRef reader As BinaryReader, ByRef runningStatus As Byte) As MIDIEvent
Dim newEvent As MIDIEvent
Dim deltaTime As New VarLengthQuantity(reader)
'MessageBox.Show(deltaTime.getValue)
Dim statusByte As Byte = reader.ReadByte
If statusByte = EventCode.MetaEventFlag Then 'Event is a Meta-Event
Dim eventTypeCode As Byte = reader.ReadByte
Dim eventLength As New VarLengthQuantity(reader)
Select Case eventTypeCode
Case EventCode.MetaEvent.EndOfTrack
newEvent = New EndOfTrack(deltaTime)
'reader.ReadByte()
Case EventCode.MetaEvent.TimeSignature
newEvent = New TimeSignature(deltaTime, reader)
Case EventCode.MetaEvent.SetTempo
newEvent = New SetTempo(deltaTime, reader)
Case EventCode.MetaEvent.SMPTEOffset
newEvent = New SMPTEOffset(deltaTime, reader)
Case EventCode.MetaEvent.KeySignature
newEvent = New KeySignature(deltaTime, reader)
Case EventCode.MetaEvent.SequenceNumber
newEvent = New SequenceNumber(deltaTime, reader)
Case EventCode.MetaEvent.SequenceName
newEvent = New SequenceName(deltaTime, eventLength, reader)
Case EventCode.MetaEvent.InstrumentName
newEvent = New InstrumentName(deltaTime, eventLength, reader)
Case EventCode.MetaEvent.Lyric
newEvent = New Lyric(deltaTime, eventLength, reader)
Case EventCode.MetaEvent.TextEventLowBound To EventCode.MetaEvent.TextEventHighBound
newEvent = New TextEvent(deltaTime, eventLength, reader)
Case EventCode.MetaEvent.ChannelPrefix
newEvent = New ChannelPrefix(deltaTime, reader)
Case EventCode.MetaEvent.SeqSpecific
newEvent = New SeqSpecific(deltaTime, eventLength, reader)
Case Else
newEvent = New UnknownMetaEvent(deltaTime, eventLength, reader)
End Select
Else 'event is not a meta-event
Dim statusCode As Byte
Dim channel As Byte
If GetHighNibble(statusByte) = &HF Then
statusCode = statusByte
ElseIf (GetMostSigBit(statusByte) = 0) Then 'running status applies
If runningStatus = 0 Then
Throw New Exception("Running status buffer was empty.")
End If
statusByte = runningStatus
End If
statusCode = GetHighNibble(statusByte)
channel = GetLowNibble(statusByte)
runningStatus = (16 * statusCode) + channel
Select Case statusCode
'Channel-voice events
Case EventCode.ChannelVoiceEvent.NoteOn
newEvent = New NoteEvent(deltaTime, True, channel, reader)
Case EventCode.ChannelVoiceEvent.NoteOff
newEvent = New NoteEvent(deltaTime, False, channel, reader)
Case EventCode.ChannelVoiceEvent.PolyKeyPressure
newEvent = New PolyKeyPressure(deltaTime, channel, reader)
Case EventCode.ChannelVoiceEvent.ControlChange
newEvent = New ControlChange(deltaTime, channel, reader)
Case EventCode.ChannelVoiceEvent.ProgramChange
newEvent = New ProgramChange(deltaTime, channel, reader)
Case EventCode.ChannelVoiceEvent.ChannelPressure
newEvent = New ChannelPressure(deltaTime, channel, reader)
Case EventCode.ChannelVoiceEvent.PitchBend
newEvent = New PitchBend(deltaTime, channel, reader)
Case EventCode.SystemCommonEvent.SysEx
newEvent = New SysEx(deltaTime, reader)
runningStatus = 0
Case EventCode.SystemCommonEvent.TimeCodeQuarterFrame
newEvent = New TimeCodeQuarterFrame(deltaTime, reader)
runningStatus = 0
Case EventCode.SystemCommonEvent.PositionPointer
newEvent = New PositionPointer(deltaTime, reader)
runningStatus = 0
Case EventCode.SystemCommonEvent.SongSelect
newEvent = New SongSelect(deltaTime, reader)
runningStatus = 0
Case EventCode.SystemCommonEvent.TuneRequest
newEvent = New TuneRequest(deltaTime)
runningStatus = 0
Case EventCode.SystemRealTimeEvent.TimingClock
newEvent = New TimingClock(deltaTime)
Case EventCode.SystemRealTimeEvent.StartSequence
newEvent = New StartSequence(deltaTime)
Case EventCode.SystemRealTimeEvent.ContinueSequence
newEvent = New ContinueSequence(deltaTime)
Case EventCode.SystemRealTimeEvent.StopSequence
newEvent = New StopSequence(deltaTime)
Case EventCode.SystemRealTimeEvent.ActiveSensing
newEvent = New ActiveSensing(deltaTime)
Case EventCode.SystemRealTimeEvent.ResetAll
newEvent = New ResetAll(deltaTime)
Case Else
Throw New Exception("Invalid event.")
End Select
End If
Debug.WriteLine(newEvent.GetType)
GetNextEvent = newEvent
End Function
如果有人能帮我找出算法中的缺陷,我将不胜感激。 非常感谢,罗伊 H
作为CL。请在评论中指出,运行状态代码是错误的,因为第一个事件数据字节已经被读取,如果应用运行状态。我通过将 BinaryReader 的 BaseStream 位置减一以“后退”并允许在事件构造函数中正常读取数据字节来解决此问题。 新代码如下:
...
Else 'event is not a meta-event
Dim statusCode As Byte
Dim channel As Byte
If GetHighNibble(statusByte) = &HF Then
statusCode = statusByte
ElseIf (GetMostSigBit(statusByte) = 0) Then 'running status applies
If runningStatus = 0 Then
Throw New Exception("Running status buffer was empty.")
End If
reader.BaseStream.Position -= 1
statusByte = runningStatus
End If
statusCode = GetHighNibble(statusByte)
channel = GetLowNibble(statusByte)
runningStatus = (16 * statusCode) + channel
...
程序现在可以完全运行,自更改以来没有观察到错误。