view minijss/jssmix.c @ 2208:90ec1ec89c56

Revamp the palette handling in lib64gfx somewhat, add helper functions to lib64util for handling external palette file options and add support for specifying one of the "internal" palettes or external (.act) palette file to gfxconv and 64vw.
author Matti Hamalainen <ccr@tnsp.org>
date Fri, 14 Jun 2019 05:01:12 +0300
parents 6aa0897265e8
children 69a5af2eb1ea
line wrap: on
line source

/*
 * miniJSS - Mixing device and channel handling
 * Programmed and designed by Matti 'ccr' Hamalainen
 * (C) Copyright 2006-2015 Tecnic Software productions (TNSP)
 */
#include "jssmix.h"


#ifdef DM_USE_C
#define JMIXER_HEADER
#include "jmix_c_in.c"
#undef JMIXER_HEADER
#endif

#undef DM_USE_SIMD

#ifdef DM_USE_SIMD
#define JMIXER_HEADER
#include "jmix_mmx_in.c"
#undef JMIXER_HEADER
#endif


typedef struct
{
    int    mixerID;
    int    outFormat;
    int    outChannels;

    int    (*jvmMixChannel_FW)(JSSMixer *, JSSChannel *, JMIXER_ADDBUF_TYPE *, const int, const DMFixedPoint);
    int    (*jvmMixChannel_BW)(JSSMixer *, JSSChannel *, JMIXER_ADDBUF_TYPE *, const int, const DMFixedPoint);
    void   (*jvmPostProcess)(JMIXER_ADDBUF_TYPE *, void *, const int);
} JSSMixingRoutine;


/* This table should be sorted from fastest to slowest, e.g. MMX/x86
 * optimized routines first, pure C versions last.
 */
static JSSMixingRoutine jvmMixRoutines[] =
{
#ifdef DM_USE_SIMD
{ JMIX_MMX, JSS_AUDIO_U8,  JSS_AUDIO_MONO,   jvmMix_Mono_MMX_FW, jvmMix_Mono_MMX_BW, jvmPostProcess_U8_MMX },
{ JMIX_MMX, JSS_AUDIO_S8,  JSS_AUDIO_MONO,   jvmMix_Mono_MMX_FW, jvmMix_Mono_MMX_BW, jvmPostProcess_S8_MMX },
{ JMIX_MMX, JSS_AUDIO_U8,  JSS_AUDIO_STEREO, jvmMix_Stereo_MMX_FW, jvmMix_Stereo_MMX_BW, jvmPostProcess_U8_MMX },
{ JMIX_MMX, JSS_AUDIO_S8,  JSS_AUDIO_STEREO, jvmMix_Stereo_MMX_FW, jvmMix_Stereo_MMX_BW, jvmPostProcess_S8_MMX },

{ JMIX_MMX, JSS_AUDIO_U16, JSS_AUDIO_MONO,   jvmMix_Mono_MMX_FW, jvmMix_Mono_MMX_BW, jvmPostProcess_U16_MMX },
{ JMIX_MMX, JSS_AUDIO_S16, JSS_AUDIO_MONO,   jvmMix_Mono_MMX_FW, jvmMix_Mono_MMX_BW, jvmPostProcess_S16_MMX },
{ JMIX_MMX, JSS_AUDIO_U16, JSS_AUDIO_STEREO, jvmMix_Stereo_MMX_FW, jvmMix_Stereo_MMX_BW, jvmPostProcess_U16_MMX },
{ JMIX_MMX, JSS_AUDIO_S16, JSS_AUDIO_STEREO, jvmMix_Stereo_MMX_FW, jvmMix_Stereo_MMX_BW, jvmPostProcess_S16_MMX },
#endif

#ifdef DM_USE_C
{ JMIX_C,   JSS_AUDIO_U8,  JSS_AUDIO_MONO,   jvmMix_Mono_C_FW, jvmMix_Mono_C_BW, jvmPostProcess_U8_C },
{ JMIX_C,   JSS_AUDIO_S8,  JSS_AUDIO_MONO,   jvmMix_Mono_C_FW, jvmMix_Mono_C_BW, jvmPostProcess_S8_C },
{ JMIX_C,   JSS_AUDIO_U8,  JSS_AUDIO_STEREO, jvmMix_Stereo_C_FW, jvmMix_Stereo_C_BW, jvmPostProcess_U8_C },
{ JMIX_C,   JSS_AUDIO_S8,  JSS_AUDIO_STEREO, jvmMix_Stereo_C_FW, jvmMix_Stereo_C_BW, jvmPostProcess_S8_C },

{ JMIX_C,   JSS_AUDIO_U16, JSS_AUDIO_MONO,   jvmMix_Mono_C_FW, jvmMix_Mono_C_BW, jvmPostProcess_U16_C },
{ JMIX_C,   JSS_AUDIO_S16, JSS_AUDIO_MONO,   jvmMix_Mono_C_FW, jvmMix_Mono_C_BW, jvmPostProcess_S16_C },
{ JMIX_C,   JSS_AUDIO_U16, JSS_AUDIO_STEREO, jvmMix_Stereo_C_FW, jvmMix_Stereo_C_BW, jvmPostProcess_U16_C },
{ JMIX_C,   JSS_AUDIO_S16, JSS_AUDIO_STEREO, jvmMix_Stereo_C_FW, jvmMix_Stereo_C_BW, jvmPostProcess_S16_C },
#endif
};

