view src/midifile.c @ 29:4df6d9714314

Automatic reindent/cleanup pass on the midi code.
author Matti Hamalainen <ccr@tnsp.org>
date Mon, 05 Aug 2013 19:17:05 +0300
parents 785057719d9b
children 26741527f3b7
line wrap: on
line source

/*
 * midiFile.c - A general purpose midi file handling library. This code
 *				can read and write MIDI files in formats 0 and 1.
 * Version 1.4
 *
 *  AUTHOR: Steven Goodwin (StevenGoodwin@gmail.com)
 *          Copyright 1998-2010, Steven Goodwin
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public License as
 *  published by the Free Software Foundation; either version 2 of
 *  the License,or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifndef  __APPLE__
#include <malloc.h>
#endif
#include "midifile.h"

/*
** Internal Data Structures
*/
typedef struct
{
    BYTE note, chn;
    BYTE valid, p2;
    DWORD end_pos;
} MIDI_LAST_NOTE;

typedef struct
{
    BYTE *ptr;
    BYTE *pBase;
    BYTE *pEnd;

    DWORD pos;
    DWORD dt;
    /* For Reading MIDI Files */
    DWORD sz;                   /* size of whole iTrack */
    /* For Writing MIDI Files */
    DWORD iBlockSize;           /* max size of track */
    BYTE iDefaultChannel;       /* use for write only */
    BYTE last_status;           /* used for running status */

    MIDI_LAST_NOTE LastNote[MAX_TRACK_POLYPHONY];
} MIDI_FILE_TRACK;

typedef struct
{
    DWORD iHeaderSize;
      /**/ WORD iVersion;       /* 0, 1 or 2 */
    WORD iNumTracks;            /* number of tracks... (will be 1 for MIDI type 0) */
    WORD PPQN;                  /* pulses per quarter note */
} MIDI_HEADER;

typedef struct
{
    FILE *pFile;
    BOOL bOpenForWriting;

    MIDI_HEADER Header;
    BYTE *ptr;                  /* to whole data block */
    DWORD file_sz;

    MIDI_FILE_TRACK Track[MAX_MIDI_TRACKS];
} _MIDI_FILE;


/*
** Internal Functions
*/
#define DT_DEF				32      /* assume maximum delta-time + msg is no more than 32 bytes */
#define SWAP_WORD(w)		(WORD)(((w)>>8)|((w)<<8))
#define SWAP_DWORD(d)		(DWORD)((d)>>24)|(((d)>>8)&0xff00)|(((d)<<8)&0xff0000)|(((d)<<24))

#define _VAR_CAST				_MIDI_FILE *pMF = (_MIDI_FILE *)_pMF
#define IsFilePtrValid(pMF)		(pMF)
#define IsTrackValid(_x)		(_midiValidateTrack(pMF, _x))
#define IsChannelValid(_x)		((_x)>=1 && (_x)<=16)
#define IsNoteValid(_x)			((_x)>=0 && (_x)<128)
#define IsMessageValid(_x)		((_x)>=msgNoteOff && (_x)<=msgMetaEvent)


static BOOL _midiValidateTrack(const _MIDI_FILE *pMF, int iTrack)
{
    if (!IsFilePtrValid(pMF))
        return FALSE;

    if (pMF->bOpenForWriting)
    {
        if (iTrack < 0 || iTrack >= MAX_MIDI_TRACKS)
            return FALSE;
    }
    else                        /* open for reading */
    {
        if (!pMF->ptr)
            return FALSE;

        if (iTrack < 0 || iTrack >= pMF->Header.iNumTracks)
            return FALSE;
    }

    return TRUE;
}

static BYTE *_midiWriteVarLen(BYTE * ptr, int n)
{
    register long buffer;
    register long value = n;

    buffer = value & 0x7f;
    while ((value >>= 7) > 0)
    {
        buffer <<= 8;
        buffer |= 0x80;
        buffer += (value & 0x7f);
    }

    while (TRUE)
    {
        *ptr++ = (BYTE) buffer;
        if (buffer & 0x80)
            buffer >>= 8;
        else
            break;
    }

    return (ptr);
}

/* Return a ptr to valid block of memory to store a message
** of up to sz_reqd bytes
*/
static BYTE *_midiGetPtr(_MIDI_FILE *pMF, int iTrack, int sz_reqd)
{
    const DWORD mem_sz_inc = 8092;      /* arbitary */
    BYTE *ptr;
    int curr_offset;
    MIDI_FILE_TRACK *pTrack = &pMF->Track[iTrack];

    ptr = pTrack->ptr;
    if (ptr == NULL || ptr + sz_reqd > pTrack->pEnd)    /* need more RAM! */
    {
        curr_offset = ptr - pTrack->pBase;
        if ((ptr =
             (BYTE *) realloc(pTrack->pBase,
                              mem_sz_inc + pTrack->iBlockSize)))
        {
            pTrack->pBase = ptr;
            pTrack->iBlockSize += mem_sz_inc;
            pTrack->pEnd = ptr + pTrack->iBlockSize;
            /* Move new ptr to continue data entry: */
            pTrack->ptr = ptr + curr_offset;
            ptr += curr_offset;
        }
        else
        {
            /* NO MEMORY LEFT */
            return NULL;
        }
    }

    return ptr;
}


