view jloadjss.c @ 510:43ea59887c69

Start work on making C64 formats encoding possible by changing DMDecodeOps to DMEncDecOps and adding fields and op enums for custom encode functions, renaming, etc. Split generic op sanity checking into a separate function in preparation for its use in generic encoding function.
author Matti Hamalainen <ccr@tnsp.org>
date Mon, 19 Nov 2012 15:06:01 +0200
parents cd57ba1130eb
children
line wrap: on
line source

/*
 * miniJSS - JSSMOD module loader
 * Programmed and designed by Matti 'ccr' Hamalainen
 * (C) Copyright 2007-2009 Tecnic Software productions (TNSP)
 */
#include "jssmod.h"
#include <string.h>


#ifdef JM_SUP_PATMODE_ALL
#    define JM_SUP_PATMODE_1 1
#    define JM_SUP_PATMODE_2 1
#    define JM_SUP_PATMODE_3 1
#    define JM_SUP_PATMODE_4 1
#    define JM_SUP_PATMODE_5 1
#endif


static BOOL jsGetBufData(Uint8 **buf, size_t *bufLeft, void *data, const size_t dataSize)
{
    if (*bufLeft >= dataSize)
    {
        memcpy(data, *buf, dataSize);
        *buf += dataSize;
        *bufLeft -= dataSize;
        return TRUE;
    }
    else
        return FALSE;
}


static BOOL jsGetBufByte(Uint8 **buf, size_t *bufLeft, Uint8 *data)
{
    if (*bufLeft > 0)
    {
        *data = **buf;
        (*buf)++;
        (*bufLeft)--;
        return TRUE;
    }
    else
        return FALSE;
}


#define JSGETBUF(XV, XT) if (!jsGetBufData(buf, bufLeft, XV, sizeof(XT))) return DMERR_OUT_OF_DATA
#define JSGETBYTE(XV) if (!jsGetBufByte(buf, bufLeft, XV)) return DMERR_OUT_OF_DATA


#if defined(JM_SUP_PATMODE_1) || defined(JM_SUP_PATMODE_3)
static int jssGetConvertedNote(Uint8 **buf, size_t *bufLeft, JSSNote *note)
{
    Uint8 tmp;

    JSGETBYTE(&tmp);

    if (tmp == 127)
        note->note = jsetNoteOff;
    else if (tmp == 0)
        note->note = jsetNotSet;
    else
        note->note = tmp - 1;

    JSGETBYTE(&tmp);
    note->instrument = (tmp > 0) ? tmp - 1 : jsetNotSet;

    JSGETBYTE(&tmp);
    note->volume = (tmp > 0) ? tmp - 1 : jsetNotSet;

    JSGETBYTE(&tmp);
    note->effect = (tmp > 0) ? tmp - 1 : jsetNotSet;

    JSGETBYTE(&tmp);
    note->param = (tmp == 0 && note->effect == jsetNotSet) ? jsetNotSet : tmp;

    return DMERR_OK;
}
#endif


