view minijss/jloadjss.c @ 1896:f80b2dc77c30

Work begins on IFF ILBM/PBM image writer. It is pretty broken, some things will not work and some things are hardcoded. The ByteRun1 compression implementation is somewhat inefficient. Interleaved files do not work yet.
author Matti Hamalainen <ccr@tnsp.org>
date Tue, 26 Jun 2018 03:13:38 +0300
parents ca9fe688ab6b
children 40ccc09f09be
line wrap: on
line source

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


// If all pattern modes are required, enable them here
#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


// Short helper macros for reading data
#define JSGETBYTE(XV) \
    if (!dmf_read_byte(inFile, XV)) \
        return DMERR_OUT_OF_DATA


static int jssDoGetConvertedNote(DMResource *inFile, JSSNote *pnote, Uint8 note)
{
    Uint8 tmp;

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

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

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

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

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

    return DMERR_OK;
}


static inline int jssGetConvertedNote(DMResource *inFile, JSSNote *pnote)
{
    Uint8 tmp;
    JSGETBYTE(&tmp);
    return jssDoGetConvertedNote(inFile, pnote, tmp);
}


#if defined(JM_SUP_PATMODE_2) || defined(JM_SUP_PATMODE_4)
static int jssGetCompressedNote(DMResource *inFile, JSSNote *pnote)
{
    Uint8 packb, tmp;

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

        if (packb & JM_COMP_INSTRUMENT)
        {
            JSGETBYTE(&tmp);
            pnote->instrument = tmp;
        }

        if (packb & JM_COMP_VOLUME)
        {
            JSGETBYTE(&tmp);
            pnote->volume = tmp;
        }

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

        if (packb & JM_COMP_PARAM)
        {
            JSGETBYTE(&tmp);
            pnote->param = tmp;
        }
    }
    else
    {
        int ret;
        if ((ret = jssDoGetConvertedNote(inFile, pnote, packb)) != DMERR_OK)
            return ret;
    }

    return DMERR_OK;
}
#endif


