view minijss/jssmod.c @ 2278:40ccc09f09be

Implement empty channel removal in xm2jss and make JSSMOD format support channel remapping for this.
author Matti Hamalainen <ccr@tnsp.org>
date Tue, 18 Jun 2019 12:12:51 +0300
parents 2f8506171064
children fc58f62f100c
line wrap: on
line source

/*
 * miniJSS - Module structure and handling routines
 * Programmed and designed by Matti 'ccr' Hamalainen
 * (C) Copyright 2006-2015 Tecnic Software productions (TNSP)
 */
#include "jssmod.h"


#ifndef JSS_LIGHT

/* Take given data until maxlen reached, make a string.
 * Basically a bit like strndup(), except end marker byte
 * can be specified.
 */
char *jssASCIItoStr(const char * src, const char endByte, const size_t len)
{
    size_t i, k;
    char *res;

    for (i = 0; src[i] != endByte && i < len; )
        i++;

    if ((res = dmMalloc(i + 1)) == NULL)
        return NULL;

    for (k = 0; src[k] != endByte && k < i; k++)
        res[k] = src[k];

    res[k] = 0;

    return res;
}


/* Encodes a given 8-bit sample
 */
int jssEncodeSample8(Uint8 * data, const size_t len, const int ops)
{
    Sint8 value = 0;

    for (size_t offs = 0; offs < len; offs++)
    {
        Sint8 t = data[offs];

        if (ops & jsampFlipSign)
            t ^= 0x80;

        if (ops & jsampDelta)
        {
            int n = t - value;
            value = t;
            t = n;
        }

        data[offs] = t;
    }

    return DMERR_OK;
}


/* Decodes a given 16-bit sample
 */
int jssEncodeSample16(Uint16 * data, const size_t len, const int ops)
{
    // "Split" the 16-bit samples into 8-bit halves
    if (ops & jsampSplit)
    {
        // Allocate temporary processing buffer
        Uint8 *bp1, *bp2;
        size_t bufSize = len * sizeof(Sint16);
        Sint16 *tmpBuf;

        if ((tmpBuf = dmMalloc(bufSize)) == NULL)
            return DMERR_MALLOC;

        bp1 = (Uint8 *) data;
        bp2 = bp1 + len;

        for (size_t offs = 0; offs < len; offs++)
        {
            const Sint16 t = tmpBuf[offs];
            bp1[offs] = t >> 8;
            bp2[offs] = t & 0xff;
        }

        memcpy(data, tmpBuf, bufSize);
        dmFree(tmpBuf);

        return jssEncodeSample8((Uint8 *) data, bufSize, ops);
    }
    else
    {
        Sint16 value = 0, *sdata = (Sint16 *) data;

        for (size_t offs = 0; offs < len; offs++)
        {
            Sint16 t;

            if (ops & jsampSwapEndianess)
            {
                const Sint16 p = sdata[offs];
                t = ((p >> 8) & 0xff) | ((p & 0xff) << 8);
            }
            else
                t = sdata[offs];

            if (ops & jsampDelta)
            {
                const int n = t - value;
                value = t;
                t = n;
            }

            if (ops & jsampFlipSign)
                t ^= 0x8000;

            sdata[offs] = t;
        }
    }

    return DMERR_OK;
}

#endif


/* Decodes a given 8-bit sample
 */
int jssDecodeSample8(Uint8 * data, const size_t len, const int ops)
{
    Sint8 value = 0;

    for (size_t offs = 0; offs < len; offs++)
    {
        Sint8 t = data[offs];

        if (ops & jsampDelta)
            t = value = t + value;

        if (ops & jsampFlipSign)
            t ^= 0x80;

        data[offs] = t;
    }

    return DMERR_OK;
}


/* Decodes a given 16-bit sample
 */