static int _midiGetLength(int ppqn, int iNoteLen, BOOL bOverride)
{
    int length = ppqn;

    if (bOverride)
    {
        length = iNoteLen;
    }
    else
    {
        switch (iNoteLen)
        {
        case MIDI_NOTE_DOTTED_MINIM:
            length *= 3;
            break;

        case MIDI_NOTE_DOTTED_CROCHET:
            length *= 3;
            length /= 2;
            break;

        case MIDI_NOTE_DOTTED_QUAVER:
            length *= 3;
            length /= 4;
            break;

        case MIDI_NOTE_DOTTED_SEMIQUAVER:
            length *= 3;
            length /= 8;
            break;

        case MIDI_NOTE_DOTTED_SEMIDEMIQUAVER:
            length *= 3;
            length /= 16;
            break;

        case MIDI_NOTE_BREVE:
            length *= 4;
            break;

        case MIDI_NOTE_MINIM:
            length *= 2;
            break;

        case MIDI_NOTE_QUAVER:
            length /= 2;
            break;

        case MIDI_NOTE_SEMIQUAVER:
            length /= 4;
            break;

        case MIDI_NOTE_SEMIDEMIQUAVER:
            length /= 8;
            break;

        case MIDI_NOTE_TRIPLE_CROCHET:
            length *= 2;
            length /= 3;
            break;
        }
    }

    return length;
}

/*
** midiFile* Functions
*/
MIDI_FILE *midiFileCreate(const char *pFilename, BOOL bOverwriteIfExists)
{
    _MIDI_FILE *pMF = (_MIDI_FILE *) malloc(sizeof(_MIDI_FILE));
    int i;

    if (!pMF)
        return NULL;

    if (!bOverwriteIfExists)
    {
        if ((pMF->pFile = fopen(pFilename, "r")))
        {
            fclose(pMF->pFile);
            free(pMF);
            return NULL;
        }
    }

    if ((pMF->pFile = fopen(pFilename, "wb+")))
    {                           /*empty */
    }
    else
    {
        free((void *) pMF);
        return NULL;
    }

    pMF->bOpenForWriting = TRUE;
    pMF->Header.PPQN = MIDI_PPQN_DEFAULT;
    pMF->Header.iVersion = MIDI_VERSION_DEFAULT;

    for (i = 0; i < MAX_MIDI_TRACKS; ++i)
    {
        pMF->Track[i].pos = 0;
        pMF->Track[i].ptr = NULL;
        pMF->Track[i].pBase = NULL;
        pMF->Track[i].pEnd = NULL;
        pMF->Track[i].iBlockSize = 0;
        pMF->Track[i].dt = 0;
        pMF->Track[i].iDefaultChannel = (BYTE) (i & 0xf);

        memset(pMF->Track[i].LastNote, '\0', sizeof(pMF->Track[i].LastNote));
    }

    return (MIDI_FILE *) pMF;
}

int midiFileSetTracksDefaultChannel(MIDI_FILE *_pMF, int iTrack,
                                    int iChannel)
{
    int prev;

    _VAR_CAST;
    if (!IsFilePtrValid(pMF))
        return 0;
    if (!IsTrackValid(iTrack))
        return 0;
    if (!IsChannelValid(iChannel))
        return 0;

    /* For programmer each, iChannel is between 1 & 16 - but MIDI uses
     ** 0-15. Thus, the fudge factor of 1 :)
     */
    prev = pMF->Track[iTrack].iDefaultChannel + 1;
    pMF->Track[iTrack].iDefaultChannel = (BYTE) (iChannel - 1);
    return prev;
}

int midiFileGetTracksDefaultChannel(const MIDI_FILE *_pMF, int iTrack)
{
    _VAR_CAST;
    if (!IsFilePtrValid(pMF))
        return 0;
    if (!IsTrackValid(iTrack))
        return 0;

    return pMF->Track[iTrack].iDefaultChannel + 1;
}

int midiFileSetPPQN(MIDI_FILE *_pMF, int PPQN)
{
    int prev;

    _VAR_CAST;
    if (!IsFilePtrValid(pMF))
        return MIDI_PPQN_DEFAULT;
    prev = pMF->Header.PPQN;
    pMF->Header.PPQN = (WORD) PPQN;
    return prev;
}

int midiFileGetPPQN(const MIDI_FILE *_pMF)
{
    _VAR_CAST;
    if (!IsFilePtrValid(pMF))
        return MIDI_PPQN_DEFAULT;
    return (int) pMF->Header.PPQN;
}

int midiFileSetVersion(MIDI_FILE *_pMF, int iVersion)
{
    int prev;

    _VAR_CAST;
    if (!IsFilePtrValid(pMF))
        return MIDI_VERSION_DEFAULT;
    if (iVersion < 0 || iVersion > 2)
        return MIDI_VERSION_DEFAULT;
    prev = pMF->Header.iVersion;
    pMF->Header.iVersion = (WORD) iVersion;
    return prev;
}

int midiFileGetVersion(const MIDI_FILE *_pMF)
{
    _VAR_CAST;
    if (!IsFilePtrValid(pMF))
        return MIDI_VERSION_DEFAULT;
    return pMF->Header.iVersion;
}

