SMFを読み取るクラス

SMF(Standard MIDI File)を読み取るクラスがほしくなった。用途はピアノ練習ソフトのため。コントロールチェンジとかチャネルとかは基本的に無視。ノートが取れればよい。
ネットを検索すると、Cで書かれたリーダーは結構あるけど、vb.net とかで書かれたソースは発見できず。車輪の再発明をすることにした。SMFにはFormat0/Format1/Format2の3タイプがあるらしいけど、SMF2はマイナーなので無視することにする。あと、データの格納がなぜかビックエンディアンなのでちょっといらっとしますが、もりもり書いてみた。
SMFの仕様については以下のサイト等を参考にしてみた。書いてくれた人サンクス!
http://www.geocities.co.jp/SiliconValley-SanJose/8132/

'// SMFを読み取る
dim reader as SmfReader = SmfReader.Read("c:\test.mid")
'// トラック0の最初のイベントを取得する
reader.Track(0).Events(0)

こんな感じでノートを取得できるような感じ。一応メタイベントとかも適当に格納してある。

ソースは以下から

Public Class SmfReader

    Public Class SmfException
        Inherits ApplicationException

        Public Sub New()
            MyBase.New()
        End Sub
        Public Sub New(ByVal message As String)
            MyBase.New(message)
        End Sub
        Public Sub New(ByVal message As String, ByVal innerException As Exception)
            MyBase.New(message, innerException)
        End Sub
    End Class


    Private m_header As SmfHeader
    Private m_tracks As New List(Of SmfTrack)
    ''' <summary>
    ''' SMFのヘッダ情報を取得します。
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property Header() As SmfHeader
        Get
            Return m_header
        End Get
        Set(ByVal value As SmfHeader)
            m_header = value
        End Set
    End Property
    ''' <summary>
    ''' トラック情報を取得します。
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property Tracks() As List(Of SmfTrack)
        Get
            Return m_tracks
        End Get
        Set(ByVal value As List(Of SmfTrack))
            m_tracks = value
        End Set
    End Property

    '// こんな感じの階層を作るよ
    '// SmfReader
    '//   -Header
    '//   -Tracks
    '//      -Track0
    '//         -Events
    '//            -MidiEvent
    '//            -SysExEvent
    '//            -MetaEvent
    '//      -Track1
    '//         -Events
    '//            -MidiEvent
    '//            -SysExEvent
    '//            -MetaEvent

    Public Enum MetaEventType
        V00シーケンス番号 = 0
        V01テキスト = 1
        V02著作権表示 = 2
        V03トラック名 = 3
        V04楽器名 = 4
        V05歌詞 = 5
        V06マーカ = 6
        V07キューポイント = 7
        V20MIDIチャンネルプリフィックス = 32
        V2Fエンドオブトラック = 47
        V51セットテンポ = 81
        V54SMPTEオフセット = 84
        V58拍子 = 88
        V59調号 = 89
        V7Fシーケンサ固有のメタイベント = 127
    End Enum

    Public Enum MidiEventType
        V8xノートオフ
        V9xノートオン
        VAxポリフォニック・キープレッシャー
        VBxコントロールチェンジ
        VCxプログラム・チェンジ
        VDxチャネル・プレッシャー
        VExピッチベンド・チェンジ
        VXxUnknown
    End Enum
    ''' <summary>
    ''' SMFファイルを読み込みます。
    ''' </summary>
    ''' <param name="path">ファイルパス</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Shared Function ReadData(ByVal path As String) As SmfReader

        Dim reader As New SmfReader
        Dim bytes() As Byte = System.IO.File.ReadAllBytes(path)

        Dim data As String = ToHexString(bytes)

        '// バイト配列をちょん切ったりなんかするからListのほうが便利
        '// 読み取ったバイトは削除していくので現在読み取っている場所はindex0を読み取ればよし
        Dim bytesList As New List(Of Byte)(bytes)

        Try
            '// MThd ヘッダを探す
            If Not data.StartsWith("4D546864") Then
                Throw New SmfException("SMFファイルのヘッダが不正です。MThdで開始していません。")
            End If

            '// ヘッダ情報を作成
            reader.m_header = CreateHeader(bytesList)

            For i As Integer = 0 To reader.m_header.TrackCount - 1
                Dim trackString As String = ToHexString(bytesList.ToArray)
                '// トラックを探して、ヘッダ情報をちょん切ったByte配列を作成
                bytesList.RemoveRange(0, CInt(trackString.IndexOf("4D54726B") / 2))
                reader.m_tracks.Add(CreateTrack(bytesList))
            Next

            If reader.Tracks.Count <= 0 Then
                Throw New SmfException("SMFファイルのトラック情報が不正です。トラックが見つかりません。")
            End If
        Catch ex As Exception
            Throw New SmfException("SMFの読み込み中にエラーが発生しました。", ex)
        End Try

        Return reader
    End Function
    ''' <summary>
    ''' ヘッダを作成します。
    ''' </summary>
    ''' <param name="headerByte"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Shared Function CreateHeader(ByVal headerByte As List(Of Byte)) As SmfHeader
        Dim header As New SmfHeader
        '// Format
        Select Case ReverseToInt16(headerByte, 8)
            Case 0
                header.Format = "SMF0"
                header.FormatValue = 0
            Case 1
                header.Format = "SMF1"
                header.FormatValue = 1
            Case Else
                Throw New SmfException("対応していないSMFフォーマットです。SMF0かSMF1のみ対応しています。")
        End Select
        '// トラック数
        header.TrackCount = ReverseToInt16(headerByte, 10)
        '// 時間単位(分解能)
        header.TimeBase = ReverseToInt16(headerByte, 12)
        Return header
    End Function
    ''' <summary>
    ''' トラックを作成します。
    ''' </summary>
    ''' <param name="trackByte"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Shared Function CreateTrack(ByVal trackByte As List(Of Byte)) As SmfTrack
        Dim track As New SmfTrack

        '// データ長を読み取って読み取り済みのところは捨てる
        track.DataLength = ReverseToInt32(trackByte, 4)
        trackByte.RemoveRange(0, 8)
        '// トラックが終わるであろう位置を計算
        Dim endTrackIndex As Integer = trackByte.Count - track.DataLength

        Do While trackByte.Count > endTrackIndex

            Dim delta As Integer = ReadDeltaTime(trackByte)

            Dim smfEvent As SmfEvent
            Select Case trackByte(0)
                Case 240, 247
                    '// SysExイベント
                    smfEvent = ReadSysExEvent(trackByte)
                Case 255
                    '// メタイベント
                    Dim metaEvent As SmfMetaEvent = ReadMetaEvent(trackByte)
                    '// タイトルを読み込んだのならタイトルに設定(一番最初のトラック名だけ採用)
                    If metaEvent.DataType = MetaEventType.V03トラック名 Then
                        If track.Title.Length <= 0 Then
                            track.Title = metaEvent.Text
                        End If
                    End If
                    smfEvent = metaEvent
                Case Else
                    '// Midiイベント
                    smfEvent = ReadMidiEvent(trackByte)
            End Select
            smfEvent.DeltaTime = delta
            track.Events.Add(smfEvent)
        Loop

        Return track
    End Function

    ''' <summary>
    ''' 与えられたByteからDeltaTimeを読み取って返します。読み取った部分元のリストから削除されます。
    ''' </summary>
    ''' <param name="bytes"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Shared Function ReadDeltaTime(ByVal bytes As List(Of Byte)) As Integer
        Dim result As Integer
        If bytes(0) <= &H7F Then
            '// 最初のByteの最上位Bitが立っていないのでDeltaTimeは1バイト
            result = bytes(0)
            bytes.RemoveRange(0, 1)
        ElseIf bytes(1) <= &H7F Then
            '// 2Byte目の最上位Bitが立っていないのでDeltaTimeは2バイト
            result = (bytes(0) And &H7F) * &H80
            result = (result Or (bytes(1) And &H7F))
            bytes.RemoveRange(0, 2)
        ElseIf bytes(2) <= &H7F Then
            '// 3Byte目の最上位Bitが立っていないのでDeltaTimeは3バイト
            result = (bytes(0) And &H7F) * &H80
            result = (result Or (bytes(1) And &H7F)) * &H80
            result = (result Or (bytes(2) And &H7F))
            bytes.RemoveRange(0, 3)
        ElseIf bytes(3) <= &H7F Then
            '// 4Byte目の最上位Bitが立っていないのでDeltaTimeは4バイト
            result = (bytes(0) And &H7F) * &H80
            result = (result Or (bytes(1) And &H7F)) * &H80
            result = (result Or (bytes(2) And &H7F)) * &H80
            result = (result Or (bytes(3) And &H7F))
            bytes.RemoveRange(0, 4)
        End If

        Return result
    End Function
    ''' <summary>
    ''' Midiを読み取ります。
    ''' </summary>
    ''' <param name="bytes"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Shared Function ReadMidiEvent(ByVal bytes As List(Of Byte)) As SmfMidiEvent
        If bytes.Count < 2 Then
            Throw New SmfException("MidiEventの読み込みに失敗しました。長さが足りません。")
        End If

        Dim midiEvent As New SmfMidiEvent


        Static runningStatus As Byte = bytes(0)
        Static runningDataType As MidiEventType

        '// チャネルステータスの判定
        Select Case bytes(0) And &HF0
            Case &H80
                '// ノートオフ
                midiEvent.Channel = bytes(0) And &HF
                midiEvent.DataType = MidiEventType.V8xノートオフ
                bytes.RemoveRange(0, 1)
            Case &H90
                '// ノートオン
                midiEvent.Channel = bytes(0) And &HF
                midiEvent.DataType = MidiEventType.V9xノートオン
                bytes.RemoveRange(0, 1)
            Case &HA0
                '// ポリフォニック・キープレッシャー
                midiEvent.Channel = bytes(0) And &HF
                midiEvent.DataType = MidiEventType.VAxポリフォニック・キープレッシャー
                runningDataType = midiEvent.DataType
                bytes.RemoveRange(0, 1)
            Case &HB0
                '// コントロールチェンジ
                midiEvent.Channel = bytes(0) And &HF
                midiEvent.DataType = MidiEventType.VBxコントロールチェンジ
                bytes.RemoveRange(0, 1)
            Case &HC0
                '// プログラム・チェンジ
                midiEvent.Channel = bytes(0) And &HF
                midiEvent.DataType = MidiEventType.VCxプログラム・チェンジ
                bytes.RemoveRange(0, 1)
            Case &HD0
                '// チャネル・プレッシャー
                midiEvent.Channel = bytes(0) And &HF
                midiEvent.DataType = MidiEventType.VDxチャネル・プレッシャー
                runningDataType = midiEvent.DataType
                bytes.RemoveRange(0, 1)
            Case &HE0
                '// ピッチベンド・チェンジ
                midiEvent.Channel = bytes(0) And &HF
                midiEvent.DataType = MidiEventType.VExピッチベンド・チェンジ
                bytes.RemoveRange(0, 1)
            Case Else
                If bytes(0) <= &H7F Then
                    '// 最上位Bitに1が立っていないのでこれはランニングステータス
                    midiEvent.Channel = runningStatus And &HF
                    midiEvent.DataType = runningDataType
                    '// ここは1バイト取り除く必要はない
                Else
                    '// 未知のステータス・・・
                    midiEvent.Channel = bytes(0) And &HF
                    midiEvent.DataType = MidiEventType.VXxUnknown
                    bytes.RemoveRange(0, 1)
                End If
        End Select

        runningDataType = midiEvent.DataType
        '// ノートとvelocityを取得
        midiEvent.Note = bytes(0)
        bytes.RemoveRange(0, 1)

        '// プログラム・チェンジとチャネル・プレッシャー以外は2バイト目も取得
        Select Case midiEvent.DataType
            Case MidiEventType.VCxプログラム・チェンジ, MidiEventType.VDxチャネル・プレッシャー

            Case Else
                midiEvent.Velocity = bytes(0)
                bytes.RemoveRange(0, 1)
        End Select

        '// velocity 0 のノートオンはノートオフに置き換える
        If midiEvent.DataType = MidiEventType.V9xノートオン Then
            If midiEvent.Velocity = 0 Then
                midiEvent.DataType = MidiEventType.V8xノートオフ
            End If
        End If
        Return midiEvent
    End Function

    ''' <summary>
    ''' メタイベントを読み取ります。
    ''' </summary>
    ''' <param name="bytes"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Shared Function ReadMetaEvent(ByVal bytes As List(Of Byte)) As SmfMetaEvent
        If bytes.Count < 3 Then
            Throw New SmfException("メタデータの読み込みに失敗しました。長さが足りません。")
        End If

        '// タイプとデータ長を読み取って消す
        Dim metaEvent As New SmfMetaEvent
        metaEvent.DataType = CType(bytes(1), MetaEventType)
        metaEvent.DataLength = bytes(2)
        bytes.RemoveRange(0, 3)

        '// データを読み取って消す
        Dim dataBytes As New List(Of Byte)
        For i As Integer = 0 To metaEvent.DataLength - 1
            dataBytes.Add(bytes(i))
        Next
        bytes.RemoveRange(0, metaEvent.DataLength)

        metaEvent.OriginalData = dataBytes.ToArray


        Select Case metaEvent.DataType
            Case MetaEventType.V00シーケンス番号

            Case MetaEventType.V01テキスト
                metaEvent.Text = System.Text.Encoding.Default.GetString(dataBytes.ToArray, 0, metaEvent.DataLength)
            Case MetaEventType.V02著作権表示
                metaEvent.Text = System.Text.Encoding.Default.GetString(dataBytes.ToArray, 0, metaEvent.DataLength)
            Case MetaEventType.V03トラック名
                metaEvent.Text = System.Text.Encoding.Default.GetString(dataBytes.ToArray, 0, metaEvent.DataLength)
            Case MetaEventType.V04楽器名
                metaEvent.Text = System.Text.Encoding.Default.GetString(dataBytes.ToArray, 0, metaEvent.DataLength)
            Case MetaEventType.V05歌詞
                metaEvent.Text = System.Text.Encoding.Default.GetString(dataBytes.ToArray, 0, metaEvent.DataLength)
            Case MetaEventType.V06マーカ
                metaEvent.Text = System.Text.Encoding.Default.GetString(dataBytes.ToArray, 0, metaEvent.DataLength)
            Case MetaEventType.V07キューポイント
                metaEvent.Text = System.Text.Encoding.Default.GetString(dataBytes.ToArray, 0, metaEvent.DataLength)
            Case MetaEventType.V20MIDIチャンネルプリフィックス

            Case MetaEventType.V2Fエンドオブトラック
                'トラックの終了
            Case MetaEventType.V51セットテンポ
                '4分音符の長さをμsec単位で表し、一拍当たりの時間でテンポを表す。 
                metaEvent.DataValue = ReverseToInt32Ex(dataBytes, 0)
            Case MetaEventType.V54SMPTEオフセット

            Case MetaEventType.V58拍子
                '[nn:dd:cc:bb]
                '拍子記号の分子nn
                '2のdd乗で表される分母(dが2の場合4、3の場合8)
                'メトロノーム1カウントあたりのMIDIクロック数cc
                '4分音符中の32分音符数bb

            Case MetaEventType.V59調号
                '[sf:mi]
                'シャープまたはフラット記号の数を表すsf
                'メジャー/マイナーを示すmi
                'sfはフラットの数を表すときはマイナス数値になる。
                'また、miはメジャーのとき0、マイナのとき1になる。 

            Case MetaEventType.V7Fシーケンサ固有のメタイベント

        End Select

        Return metaEvent
    End Function

    ''' <summary>
    ''' システムエクスクルーシブイベントを読み取ります。
    ''' </summary>
    ''' <param name="bytes"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Shared Function ReadSysExEvent(ByVal bytes As List(Of Byte)) As SmfSysExEvent
        Dim sysExEvent As New SmfSysExEvent
        bytes.RemoveRange(0, 1)
        sysExEvent.DataLength = ReadDeltaTime(bytes)


        '// データを読み取る
        Dim dataBytes As New List(Of Byte)
        For i As Integer = 0 To sysExEvent.DataLength - 1
            dataBytes.Add(bytes(i))
        Next
        bytes.RemoveRange(0, sysExEvent.DataLength)

        '// メッセージを格納
        sysExEvent.OriginalData = dataBytes.ToArray

        Return sysExEvent
    End Function


    Private Shared Function ReverseToInt16(ByVal bytes As List(Of Byte), ByVal startIndex As Integer) As Short
        '// ビックエンディアンでInt16を作成
        If bytes.Count < startIndex + 1 Then
            Throw New SmfException("指定されたByte配列はInt16を作成するのに十分な長さがありません。")
        End If
        Dim reverseBytes(1) As Byte
        reverseBytes(0) = bytes(startIndex + 1)
        reverseBytes(1) = bytes(startIndex)
        Return BitConverter.ToInt16(reverseBytes, 0)
    End Function
    Private Shared Function ReverseToInt32(ByVal bytes As List(Of Byte), ByVal startIndex As Integer) As Integer
        '// ビックエンディアンでInt16を作成
        If bytes.Count < startIndex + 3 Then
            Throw New SmfException("指定されたByte配列はInt32を作成するのに十分な長さがありません。")
        End If
        Dim reverseBytes(3) As Byte
        reverseBytes(0) = bytes(startIndex + 3)
        reverseBytes(1) = bytes(startIndex + 2)
        reverseBytes(2) = bytes(startIndex + 1)
        reverseBytes(3) = bytes(startIndex)
        Return BitConverter.ToInt32(reverseBytes, 0)
    End Function

    Private Shared Function ReverseToInt32Ex(ByVal bytes As List(Of Byte), ByVal startIndex As Integer) As Integer
        '// ビックエンディアンでInt16を作成
        If bytes.Count < startIndex + 2 Then
            Throw New SmfException("指定されたByte配列はInt32を作成するのに十分な長さがありません。")
        End If
        bytes.Insert(0, 0)
        Dim reverseBytes(3) As Byte
        reverseBytes(0) = bytes(startIndex + 3)
        reverseBytes(1) = bytes(startIndex + 2)
        reverseBytes(2) = bytes(startIndex + 1)
        reverseBytes(3) = bytes(startIndex)

        bytes.RemoveAt(0)
        Return BitConverter.ToInt32(reverseBytes, 0)
    End Function

    ''' <summary>
    ''' バイト配列を16進文字列に変換します。
    ''' </summary>
    ''' <param name="target"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Shared Function ToHexString(ByVal target() As Byte) As String
        '// 自分でごにゃごにゃするより、BitConverterのが速い・・
        Return BitConverter.ToString(target).Replace("-"c, "")
    End Function