#if defined(JM_SUP_PATMODE_2) || defined(JM_SUP_PATMODE_4)
static int jssGetCompressedNote(Uint8 **buf, size_t *bufLeft, JSSNote *note)
{
    Uint8 packb, tmp;

    JSGETBYTE(&packb);
    if (packb & 0x80)
    {
        if (packb & COMP_NOTE)
        {
            JSGETBYTE(&tmp);
            if (tmp == 127)
                note->note = jsetNoteOff;
            else
                note->note = tmp;
        }

        if (packb & COMP_INSTRUMENT)
        {
            JSGETBYTE(&tmp);
            note->instrument = tmp;
        }

        if (packb & COMP_VOLUME)
        {
            JSGETBYTE(&tmp);
            note->volume = tmp;
        }

        if (packb & COMP_EFFECT)
        {
            JSGETBYTE(&tmp);
            note->effect = tmp;
            note->param = 0;
        }

        if (packb & COMP_PARAM)
        {
            JSGETBYTE(&tmp);
            note->param = tmp;
        }
    }
    else
    {
        tmp = packb;

        if (tmp == 127)
            note->note = jsetNoteOff;
        else if (tmp == 0)
            note->note = jsetNotSet;
        else
            note->note = tmp - 1;

        JSGETBYTE(&tmp);
        note->instrument = (tmp > 0) ? tmp - 1 : jsetNotSet;

        JSGETBYTE(&tmp);
        note->volume = (tmp > 0) ? tmp - 1 : jsetNotSet;

        JSGETBYTE(&tmp);
        note->effect = (tmp > 0) ? tmp - 1 : jsetNotSet;

        JSGETBYTE(&tmp);
        note->param = (tmp == 0 && note->effect == jsetNotSet) ? jsetNotSet : tmp;
    }

    return DMERR_OK;
}
#endif


#ifdef JM_SUP_PATMODE_2
static int jssGetPatternCompHoriz(Uint8 *buf, size_t *bufLeft, JSSPattern *pattern)
{
    int row, channel;

    assert(buf != NULL);
    assert(pattern != NULL);

    for (row = 0; row < pattern->nrows; row++)
    for (channel = 0; channel < pattern->nchannels; channel++)
    {
        JSSNote *note = &pattern->data[(pattern->nchannels * row) + channel];
        int res = jssGetCompressedNote(&buf, bufLeft, note);
        if (res != DMERR_OK)
            JSSERROR(res, res, "Error uncompressing note on row=%d, chn=%d\n", row, channel);
    }

    return DMERR_OK;
}
#endif


#ifdef JM_SUP_PATMODE_4
static int jssGetPatternCompVert(Uint8 *buf, size_t *bufLeft, JSSPattern *pattern)
{
    int row, channel;

    assert(buf != NULL);
    assert(pattern != NULL);

    for (channel = 0; channel < pattern->nchannels; channel++)
    for (row = 0; row < pattern->nrows; row++)
    {
        JSSNote *note = &pattern->data[(pattern->nchannels * row) + channel];
        int res = jssGetCompressedNote(&buf, bufLeft, note);
        if (res != DMERR_OK)
            JSSERROR(res, res, "Error uncompressing note on row=%d, chn=%d\n", row, channel);
    }

    return DMERR_OK;
}
#endif


#ifdef JM_SUP_PATMODE_1
static int jssGetPatternRawHoriz(Uint8 *buf, size_t *bufLeft, JSSPattern *pattern)
{
    int row, channel;

    assert(buf != NULL);
    assert(pattern != NULL);

    for (row = 0; row < pattern->nrows; row++)
    for (channel = 0; channel < pattern->nchannels; channel++)
    {
        JSSNote *note = &pattern->data[(pattern->nchannels * row) + channel];
        int res = jssGetConvertedNote(&buf, bufLeft, note);
        if (res != DMERR_OK)
            JSSERROR(res, res, "Error converting note on row=%d, chn=%d\n", row, channel);
    }

    return DMERR_OK;
}
#endif


#ifdef JM_SUP_PATMODE_3
static int jssGetPatternRawVert(Uint8 *buf, size_t *bufLeft, JSSPattern *pattern)
{
    int row, channel;

    assert(buf != NULL);
    assert(pattern != NULL);

    for (channel = 0; channel < pattern->nchannels; channel++)
    for (row = 0; row < pattern->nrows; row++)
    {
        JSSNote *note = &pattern->data[(pattern->nchannels * row) + channel];
        int res = jssGetConvertedNote(&buf, bufLeft, note);
        if (res != DMERR_OK)
            JSSERROR(res, res, "Error converting note on row=%d, chn=%d\n", row, channel);
    }

    return DMERR_OK;
}
#endif


#ifdef JM_SUP_PATMODE_5