MIDI_FILE *midiFileOpen(const char *pFilename)
{
    FILE *fp = fopen(pFilename, "rb");
    _MIDI_FILE *pMF = NULL;
    BYTE *ptr;
    BOOL bValidFile = FALSE;
    long size;

    if (fp)
    {
        if ((pMF = (_MIDI_FILE *) malloc(sizeof(_MIDI_FILE))))
        {
            fseek(fp, 0L, SEEK_END);
            size = ftell(fp);
            if ((pMF->ptr = (BYTE *) malloc(size)))
            {
                fseek(fp, 0L, SEEK_SET);
                fread(pMF->ptr, sizeof(BYTE), size, fp);
                /* Is this a valid MIDI file ? */
                ptr = pMF->ptr;
                if (*(ptr + 0) == 'M' && *(ptr + 1) == 'T' &&
                    *(ptr + 2) == 'h' && *(ptr + 3) == 'd')
                {
                    DWORD dwData;
                    WORD wData;
                    int i;

                    dwData = *((DWORD *) (ptr + 4));
                    pMF->Header.iHeaderSize = SWAP_DWORD(dwData);

                    wData = *((WORD *) (ptr + 8));
                    pMF->Header.iVersion = (WORD) SWAP_WORD(wData);

                    wData = *((WORD *) (ptr + 10));
                    pMF->Header.iNumTracks = (WORD) SWAP_WORD(wData);

                    wData = *((WORD *) (ptr + 12));
                    pMF->Header.PPQN = (WORD) SWAP_WORD(wData);

                    ptr += pMF->Header.iHeaderSize + 8;
                    /*
                     **      Get all tracks
                     */
                    for (i = 0; i < MAX_MIDI_TRACKS; ++i)
                    {
                        pMF->Track[i].pos = 0;
                        pMF->Track[i].last_status = 0;
                    }

                    for (i = 0; i < pMF->Header.iNumTracks; ++i)
                    {
                        pMF->Track[i].pBase = ptr;
                        pMF->Track[i].ptr = ptr + 8;
                        dwData = *((DWORD *) (ptr + 4));
                        pMF->Track[i].sz = SWAP_DWORD(dwData);
                        pMF->Track[i].pEnd = ptr + pMF->Track[i].sz + 8;
                        ptr += pMF->Track[i].sz + 8;
                    }

                    pMF->bOpenForWriting = FALSE;
                    pMF->pFile = NULL;
                    bValidFile = TRUE;
                }
            }
        }

        fclose(fp);
    }

    if (!bValidFile)
    {
        if (pMF)
            free((void *) pMF);
        return NULL;
    }

    return (MIDI_FILE *) pMF;
}

typedef struct
{
    int iIdx;
    int iEndPos;
} MIDI_END_POINT;

static int qs_cmp_pEndPoints(const void *e1, const void *e2)
{
    MIDI_END_POINT *p1 = (MIDI_END_POINT *) e1;
    MIDI_END_POINT *p2 = (MIDI_END_POINT *) e2;

    return p1->iEndPos - p2->iEndPos;
}

BOOL midiFileFlushTrack(MIDI_FILE *_pMF, int iTrack, BOOL bFlushToEnd,
                        DWORD dwEndTimePos)
{
    int sz;
    BYTE *ptr;
    MIDI_END_POINT *pEndPoints;
    int num, i, mx_pts;
    BOOL bNoChanges = TRUE;

    _VAR_CAST;
    if (!IsFilePtrValid(pMF))
        return FALSE;
    if (!_midiValidateTrack(pMF, iTrack))
        return FALSE;
    sz = sizeof(pMF->Track[0].LastNote) / sizeof(pMF->Track[0].LastNote[0]);

    /*
     ** Flush all
     */
    pEndPoints = (MIDI_END_POINT *) malloc(sz * sizeof(MIDI_END_POINT));
    mx_pts = 0;
    for (i = 0; i < sz; ++i)
        if (pMF->Track[iTrack].LastNote[i].valid)
        {
            pEndPoints[mx_pts].iIdx = i;
            pEndPoints[mx_pts].iEndPos =
                pMF->Track[iTrack].LastNote[i].end_pos;
            mx_pts++;
        }

    if (bFlushToEnd)
    {
        if (mx_pts)
            dwEndTimePos = pEndPoints[mx_pts - 1].iEndPos;
        else
            dwEndTimePos = pMF->Track[iTrack].pos;
    }

    if (mx_pts)
    {
        /* Sort, smallest first, and add the note off msgs */
        qsort(pEndPoints, mx_pts, sizeof(MIDI_END_POINT), qs_cmp_pEndPoints);

        i = 0;
        while ((dwEndTimePos >= (DWORD) pEndPoints[i].iEndPos || bFlushToEnd)
               && i < mx_pts)
        {
            ptr = _midiGetPtr(pMF, iTrack, DT_DEF);
            if (!ptr)
                return FALSE;

            num = pEndPoints[i].iIdx;   /* get 'LastNote' index */

            ptr =
                _midiWriteVarLen(ptr,
                                 pMF->Track[iTrack].LastNote[num].end_pos -
                                 pMF->Track[iTrack].pos);
            /* msgNoteOn  msgNoteOff */
            *ptr++ =
                (BYTE) (msgNoteOff | pMF->Track[iTrack].LastNote[num].chn);
            *ptr++ = pMF->Track[iTrack].LastNote[num].note;
            *ptr++ = 0;

            pMF->Track[iTrack].LastNote[num].valid = FALSE;
            pMF->Track[iTrack].pos = pMF->Track[iTrack].LastNote[num].end_pos;

            pMF->Track[iTrack].ptr = ptr;

            ++i;
            bNoChanges = FALSE;
        }
    }

    free((void *) pEndPoints);
    /*
     ** Re-calc current position
     */
    pMF->Track[iTrack].dt = dwEndTimePos - pMF->Track[iTrack].pos;

    return TRUE;
}

