カクタスソフトウェア
カクタスソフトウェア
サウンド MIDI マルチメディア アプリケーション

Standard MIDI File からイベントを読み込む

トラックチャンクからMIDIイベントをロードする

トラックチャンクは、前のイベントからの経過時間を示すデルタチックとMIDIイベントが交互に連なった構造をしています。そのため、データの読み出しは、デルタチックとMIDIイベントを交互に読み出します。処理に失敗したり、読み出すデータが無くなったら終了するようにします。

LoadTrackクラスは、チック位置の管理と、読み出したMIDIイベントをStreamクラスに保存するヘルパークラスです。

スタンダードMIDIファイルには複数のトラックチャンクがあるため、ここで紹介する作業をトラックの数だけ行います。

Error CLoadSMF::LoadSequence(
    CStream*    pStream,
    BYTE*       pData)
{
    CLoadTrack loadTrack;

    loadTrack.m_pStream = pStream;
    loadTrack.m_pData = pData;

    while (true)
    {
        if (loadTrack.IncDTick() != NoErr)      break;
        if (loadTrack.TransEvent() != NoErr)    break;
    }
    return NoErr;
}
        Error LoadSequence(
            Stream      stream,
            byte[]      data)
        {
            LoadTrack loadTrack = new LoadTrack();

            loadTrack.Stream = stream;
            loadTrack.Data = data;

            while (true)
            {
                if (loadTrack.IncDTick() != Error.NoErr)        break;
                if (loadTrack.TransEvent() != Error.NoErr)      break;
            }
            return Error.NoErr;
        }

LoadTrackクラス

LoadTrackクラスは、トラックチャンクのバイナリイメージからMIDIイベントを抽出するヘルパークラスです。作業を進めるために以下の変数を用意します。

チック位置とデータ読み出し位置の管理

現在のチック位置は、CurTickで示し、SetGetを用いたプロパティとしています。GetCurTickでは、処理が終了している場合、intの最大値を戻します。

EndOfTrackは、トラックのデータがすべて読み出されたとき、trueを戻します。

private:
    int         _curTick;
    bool        _eot;
int CLoadTrack::GetCurTick()
{
    return (EndOfTrack() == true) ? MAXINT : _curTick;
}

void CLoadTrack::SetCurTick(int value)
{
    _curTick = value;
}

bool CLoadTrack::EndOfTrack()
{
    if (_eot == false)
    {
        if (m_DataSize <= m_CurPos)
        {
            _eot = true;
        }
    }
    return _eot;
}
        private int     _curTick;

        public int CurTick
        {
            get
            {
                return (EndOfTrack == true) ? int.MaxValue : _curTick;
            }
            set
            {
                _curTick = value;
            }
        }

        private bool    _eot;

        public bool EndOfTrack
        {
            get
            {
                if (_eot == false)
                {
                    if (DataSize <= CurPos)
                    {
                        _eot = true;
                    }
                }
                return _eot;
            }
        }

データ読み出し関数

データの読み出しは、1バイトづつ行います。また符号付き・無しのバージョンと、先読みのバージョンを用意します。

BYTE CLoadTrack::NextByte()
{
    if (EndOfTrack() == false)  return m_pData[m_CurPos++];
    else                        return 0x00;
}

SBYTE CLoadTrack::NextSbyte()
{
    if (EndOfTrack() == false)  return (SBYTE)m_pData[m_CurPos++];
    else                        return 0x00;
}

BYTE CLoadTrack::PeepData(int pos)
{
    if ((m_CurPos + pos) < m_DataSize)
        return m_pData[m_CurPos + pos];
    else
        return 0x00;
}
        public byte NextByte()
        {
            if (EndOfTrack == false)    return Data[CurPos++];
            else                        return 0x00;
        }

        public sbyte NextSbyte()
        {
            if (EndOfTrack == false)    return (sbyte)Data[CurPos++];
            else                        return 0x00;
        }

        public byte PeepData(int pos)
        {
            if ((CurPos + pos) < DataSize)
                return Data[CurPos + pos];
            else
                return 0x00;
        }

数値を読み込む

スタンダードMIDIファイルで用いる可変長の数値を読み込む関数です。デルタチックを始め、様々な場面で用いられます。

int CLoadTrack::GetNumber()
{
    int num = 0;

    for (int i=0; i<4; i++)
    {
        BYTE u = NextByte();
        num <<= 7;
        num |= u & 0x7f;
        if ((u & 0x80) == 0)    break;
    }
    return num;
}
        public int GetNumber()
        {
            int num = 0;

            for (int i=0; i<4; i++)
            {
                byte u = NextByte();
                num <<= 7;
                num |= u & 0x7f;
                if ((u & 0x80) == 0)    break;
            }
            return num;
        }

イベントを調べる

次に来るイベントが何かを調べ、そのイベントを担当する関数に処理を振り分けます。ランニングステータスが有効な場合は、イベントの種類をランニングステータスから得ます。

次のデータからステータスバイトを得ます。ランニングステータスは、0x80から0xefまでの範囲で有効なので、この範囲に入ったデータをRunStatusに登録します。ステータスが0xf0から0xf7までは、ランニングステータスが無効になります。次のデータがステータスバイトでないときは、RunStatusをステータスバイトとし、データの読み出し位置を一つ戻します。

MIDIイベントのステータスに応じて担当する関数をコールします。0xb0は、コントロールチェンジとモードメッセージが共有しているため、次のデータバイトを調べて振り分けます。