#undef JSGETBYTE
#define JSGETBYTE(XV) if (!jsGetBufByte(&buf, bufLeft, XV)) return DMERR_OUT_OF_DATA

#define JSFOREACHNOTE1                                                              \
  for (channel = 0; channel < pattern->nchannels; channel++)                        \
  for (row = 0; row < pattern->nrows; row++) {                                      \
      JSSNote *note = pattern->data + (pattern->nchannels * row) + channel;

#define JSFOREACHNOTE2 }

static int jssGetPatternRawVertElem(Uint8 *buf, size_t *bufLeft, JSSPattern *pattern)
{
    int row, channel;
    Uint8 tmp;

    assert(buf != NULL);
    assert(pattern != NULL);

    JSFOREACHNOTE1
    JSGETBYTE(&tmp);
    if (tmp == 0)
        note->note = jsetNotSet;
    else if (tmp == 127)
        note->note = jsetNoteOff;
    else
        note->note = tmp - 1;
    JSFOREACHNOTE2
    
    JSFOREACHNOTE1
    JSGETBYTE(&tmp);
    note->instrument = (tmp > 0) ? tmp - 1 : jsetNotSet;
    JSFOREACHNOTE2
    
    JSFOREACHNOTE1
    JSGETBYTE(&tmp);
    note->volume = (tmp > 0) ? tmp - 1 : jsetNotSet;
    JSFOREACHNOTE2
    
    JSFOREACHNOTE1
    JSGETBYTE(&tmp);
    note->effect = (tmp > 0) ? tmp - 1 : jsetNotSet;
    JSFOREACHNOTE2
    
    JSFOREACHNOTE1
    JSGETBYTE(&tmp);
    note->param = (tmp == 0 && note->effect == jsetNotSet) ? jsetNotSet : tmp;
    JSFOREACHNOTE2

    return DMERR_OK;
}
#endif


#undef JSGETBUF
#undef JSGETBYTE
#define JSGETBUF(XV, XT) do { \
    if (!jsGetBufData(&buf, &bufLeft, XV, sizeof(XT)))  \
        JSSERROR(DMERR_OUT_OF_DATA, DMERR_OUT_OF_DATA,    \
        "Out of data at getting " # XT " (%d bytes)\n", sizeof(XT)); \
} while (0)
#define JSGETBYTE(XV) if (!jsGetBufByte(&buf, &bufLeft, XV)) return DMERR_OUT_OF_DATA


#ifdef JM_SUP_EXT_INSTR
static void jssCopyEnvelope(JSSEnvelope *e, JSSMODEnvelope *je)
{
    int i;

    e->flags   = je->flags;
    e->npoints = je->npoints;
    e->sustain = je->sustain;
    e->loopS   = je->loopS;
    e->loopE   = je->loopE;

    for (i = 0; i < je->npoints; i++)
    {
        e->points[i].frame = je->points[i].frame;
        e->points[i].value = je->points[i].value;
    }
}
#endif


int jssLoadJSSMOD(Uint8 *bufStart, const size_t bufSize, JSSModule **ppModule)
{
    JSSModule *module;
    JSSMODHeader jssH;
    Uint8 *buf = bufStart;
    size_t bufLeft = bufSize;
    int index;

    assert(ppModule != NULL);
    assert(bufStart != NULL);
    *ppModule = NULL;

    // Check the JSSMOD header
    memset(&jssH, 0, sizeof(jssH));
    JSGETBUF(&jssH, JSSMODHeader);

    if (memcmp(jssH.idMagic, "JM", 2) != 0)
    {
        JSSERROR(DMERR_INVALID_DATA, DMERR_INVALID_DATA,
        "Not a valid JSSMOD file, header signature missing!\n");
    }

    if (jssH.idVersion != JSSMOD_VERSION)
    {
        JSSERROR(DMERR_VERSION, DMERR_VERSION,
        "Unsupported version of JSSMOD 0x%4x, this version only supports 0x%4x!\n",
        jssH.idVersion, JSSMOD_VERSION);
    }

    // Allocate the module
    module = jssAllocateModule();
    if (module == NULL)
    {
        JSSERROR(DMERR_MALLOC, DMERR_MALLOC,
        "Could not allocate memory for module structure.\n");
    }
    *ppModule = module;

    // Copy header information
    module->norders         = jssH.norders;
    module->npatterns       = jssH.npatterns;
    module->nchannels       = jssH.nchannels;
    module->nextInstruments = jssH.nextInstruments;
    module->ninstruments    = jssH.ninstruments;
    module->defFlags        = jssH.defFlags;
    module->intVersion      = jssH.intVersion;
    module->defRestartPos   = jssH.defRestartPos;
    module->defSpeed        = jssH.defSpeed;
    module->defTempo        = jssH.defTempo;

    // Get the orders list
    for (index = 0; index < module->norders; index++)
    {
        Sint16 order;
        JSGETBUF(&order, Sint16);
        module->orderList[index] = order;
    }

    // Parse the patterns
    for (index = 0; index < module->npatterns; index++)
    {
        JSSMODPattern jssP;
        int result = DMERR_INVALID_DATA;
        size_t bufSize;

        // Get header and check size
        memset(&jssP, 0, sizeof(jssP));
        JSGETBUF(&jssP, JSSMODPattern);
        bufSize = jssP.size;
        if (bufLeft < jssP.size)
        {
            JSSERROR(DMERR_OUT_OF_DATA, DMERR_OUT_OF_DATA,
            "Out of data for pattern #%d.\n", index);
        }

        // Allocate pattern
        module->patterns[index] = jssAllocatePattern(jssP.nrows, module->nchannels);
        if (module->patterns[index] == NULL)
        {
            JSSERROR(DMERR_MALLOC, DMERR_MALLOC,
            "Could not allocate memory for pattern #%d.\n", index);
        }

        // Get pattern data
        switch (jssH.patMode)
        {
#ifdef JM_SUP_PATMODE_1
            case PATMODE_RAW_HORIZ:
                result = jssGetPatternRawHoriz(buf, &bufSize, module->patterns[index]);
                break;
#endif
#ifdef JM_SUP_PATMODE_2
            case PATMODE_COMP_HORIZ:
                result = jssGetPatternCompHoriz(buf, &bufSize, module->patterns[index]);
                break;
#endif
#ifdef JM_SUP_PATMODE_3
            case PATMODE_RAW_VERT:
                result = jssGetPatternRawVert(buf, &bufSize, module->patterns[index]);
                break;
#endif
#ifdef JM_SUP_PATMODE_4
            case PATMODE_COMP_VERT:
                result = jssGetPatternCompVert(buf, &bufSize, module->patterns[index]);
                break;
#endif
#ifdef JM_SUP_PATMODE_5
            case PATMODE_RAW_ELEM:
                result = jssGetPatternRawVertElem(buf, &bufSize, module->patterns[index]);
                break;
#endif
            default:
                JSSERROR(DMERR_INVALID_DATA, DMERR_INVALID_DATA,
                "Unsupported pattern mode %d. Check compilation options.", jssH.patMode);
                break;
        }

        if (bufSize > 0)
        {
            JSSWARNING(DMERR_EXTRA_DATA, DMERR_EXTRA_DATA,
            "Unparsed data after pattern (%d bytes), possibly broken file.\n", bufSize);
        }

        if (result != DMERR_OK)
        {
            JSSERROR(result, result, "Error in unpacking pattern #%i data\n", index);
        }

        buf += jssP.size;
        bufLeft -= jssP.size;
    }

#ifdef JM_SUP_EXT_INSTR
    // Read extended instruments
    for (index = 0; index < module->nextInstruments; index++)
    {
        JSSMODExtInstrument jssE;
        JSSExtInstrument *einst;
        int i;

        memset(&jssE, 0, sizeof(jssE));
        JSGETBUF(&jssE, JSSMODExtInstrument);

        if ((einst = jssAllocateExtInstrument()) == NULL)
        {
            JSSERROR(DMERR_MALLOC, DMERR_MALLOC,
            "Could not allocate extended instrument structure #%i\n", index);
        }

        module->extInstruments[index] = einst;

        einst->nsamples     = jssE.nsamples;
        einst->vibratoType  = jssE.vibratoType;
        einst->vibratoSweep = jssE.vibratoSweep;
        einst->vibratoDepth = jssE.vibratoDepth;
        einst->vibratoRate  = jssE.vibratoRate;
        einst->fadeOut      = jssE.fadeOut;

        for (i = 0; i < jsetNNotes; i++)
        {
            int snum = jssE.sNumForNotes[i];
            einst->sNumForNotes[i] = (snum > 0) ? snum : jsetNotSet;
        }

        jssCopyEnvelope(&(einst->volumeEnv), &jssE.volumeEnv);
        jssCopyEnvelope(&(einst->panningEnv), &jssE.panningEnv);
    }

#ifdef JM_SUP_INSTR
    // Read sample instrument headers
    for (index = 0; index < module->ninstruments; index++)
    {
        JSSMODInstrument jssI;
        JSSInstrument *inst;

        memset(&jssI, 0, sizeof(jssI));
        JSGETBUF(&jssI, JSSMODInstrument);

        if ((inst = jssAllocateInstrument()) == NULL)
        {
            JSSERROR(DMERR_MALLOC, DMERR_MALLOC,
            "Could not allocate instrument structure #%i\n", index);
        }

        module->instruments[index] = inst;

        inst->size          = jssI.size;
        inst->loopS         = jssI.loopS;
        inst->loopE         = jssI.loopE;
        inst->volume        = jssI.volume;
        inst->flags         = jssI.flags;
        inst->C4BaseSpeed   = jssI.C4BaseSpeed;
        inst->ERelNote      = jssI.ERelNote;
        inst->EFineTune     = jssI.EFineTune;
        inst->EPanning      = jssI.EPanning;
        inst->hasData       = jssI.hasData;
        inst->convFlags     = jssI.convFlags;
    }

#ifdef JM_SUP_SAMPLES
    // Read sample data
    for (index = 0; index < module->ninstruments; index++)
    {
        JSSInstrument *inst = module->instruments[index];

        if (inst && inst->hasData)
        {
            size_t sz;

            // Calculate data size
            if (inst->flags & jsf16bit)
                sz = inst->size * sizeof(Uint16);
            else
                sz = inst->size * sizeof(Uint8);

            // Check if we can get as much?
            if (bufLeft < sz)
            {
                JSSERROR(DMERR_OUT_OF_DATA, DMERR_OUT_OF_DATA,
                "Out of data for instrument sample #%d (%d < %d)\n",
                index, bufLeft, sz);
            }

            // Allocate
            if ((inst->data = dmMalloc(sz)) == NULL)
            {
                JSSERROR(DMERR_MALLOC, DMERR_MALLOC,
                "Could not allocate sample data #%d\n", index);
            }

            // Copy data
            memcpy(inst->data, buf, sz);
            buf += sz;
            bufLeft -= sz;

            // Convert, if needed
            if (inst->flags & jsf16bit)
                jssDecodeSample16(inst->data, inst->size, inst->convFlags);
            else
                jssDecodeSample8(inst->data, inst->size, inst->convFlags);
        }
    }
#else
#    warning Not including JSSMOD sample loading!
#endif  // JM_SUP_SAMPLES
#else
#    warning Not including JSSMOD instrument loading!
#endif  // JM_SUP_INSTR
#else
#    warning Not including JSSMOD ext.instrument loading!
#endif  // JM_SUP_EXT_INSTR

    return DMERR_OK;
}