BOOL midiFileSyncTracks(MIDI_FILE *_pMF, int iTrack1, int iTrack2)
{
    int p1, p2;

    _VAR_CAST;
    if (!IsFilePtrValid(pMF))
        return FALSE;
    if (!IsTrackValid(iTrack1))
        return FALSE;
    if (!IsTrackValid(iTrack2))
        return FALSE;

    p1 = pMF->Track[iTrack1].pos + pMF->Track[iTrack1].dt;
    p2 = pMF->Track[iTrack2].pos + pMF->Track[iTrack2].dt;

    if (p1 < p2)
        midiTrackIncTime(pMF, iTrack1, p2 - p1, TRUE);
    else if (p2 < p1)
        midiTrackIncTime(pMF, iTrack2, p1 - p2, TRUE);

    return TRUE;
}


BOOL midiFileClose(MIDI_FILE *_pMF)
{
    _VAR_CAST;
    if (!IsFilePtrValid(pMF))
        return FALSE;

    if (pMF->bOpenForWriting)
    {
        WORD iNumTracks = 0;
        WORD wTest = 256;
        BOOL bSwap = FALSE;
        int i;

        /* Intel processor style-endians need byte swap :( */
        if (*((BYTE *) & wTest) == 0)
            bSwap = TRUE;

        /* Flush our buffers  */
        for (i = 0; i < MAX_MIDI_TRACKS; ++i)
        {
            if (pMF->Track[i].ptr)
            {
                midiSongAddEndSequence(pMF, i);
                midiFileFlushTrack(pMF, i, TRUE, 0);
                iNumTracks++;
            }
        }
        /*
         ** Header
         */
        {
            const BYTE mthd[4] = { 'M', 'T', 'h', 'd' };
            DWORD dwData;
            WORD wData;
            WORD version, PPQN;

            fwrite(mthd, sizeof(BYTE), 4, pMF->pFile);
            dwData = 6;
            if (bSwap)
                dwData = SWAP_DWORD(dwData);
            fwrite(&dwData, sizeof(DWORD), 1, pMF->pFile);

            wData = (WORD) (iNumTracks == 1 ? pMF->Header.iVersion : 1);
            if (bSwap)
                version = SWAP_WORD(wData);
            else
                version = (WORD) wData;
            if (bSwap)
                iNumTracks = SWAP_WORD(iNumTracks);
            wData = pMF->Header.PPQN;
            if (bSwap)
                PPQN = SWAP_WORD(wData);
            else
                PPQN = wData;
            fwrite(&version, sizeof(WORD), 1, pMF->pFile);
            fwrite(&iNumTracks, sizeof(WORD), 1, pMF->pFile);
            fwrite(&PPQN, sizeof(WORD), 1, pMF->pFile);
        }
        /*
         ** Track data
         */
        for (i = 0; i < MAX_MIDI_TRACKS; ++i)
            if (pMF->Track[i].ptr)
            {
                const BYTE mtrk[4] = { 'M', 'T', 'r', 'k' };
                DWORD sz, dwData;

                /* Write track header */
                fwrite(&mtrk, sizeof(BYTE), 4, pMF->pFile);

                /* Write data size */
                sz = dwData = (int) (pMF->Track[i].ptr - pMF->Track[i].pBase);
                if (bSwap)
                    sz = SWAP_DWORD(sz);
                fwrite(&sz, sizeof(DWORD), 1, pMF->pFile);

                /* Write data */
                fwrite(pMF->Track[i].pBase, sizeof(BYTE), dwData, pMF->pFile);

                /* Free memory */
                free((void *) pMF->Track[i].pBase);
            }

    }

    if (pMF->pFile)
        return fclose(pMF->pFile) ? FALSE : TRUE;
    free((void *) pMF);
    return TRUE;
}


/*
** midiSong* Functions
*/
BOOL midiSongAddSMPTEOffset(MIDI_FILE *_pMF, int iTrack, int iHours,
                            int iMins, int iSecs, int iFrames, int iFFrames)
{
    static BYTE tmp[] =
        { msgMetaEvent, metaSMPTEOffset, 0x05, 0, 0, 0, 0, 0 };

    _VAR_CAST;
    if (!IsFilePtrValid(pMF))
        return FALSE;
    if (!IsTrackValid(iTrack))
        return FALSE;

    if (iMins < 0 || iMins > 59)
        iMins = 0;
    if (iSecs < 0 || iSecs > 59)
        iSecs = 0;
    if (iFrames < 0 || iFrames > 24)
        iFrames = 0;

    tmp[3] = (BYTE) iHours;
    tmp[4] = (BYTE) iMins;
    tmp[5] = (BYTE) iSecs;
    tmp[6] = (BYTE) iFrames;
    tmp[7] = (BYTE) iFFrames;
    return midiTrackAddRaw(pMF, iTrack, sizeof(tmp), tmp, FALSE, 0);
}


BOOL midiSongAddSimpleTimeSig(MIDI_FILE *_pMF, int iTrack, int iNom,
                              int iDenom)
{
    return midiSongAddTimeSig(_pMF, iTrack, iNom, iDenom, 24, 8);
}