Error CLoadTrack::TransEvent()
{
    if (EndOfTrack() == true)   return SMFFormat;

    m_CurStatus = PeepData(0);

    if ((0x80 <= m_CurStatus)&&(m_CurStatus <= 0xef))
    {
        m_RunStatus = m_CurStatus;
    }
    if ((0xf0 <= m_CurStatus)&&(m_CurStatus <= 0xf7))
    {
        m_RunStatus = 0;
    }

    if (m_CurStatus < 0x80)
    {
        m_CurStatus = m_RunStatus;
        --m_CurPos;
    }
    Error err = NoErr;

    switch (m_CurStatus & 0xf0)
    {
    case 0x90:      err = MoveNoteOn();         break;
    case 0x80:      err = MoveNoteOff();        break;

    case 0xb0:
        if (PeepData(1) < NumControl)
            err = MoveControl();
        else
            err = MoveModeMsg();
        break;

    case 0xc0:      err = MoveProgram();        break;
    case 0xe0:      err = MovePitch();          break;
    case 0xa0:      err = MovePlTouch();        break;
    case 0xd0:      err = MoveChTouch();        break;

    case 0xf0:
        switch (m_CurStatus)
        {
        case 0xf0:                  // System Exclusive
        case 0xf7:
            err = MoveSysEx();                  break;
        case 0xff:                  // MetaEvent
            err = MoveMeta();                   break;
        default:
            err = SMFFormat;                    break;
        }
        break;

    default:
            err = SMFFormat;                    break;
    }
    return err;
}
        public Error TransEvent()
        {
            if (EndOfTrack == true) return Error.SMFFormat;

            CurStatus = PeepData(0);

            if ((0x80 <= CurStatus)&&(CurStatus <= 0xef))
            {
                RunStatus = CurStatus;
            }
            if ((0xf0 <= CurStatus)&&(CurStatus <= 0xf7))
            {
                RunStatus = 0;
            }

            if (CurStatus < 0x80)
            {
                CurStatus = RunStatus;
                --CurPos;
            }
            Error err = Error.NoErr;

            switch (CurStatus & 0xf0)
            {
            case 0x90:      err = MoveNoteOn();         break;
            case 0x80:      err = MoveNoteOff();        break;

            case 0xb0:
                if (PeepData(1) < Const.NumControl)
                    err = MoveControl();
                else
                    err = MoveModeMsg();
                break;

            case 0xc0:      err = MoveProgram();        break;
            case 0xe0:      err = MovePitch();          break;
            case 0xa0:      err = MovePlTouch();        break;
            case 0xd0:      err = MoveChTouch();        break;

            case 0xf0:
                switch (CurStatus)
                {
                case 0xf0:                  // System Exclusive
                case 0xf7:
                    err = MoveSysEx();                  break;
                case 0xff:                  // MetaEvent
                    err = MoveMeta();                   break;
                default:
                    err = Error.SMFFormat;              break;
                }
                break;

            default:
                    err = Error.SMFFormat;              break;
            }
            return err;
        }

メタイベントを振り分ける

ステータスが0xffの場合、メタイベントを意味します。メタイベントには沢山の種類があるため、さらに次のメタステータスによる振り分けが必要になります。

Error CLoadTrack::MoveMeta()
{
    ++m_CurPos;                             // Status

    switch (NextByte())     // Meta Status
    {
    case 0x51:      return MoveTempo();             // Tempo
    case 0x54:      return MoveSMPTE();             // SMPTE
    case 0x58:      return MoveTimeSig();           // Time Signature
    case 0x59:      return MoveKeySig();            // Key Signature
    case 0x00:      return MoveSeqNum();            // Sequence Number
    case 0x20:      return MoveChPrefix();          // Channel Prefix
    case 0x21:      return MovePort();              // Port
    case 0x2f:      return MoveEndOfTrack();        // End of Track

    case 0x01:      return MoveText();              // Text
    case 0x02:      return MoveCopyright();         // Copyright
    case 0x03:      return MoveTrackName();         // Track Name
    case 0x04:      return MoveInstrumentName();    // Instrument Name
    case 0x05:      return MoveLyric();             // Lyric
    case 0x06:      return MoveMarker();            // Marker
    case 0x07:      return MoveCuePoint();          // Cue Point

    case 0x7f:      return MoveSeqSpec();           // Sequencer Specific
    }
    return MoveOtherMeta();                         // Other MetaEvent
}
        public Error MoveMeta()
        {
            ++CurPos;                               // Status

            switch (NextByte())     // Meta Status
            {
            case 0x51:      return MoveTempo();             // Tempo
            case 0x54:      return MoveSMPTE();             // SMPTE
            case 0x58:      return MoveTimeSig();           // Time Signature
            case 0x59:      return MoveKeySig();            // Key Signature
            case 0x00:      return MoveSeqNum();            // Sequence Number
            case 0x20:      return MoveChPrefix();          // Channel Prefix
            case 0x21:      return MovePort();              // Port
            case 0x2f:      return MoveEndofTrack();        // End of Track

            case 0x01:      return MoveText();              // Text
            case 0x02:      return MoveCopyright();         // Copyright
            case 0x03:      return MoveTrackName();         // Track Name
            case 0x04:      return MoveInstrumentName();    // Instrument Name
            case 0x05:      return MoveLyric();             // Lyric
            case 0x06:      return MoveMarker();            // Marker
            case 0x07:      return MoveCuePoint();          // Cue Point

            case 0x7f:      return MoveSeqSpec();           // Sequencer Specific
            }
            return MoveOtherMeta();                         // Other MetaEvent
        }

ドキュメントの先頭へ

カクタスソフトウェア 技術協力 資料室 資料室の広場 SourceForge.jp お問い合わせ