End Class



Public Class SmfHeader
    Private m_formatValue As Integer
    Private m_format As String
    Private m_trackCount As Integer
    Private m_timeBase As Integer
    ''' <summary>SMFのフォーマットを取得します。</summary>
    Public Property Format() As String
        Get
            Return m_format
        End Get
        Set(ByVal value As String)
            m_format = value
        End Set
    End Property
    ''' <summary>SMFのフォーマット番号を取得します。</summary>
    Public Property FormatValue() As Integer
        Get
            Return m_formatValue
        End Get
        Set(ByVal value As Integer)
            m_formatValue = value
        End Set
    End Property

    ''' <summary>トラック数を取得します。</summary>
    Public Property TrackCount() As Integer
        Get
            Return m_trackCount
        End Get
        Set(ByVal value As Integer)
            m_trackCount = value
        End Set
    End Property
    ''' <summary>時間単位(分解能)を取得します。4分音符の長さを何分割するかの数値</summary>
    Public Property TimeBase() As Integer
        Get
            Return m_timeBase
        End Get
        Set(ByVal value As Integer)
            m_timeBase = value
        End Set
    End Property
End Class

Public Class SmfTrack
    Private m_dataLength As Integer
    Private m_events As New List(Of SmfEvent)
    Private m_title As String = ""

    ''' <summary>タイトルを取得します。</summary>
    Public Property Title() As String
        Get
            Return m_title
        End Get
        Set(ByVal value As String)
            m_title = value
        End Set
    End Property

    ''' <summary>データ長を取得します。</summary>
    Public Property DataLength() As Integer
        Get
            Return m_dataLength
        End Get
        Set(ByVal value As Integer)
            m_dataLength = value
        End Set
    End Property
    ''' <summary>イベント情報を取得します。</summary>
    Public Property Events() As List(Of SmfEvent)
        Get
            Return m_events
        End Get
        Set(ByVal value As List(Of SmfEvent))
            m_events = value
        End Set
    End Property

End Class


''' <summary>
''' Eventの基底クラス
''' </summary>
''' <remarks></remarks>
Public Class SmfEvent
    Protected m_deltaTime As Integer
    ''' <summary>デルタタイムを取得します。</summary>
    Public Property DeltaTime() As Integer
        Get
            Return m_deltaTime
        End Get
        Set(ByVal value As Integer)
            m_deltaTime = value
        End Set
    End Property