static const int jvmNMixRoutines = sizeof(jvmMixRoutines) / sizeof(jvmMixRoutines[0]);


static int jvmFindMixRoutine(int outFormat, int outChannels, int mixerID)
{
    for (int i = 0; i < jvmNMixRoutines; i++)
    {
        if (jvmMixRoutines[i].outFormat == outFormat &&
            jvmMixRoutines[i].outChannels == outChannels &&
            (mixerID == JMIX_AUTO || jvmMixRoutines[i].mixerID == mixerID))
            return i;
    }

    return -1;
}


JSSMixer *jvmInit(const int outFormat, const int outChannels, const int outFreq, const int mixerID)
{
    JSSMixer *mixer;
    int mixerIdx;

    // Sanity check
    if (jvmNMixRoutines == 0)
    {
        JSSERROR(DMERR_INTERNAL, NULL,
        "No mixing routines compiled in?\n");
        return NULL;
    }

    // Check settings
    if (outChannels < 1)
    {
        JSSERROR(DMERR_INVALID_ARGS, NULL,
        "Invalid number of channels %d\n", outChannels);
    }

    if (outFreq < 1000)
    {
        JSSERROR(DMERR_INVALID_ARGS, NULL,
        "Invalid mixing frequency %d\n", outFreq);
    }

    /* Select mixing routines:
     * Here we try to choose the most fitting mixing routines
     * from the compiled in routines, unless caller is forcing
     * us to select specific ones.
     */
    if (mixerID == JMIX_AUTO)
    {
        mixerIdx = jvmFindMixRoutine(outFormat, outChannels, JMIX_SSE);
        if (mixerIdx < 0)
            mixerIdx = jvmFindMixRoutine(outFormat, outChannels, JMIX_MMX);
        if (mixerIdx < 0)
            mixerIdx = jvmFindMixRoutine(outFormat, outChannels, JMIX_AUTO);
    }
    else
    {
        mixerIdx = jvmFindMixRoutine(outFormat, outChannels, mixerID);
    }

    if (mixerIdx < 0)
    {
        JSSERROR(DMERR_INVALID_ARGS, NULL,
        "Could not find mixing routine for outFormat=%d, outChannels=%d, outFreq=%d.\n",
        outFormat, outChannels, outFreq);
        return NULL;
    }

    // Allocate a mixer device structure
    mixer = dmMalloc0(sizeof(JSSMixer));
    if (mixer == NULL)
    {
        JSSERROR(DMERR_MALLOC, NULL,
        "Could not allocate mixing device structure.\n");
    }

    // Initialize variables
#ifdef JSS_SUP_THREADS
    mixer->mutex = dmCreateMutex();
#endif
    mixer->outFormat = outFormat;
    mixer->outChannels = outChannels;
    mixer->outFreq = outFreq;

    mixer->jvmMixChannel_FW = jvmMixRoutines[mixerIdx].jvmMixChannel_FW;
    mixer->jvmMixChannel_BW = jvmMixRoutines[mixerIdx].jvmMixChannel_BW;
    mixer->jvmPostProcess = jvmMixRoutines[mixerIdx].jvmPostProcess;

    // Allocate addBuffer
    mixer->addBufSize = outChannels * outFreq * 2;
    mixer->addBuffer = dmMalloc(mixer->addBufSize * sizeof(JMIXER_ADDBUF_TYPE));
    if (mixer->addBuffer == NULL)
    {
        JSSERROR(DMERR_MALLOC, NULL,
        "Could not allocate mixing addition buffer.\n");
    }

    return mixer;
}


