view src/dmengine.c @ 2298:b5abfff07ca9

Add new DMGrowBuf helper functions dmGrowBufCopyOffsSize() and dmGrowBufConstCopyOffsSize().
author Matti Hamalainen <ccr@tnsp.org>
date Thu, 04 Jul 2019 10:54:16 +0300
parents 5e5f75b45f8d
children 36edd316184a
line wrap: on
line source

/*
 * dmlib
 * -- Demo engine / editor common code and definitions
 * Programmed and designed by Matti 'ccr' Hamalainen
 * (C) Copyright 2012-2015 Tecnic Software productions (TNSP)
 */
#include "dmengine.h"
#include "dmimage.h"
#include <SDL_timer.h>


#ifdef DM_USE_TREMOR
#include <tremor/ivorbiscodec.h>
#include <tremor/ivorbisfile.h>
#endif


DMEngineData engine;
DMEffect *engineEffects = NULL;
int nengineEffects = 0, nengineEffectsAlloc = 0;


int engineRegisterEffect(const DMEffect *ef)
{
    if (ef == NULL)
        return DMERR_NULLPTR;

    // Allocate more space for effects
    if (nengineEffects + 1 >= nengineEffectsAlloc)
    {
        nengineEffectsAlloc += 16;
        engineEffects = dmRealloc(engineEffects, sizeof(DMEffect) * nengineEffectsAlloc);
        if (engineEffects == NULL)
        {
            return dmErrorDBG(DMERR_INIT_FAIL,
                "Could not expand effects structure.\n");
        }
    }

    // Copy effects structure
    memcpy(engineEffects + nengineEffects, ef, sizeof(DMEffect));
    nengineEffects++;

    return DMERR_OK;
}


int engineInitializeEffects(DMEngineData *engine)
{
    int i, res;

    dmFree(engine->effectData);
    engine->effectData = dmCalloc(nengineEffectsAlloc, sizeof(void *));
    if (engine->effectData == NULL)
    {
        return dmErrorDBG(DMERR_INIT_FAIL,
            "Could not expand effects data structure.\n");
    }

    for (i = 0; i < nengineEffects; i++)
    {
        DMEffect *eff = &engineEffects[i];
        if (eff->init != NULL &&
            (res = eff->init(engine, eff, &engine->effectData[i])) != DMERR_OK)
            return res;
    }

    return DMERR_OK;
}


void engineShutdownEffects(DMEngineData *engine)
{
    if (engine != NULL && engine->effectData != NULL)
    {
        int i;
        for (i = 0; i < nengineEffects; i++)
        {
            DMEffect *eff = &engineEffects[i];
            if (eff->shutdown != NULL)
                eff->shutdown(engine, eff, engine->effectData[i]);
        }
        dmFree(engine->effectData);
        engine->effectData = NULL;
    }
}


DMEffect *engineFindEffect(const char *name, const int nparams)
{
    int i;
    for (i = 0; i < nengineEffects; i++)
    {
        DMEffect *eff = &engineEffects[i];
        if (strcmp(eff->name, name) == 0 &&
            eff->nparams == nparams)
            return eff;
    }
    return NULL;
}


DMEffect *engineFindEffectByName(const char *name)
{
    int i;
    for (i = 0; i < nengineEffects; i++)
    {
        DMEffect *eff = &engineEffects[i];
        if (strcmp(eff->name, name) == 0)
            return eff;
    }
    return NULL;
}


static int engineResImageLoad(DMResource *res)
{
    if ((res->resData = dmLoadImage(res)) != NULL)
        return DMERR_OK;
    else
        return dmferror(res);
}


static void engineResImageFree(DMResource *res)
{
    SDL_FreeSurface((SDL_Surface *)res->resData);
}

static BOOL engineResImageProbe(DMResource *res, const char *fext)
{
    (void) res;
    return fext != NULL && (strcasecmp(fext, ".jpg") == 0 || strcasecmp(fext, ".png") == 0);
}


#ifdef JSS_SUP_XM
static int engineResXMModuleLoad(DMResource *res)
{
    return jssLoadXM(res, (JSSModule **) &(res->resData), FALSE);
}

static void engineResXMModuleFree(DMResource *res)
{
    jssFreeModule((JSSModule *) res->resData);
}

static BOOL engineResXMModuleProbe(DMResource *res, const char *fext)
{
    (void) res;
    return fext != NULL && strcasecmp(fext, ".xm") == 0;
}
#endif


#ifdef JSS_SUP_JSSMOD
static int engineResJSSModuleLoad(DMResource *res)
{
    return jssLoadJSSMOD(res, (JSSModule **) &(res->resData), FALSE);
}

static void engineResJSSModuleFree(DMResource *res)
{
    jssFreeModule((JSSModule *) res->resData);
}

static BOOL engineResJSSModuleProbe(DMResource *res, const char *fext)
{
    (void) res;
    return fext != NULL &&
        (strcasecmp(fext, ".jss") == 0 || strcasecmp(fext, ".jmod") == 0);
}
#endif