int jssDecodeSample16(Uint16 * data, const size_t len, const int ops)
{
    if (ops & jsampSplit)
    {
        size_t bufSize = len * sizeof(Uint16);
        Uint8 *bp1, *bp2;
        Sint16 *tmpBuf;
        int ret;

        if ((ret = jssDecodeSample8((Uint8 *) data, bufSize, ops)) != DMERR_OK)
            return ret;

        if ((tmpBuf = dmMalloc(bufSize)) == NULL)
            return DMERR_MALLOC;

        // Copy original data to temporary buffer
        memcpy(tmpBuf, data, bufSize);

        bp1 = (Uint8 *) tmpBuf;
        bp2 = bp1 + len;

        for (size_t offs = 0; offs < len; offs++)
        {
            data[offs] = (bp1[offs] << 8) | (bp2[offs] & 0xff);
        }

        dmFree(tmpBuf);
    }
    else
    {
        Sint16 value = 0, *sdata = (Sint16 *) data;

        for (size_t offs = 0; offs < len; offs++)
        {
            Sint16 t;

            if (ops & jsampSwapEndianess)
            {
                const Sint16 p = sdata[offs];
                t = ((p >> 8) & 0xff) | ((p & 0xff) << 8);
            }
            else
                t = sdata[offs];

            if (ops & jsampDelta)
                t = value = t + value;

            if (ops & jsampFlipSign)
                t ^= 0x8000;

            sdata[offs] = t;
        }
    }

    return DMERR_OK;
}


/* Convert sample data from U8 to S16
 */
int jssConvertSampleTo16(void **dst, void * src, const size_t len)
{
    Uint8 *in = (Uint8 *) src;
    Sint16 *out;

    if ((*dst = out = dmMalloc(sizeof(Sint16) * len)) == NULL)
        return DMERR_MALLOC;

    for (size_t offs = 0; offs < len; offs++)
    {
        out[offs] = (in[offs] * 256) - 32768;
    }

    return DMERR_OK;
}


/* Converts the given module in preparation for playing it.
 * This involves sample format conversion (8 to 16 bit, etc.)
 *
 * NOTICE! The converted module can only be saved in JSSMOD
 * format, but this is not recommended.
 */
int jssConvertModuleForPlaying(JSSModule *module)
{
    if (module == NULL)
        return DMERR_NULLPTR;

    // Convert instruments
    for (int i = 0; i < module->ninstruments; i++)
    {
        JSSInstrument *inst = module->instruments[i];
        if (inst != NULL && inst->data != NULL)
        {
            int res;
            void *data = NULL;

            if (inst->flags & jsf16bit)
                continue;

            if ((res = jssConvertSampleTo16(&data, inst->data, inst->size)) != DMERR_OK)
                return res;

            inst->flags |= jsf16bit;
            dmFree(inst->data);
            inst->data = data;
        }
    }

    return DMERR_OK;
}


/* Allocates a new module structure or returns errorvalue if failed.
 * Memory is allocated only for the basic structure. Sample- and pattern
 * areas must be allocated separately with appropriate routines.
 */
JSSModule *jssAllocateModule(void)
{
    JSSModule *module;

    // Allocate module structure
    module = dmMalloc0(sizeof(JSSModule));
    if (module == NULL)
        return NULL;

    // Initialize structure
    for (int i = 0; i < jsetNChannels; i++)
        module->defPanning[i] = jchPanMiddle;

    for (int i = 0; i < jsetMaxOrders; i++)
        module->orderList[i] = jsetOrderEnd;

    // Allocate mutex
#ifdef JSS_SUP_THREADS
    module->mutex = dmCreateMutex();
#endif

    return module;
}


/* Frees a given module structure, freeing all memory areas
 * that were allocated for it (including patterns, samples, etc.)
 */
int jssFreeModule(JSSModule * module)
{
    if (module == NULL)
        return DMERR_NULLPTR;

    // Free strings
#ifndef JSS_LIGHT
    dmFree(module->moduleName);
    dmFree(module->trackerName);
#endif

    // Free patterns
    for (int i = 0; i < module->npatterns; i++)
        jssFreePattern(module->patterns[i]);

    // Free the "empty" pattern
    jssFreePattern(module->patterns[jsetMaxPatterns]);

    // Free instruments
    for (int i = 0; i < module->ninstruments; i++)
        jssFreeInstrument(module->instruments[i]);

    // Free extended instruments
    for (int i = 0; i < module->nextInstruments; i++)
        jssFreeExtInstrument(module->extInstruments[i]);

    // Free mutex
#ifdef JSS_SUP_THREADS
    dmDestroyMutex(module->mutex);
#endif

    // Free the module structure
    dmMemset(module, 0, sizeof(JSSModule));
    dmFree(module);

    return DMERR_OK;
}