int jvmClose(JSSMixer * mixer)
{
    if (mixer == NULL)
        return DMERR_NULLPTR;

    // Deallocate resources
#ifdef JSS_SUP_THREADS
    dmDestroyMutex(mixer->mutex);
#endif
    dmFree(mixer->addBuffer);

    dmMemset(mixer, 0, sizeof(JSSMixer));
    dmFree(mixer);

    return DMERR_OK;
}


int jvmGetSampleSize(JSSMixer *mixer)
{
    int sampSize = 1;
    assert(mixer);

    switch (mixer->outChannels)
    {
        case JSS_AUDIO_STEREO:
        case JSS_AUDIO_MONO:
            sampSize = mixer->outChannels;
            break;
        default:
            JSSERROR(DMERR_INVALID_ARGS, -1,
            "outChannels=%d not stereo or mono!\n", mixer->outChannels);
            break;
    }

    switch (mixer->outFormat)
    {
        case JSS_AUDIO_U16: sampSize *= sizeof(Uint16); break;
        case JSS_AUDIO_S16: sampSize *= sizeof(Sint16); break;
        case JSS_AUDIO_U8:  sampSize *= sizeof(Uint8); break;
        case JSS_AUDIO_S8:  sampSize *= sizeof(Sint8); break;
        default:
            JSSERROR(DMERR_INVALID_ARGS, -1,
            "outFormat=%d is not supported!\n", mixer->outFormat);
    }

    return sampSize;
}


int jvmGetSampleRes(JSSMixer *mixer)
{
    int sampRes = 0;

    assert(mixer);

    switch (mixer->outFormat)
    {
        case JSS_AUDIO_U16: case JSS_AUDIO_S16: sampRes = 16; break;
        case JSS_AUDIO_U8:  case JSS_AUDIO_S8:  sampRes = 8; break;
        default:
            JSSERROR(DMERR_INVALID_ARGS, -1,
            "outFormat=%d is not supported!\n", mixer->outFormat);
    }

    return sampRes;
}