#ifdef JM_SUP_PATMODE_2
static int jssGetPatternCompHoriz(DMResource *inFile, 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 *pnote = &pattern->data[(pattern->nchannels * row) + channel];
        int res = jssGetCompressedNote(inFile, pnote);
        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(DMResource *inFile, 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 *pnote = &pattern->data[(pattern->nchannels * row) + channel];
        int res = jssGetCompressedNote(inFile, pnote);
        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(DMResource *inFile, 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 *pnote = &pattern->data[(pattern->nchannels * row) + channel];
        int res = jssGetConvertedNote(inFile, pnote);
        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(DMResource *inFile, 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 *pnote = &pattern->data[(pattern->nchannels * row) + channel];
        int res = jssGetConvertedNote(inFile, pnote);
        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 (!dmf_read_byte(inFile, XV)) return DMERR_OUT_OF_DATA

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

#define JSFOREACHNOTE2 }

static int jssGetPatternRawVertElem(DMResource *inFile, JSSPattern *pattern)
{
    int row, channel;
    Uint8 tmp;

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

    JSFOREACHNOTE1
    JSGETBYTE(&tmp);
    if (tmp == 0)
        pnote->note = jsetNotSet;
    else if (tmp == 127)
        pnote->note = jsetNoteOff;
    else
        pnote->note = tmp - 1;
    JSFOREACHNOTE2

    JSFOREACHNOTE1
    JSGETBYTE(&tmp);
    pnote->instrument = (tmp > 0) ? tmp - 1 : jsetNotSet;
    JSFOREACHNOTE2

    JSFOREACHNOTE1
    JSGETBYTE(&tmp);
    pnote->volume = (tmp > 0) ? tmp - 1 : jsetNotSet;
    JSFOREACHNOTE2

    JSFOREACHNOTE1
    JSGETBYTE(&tmp);
    pnote->effect = (tmp > 0) ? tmp - 1 : jsetNotSet;
    JSFOREACHNOTE2

    JSFOREACHNOTE1
    JSGETBYTE(&tmp);
    pnote->param = (tmp == 0 && pnote->effect == jsetNotSet) ? jsetNotSet : tmp;
    JSFOREACHNOTE2

    return DMERR_OK;
}
#endif


#ifdef JM_SUP_EXT_INSTR
static int jssMODLoadEnvelope(DMResource *inFile,
    JSSEnvelope *env, const char *name, const int ninst)
{
    JSSMODEnvelope jssEnv;

    (void) name;
    (void) ninst;

    // Read envelope data
    if (!dmf_read_byte(inFile, &jssEnv.flags) ||
        !dmf_read_byte(inFile, &jssEnv.npoints) ||
        !dmf_read_byte(inFile, &jssEnv.sustain) ||
        !dmf_read_byte(inFile, &jssEnv.loopS) ||
        !dmf_read_byte(inFile, &jssEnv.loopE))
    {
        JSSERROR(DMERR_FREAD, DMERR_FREAD,
        "Failed to read %s-envelope data for instrument #%d.\n",
        name, ninst);
    }

    // Do some sanity checking
    if (jssEnv.npoints > jsetMaxEnvPoints ||
        jssEnv.loopS > jssEnv.loopE ||
        jssEnv.loopS > jssEnv.npoints ||
        jssEnv.loopE > jssEnv.npoints)
    {
        JSSERROR(DMERR_INVALID_DATA, DMERR_INVALID_DATA,
        "Invalid values in %s-envelope for instrument #%d.\n",
        name, ninst);
    }

    // Copy data
    env->flags   = jssEnv.flags;
    env->npoints = jssEnv.npoints;
    env->sustain = jssEnv.sustain;
    env->loopS   = jssEnv.loopS;
    env->loopE   = jssEnv.loopE;

    for (int i = 0; i < jssEnv.npoints; i++)
    {
        Uint16 frame, value;
        if (!dmf_read_le16(inFile, &frame) ||
            !dmf_read_le16(inFile, &value))
        {
            JSSERROR(DMERR_FREAD, DMERR_FREAD,
            "Failed to read %s-envelope values (%d) for instrument #%d.\n",
            name, i, ninst);
        }

        env->points[i].frame = frame;
        env->points[i].value = value;
    }

    return DMERR_OK;
}
#endif


int jssLoadJSSMOD(DMResource *inFile, JSSModule **pmodule, BOOL probe)
{
    JSSModule *module;
    JSSMODHeader jssH;
    int index, ret = DMERR_OK;

    *pmodule = NULL;

    // Check the JSSMOD header
    if (!dmf_read_str(inFile, &jssH.idMagic, sizeof(jssH.idMagic)) ||
        !dmf_read_byte(inFile, &jssH.idVersion))
        JSSERROR(DMERR_FREAD, DMERR_FREAD,
        "Failed to read JSSMOD header #1.\n");

    if (memcmp(jssH.idMagic, "JM", 2) != 0)
    {
        if (probe)
            return DMERR_NOT_SUPPORTED;
        else
        {
            JSSERROR(DMERR_NOT_SUPPORTED, DMERR_NOT_SUPPORTED,
            "Not a JSSMOD file, header signature mismatch!\n");
        }
    }

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

    // Read rest of the header
    if (!dmf_read_le16(inFile, &jssH.defFlags) ||
        !dmf_read_le16(inFile, &jssH.intVersion) ||
        !dmf_read_le16(inFile, &jssH.norders) ||
        !dmf_read_le16(inFile, &jssH.npatterns) ||
        !dmf_read_le16(inFile, &jssH.nextInstruments) ||
        !dmf_read_le16(inFile, &jssH.ninstruments) ||
        !dmf_read_le16(inFile, &jssH.defRestartPos) ||

        !dmf_read_byte(inFile, &jssH.nchannels) ||
        !dmf_read_byte(inFile, &jssH.defSpeed) ||
        !dmf_read_byte(inFile, &jssH.defTempo) ||
        !dmf_read_byte(inFile, &jssH.patMode))
    {
        JSSERROR(DMERR_FREAD, DMERR_FREAD,
        "Failed to read JSSMOD header #2.\n");
    }

    if (probe)
        return DMERR_OK;

    // Check some of the things for sanity
    if (jssH.nchannels > jsetMaxChannels ||
        jssH.nchannels == 0 ||
        jssH.norders > jsetMaxOrders ||
        jssH.npatterns > jsetMaxPatterns ||
        jssH.nextInstruments > jsetMaxInstruments ||
        jssH.ninstruments > jsetMaxInstruments)
    {
        JSSERROR(DMERR_INVALID_DATA, DMERR_INVALID_DATA,
        "Invalid values in JSSMOD header.\n");
    }

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

    // Copy header information
    module->defFlags        = jssH.defFlags;
    module->intVersion      = jssH.intVersion;

    module->norders         = jssH.norders;
    module->npatterns       = jssH.npatterns;
    module->nchannels       = jssH.nchannels;
    module->nextInstruments = jssH.nextInstruments;
    module->ninstruments    = jssH.ninstruments;
    module->defRestartPos   = jssH.defRestartPos;
    module->defSpeed        = jssH.defSpeed;
    module->defTempo        = jssH.defTempo;

    // Get the orders list
    for (index = 0; index < module->norders; index++)
    {
        Uint16 tmp;
        if (!dmf_read_le16(inFile, &tmp))
            JSSERROR(DMERR_FREAD, DMERR_FREAD,
            "Failed to read orders list entry #%d.\n",
            index);

        if (tmp != 0xffff && tmp > jssH.npatterns)
            JSSERROR(DMERR_INVALID_DATA, DMERR_INVALID_DATA,
            "Invalid orders list entry #%d value %d > %d.\n",
            index, tmp, jssH.npatterns);

        module->orderList[index] = (tmp == 0xffff) ? jsetNotSet : tmp;
    }

    // Parse the patterns
    for (index = 0; index < module->npatterns; index++)
    {
        JSSPattern *pattern;
        JSSMODPattern jssP;

        // Read pattern header
        if (!dmf_read_le32(inFile, &jssP.size) ||
            !dmf_read_le16(inFile, &jssP.nrows))
            JSSERROR(DMERR_FREAD, DMERR_FREAD,
            "Failed to read JSSMOD pattern header #%d.\n",
            index);

        // Validate
        if (jssP.nrows == 0 || jssP.nrows > jsetMaxRows)
            JSSERROR(DMERR_INVALID_DATA, DMERR_INVALID_DATA,
            "Invalid number of rows in pattern #%d: %d.\n",
            index, jssP.nrows);

        // Allocate pattern
        pattern = module->patterns[index] = jssAllocatePattern(jssP.nrows, module->nchannels);
        if (pattern == 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:
                ret = jssGetPatternRawHoriz(inFile, pattern);
                break;
#endif
#ifdef JM_SUP_PATMODE_2
            case PATMODE_COMP_HORIZ:
                ret = jssGetPatternCompHoriz(inFile, pattern);
                break;
#endif
#ifdef JM_SUP_PATMODE_3
            case PATMODE_RAW_VERT:
                ret = jssGetPatternRawVert(inFile, pattern);
                break;
#endif
#ifdef JM_SUP_PATMODE_4
            case PATMODE_COMP_VERT:
                ret = jssGetPatternCompVert(inFile, pattern);
                break;
#endif
#ifdef JM_SUP_PATMODE_5
            case PATMODE_RAW_ELEM:
                ret = jssGetPatternRawVertElem(inFile, pattern);
                break;
#endif
            default:
                JSSERROR(DMERR_INVALID_DATA, DMERR_INVALID_DATA,
                "Unsupported pattern mode %d. Check compilation options.",
                jssH.patMode);
                break;
        }

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

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

        // Read header data
        if (!dmf_read_byte(inFile, &jssE.nsamples) ||
            !dmf_read_byte(inFile, &jssE.vibratoType) ||
            !dmf_read_le16(inFile, &jssE.vibratoSweep) ||
            !dmf_read_le16(inFile, &jssE.vibratoDepth) ||
            !dmf_read_le16(inFile, &jssE.vibratoRate) ||
            !dmf_read_le16(inFile, &jssE.fadeOut))
        {
            JSSERROR(DMERR_FREAD, DMERR_FREAD,
            "Failed to read ext.instrument #%d header.\n",
            index);
        }

        // Allocate instrument
        einst = module->extInstruments[index] = jssAllocateExtInstrument();
        if (einst == NULL)
        {
            JSSERROR(DMERR_MALLOC, DMERR_MALLOC,
            "Could not allocate extended instrument structure #%d\n", index);
        }

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

        // Read and somewhat validate sNumForNotes
        for (i = 0; i < jsetNNotes; i++)
        {
            int snum;
            Uint32 tmp;

            if (!dmf_read_le32(inFile, &tmp))
            {
                JSSERROR(DMERR_FREAD, DMERR_FREAD,
                "Failed to read ext.instrument #%d sNumForNotes[%d].\n",
                index, i);
            }

            einst->sNumForNotes[i] = snum = (tmp > 0) ? ((int) tmp - 1) : jsetNotSet;

            if (snum != jsetNotSet && snum > module->ninstruments)
            {
                JSSERROR(DMERR_INVALID_DATA, DMERR_INVALID_DATA,
                "Ext.instrument #%d has invalid sNumForNotes[%d] = %d > %d max.\n",
                index, i, snum, module->ninstruments);
            }
        }

        // Read and validate envelopes
        if ((ret = jssMODLoadEnvelope(inFile, &einst->volumeEnv, "volume", index)) != DMERR_OK ||
            (ret = jssMODLoadEnvelope(inFile, &einst->panningEnv, "panning", index)) != DMERR_OK)
            return ret;
    }

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

        // Read header data
        if (!dmf_read_le32(inFile, &jssI.size) ||
            !dmf_read_le32(inFile, &jssI.loopS) ||
            !dmf_read_le32(inFile, &jssI.loopE) ||
            !dmf_read_le16(inFile, &jssI.flags) ||
            !dmf_read_le16(inFile, &jssI.C4BaseSpeed) ||
            !dmf_read_le16(inFile, (Uint16 *) &jssI.ERelNote) ||
            !dmf_read_le16(inFile, (Uint16 *) &jssI.EFineTune) ||
            !dmf_read_le16(inFile, (Uint16 *) &jssI.EPanning) ||
            !dmf_read_byte(inFile, &jssI.volume) ||
            !dmf_read_byte(inFile, &jssI.convFlags))
        {
            JSSERROR(DMERR_FREAD, DMERR_FREAD,
            "Failed to read sample instrument #%d header.\n",
            index);
        }

        // Validate what we can
        if (jssI.loopS > jssI.size ||
            jssI.loopE > jssI.size ||
            jssI.loopS > jssI.loopE)
        {
            JSSERROR(DMERR_INVALID_DATA, DMERR_INVALID_DATA,
            "Invalid or corrupted sample instrument #%d.\n",
            index);
        }

        // Allocate instrument
        inst = module->instruments[index] = jssAllocateInstrument();
        if (inst == NULL)
        {
            JSSERROR(DMERR_MALLOC, DMERR_MALLOC,
            "Could not allocate sample instrument structure #%d\n",
            index);
        }

        // Copy data
        inst->size          = jssI.size;
        inst->loopS         = jssI.loopS;
        inst->loopE         = jssI.loopE;
        inst->flags         = jssI.flags;
        inst->C4BaseSpeed   = jssI.C4BaseSpeed;
        inst->ERelNote      = jssI.ERelNote;
        inst->EFineTune     = jssI.EFineTune;
        inst->EPanning      = jssI.EPanning;
        inst->volume        = jssI.volume;
        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 != NULL && (inst->convFlags & jsampHasData) != 0)
        {
            int ret;
            size_t bsize = (inst->flags & jsf16bit) ? sizeof(Uint16) : sizeof(Uint8);
            bsize *= inst->size;

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

            // Read data
            if (!dmf_read_str(inFile, inst->data, bsize))
            {
                JSSERROR(DMERR_FREAD, DMERR_FREAD,
                "Could not read %d bytes of sample data for #%d\n",
                bsize, index);
            }

            // Convert, if needed
            if (inst->flags & jsf16bit)
                ret = jssDecodeSample16(inst->data, inst->size, inst->convFlags);
            else
                ret = jssDecodeSample8(inst->data, inst->size, inst->convFlags);

            if (ret != DMERR_OK)
            {
                JSSERROR(ret, ret,
                "Failed to decode sample data for #%d\n", index);
            }
        }
    }
#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;
}