#ifdef DM_USE_TREMOR
static size_t vorbisFileRead(void *ptr, size_t size, size_t nmemb, void *datasource)
{
    return dmfread(ptr, size, nmemb, (DMResource *) datasource);
}

static int vorbisFileSeek(void *datasource, ogg_int64_t offset, int whence)
{
    return dmfseek((DMResource *) datasource, offset, whence);
}

static int vorbisFileClose(void *datasource)
{
    (void) datasource;
    return 0;
}

static long vorbisFileTell(void *datasource)
{
    return dmftell((DMResource *) datasource);
}


static ov_callbacks vorbisFileCBS =
{
    vorbisFileRead,
    vorbisFileSeek,
    vorbisFileClose,
    vorbisFileTell
};

static int engineResVorbisLoad(DMResource *res)
{
    OggVorbis_File vf;

    dmMsg(2, "vorbisfile '%s', %d bytes resource loading\n",
        res->filename, res->rawSize);

    if (ov_open_callbacks(res, &vf, NULL, 0, vorbisFileCBS) < 0)
        return DMERR_FOPEN;

    res->resSize = ov_pcm_total(&vf, -1) * 2 * 2;
    if ((res->resData = dmMalloc(res->resSize + 16)) == NULL)
    {
        ov_clear(&vf);
        return DMERR_MALLOC;
    }

    dmMsg(2, "rdataSize=%d bytes?\n", res->resSize);

    BOOL eof = FALSE;
    int left = res->resSize;
    char *ptr = res->resData;
    int current_section;
    while (!eof && left > 0)
    {
        int ret = ov_read(&vf, ptr, left > 4096 ? 4096 : left, &current_section);
        if (ret == 0)
            eof = TRUE;
        else
        if (ret < 0)
        {
            ov_clear(&vf);
            return DMERR_INVALID_DATA;
        }
        else
        {
            left -= ret;
            ptr += ret;
        }
    }

    ov_clear(&vf);
    return DMERR_OK;
}

static void engineResVorbisFree(DMResource *res)
{
    dmFree(res->resData);
}

static BOOL engineResVorbisProbe(DMResource *res, const char *fext)
{
    (void) res;
    return fext != NULL && (strcasecmp(fext, ".ogg") == 0);
}
#endif


static DMResourceDataOps engineResOps[] =
{
    {
        engineResImageProbe,
        engineResImageLoad,
        engineResImageFree
    },

#ifdef JSS_SUP_JSSMOD
    {
        engineResJSSModuleProbe,
        engineResJSSModuleLoad,
        engineResJSSModuleFree
    },
#endif

#ifdef JSS_SUP_XM
    {
        engineResXMModuleProbe,
        engineResXMModuleLoad,
        engineResXMModuleFree
    },
#endif

#ifdef DM_USE_TREMOR
    {
        engineResVorbisProbe,
        engineResVorbisLoad,
        engineResVorbisFree
    },
#endif

};

static const int nengineResOps = sizeof(engineResOps) / sizeof(engineResOps[0]);


int engineClassifier(DMResource *res)
{
    int i;
    char *fext;

    if (res == NULL)
        return DMERR_NULLPTR;

    fext = strrchr(res->filename, '.');
    for (i = 0; i < nengineResOps; i++)
    {
        DMResourceDataOps *rops = &engineResOps[i];
        if (rops->probe != NULL && rops->probe(res, fext))
        {
            res->rops = rops;
            return DMERR_OK;
        }
    }

    return DMERR_OK;
}


void *engineGetResource(DMEngineData *eng, const char *name)
{
    DMResource *res;
    if (eng == NULL)
    {
        dmErrorDBGMsg(
            "Engine not initialized but engineGetResource('%s') called.\n",
            name);
        return NULL;
    }

    if ((res = dmResourceFind(eng->resources, name)) != NULL)
    {
        if (res->resData != NULL)
            return res->resData;

        dmErrorDBGMsg(
            "Could not find resource DATA '%s' (resource was not preloaded).\n",
            name);
        return NULL;
    }
    else
    {
        dmErrorDBGMsg(
            "Could not find resource '%s'.\n",
            name);
        return NULL;
    }
}


#ifdef DM_USE_JSS
void engineGetJSSInfo(DMEngineData *eng, BOOL *playing, int *order, JSSPattern **pat, int *npattern, int *row)
{
    JSS_LOCK(eng->jssPlr);

    *playing  = eng->jssPlr->isPlaying;
    *row      = eng->jssPlr->row;
    *pat      = eng->jssPlr->pattern;
    *npattern = eng->jssPlr->npattern;
    *order    = eng->jssPlr->order;

    JSS_UNLOCK(eng->jssPlr);
}


void engineGetJSSChannelInfo(DMEngineData *eng, const int channel, int *ninst, int *nextInst, int *freq, int *note)
{
    JSS_LOCK(eng->jssPlr);

    JSSPlayerChannel *chn = &(eng->jssPlr->channels[channel]);
    *ninst    = chn->ninstrument;
    *nextInst = chn->nextInstrument;
    *freq     = chn->freq;
    *note     = chn->note;

    JSS_UNLOCK(eng->jssPlr);
}
#endif