BOOL midiSongAddTimeSig(MIDI_FILE *_pMF, int iTrack, int iNom, int iDenom,
                        int iClockInMetroTick, int iNotated32nds)
{
    static BYTE tmp[] = { msgMetaEvent, metaTimeSig, 0x04, 0, 0, 0, 0 };

    _VAR_CAST;
    if (!IsFilePtrValid(pMF))
        return FALSE;
    if (!IsTrackValid(iTrack))
        return FALSE;

    tmp[3] = (BYTE) iNom;
    tmp[4] = (BYTE) (MIDI_NOTE_MINIM / iDenom);
    tmp[5] = (BYTE) iClockInMetroTick;
    tmp[6] = (BYTE) iNotated32nds;
    return midiTrackAddRaw(pMF, iTrack, sizeof(tmp), tmp, FALSE, 0);
}

BOOL midiSongAddKeySig(MIDI_FILE *_pMF, int iTrack, tMIDI_KEYSIG iKey)
{
    static BYTE tmp[] = { msgMetaEvent, metaKeySig, 0x02, 0, 0 };

    _VAR_CAST;
    if (!IsFilePtrValid(pMF))
        return FALSE;
    if (!IsTrackValid(iTrack))
        return FALSE;

    tmp[3] = (BYTE) ((iKey & keyMaskKey) * ((iKey & keyMaskNeg) ? -1 : 1));
    tmp[4] = (BYTE) ((iKey & keyMaskMin) ? 1 : 0);
    return midiTrackAddRaw(pMF, iTrack, sizeof(tmp), tmp, FALSE, 0);
}

BOOL midiSongAddTempo(MIDI_FILE *_pMF, int iTrack, int iTempo)
{
    static BYTE tmp[] = { msgMetaEvent, metaSetTempo, 0x03, 0, 0, 0 };
    int us;                     /* micro-seconds per qn */

    _VAR_CAST;
    if (!IsFilePtrValid(pMF))
        return FALSE;
    if (!IsTrackValid(iTrack))
        return FALSE;

    us = 60000000L / iTempo;
    tmp[3] = (BYTE) ((us >> 16) & 0xff);
    tmp[4] = (BYTE) ((us >> 8) & 0xff);
    tmp[5] = (BYTE) ((us >> 0) & 0xff);
    return midiTrackAddRaw(pMF, iTrack, sizeof(tmp), tmp, FALSE, 0);
}

BOOL midiSongAddMIDIPort(MIDI_FILE *_pMF, int iTrack, int iPort)
{
    static BYTE tmp[] = { msgMetaEvent, metaMIDIPort, 1, 0 };

    _VAR_CAST;
    if (!IsFilePtrValid(pMF))
        return FALSE;
    if (!IsTrackValid(iTrack))
        return FALSE;
    tmp[3] = (BYTE) iPort;
    return midiTrackAddRaw(pMF, iTrack, sizeof(tmp), tmp, FALSE, 0);
}

BOOL midiSongAddEndSequence(MIDI_FILE *_pMF, int iTrack)
{
    static BYTE tmp[] = { msgMetaEvent, metaEndSequence, 0 };

    _VAR_CAST;
    if (!IsFilePtrValid(pMF))
        return FALSE;
    if (!IsTrackValid(iTrack))
        return FALSE;

    return midiTrackAddRaw(pMF, iTrack, sizeof(tmp), tmp, FALSE, 0);
}


/*
** midiTrack* Functions
*/
BOOL midiTrackAddRaw(MIDI_FILE *_pMF, int iTrack, int data_sz,
                     const BYTE * pData, BOOL bMovePtr, int dt)
{
    MIDI_FILE_TRACK *pTrk;
    BYTE *ptr;
    int dtime;

    _VAR_CAST;
    if (!IsFilePtrValid(pMF))
        return FALSE;
    if (!IsTrackValid(iTrack))
        return FALSE;

    pTrk = &pMF->Track[iTrack];
    ptr = _midiGetPtr(pMF, iTrack, data_sz + DT_DEF);
    if (!ptr)
        return FALSE;

    dtime = pTrk->dt;
    if (bMovePtr)
        dtime += dt;

    ptr = _midiWriteVarLen(ptr, dtime);
    memcpy(ptr, pData, data_sz);

    pTrk->pos += dtime;
    pTrk->dt = 0;
    pTrk->ptr = ptr + data_sz;

    return TRUE;
}


BOOL midiTrackIncTime(MIDI_FILE *_pMF, int iTrack, int iDeltaTime,
                      BOOL bOverridePPQN)
{
    DWORD will_end_at;

    _VAR_CAST;
    if (!IsFilePtrValid(pMF))
        return FALSE;
    if (!IsTrackValid(iTrack))
        return FALSE;

    will_end_at = _midiGetLength(pMF->Header.PPQN, iDeltaTime, bOverridePPQN);
    will_end_at += pMF->Track[iTrack].pos + pMF->Track[iTrack].dt;

    midiFileFlushTrack(pMF, iTrack, FALSE, will_end_at);

    return TRUE;
}

BOOL midiTrackAddText(MIDI_FILE *_pMF, int iTrack, tMIDI_TEXT iType,
                      const char *pTxt)
{
    BYTE *ptr;
    int sz;

    _VAR_CAST;
    if (!IsFilePtrValid(pMF))
        return FALSE;
    if (!IsTrackValid(iTrack))
        return FALSE;

    sz = strlen(pTxt);
    if ((ptr = _midiGetPtr(pMF, iTrack, sz + DT_DEF)))
    {
        *ptr++ = 0;             /* delta-time=0 */
        *ptr++ = msgMetaEvent;
        *ptr++ = (BYTE) iType;
        ptr = _midiWriteVarLen((BYTE *) ptr, sz);
        strcpy((char *) ptr, pTxt);
        pMF->Track[iTrack].ptr = ptr + sz;
        return TRUE;
    }
    else
    {
        return FALSE;
    }
}

