view src/midifile.c @ 54:7fd43d272c93 good64bit

Ahh .. yup.
author Matti Hamalainen <ccr@tnsp.org>
date Tue, 06 Aug 2013 15:57:30 +0300
parents a2f736d06b70
children 79977d487182
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 "midifile.h"
#include <stdio.h>
#include <string.h>
#include <stdarg.h>


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

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

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

    MIDI_LAST_NOTE LastNote[MAX_TRACK_POLYPHONY];
} MIDI_FILE_TRACK;

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

typedef struct
{
    FILE *pFile;
    BOOL bOpenForWriting;

    MIDI_HEADER Header;

    Uint8 *ptr, *curr, *end;
    size_t file_size;

    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 _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 void midiErrorV(_MIDI_FILE *pMF, const char *fmt, va_list ap)
{
    (void) pMF;
    printf("MIDI: ");
    vprintf(fmt, ap);
}


static void midiError(_MIDI_FILE *pMF, const char *fmt, ...)
{
    va_list ap;

    va_start(ap, fmt);
    midiErrorV(pMF, fmt, ap);
    va_end(ap);
}


static int midiStrNCmp(_MIDI_FILE *pMF, const char *str, const size_t n)
{
    if (pMF->curr + n < pMF->end)
        return memcmp(pMF->curr, str, n);
    else
        return 1;
}


static BOOL midiSkip(_MIDI_FILE *pMF, const size_t n)
{
    pMF->curr += n;
    return (pMF->curr < pMF->end);
}


static BOOL midiGetBE32(_MIDI_FILE *pMF, Uint32 *val)
{
    if (pMF->curr + sizeof(Uint32) < pMF->end)
    {
        Uint32 tmp;
        memcpy(&tmp, pMF->curr, sizeof(Uint32));
        *val = DM_BE32_TO_NATIVE(tmp);

        pMF->curr += sizeof(Uint32);
        return TRUE;
    }
    else
        return FALSE;
}


static BOOL midiGetBE16(_MIDI_FILE *pMF, Uint16 *val)
{
    if (pMF->curr + sizeof(Uint16) < pMF->end)
    {
        Uint16 tmp;
        memcpy(&tmp, pMF->curr, sizeof(Uint16));
        *val = DM_BE16_TO_NATIVE(tmp);

        pMF->curr += sizeof(Uint16);
        return TRUE;
    }
    else
        return FALSE;
}


static BOOL midiGetByte(_MIDI_FILE *pMF, Uint8 *val)
{
    if (pMF->curr + sizeof(Uint8) < pMF->end)
    {
        memcpy(val, pMF->curr, sizeof(Uint8));
        pMF->curr += sizeof(Uint8);
        return TRUE;
    }
    else
        return FALSE;
}


static void midiFree(void *ptr)
{
    if (ptr != NULL)
        free(ptr);
}


static BOOL midiWriteBE32(FILE *fp, const Uint32 val)
{
    Uint32 result = DM_NATIVE_TO_BE32(val);
    return (fwrite(&result, sizeof(result), 1, fp) == 1);
}


static BOOL midiWriteBE16(FILE *fp, const Uint16 val)
{
    Uint16 result = DM_NATIVE_TO_BE16(val);
    return (fwrite(&result, sizeof(result), 1, fp) == 1);
}


static BOOL midiWriteByte(FILE *fp, const Uint8 val)
{
    return fputc(val, fp) == val;
}


static BOOL midiWriteData(FILE *fp, const Uint8 *data, const size_t n)
{
    return fwrite(data, sizeof(Uint8), n, fp) == n;
}


static BOOL midiWriteStr(FILE *fp, const char *str)
{
    size_t len = strlen(str);
    return fwrite(str, sizeof(char), strlen(str), fp) == len;
}


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 Uint8 *_midiWriteVarLen(Uint8 * 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++ = (Uint8) 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 Uint8 *_midiGetPtr(_MIDI_FILE *pMF, int iTrack, int sz_reqd)
{
    const Uint32 mem_sz_inc = 8092;      /* arbitary */
    Uint8 *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 =
             (Uint8 *) 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 == NULL)
        return NULL;

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

    if ((pMF->pFile = fopen(pFilename, "wb+")) == NULL)
    {
        midiFree(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 = (Uint8) (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 = (Uint8) (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 = (Uint16) 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 = (Uint16) 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)
{
    _MIDI_FILE *pMF = NULL;
    BOOL bValidFile = FALSE;
    FILE *fp;
    int i;

    if ((fp = fopen(pFilename, "rb")) == NULL)
        goto error;

    if ((pMF = (_MIDI_FILE *) malloc(sizeof(_MIDI_FILE))) == NULL)
        goto error;

    fseek(fp, 0L, SEEK_END);
    pMF->file_size = ftell(fp);

    if ((pMF->curr = pMF->ptr = (Uint8 *) malloc(pMF->file_size)) == NULL)
    {
        midiError(pMF, "Could not allocate %d bytes for MIDI file.\n", pMF->file_size);
        goto error;
    }

    pMF->end = pMF->ptr + pMF->file_size;

    fseek(fp, 0L, SEEK_SET);
    if (fread(pMF->ptr, sizeof(Uint8), pMF->file_size, fp) != pMF->file_size)
    {
        midiError(pMF, "Error reading file data.\n");
        goto error;
    }

    /* Is this a valid MIDI file ? */
    if (midiStrNCmp(pMF, "MThd", 4) != 0)
    {
        midiError(pMF, "Not a MIDI file.\n");
        goto error;
    }

    midiSkip(pMF, 4);

    if (!midiGetBE32(pMF, &pMF->Header.iHeaderSize) ||
        !midiGetBE16(pMF, &pMF->Header.iVersion) ||
        !midiGetBE16(pMF, &pMF->Header.iNumTracks) ||
        !midiGetBE16(pMF, &pMF->Header.PPQN))
    {
        midiError(pMF, "Error reading MIDI file header.\n");
        goto error;
    }

    midiSkip(pMF, pMF->Header.iHeaderSize - 3 * sizeof(Uint16));

    /*
     **      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)
    {
        if (midiStrNCmp(pMF, "MTrk", 4) != 0)
        {
            midiError(pMF, "Expected track %d data, not found.\n", i);
            goto error;
        }
        
        pMF->Track[i].pBase = pMF->curr;

        if (!midiSkip(pMF, 4) ||
            !midiGetBE32(pMF, &pMF->Track[i].sz))
        {
            midiError(pMF, "Could not read MTrk size.\n");
            goto error;
        }

        pMF->Track[i].ptr = pMF->curr;
        pMF->Track[i].pEnd = pMF->curr + pMF->Track[i].sz;

        midiSkip(pMF, pMF->Track[i].sz);
    }

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


error:
    // Cleanup
    if (fp != NULL)
        fclose(fp);

    if (!bValidFile)
    {
        midiFree(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,
                        Uint32 dwEndTimePos)
{
    size_t sz, index;
    Uint8 *ptr;
    MIDI_END_POINT *pEndPoints;
    int num, 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 (index = 0; index < sz; index++)
    {
        if (pMF->Track[iTrack].LastNote[index].valid)
        {
            pEndPoints[mx_pts].iIdx = index;
            pEndPoints[mx_pts].iEndPos = pMF->Track[iTrack].LastNote[index].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);

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

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

            ptr = _midiWriteVarLen(ptr,
                pMF->Track[iTrack].LastNote[num].end_pos -
                pMF->Track[iTrack].pos);

            /* msgNoteOn  msgNoteOff */
            *ptr++ = (Uint8) (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;

            //bNoChanges = FALSE;
            n++;
        }
    }

    midiFree(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)
    {
        Uint16 iNumTracks = 0;
        int i;

        /* 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
         */
        if (!midiWriteStr(pMF->pFile, "MThd") ||
            !midiWriteBE32(pMF->pFile, 6) ||
            !midiWriteBE16(pMF->pFile, iNumTracks == 1 ? pMF->Header.iVersion : 1) ||
            !midiWriteBE16(pMF->pFile, iNumTracks) ||
            !midiWriteBE16(pMF->pFile, pMF->Header.PPQN))
        {
            midiError(pMF, "Could not write MThd header.\n");
            goto error;
        }

        /*
         ** Track data
         */
        for (i = 0; i < MAX_MIDI_TRACKS; ++i)
        {
            if (pMF->Track[i].ptr)
            {
                size_t sz = pMF->Track[i].ptr - pMF->Track[i].pBase;
                if (!midiWriteStr(pMF->pFile, "MTrk") ||
                    !midiWriteBE32(pMF->pFile, sz) ||
                    !midiWriteData(pMF->pFile, pMF->Track[i].pBase, sz))
                {
                    midiError(pMF, "Could not write track #%d data.\n", i);
                    goto error;
                }

                midiFree(pMF->Track[i].pBase);
            }
        }
    }

error:
    if (pMF->pFile != NULL)
        return fclose(pMF->pFile) ? FALSE : TRUE;

    midiFree(pMF);
    return TRUE;
}


/*
** midiSong* Functions
*/
BOOL midiSongAddSMPTEOffset(MIDI_FILE *_pMF, int iTrack, int iHours,
                            int iMins, int iSecs, int iFrames, int iFFrames)
{
    static Uint8 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] = (Uint8) iHours;
    tmp[4] = (Uint8) iMins;
    tmp[5] = (Uint8) iSecs;
    tmp[6] = (Uint8) iFrames;
    tmp[7] = (Uint8) 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 Uint8 tmp[] = { msgMetaEvent, metaTimeSig, 0x04, 0, 0, 0, 0 };

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

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

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

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

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

BOOL midiSongAddTempo(MIDI_FILE *_pMF, int iTrack, int iTempo)
{
    static Uint8 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] = (Uint8) ((us >> 16) & 0xff);
    tmp[4] = (Uint8) ((us >> 8) & 0xff);
    tmp[5] = (Uint8) ((us >> 0) & 0xff);
    return midiTrackAddRaw(pMF, iTrack, sizeof(tmp), tmp, FALSE, 0);
}

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

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

BOOL midiSongAddEndSequence(MIDI_FILE *_pMF, int iTrack)
{
    static Uint8 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 Uint8 * pData, BOOL bMovePtr, int dt)
{
    MIDI_FILE_TRACK *pTrk;
    Uint8 *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)
{
    Uint32 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)
{
    Uint8 *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++ = (Uint8) iType;
        ptr = _midiWriteVarLen((Uint8 *) 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)
{
    Uint16 wheel = (Uint16) 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)
{
    Uint8 *ptr;
    Uint8 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] = (Uint8) (iMsg | pMF->Track[iTrack].iDefaultChannel);
    data[1] = (Uint8) (iParam1 & 0x7f);
    data[2] = (Uint8) (iParam2 & 0x7f);
    /*
     ** Is this msg a single, or double Uint8, 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;
    Uint8 *ptr;
    BOOL bSuccess = FALSE;
    int chn;
    size_t i;

    _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 = (Uint8) iNote;
            pTrk->LastNote[i].chn = (Uint8) 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++ = (Uint8) (msgNoteOn | chn);
            *ptr++ = (Uint8) iNote;
            *ptr++ = (Uint8) 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 Uint8 *_midiReadVarLen(Uint8 * ptr, Uint32 * num)
{
    register Uint32 value;
    register Uint8 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, Uint8 * ptr, Uint32 sz,
                                   BOOL bCopyPtrData)
{
    if (sz > pMsg->data_sz)
    {
        pMsg->data = (Uint8 *) 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;
    Uint8 *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 = (Uint8) ((*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:
            {
                Uint32 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 = (tMIDI_KEYSIG) (((256 - *pTrack->ptr) & keyMaskKey) | keyMaskNeg);
            }
            else
            {
                pMsg->MsgData.MetaEvent.Data.KeySig.iKey = (tMIDI_KEYSIG) (*pTrack->ptr & keyMaskKey);
            }
            if (*(pTrack->ptr + 1))
                pMsg->MsgData.MetaEvent.Data.KeySig.iKey = (tMIDI_KEYSIG) (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)
{
    midiFree(pMsg->data);
    pMsg->data = NULL;
}