/* Allocates and initializes a internal pattern structure.
 */
JSSPattern *jssAllocatePattern(const int nrows, const int nchannels)
{
    JSSPattern *pattern;
    JSSNote *pnote;

    // Check arguments
    if (nrows <= 0 || nchannels <= 0)
        JSSERROR(DMERR_INVALID_ARGS, NULL, "Invalid nrows=%d or nchannels=%d.\n", nrows, nchannels);

    // Allocate a pattern structure
    if ((pattern = dmMalloc0(sizeof(JSSPattern))) == NULL)
        JSSERROR(DMERR_MALLOC, NULL, "Could not allocate pattern structure.\n");

    // Allocate notedata
    pattern->data = dmMalloc(nrows * nchannels * sizeof(JSSNote));
    if (pattern->data == NULL)
    {
        jssFreePattern(pattern);
        JSSERROR(DMERR_MALLOC, NULL, "Could not allocate pattern data (nrows=%d, nchannels=%d).\n",
            nrows, nchannels);
    }

    // Initialize structure
    pattern->nrows     = nrows;
    pattern->nchannels = nchannels;
    pattern->nmap      = nchannels;

    pnote = pattern->data;
    for (int row = 0; row < nrows; row++)
    for (int chn = 0; chn < nchannels; chn++)
    {
        pnote->note = pnote->instrument = pnote->volume =
            pnote->effect = pnote->param = jsetNotSet;

        pnote++;
    }

    // Initialize pattern channel map
    pattern->map = dmMalloc(nchannels * sizeof(pattern->map[0]));
    pattern->used = dmMalloc(nchannels * sizeof(pattern->used[0]));
    if (pattern->map == NULL || pattern->used == NULL)
    {
        jssFreePattern(pattern);
        JSSERROR(DMERR_MALLOC, NULL, "Could not allocate pattern map (nchannels=%d).\n",
             nchannels);
    }

    for (int chn = 0; chn < nchannels; chn++)
        pattern->map[chn] = chn;

    return pattern;
}


void jssFreePattern(JSSPattern *pattern)
{
    if (pattern != NULL)
    {
        dmFree(pattern->used);
        dmFree(pattern->map);
        dmFree(pattern->data);
        dmFree(pattern);
    }
}


/* Allocates and initializes internal "normal" instrument structure.
 */
JSSInstrument *jssAllocateInstrument(void)
{
    JSSInstrument *inst;

    // Allocate a instrument structure
    if ((inst = dmMalloc0(sizeof(JSSInstrument))) == NULL)
        return NULL;

    return inst;
}


void jssFreeInstrument(JSSInstrument *inst)
{
    if (inst != NULL)
    {
#ifndef JSS_LIGHT
        dmFree(inst->desc);
#endif
        dmFree(inst->data);
        dmFree(inst);
    }
}


/* Allocates and initializes "extended" instrument structure.
 */
JSSExtInstrument *jssAllocateExtInstrument(void)
{
    JSSExtInstrument *inst;

    // Allocate a instrument structure
    if ((inst = dmMalloc0(sizeof(JSSExtInstrument))) == NULL)
        return NULL;

    // Initialize the requisite fields
    for (int i = 0; i < jsetNNotes; i++)
        inst->sNumForNotes[i] = jsetNotSet;

    return inst;
}


void jssFreeExtInstrument(JSSExtInstrument *inst)
{
    if (inst != NULL)
    {
#ifndef JSS_LIGHT
        dmFree(inst->desc);
#endif
        dmFree(inst->instConvTable);
        dmFree(inst);
    }
}