BOOL midiTrackSetKeyPressure(MIDI_FILE *pMF, int iTrack, int iNote,
                             int iAftertouch)
{
    return midiTrackAddMsg(pMF, iTrack, msgNoteKeyPressure, iNote,
                           iAftertouch);
}

BOOL midiTrackAddControlChange(MIDI_FILE *pMF, int iTrack, tMIDI_CC iCCType,
                               int iParam)
{
    return midiTrackAddMsg(pMF, iTrack, msgControlChange, iCCType, iParam);
}

BOOL midiTrackAddProgramChange(MIDI_FILE *pMF, int iTrack, int iInstrPatch)
{
    return midiTrackAddMsg(pMF, iTrack, msgSetProgram, iInstrPatch, 0);
}

BOOL midiTrackChangeKeyPressure(MIDI_FILE *pMF, int iTrack,
                                int iDeltaPressure)
{
    return midiTrackAddMsg(pMF, iTrack, msgChangePressure,
                           iDeltaPressure & 0x7f, 0);
}

BOOL midiTrackSetPitchWheel(MIDI_FILE *pMF, int iTrack, int iWheelPos)
{
    WORD wheel = (WORD) iWheelPos;

    /* bitshift 7 instead of eight because we're dealing with 7 bit numbers */
    wheel += MIDI_WHEEL_CENTRE;
    return midiTrackAddMsg(pMF, iTrack, msgSetPitchWheel, wheel & 0x7f,
                           (wheel >> 7) & 0x7f);
}

BOOL midiTrackAddMsg(MIDI_FILE *_pMF, int iTrack, tMIDI_MSG iMsg,
                     int iParam1, int iParam2)
{
    BYTE *ptr;
    BYTE data[3];
    int sz;

    _VAR_CAST;
    if (!IsFilePtrValid(pMF))
        return FALSE;
    if (!IsTrackValid(iTrack))
        return FALSE;
    if (!IsMessageValid(iMsg))
        return FALSE;

    ptr = _midiGetPtr(pMF, iTrack, DT_DEF);
    if (!ptr)
        return FALSE;

    data[0] = (BYTE) (iMsg | pMF->Track[iTrack].iDefaultChannel);
    data[1] = (BYTE) (iParam1 & 0x7f);
    data[2] = (BYTE) (iParam2 & 0x7f);
    /*
     ** Is this msg a single, or double BYTE, prm?
     */
    switch (iMsg)
    {
    case msgSetProgram:        /* only one byte required for these msgs */
    case msgChangePressure:
        sz = 2;
        break;

    default:                   /* double byte messages */
        sz = 3;
        break;
    }

    return midiTrackAddRaw(pMF, iTrack, sz, data, FALSE, 0);

}

BOOL midiTrackAddNote(MIDI_FILE *_pMF, int iTrack, int iNote, int iLength,
                      int iVol, BOOL bAutoInc, BOOL bOverrideLength)
{
    MIDI_FILE_TRACK *pTrk;
    BYTE *ptr;
    BOOL bSuccess = FALSE;
    int i, chn;

    _VAR_CAST;
    if (!IsFilePtrValid(pMF))
        return FALSE;
    if (!IsTrackValid(iTrack))
        return FALSE;
    if (!IsNoteValid(iNote))
        return FALSE;

    pTrk = &pMF->Track[iTrack];
    ptr = _midiGetPtr(pMF, iTrack, DT_DEF);
    if (!ptr)
        return FALSE;

    chn = pTrk->iDefaultChannel;
    iLength = _midiGetLength(pMF->Header.PPQN, iLength, bOverrideLength);

    for (i = 0; i < sizeof(pTrk->LastNote) / sizeof(pTrk->LastNote[0]); ++i)
        if (pTrk->LastNote[i].valid == FALSE)
        {
            pTrk->LastNote[i].note = (BYTE) iNote;
            pTrk->LastNote[i].chn = (BYTE) chn;
            pTrk->LastNote[i].end_pos = pTrk->pos + pTrk->dt + iLength;
            pTrk->LastNote[i].valid = TRUE;
            bSuccess = TRUE;

            ptr = _midiWriteVarLen(ptr, pTrk->dt);      /* delta-time */
            *ptr++ = (BYTE) (msgNoteOn | chn);
            *ptr++ = (BYTE) iNote;
            *ptr++ = (BYTE) iVol;
            break;
        }

    if (!bSuccess)
        return FALSE;

    pTrk->ptr = ptr;

    pTrk->pos += pTrk->dt;
    pTrk->dt = 0;

    if (bAutoInc)
        return midiTrackIncTime(pMF, iTrack, iLength, bOverrideLength);

    return TRUE;
}

BOOL midiTrackAddRest(MIDI_FILE *_pMF, int iTrack, int iLength,
                      BOOL bOverridePPQN)
{
    _VAR_CAST;
    if (!IsFilePtrValid(pMF))
        return FALSE;
    if (!IsTrackValid(iTrack))
        return FALSE;

    iLength = _midiGetLength(pMF->Header.PPQN, iLength, bOverridePPQN);
    return midiTrackIncTime(pMF, iTrack, iLength, bOverridePPQN);
}

int midiTrackGetEndPos(MIDI_FILE *_pMF, int iTrack)
{
    _VAR_CAST;
    if (!IsFilePtrValid(pMF))
        return FALSE;
    if (!IsTrackValid(iTrack))
        return FALSE;

    return pMF->Track[iTrack].pos;
}