static void jvmMixChannel(JSSMixer *mixer, JSSChannel *chn, JMIXER_ADDBUF_TYPE *addBuffer, const int mixLength)
{
    int mixDone = mixLength, mixResult;
    JMIXER_ADDBUF_TYPE *ab = addBuffer;

    if (!chn->chPlaying || chn->chMute)
        return;

    DBG("%s(%p, %d)\n", __FUNCTION__, chn, mixLength);

    while (mixDone > 0)
    {
        if (chn->chDirection)
        {
            // Channel is playing FORWARDS
            if (chn->chFlags & jsfLooped)
            {
                // Sample is looped
                if (chn->chFlags & jsfBiDi)
                {
                    // Bi-directional loop
                    if (chn->chPos.dw >= chn->chLoopE.dw)
                    {
                        DMFixedPoint end;
                        FP_ADD_R(end, chn->chLoopE, chn->chLoopE);
                        FP_SUB_R(chn->chPos, end, chn->chPos);
                        chn->chDirection = FALSE;
                    }
                }
                else
                {
                    // Normal forward loop
                    if (chn->chPos.dw >= chn->chLoopE.dw)
                    {
                        DMFixedPoint diff;
                        FP_SUB_R(diff, chn->chPos, chn->chLoopE);
                        FP_ADD_R(chn->chPos, chn->chLoopS, diff);
                    }
                }
            }
            else
            {
                // Normal (non-looped) sample
                if (chn->chPos.dw >= chn->chSize.dw)
                {
                    chn->chPlaying = FALSE;
                    return;
                }
            }
        }
        else
        {
            // Channel is playing BACKWARDS
            if (chn->chFlags & jsfLooped)
            {
                // Sample is looped
                if (chn->chFlags & jsfBiDi)
                {
                    // Bi-directional loop
                    if (chn->chPos.dw <= chn->chLoopS.dw)
                    {
                        DMFixedPoint start;
                        FP_ADD_R(start, chn->chLoopS, chn->chLoopS);
                        FP_SUB_R(chn->chPos, start, chn->chPos);
                        chn->chDirection = TRUE;
                    }
                }
                else
                {
                    // Normal forward loop
                    if (chn->chPos.dw <= chn->chLoopS.dw)
                    {
                        DMFixedPoint diff;
                        FP_SUB_R(diff, chn->chLoopE, chn->chLoopS);
                        FP_ADD(chn->chPos, diff);
                    }
                }
            }
            else
            {
                // Normal (non-looped) sample
                if (chn->chPos.dw <= 0)
                {
                    chn->chPlaying = FALSE;
                    return;
                }
            }
        }

        // Call the mixing innerloop functions
        if (chn->chDirection)
        {
            DBG("MIX_FW[%p : %d : ", ab, mixDone);
            if (chn->chFlags & jsfLooped)
            {
                DBG("%d (%x)] {loop}\n", FP_GETH32(chn->chLoopE), FP_GETH32(chn->chLoopE));
                mixResult = mixer->jvmMixChannel_FW((void *) mixer, chn,
                    ab, mixDone, chn->chLoopE);
            }
            else
            {
                DBG("%d (%x)]\n", FP_GETH32(chn->chSize), FP_GETH32(chn->chSize));
                mixResult = mixer->jvmMixChannel_FW((void *) mixer, chn,
                    ab, mixDone, chn->chSize);
            }
        }
        else
        {
            DBG("MIX_BW[%p : %d : ", ab, mixDone);
            if (chn->chFlags & jsfLooped)
            {
                DBG("%d (%x)] {loop}\n", chn->chLoopS, chn->chLoopS);
                mixResult = mixer->jvmMixChannel_BW(mixer, chn,
                    ab, mixDone, chn->chLoopS);
            }
            else
            {
                static const DMFixedPoint zero = { 0 };
                DBG("%d (%x)]\n", 0, 0);
                mixResult = mixer->jvmMixChannel_BW(mixer, chn,
                    ab, mixDone, zero);
            }
        }

        mixDone -= mixResult;
        ab += mixResult * mixer->outChannels;
    }

#ifdef JSS_DEBUG
    if (mixDone < 0)
        JSSWARNING(DMERR_BOUNDS,, "mixDone < 0 in mixing logic loop.\n");
#endif
}


void jvmRenderAudio(JSSMixer *mixer, void *mixBuffer, const int mixLength)
{
    int blockLength, mixLeft;
    JMIXER_ADDBUF_TYPE *ab;

    JSS_LOCK(mixer);

    assert(mixer != NULL);
    assert(mixBuffer != NULL);
    assert(mixLength > 0);
    assert(mixLength * mixer->outChannels <= mixer->addBufSize);

    // Clear mixer->addBuffer
    dmMemset(mixer->addBuffer, 0, mixLength * mixer->outChannels * sizeof(JMIXER_ADDBUF_TYPE));

    ab = mixer->addBuffer;
    mixLeft = mixLength;
    while (mixLeft > 0)
    {
        // Check for callbacks
        blockLength = mixLeft;

        if (mixer->cbFunction)
        {
            if (mixer->cbCounter <= 0)
            {
                mixer->cbFunction(mixer, mixer->cbData);
                mixer->cbCounter = mixer->cbFreq;
            }

            if (mixer->cbCounter < blockLength)
                blockLength = mixer->cbCounter;
        }

        // Do mixing
        for (int i = 0; i < jsetNChannels; i++)
        {
            JSSChannel *chn = &(mixer->channels[i]);
            if (chn->chPlaying && !chn->chMute)
                jvmMixChannel(mixer, chn, ab, blockLength);
        }

/*
            if (chn->chPlaying)
            {
                if (!chn->chMute)
                    jvmMixChannel(mixer, chn, ab, blockLength);
                else
                    jvmAdvanceChannel(mixer, chn, blockLength);
            }
*/

        ab += blockLength * mixer->outChannels;
        mixLeft -= blockLength;
        mixer->cbCounter -= blockLength;
    }

    // Post-process
    mixer->jvmPostProcess(mixer->addBuffer, mixBuffer, mixLength * mixer->outChannels);

    JSS_UNLOCK(mixer);
}