End Class
''' <summary>
''' MidiEvent
''' </summary>
''' <remarks></remarks>
Public Class SmfMidiEvent
    Inherits SmfEvent

    Private m_dataType As SmfReader.MidiEventType
    Private m_channel As Integer
    Private m_note As Integer
    Private m_velocity As Integer
    ''' <summary>データ種別を取得します。</summary>
    Public Property DataType() As SmfReader.MidiEventType
        Get
            Return m_dataType
        End Get
        Set(ByVal value As SmfReader.MidiEventType)
            m_dataType = value
        End Set
    End Property
    ''' <summary>チャンネルを取得します。</summary>
    Public Property Channel() As Integer
        Get
            Return m_channel
        End Get
        Set(ByVal value As Integer)
            m_channel = value
        End Set
    End Property
    ''' <summary>ノート(音階)を取得します。</summary>
    Public Property Note() As Integer
        Get
            Return m_note
        End Get
        Set(ByVal value As Integer)
            m_note = value
        End Set
    End Property
    ''' <summary>ヴェロシティー(強さ)を取得します。</summary>
    Public Property Velocity() As Integer
        Get
            Return m_velocity
        End Get
        Set(ByVal value As Integer)
            m_velocity = value
        End Set
    End Property

    Public Overrides Function ToString() As String
        Dim result As New System.Text.StringBuilder
        result.Append("Delta = " & m_deltaTime & " ")
        result.Append("Type = " & m_dataType.ToString & " ")
        result.Append("channel = " & m_channel.ToString & " ")
        result.Append("note = " & m_note.ToString & " ")
        result.Append("velocity = " & m_velocity.ToString & " ")
        Return result.ToString
    End Function