/*
** midiRead* Functions
*/
static BYTE *_midiReadVarLen(BYTE * ptr, DWORD * num)
{
    register DWORD value;
    register BYTE c;

    if ((value = *ptr++) & 0x80)
    {
        value &= 0x7f;
        do
        {
            value = (value << 7) + ((c = *ptr++) & 0x7f);
        }
        while (c & 0x80);
    }
    *num = value;
    return (ptr);
}


static BOOL _midiReadTrackCopyData(MIDI_MSG * pMsg, BYTE * ptr, DWORD sz,
                                   BOOL bCopyPtrData)
{
    if (sz > pMsg->data_sz)
    {
        pMsg->data = (BYTE *) realloc(pMsg->data, sz);
        pMsg->data_sz = sz;
    }

    if (!pMsg->data)
        return FALSE;

    if (bCopyPtrData && ptr)
        memcpy(pMsg->data, ptr, sz);

    return TRUE;
}

int midiReadGetNumTracks(const MIDI_FILE *_pMF)
{
    _VAR_CAST;
    return pMF->Header.iNumTracks;
}

BOOL midiReadGetNextMessage(const MIDI_FILE *_pMF, int iTrack,
                            MIDI_MSG * pMsg)
{
    MIDI_FILE_TRACK *pTrack;
    BYTE *bptr, *pMsgDataPtr;
    int sz;

    _VAR_CAST;
    if (!IsTrackValid(iTrack))
        return FALSE;

    pTrack = &pMF->Track[iTrack];
    /* FIXME: Check if there is data on this track first!!! */
    if (pTrack->ptr >= pTrack->pEnd)
        return FALSE;

    pTrack->ptr = _midiReadVarLen(pTrack->ptr, &pMsg->dt);
    pTrack->pos += pMsg->dt;

    pMsg->dwAbsPos = pTrack->pos;

    if (*pTrack->ptr & 0x80)    /* Is this is sys message */
    {
        pMsg->iType = (tMIDI_MSG) ((*pTrack->ptr) & 0xf0);
        pMsgDataPtr = pTrack->ptr + 1;

        /* SysEx & Meta events don't carry channel info, but something
         ** important in their lower bits that we must keep */
        if (pMsg->iType == 0xf0)
            pMsg->iType = (tMIDI_MSG) (*pTrack->ptr);
    }
    else                        /* just data - so use the last msg type */
    {
        pMsg->iType = pMsg->iLastMsgType;
        pMsgDataPtr = pTrack->ptr;
    }

    pMsg->iLastMsgType = (tMIDI_MSG) pMsg->iType;
    pMsg->iLastMsgChnl = (BYTE) ((*pTrack->ptr) & 0x0f) + 1;

    switch (pMsg->iType)
    {
    case msgNoteOn:
        pMsg->MsgData.NoteOn.iChannel = pMsg->iLastMsgChnl;
        pMsg->MsgData.NoteOn.iNote = *(pMsgDataPtr);
        pMsg->MsgData.NoteOn.iVolume = *(pMsgDataPtr + 1);
        pMsg->iMsgSize = 3;
        break;

    case msgNoteOff:
        pMsg->MsgData.NoteOff.iChannel = pMsg->iLastMsgChnl;
        pMsg->MsgData.NoteOff.iNote = *(pMsgDataPtr);
        pMsg->iMsgSize = 3;
        break;

    case msgNoteKeyPressure:
        pMsg->MsgData.NoteKeyPressure.iChannel = pMsg->iLastMsgChnl;
        pMsg->MsgData.NoteKeyPressure.iNote = *(pMsgDataPtr);
        pMsg->MsgData.NoteKeyPressure.iPressure = *(pMsgDataPtr + 1);
        pMsg->iMsgSize = 3;
        break;

    case msgSetParameter:
        pMsg->MsgData.NoteParameter.iChannel = pMsg->iLastMsgChnl;
        pMsg->MsgData.NoteParameter.iControl = (tMIDI_CC) * (pMsgDataPtr);
        pMsg->MsgData.NoteParameter.iParam = *(pMsgDataPtr + 1);
        pMsg->iMsgSize = 3;
        break;

    case msgSetProgram:
        pMsg->MsgData.ChangeProgram.iChannel = pMsg->iLastMsgChnl;
        pMsg->MsgData.ChangeProgram.iProgram = *(pMsgDataPtr);
        pMsg->iMsgSize = 2;
        break;

    case msgChangePressure:
        pMsg->MsgData.ChangePressure.iChannel = pMsg->iLastMsgChnl;
        pMsg->MsgData.ChangePressure.iPressure = *(pMsgDataPtr);
        pMsg->iMsgSize = 2;
        break;

    case msgSetPitchWheel:
        pMsg->MsgData.PitchWheel.iChannel = pMsg->iLastMsgChnl;
        pMsg->MsgData.PitchWheel.iPitch =
            *(pMsgDataPtr) | (*(pMsgDataPtr + 1) << 7);
        pMsg->MsgData.PitchWheel.iPitch -= MIDI_WHEEL_CENTRE;
        pMsg->iMsgSize = 3;
        break;

    case msgMetaEvent:
        /* We can use 'pTrack->ptr' from now on, since meta events
         ** always have bit 7 set */
        bptr = pTrack->ptr;
        pMsg->MsgData.MetaEvent.iType = (tMIDI_META) * (pTrack->ptr + 1);
        pTrack->ptr = _midiReadVarLen(pTrack->ptr + 2, &pMsg->iMsgSize);
        sz = (pTrack->ptr - bptr) + pMsg->iMsgSize;

        if (_midiReadTrackCopyData(pMsg, pTrack->ptr, sz, FALSE) == FALSE)
            return FALSE;

        /* Now copy the data... */
        memcpy(pMsg->data, bptr, sz);

        /* Now place it in a neat structure */
        switch (pMsg->MsgData.MetaEvent.iType)
        {
        case metaMIDIPort:
            pMsg->MsgData.MetaEvent.Data.iMIDIPort = *(pTrack->ptr + 0);
            break;
        case metaSequenceNumber:
            pMsg->MsgData.MetaEvent.Data.iSequenceNumber = *(pTrack->ptr + 0);
            break;
        case metaTextEvent:
        case metaCopyright:
        case metaTrackName:
        case metaInstrument:
        case metaLyric:
        case metaMarker:
        case metaCuePoint:
            /* TODO - Add NULL terminator ??? */
            pMsg->MsgData.MetaEvent.Data.Text.pData = pTrack->ptr;
            break;
        case metaEndSequence:
            /* NO DATA */
            break;
        case metaSetTempo:
            {
                DWORD us =
                    ((*(pTrack->ptr + 0)) << 16) | ((*(pTrack->ptr + 1)) << 8)
                    | (*(pTrack->ptr + 2));
                pMsg->MsgData.MetaEvent.Data.Tempo.iBPM = 60000000L / us;
            }
            break;
        case metaSMPTEOffset:
            pMsg->MsgData.MetaEvent.Data.SMPTE.iHours = *(pTrack->ptr + 0);
            pMsg->MsgData.MetaEvent.Data.SMPTE.iMins = *(pTrack->ptr + 1);
            pMsg->MsgData.MetaEvent.Data.SMPTE.iSecs = *(pTrack->ptr + 2);
            pMsg->MsgData.MetaEvent.Data.SMPTE.iFrames = *(pTrack->ptr + 3);
            pMsg->MsgData.MetaEvent.Data.SMPTE.iFF = *(pTrack->ptr + 4);
            break;
        case metaTimeSig:
            pMsg->MsgData.MetaEvent.Data.TimeSig.iNom = *(pTrack->ptr + 0);
            pMsg->MsgData.MetaEvent.Data.TimeSig.iDenom =
                *(pTrack->ptr + 1) * MIDI_NOTE_MINIM;
            /* TODO: Variations without 24 & 8 */
            break;
        case metaKeySig:
            if (*pTrack->ptr & 0x80)
            {
                /* Do some trendy sign extending in reverse :) */
                pMsg->MsgData.MetaEvent.Data.KeySig.iKey =
                    ((256 - *pTrack->ptr) & keyMaskKey);
                pMsg->MsgData.MetaEvent.Data.KeySig.iKey |= keyMaskNeg;
            }
            else
            {
                pMsg->MsgData.MetaEvent.Data.KeySig.iKey =
                    (tMIDI_KEYSIG) (*pTrack->ptr & keyMaskKey);
            }
            if (*(pTrack->ptr + 1))
                pMsg->MsgData.MetaEvent.Data.KeySig.iKey |= keyMaskMin;
            break;
        case metaSequencerSpecific:
            pMsg->MsgData.MetaEvent.Data.Sequencer.iSize = pMsg->iMsgSize;
            pMsg->MsgData.MetaEvent.Data.Sequencer.pData = pTrack->ptr;
            break;
        }

        pTrack->ptr += pMsg->iMsgSize;
        pMsg->iMsgSize = sz;
        break;

    case msgSysEx1:
    case msgSysEx2:
        bptr = pTrack->ptr;
        pTrack->ptr = _midiReadVarLen(pTrack->ptr + 1, &pMsg->iMsgSize);
        sz = (pTrack->ptr - bptr) + pMsg->iMsgSize;

        if (_midiReadTrackCopyData(pMsg, pTrack->ptr, sz, FALSE) == FALSE)
            return FALSE;

        /* Now copy the data... */
        memcpy(pMsg->data, bptr, sz);
        pTrack->ptr += pMsg->iMsgSize;
        pMsg->iMsgSize = sz;
        pMsg->MsgData.SysEx.pData = pMsg->data;
        pMsg->MsgData.SysEx.iSize = sz;
        break;
    }
    /*
     ** Standard MIDI messages use a common copy routine
     */
    pMsg->bImpliedMsg = FALSE;
    if ((pMsg->iType & 0xf0) != 0xf0)
    {
        if (*pTrack->ptr & 0x80)
        {
        }
        else
        {
            pMsg->bImpliedMsg = TRUE;
            pMsg->iImpliedMsg = pMsg->iLastMsgType;
            pMsg->iMsgSize--;
        }
        _midiReadTrackCopyData(pMsg, pTrack->ptr, pMsg->iMsgSize, TRUE);
        pTrack->ptr += pMsg->iMsgSize;
    }
    return TRUE;
}

void midiReadInitMessage(MIDI_MSG * pMsg)
{
    pMsg->data = NULL;
    pMsg->data_sz = 0;
    pMsg->bImpliedMsg = FALSE;
}

void midiReadFreeMessage(MIDI_MSG * pMsg)
{
    if (pMsg->data)
        free((void *) pMsg->data);
    pMsg->data = NULL;
}