int jvmSetCallback(JSSMixer * mixer, void (*cbFunction) (void *, void *), void *cbData)
{
    assert(mixer);

    if (cbFunction == NULL)
        JSSERROR(DMERR_NULLPTR, DMERR_NULLPTR, "NULL pointer given as cbFunction");

    JSS_LOCK(mixer);

    mixer->cbFunction = cbFunction;
    mixer->cbData = cbData;

    JSS_UNLOCK(mixer);

    return DMERR_OK;
}


void jvmRemoveCallback(JSSMixer * mixer)
{
    assert(mixer);

    JSS_LOCK(mixer);

    mixer->cbFunction = NULL;
    mixer->cbData = NULL;
    mixer->cbFreq = mixer->cbCounter = 0;

    JSS_UNLOCK(mixer);
}


int jvmSetCallbackFreq(JSSMixer * mixer, const int cbFreq)
{
    assert(mixer);

    JSS_LOCK(mixer);

    mixer->cbFreq = cbFreq;
    mixer->cbCounter = 0;

//fprintf(stderr, "set(outFreq = %d, cbFreq = %d) = %d\n", mixer->outFreq, cbFreq, mixer->cbFreq);

    JSS_UNLOCK(mixer);
    return DMERR_OK;
}


/* Channel manipulation routines
 */
void jvmPlay(JSSMixer * mixer, const int channel)
{
    JSS_LOCK(mixer);
    mixer->channels[channel].chPlaying = TRUE;
    JSS_UNLOCK(mixer);
}


void jvmStop(JSSMixer * mixer, const int channel)
{
    JSS_LOCK(mixer);
    mixer->channels[channel].chPlaying = FALSE;
    JSS_UNLOCK(mixer);
}


void jvmReset(JSSMixer * mixer, const int channel)
{
    JSSChannel *c;

    JSS_LOCK(mixer);
    c = &mixer->channels[channel];

    c->chDirection = TRUE;
    c->chPos.dw    = c->chDeltaO.dw = 0;

    JSS_UNLOCK(mixer);
}


void jvmSetSample(JSSMixer * mixer, const int channel,
          void *data, const Sint32 size, const Sint32 loopS,
          const Sint32 loopE, const int flags)
{
    JSSChannel *c;

    JSS_LOCK(mixer);
    c = &mixer->channels[channel];

    FP_SETHL(c->chSize, size, 0);
    FP_SETHL(c->chLoopS, loopS, 0);
    FP_SETHL(c->chLoopE, loopE, 0);
    c->chData      = data;
    c->chFlags     = flags;
    c->chDirection = TRUE;
    c->chPos.dw    = c->chDeltaO.dw = 0;

    JSS_UNLOCK(mixer);
}


void jvmSetFreq(JSSMixer * mixer, const int channel, const int freq)
{
    JSS_LOCK(mixer);

    mixer->channels[channel].chFreq = freq;

    if (mixer->outFreq > 0)
    {
        DMFixedPoint a, b;
        FP_SETHL(a, freq, 0);
        FP_CONV(b, mixer->outFreq);
        FP_DIV_R(mixer->channels[channel].chDeltaO, a, b);
    }
    else
    {
        FP_SET(mixer->channels[channel].chDeltaO, 0);
    }

    JSS_UNLOCK(mixer);
}


int jvmGetFreq(JSSMixer * mixer, const int channel)
{
    int tmp;

    JSS_LOCK(mixer);
    tmp = mixer->channels[channel].chFreq;
    JSS_UNLOCK(mixer);

    return tmp;
}


