MIDI 解析器在某些文件上运行良好,但在其他文件上运行良好

问题描述 投票:0回答:1

一段时间以来,我一直假设我正在进行的 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

vb.net parsing midi audio-player
1个回答
0
投票

作为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
...

程序现在可以完全运行,自更改以来没有观察到错误。

© www.soinside.com 2019 - 2024. All rights reserved.