int engineGetTick(DMEngineData *engine)
{
    return engine->frameTime - engine->startTime - engine->offsetTime;
}


float engineGetTimeDT(DMEngineData *engine)
{
    return (float) engineGetTick(engine) / 1000.0f;
}


int engineGetTimeDTi(DMEngineData *engine)
{
    return (float) engineGetTick(engine) / 1000;
}


int engineGetTime(DMEngineData *engine, int t)
{
    return engineGetTick(engine) - (1000 * t);
}


int engineGetDT(DMEngineData *engine, int t)
{
    return engineGetTime(engine, t) / 1000;
}


int engineGetVideoAspect(int width, int height)
{
    if (width > 0 && height > 0)
        return (width * 1000) / height;
    else
        return 1;
}


void enginePauseAudio(DMEngineData *engine, int status)
{
    if (status)
        engine->audioStatus = SDL_AUDIO_PAUSED;
    else
        engine->audioStatus = SDL_AUDIO_PLAYING;

    SDL_PauseAudio(status);
}


void engineAudioCallback(void *userdata, Uint8 *stream, int len)
{
    DMEngineData *engine = (DMEngineData *) userdata;

    if (engine == NULL)
        return;

    dmMutexLock(engine->audioStreamMutex);

    // Update variables for analysis buffer (FFT, etc.)
    engine->audioStreamBuf  = stream;
    engine->audioStreamLen  = len / engine->audioSampleSize;

    // Update audio stream time position
    engine->audioTimePos   += (1000 * engine->audioStreamLen) / engine->optAfmt.freq;

    // If paused, just render nothing
    if (engine->paused)
    {
        dmMemset(stream, 0, len);
    }
    else
    // Otherwise, render audio
    switch (engine->optAudioSetup)
    {
#ifdef DM_USE_JSS
        case DM_ASETUP_JSS:
            if (engine->jssDev != NULL)
                jvmRenderAudio(engine->jssDev, stream, len / engine->audioSampleSize);
            break;
#endif
#ifdef DM_USE_TREMOR
        case DM_ASETUP_TREMOR:
            if (engine->audioPos + len >= engine->audioRes->resSize)
                engine->exitFlag = TRUE;
            else
            {
                memcpy(stream, (Uint8 *) engine->audioRes->resData + engine->audioPos, len);
                engine->audioPos += len;
            }
            break;
#endif

        default:
            break;
    }

    // First round of audio callback sets up the
    // necessary variables for audio synchro
    if (engine->startTimeAudio == -1 && engine->startTime != -1)
    {
        engine->startTimeAudio = SDL_GetTicks();
        engine->offsetTime = engine->startTimeAudio - engine->startTime;
    }

    dmMutexUnlock(engine->audioStreamMutex);
}


static int engineAudioThreadFunc(void *userdata)
{
    DMEngineData *engine = (DMEngineData *) userdata;
    if (engine == NULL)
        return 0;
    do
    {
        dmMutexLock(engine->audioStreamMutex);
        if (engine->audioStatus == SDL_AUDIO_PLAYING)
        {
            engineAudioCallback(userdata, engine->audioSimBuf, engine->audioSimBufSize);
        }
        dmMutexUnlock(engine->audioStreamMutex);

        SDL_Delay(engine->audioSimDelay);
    } while (!engine->audioSimDone);

    return 0;
}


int engineInitAudioParts(DMEngineData *engine)
{
    if (engine == NULL)
        return DMERR_NULLPTR;

    engine->audioStreamMutex = dmCreateMutex();
    engine->audioStatus      = SDL_AUDIO_STOPPED;
    engine->optAfmt.callback = engineAudioCallback;
    engine->optAfmt.userdata = (void *) engine;

    engine->audioSampleSize  = engine->optAfmt.channels;
    switch (engine->optAfmt.format)
    {
        case AUDIO_S16SYS:
        case AUDIO_U16SYS: engine->audioSampleSize *= 2; break;
    }

    if (SDL_OpenAudio(&engine->optAfmt, NULL) < 0)
    {
        // We'll let this pass, as we want to support no-sound.
        dmMsg(0,
            "Couldn't open SDL audio, falling back to no sound: %s\n",
            SDL_GetError());

        // Set up simulated audio thread
        engine->audioSimDelay   = 1000 / 45;
        engine->audioSimBufSize = (engine->optAfmt.freq / 45) * engine->audioSampleSize;
        engine->audioSimBuf     = dmMalloc(engine->audioSimBufSize);
        engine->audioSimDone    = FALSE;
        engine->audioSimThread  = SDL_CreateThread(engineAudioThreadFunc, "DMLib Audio Simulation Thread", engine);
        if (engine->audioSimThread == NULL)
            return DMERR_INIT_FAIL;
    }

    return DMERR_OK;
}