void jvmSetVolume(JSSMixer * mixer, const int channel, const int volume)
{
    JSS_LOCK(mixer);
    FP_SETHL(mixer->channels[channel].chVolume, volume, 0);
    mixer->channels[channel].chVolumeD = 0;
    mixer->channels[channel].chDeltaV.dw = 0;
    JSS_UNLOCK(mixer);
}


void jvmSetVolumeRamp(JSSMixer * mixer, const int channel, const int start, const int end, const int len)
{
    int tmp;
    DMFixedPoint a, b;
    JSS_LOCK(mixer);
    FP_SETHL(mixer->channels[channel].chVolume, start, 0);

    tmp = mixer->channels[channel].chVolumeD =
        len > 0 ? ((mixer->outFreq * len) / 1000) : mixer->cbFreq;

    FP_SETHL(a, (end - start), 0);
    FP_CONV(b, tmp);
    FP_DIV_R(mixer->channels[channel].chDeltaV, a, b);

    JSS_UNLOCK(mixer);
}


int jvmGetVolume(JSSMixer * mixer, const int channel)
{
    int tmp;

    JSS_LOCK(mixer);
    tmp = FP_GETH32(mixer->channels[channel].chVolume);
    JSS_UNLOCK(mixer);

    return tmp;
}


void jvmSetPos(JSSMixer * mixer, const int channel, const Sint32 pos)
{
    JSS_LOCK(mixer);
    FP_SETHL(mixer->channels[channel].chPos, pos, 0);
    JSS_UNLOCK(mixer);
}


Sint32 jvmGetPos(JSSMixer * mixer, const int channel)
{
    Sint32 tmp;

    JSS_LOCK(mixer);
    tmp = FP_GETH32(mixer->channels[channel].chPos);
    JSS_UNLOCK(mixer);

    return tmp;
}


void jvmSetPan(JSSMixer * mixer, const int channel, const int panning)
{
    JSS_LOCK(mixer);
    FP_SETHL(mixer->channels[channel].chPanning, panning, 0);
    mixer->channels[channel].chPanningD = 0;
    mixer->channels[channel].chDeltaP.dw = 0;
    JSS_UNLOCK(mixer);
}


void jvmSetPanRamp(JSSMixer * mixer, const int channel, const int start, const int end, const int len)
{
    int tmp;
    DMFixedPoint a, b;
    JSS_LOCK(mixer);

    FP_SETHL(mixer->channels[channel].chPanning, start, 0);

    tmp = mixer->channels[channel].chPanningD =
        len > 0 ? ((mixer->outFreq * len) / 1000) : mixer->cbFreq;

    FP_SETHL(a, (end - start), 0);
    FP_CONV(b, tmp);
    FP_DIV_R(mixer->channels[channel].chDeltaP, a, b);

    JSS_UNLOCK(mixer);
}


int jvmGetPan(JSSMixer * mixer, const int channel)
{
    int tmp;

    JSS_LOCK(mixer);
    tmp = FP_GETH32(mixer->channels[channel].chPanning);
    JSS_UNLOCK(mixer);

    return tmp;
}


void jvmMute(JSSMixer * mixer, const int channel, const BOOL mute)
{
    JSS_LOCK(mixer);
    mixer->channels[channel].chMute = mute;
    JSS_UNLOCK(mixer);
}


BOOL jvmGetMute(JSSMixer * mixer, const int channel)
{
    BOOL tmp;

    JSS_LOCK(mixer);
    tmp = mixer->channels[channel].chMute;
    JSS_UNLOCK(mixer);

    return tmp;
}


void jvmClear(JSSMixer * mixer, const int channel)
{
    JSS_LOCK(mixer);
    dmMemset(&mixer->channels[channel], 0, sizeof(JSSChannel));
    JSS_UNLOCK(mixer);
}


void jvmSetGlobalVol(JSSMixer * mixer, const int volume)
{
    JSS_LOCK(mixer);
    mixer->globalVol = volume;
    JSS_UNLOCK(mixer);
}


int jvmGetGlobalVol(JSSMixer * mixer)
{
    int tmp;

    JSS_LOCK(mixer);
    tmp = mixer->globalVol;
    JSS_UNLOCK(mixer);

    return tmp;
}