view jssmix.c @ 0:32250b436bca

Initial re-import.
author Matti Hamalainen <ccr@tnsp.org>
date Fri, 28 Sep 2012 01:54:23 +0300
parents
children 7908061da010
line wrap: on
line source

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


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

#undef DM_USE_SIMD

#ifdef DM_USE_SIMD
#define JMIXER_HEADER
#include "jmix_mmx.h"
#undef JMIXER_HEADER
#endif


typedef struct
{
    int    mixerID;
    int    outFormat;
    int    outChannels;
    int    (*jvmMixChannel_FW)(JSSMixer *, JSSChannel *, Sint32 *, const int, const Uint32);
    int    (*jvmMixChannel_BW)(JSSMixer *, JSSChannel *, Sint32 *, const int, const Uint32);
    void   (*jvmPostProcess)(Sint32 *, 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)
{
    int i;
    
    for (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, v, i;

    // Check settings
    if (outChannels < 1)
    {
        JSSERROR(DMERR_INVALID_ARGS, NULL,
        "Invalid number of channels %d\n", outChannels);
    }
    
    if (outFreq < 4000)
    {
        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->outFreq = outFreq;
    mixer->outChannels = outChannels;
    
    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;
    mixer->addBuffer = dmCalloc(mixer->addBufSize, sizeof(Sint32));
    if (mixer->addBuffer == NULL)
    {
        JSSERROR(DMERR_MALLOC, NULL,
        "Could not allocate mixing addition buffer.\n");
    }
    
    // Initialize 8-bit volume table
    for (v = jsetMinVol; v < jsetMaxVol; v++)
    {
        for (i = 0; i < 256; i++)
        {
            mixer->volTab8[v][i] = (v * (i - 128));
        }
    }

    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);

    memset(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 = 1;
    
    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, Sint32 *addBuffer, const int mixLength)
{
    int mixDone = mixLength, mixResult;
    Sint32 *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 (FP_GETH(chn->chPos) >= chn->chLoopE)
                    {
                        FP_SETH(chn->chPos, chn->chLoopE - 1);
                        FP_SETL(chn->chPos, 0);
                        chn->chDirection = FALSE;
                    }
                }
                else
                {
                    // Normal forward loop
                    if (FP_GETH(chn->chPos) >= chn->chLoopE)
                    {
                        FP_SETH(chn->chPos, chn->chLoopS /* + (FP_GETH(chn->chPos) - chn->chLoopE) */);
                        FP_SETL(chn->chPos, 0);
                    }
                }
            }
            else
            {
                // Normal (non-looped) sample
                if (FP_GETH(chn->chPos) >= chn->chSize)
                {
                    chn->chPlaying = FALSE;
                    return;
                }
            }
        }
        else
        {
            // Channel is playing BACKWARDS
            if (chn->chFlags & jsfLooped)
            {
                // Sample is looped
                if (chn->chFlags & jsfBiDi)
                {
                    // Bi-directional loop
                    if (FP_GETH(chn->chPos) <= chn->chLoopS)
                    {
                        FP_SETH(chn->chPos, chn->chLoopS);
                        FP_SETL(chn->chPos, 0);
                        chn->chDirection = TRUE;
                    }
                }
                else
                {
                    // Normal forward loop
                    if (FP_GETH(chn->chPos) <= chn->chLoopS)
                    {
                        FP_SETH(chn->chPos, chn->chLoopE - 1);
                        FP_SETL(chn->chPos, 0);
                    }
                }
            }
            else
            {
                // Normal (non-looped) sample
                if (FP_GETH(chn->chPos) <= 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", chn->chLoopE, chn->chLoopE);
                mixResult = mixer->jvmMixChannel_FW((void *) mixer, chn,
                    ab, mixDone, chn->chLoopE);
            }
            else
            {
                DBG("%d (%x)]\n", chn->chSize, 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
            {
                DBG("%d (%x)]\n", 0, 0);
                mixResult = mixer->jvmMixChannel_BW(mixer, chn,
                    ab, mixDone, 0);
            }
        }
        
        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 i, blockLength, mixLeft;
    Sint32 *ab;

    JSS_LOCK(mixer);

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

    // Clear mixer->addBuffer
    memset(mixer->addBuffer, 0, mixLength * mixer->outChannels * sizeof(Sint32));
    
    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 (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);

    if ((cbFreq < 1) || (cbFreq >= mixer->outFreq))
        JSSERROR(DMERR_INVALID_ARGS, DMERR_INVALID_ARGS,
         "Invalid callback frequency given (%i / %i)\n", cbFreq, mixer->outFreq);

    JSS_LOCK(mixer);
    
    mixer->cbFreq = (mixer->outFreq / 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 jvmSetSample(JSSMixer * mixer, const int channel,
          void *data, const Uint32 size, const Uint32 loopS,
          const Uint32 loopE, const int flags)
{
    JSSChannel *c;
    
    JSS_LOCK(mixer);
    c = &mixer->channels[channel];
    
    c->chData = data;
    c->chSize = size;
    c->chLoopS = loopS;
    c->chLoopE = loopE;
    c->chFlags = flags;
    c->chDirection = TRUE;
    c->chPrevL = c->chPrevR = 0;
    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);
    mixer->channels[channel].chVolume = volume;
    JSS_UNLOCK(mixer);
}


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

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

    return tmp;
}


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


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

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

    return tmp;
}


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


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

    JSS_LOCK(mixer);
    tmp = 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);
}


void jvmClear(JSSMixer * mixer, const int channel)
{
    JSS_LOCK(mixer);
    memset(&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;
}