End Class
''' <summary>
''' SysExEvent
''' </summary>
''' <remarks></remarks>
Public Class SmfSysExEvent
    Inherits SmfEvent

    Private m_dataLength As Integer
    Private m_originalData() As Byte

    ''' <summary>データ長を取得します。</summary>
    Public Property DataLength() As Integer
        Get
            Return m_dataLength
        End Get
        Set(ByVal value As Integer)
            m_dataLength = value
        End Set
    End Property
    ''' <summary>オリジナルのデータを取得します。</summary>
    Public Property OriginalData() As Byte()
        Get
            Return m_originalData
        End Get
        Set(ByVal value() As Byte)
            m_originalData = value
        End Set
    End Property

    Public Overrides Function ToString() As String
        Return "SmfSysExEvent"
    End Function
End Class

''' <summary>
''' MetaEvent
''' </summary>
''' <remarks></remarks>
Public Class SmfMetaEvent
    Inherits SmfEvent
    Private m_dataType As SmfReader.MetaEventType
    Private m_dataLength As Integer
    Private m_dataValue As Integer
    Private m_text As String = ""
    Private m_originalData() As Byte

    Public Property DataType() As SmfReader.MetaEventType
        Get
            Return m_dataType
        End Get
        Set(ByVal value As SmfReader.MetaEventType)
            m_dataType = value
        End Set
    End Property
    Public Property DataLength() As Integer
        Get
            Return m_dataLength
        End Get
        Set(ByVal value As Integer)
            m_dataLength = value
        End Set
    End Property
    Public Property DataValue() As Integer
        Get
            Return m_dataValue
        End Get
        Set(ByVal value As Integer)
            m_dataValue = value
        End Set
    End Property
    Public Property Text() As String
        Get
            Return m_text
        End Get
        Set(ByVal value As String)
            m_text = value
        End Set
    End Property

    Public Property OriginalData() As Byte()
        Get
            Return m_originalData
        End Get
        Set(ByVal value() As Byte)
            m_originalData = value
        End Set
    End Property

    Public Overrides Function ToString() As String
        Dim result As New System.Text.StringBuilder
        result.Append("Delta = " & m_deltaTime & " ")
        result.Append("Type = " & m_dataType.ToString & " ")
        result.Append("text = " & m_text.ToString & " ")
        Return result.ToString
    End Function

End Class