view dmsimple.c @ 300:4972ca91d062

Cleanups.
author Matti Hamalainen <ccr@tnsp.org>
date Thu, 11 Oct 2012 17:55:15 +0300
parents 919e7de91758
children 713c8018c3ce
line wrap: on
line source

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


DMEngineData engine;
DMFrameData frame;


static DMOptArg optList[] =
{
    { 0, '?', "help",       "Show this help", OPT_NONE },
    { 1, 'v', "verbose",    "Be more verbose", OPT_NONE },
    { 2, 'f', "fs",         "Fullscreen", OPT_NONE },
#ifdef DM_DEBUG
    { 3, 'd', "debug",      "Debug mode", OPT_NONE },
#endif
};

const int optListN = sizeof(optList) / sizeof(optList[0]);



static void argShowHelp()
{
    dmPrintBanner(stdout, dmProgName, "[options]");
    dmArgsPrintHelp(stdout, optList, optListN);
}


static BOOL argHandleOpt(const int optN, char *optArg, char *currArg)
{
    (void) optArg;

    switch (optN)
    {
        case 0:
            argShowHelp();
            exit(0);
            break;

        case 1:
            dmVerbosity++;
            break;
        
        case 2:
            engine.optVFlags |= SDL_FULLSCREEN;
            break;

#ifdef DM_DEBUG
        case 3:
            engine.optDebug = TRUE;
            break;
#endif

        default:
            dmError("Unknown option '%s'.\n", currArg);
            return FALSE;
    }
    
    return TRUE;
}


int engineShowProgress(int loaded, int total)
{
    int dx = 60,
        dh = 20,
        dw = engine.screen->w - (2 * dx),
        dy = (engine.screen->h - dh) / 2;
    
    if (SDL_MUSTLOCK(engine.screen) != 0 && SDL_LockSurface(engine.screen) != 0)
        return DMERR_INIT_FAIL;
    
    // Draw the progress bar
    dmClearSurface(engine.screen, dmMapRGBA(engine.screen, 0,0,0,0));
    dmFillRect(engine.screen, dx, dy, dx+dw, dy+dh, dmMapRGB(engine.screen, 255,255,255));
    dmFillRect(engine.screen, dx+1, dy+1, dx+dw-1, dy+dh-1, dmMapRGB(engine.screen, 0,0,0));

    if (total > 0)
    {
        dmFillRect(engine.screen,
            dx+3, dy+3,
            dx + 3 + ((dw - 3) * loaded) / total,
            dy + dh - 3,
            dmMapRGB(engine.screen, 200,200,200));
    }

    // Flip screen
    if (SDL_MUSTLOCK(engine.screen) != 0)
        SDL_UnlockSurface(engine.screen);

    SDL_Flip(engine.screen);
    return DMERR_OK;
}


int engineLoadResources()
{
    int err, loaded, total;
    
    err = dmres_preload(TRUE, &loaded, &total);

    while ((err = dmres_preload(FALSE, &loaded, &total)) == DMERR_PROGRESS)
    {
        // Show a nice progress bar while loading
        if (total > 0 && (loaded % 2) == 0)
        {
            if ((err = engineShowProgress(loaded, total)) != DMERR_OK)
                return err;
        }
    }
    
    return err;
}


int engineGetTick()
{
    return (frame.startTime - engine.startTime) + engine.adjustTime;
}


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


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


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


int engineGetDT(int t)
{
    return engineGetTime(t) / 1000;
}


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


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

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 engineResModuleLoad(DMResource *res)
{
    return jssLoadXM(res, (JSSModule **) &(res->rdata));
}

static void engineResModuleFree(DMResource *res)
{
    jssFreeModule((JSSModule *) res->rdata);
}

static BOOL engineResModuleProbe(DMResource *res, const char *fext)
{
    (void) res;
    return fext != NULL && (strcasecmp(fext, ".xm") == 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(1, "vorbisfile '%s', %d bytes resource loading\n",
        res->filename, res->dataSize);

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

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

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

    BOOL eof = FALSE;
    int left = res->rdataSize;
    char *ptr = res->rdata;
    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->rdata);
}

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_XM
    {
        engineResModuleProbe,
        engineResModuleLoad,
        engineResModuleFree
    },
#endif

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

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


static 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(const char *name)
{
    DMResource *res = dmres_find(name);
    if (res != NULL && res->rdata != NULL)
        return res->rdata;
    else
    {
        dmError("Could not find resource '%s'.\n", name);
        return NULL;
    }
}


#if defined(DM_DEBUG) && defined(DM_USE_JSS)
static void engineAudioCallbackDebug(void *userdata, Uint8 *stream, int len)
{
    if (engine.paused)
    {
        memset(stream, 0, len);
    }
    else
    {
        JSSMixer *d = (JSSMixer *) userdata;
        if (d != NULL)
        {
            int pos = ((engine.adjustTime * d->outFreq) / 1000) * jvmGetSampleSize(d) +
                engine.audioSamples;
            
            memcpy(stream, engine.audioBuf + pos, len);

            engine.audioSamples += len;
        }
    }
}

#endif

void engineAdjustTime(int adj)
{
#ifdef DM_DEBUG
    if (engine.optDebug)
    {
        int tmp = engine.adjustTime + adj;
        if (tmp < 0)
            tmp = 0;
        else
        if (tmp >= engine.demoDuration * 1000)
            tmp = engine.demoDuration * 1000;

        engine.pauseFlag = TRUE;
        engine.adjustTime = tmp;
        dmPrint(0, "adj=%d, adjtime=%d\n", adj, engine.adjustTime);
    }
#endif
}

static void engineAudioCallback(void *userdata, Uint8 *stream, int len)
{
    (void) userdata;

    if (engine.paused)
    {
        memset(stream, 0, len);
    }
    else
#ifdef DM_USE_JSS
    {
        if (engine.dev != NULL)
            jvmRenderAudio(engine.dev, stream, len / jvmGetSampleSize(engine.dev));
    }
#endif
#ifdef DM_USE_TREMOR
    if (engine.audioPos + len >= engine.audio->rdataSize)
    {
        engine.exitFlag = TRUE;
    }
    else
    {
        memcpy(stream, engine.audio->rdata + engine.audioPos, len);
        engine.audioPos += len;
    }
#endif
}


#ifdef DM_USE_JSS
void engineGetJSSInfo(BOOL *playing, int *order, JSSPattern **pat, int *npattern, int *row)
{
    JSS_LOCK(engine.plr);

    *playing = engine.plr->isPlaying;
    *row = engine.plr->row;
    *pat = engine.plr->pattern;
    *npattern = engine.plr->npattern;
    *order = engine.plr->order;

    JSS_UNLOCK(engine.plr);
}

void engineGetJSSChannelInfo(const int channel, int *ninst, int *nextInst, int *freq, int *note)
{
    JSS_LOCK(engine.plr);
    JSSPlayerChannel *chn = &(engine.plr->channels[channel]);
    *ninst = chn->ninstrument;
    *nextInst = chn->nextInstrument;
    *freq = chn->freq;
    *note = chn->note;
    JSS_UNLOCK(engine.plr);
}
#endif


int main(int argc, char *argv[])
{
    int err;
    BOOL initSDL = FALSE;

    memset(&frame, 0, sizeof(frame));
    memset(&engine, 0, sizeof(engine));

    // Pre-initialization
    if ((err = demoPreInit()) != DMERR_OK)
        goto error_exit;

    if (!dmArgsProcess(argc, argv, optList, optListN,
        argHandleOpt, NULL, FALSE))
        return DMERR_INIT_FAIL;

    dmPrint(0,
    "%s\n"
    "TNSP simple demoengine initializing.\n"
    "%s\n",
    dmProgDesc, dmProgAuthor);

    dmPrint(0,
    "Using libSDL, "
#ifdef DM_USE_TREMOR
    "Tremor Vorbis codec"
#endif
#ifdef DM_USE_PACKFS
    ", zlib and modified stb_image.\n"
#else
    " and modified stb_image.\n"
#endif
    "See README.txt for more information.\n");

    // Initialize resource subsystem
    dmPrint(1, "Initializing resources subsystem.\n");
    if ((err = dmres_init(engine.optPackFilename, engine.optDataPath, engine.optResFlags, engineClassifier)) != DMERR_OK)
    {
        dmError("Could not initialize resource manager: %d, %s.\n", err, dmErrorStr(err));
        goto error_exit;
    }

    // Initialize SDL components
    dmPrint(1, "Initializing libSDL.\n");
    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER) != 0)
    {
        dmError("Could not initialize SDL: %s\n", SDL_GetError());
        goto error_exit;
    }
    initSDL = TRUE;

    // Initialize audio parts
    if (engine.optAfmt.freq == 0 && engine.optAfmt.channels == 0)
    {
        // Defaults, if none seem to be set
        engine.optAfmt.freq     = 44100;
        engine.optAfmt.format   = AUDIO_S16SYS;
        engine.optAfmt.channels = 2;
        engine.optAfmt.samples  = 16*1024;
    }

#ifdef DM_USE_JSS
    jssInit();

    switch (engine.optAfmt.format)
    {
        case AUDIO_S16SYS: engine.jss_format = JSS_AUDIO_S16; break;
        case AUDIO_U16SYS: engine.jss_format = JSS_AUDIO_U16; break;
        case AUDIO_S8:     engine.jss_format = JSS_AUDIO_S8; break;
        case AUDIO_U8:     engine.jss_format = JSS_AUDIO_U8; break;
    }

    dmPrint(1, "Initializing miniJSS mixer with fmt=%d, chn=%d, freq=%d\n",
        engine.jss_format, engine.optAfmt.channels, engine.optAfmt.freq);

    if ((engine.dev = jvmInit(engine.jss_format, engine.optAfmt.channels, engine.optAfmt.freq, JMIX_AUTO)) == NULL)
    {
        dmError("jvmInit() returned NULL, voi perkele.\n");
        goto error_exit;
    }

    if ((engine.plr = jmpInit(engine.dev)) == NULL)
    {
        dmError("jmpInit() returned NULL\n");
        goto error_exit;
    }
#endif

    // Initialize SDL audio
    dmPrint(1, "Trying to init SDL audio with: fmt=%d, chn=%d, freq=%d\n",
        engine.optAfmt.format, engine.optAfmt.channels, engine.optAfmt.freq);

#if defined(DM_DEBUG) && defined(DM_USE_JSS)
    if (engine.optDebug)
        engine.optAfmt.callback = engineAudioCallbackDebug;
    else
#endif
        engine.optAfmt.callback = engineAudioCallback;
    
    if (SDL_OpenAudio(&engine.optAfmt, NULL) < 0)
    {
        dmError("Couldn't open SDL audio: %s\n", SDL_GetError());
        goto error_exit;
    }

    // Initialize SDL video
    if (engine.demoInitPreVideo != NULL &&
       (err = engine.demoInitPreVideo()) != DMERR_OK)
    {
        dmError("demoInitPreVideo() failed, %d: %s\n", err, dmErrorStr(err));
        goto error_exit;
    }

    dmPrint(1, "Initializing SDL video %d x %d x %dbpp, flags=0x%08x\n",
        engine.optScrWidth, engine.optScrHeight, engine.optBitDepth, engine.optVFlags);

    engine.screen = SDL_SetVideoMode(engine.optScrWidth, engine.optScrHeight, engine.optBitDepth, engine.optVFlags);
    if (engine.screen == NULL)
    {
        dmError("Can't SDL_SetVideoMode(): %s\n", SDL_GetError());
        goto error_exit;
    }

    SDL_ShowCursor(SDL_DISABLE);
    SDL_WM_SetCaption(dmProgDesc, dmProgName);

    if (engine.demoInitPostVideo != NULL &&
       (err = engine.demoInitPostVideo()) != DMERR_OK)
    {
        dmError("demoInitPostVideo() failed, %d: %s\n", err, dmErrorStr(err));
        goto error_exit;
    }

    // Load resources
    dmPrint(1, "Loading resources, please wait...\n");
    if ((err = engineLoadResources()) != DMERR_OK)
    {
        dmError("Error loading resources, %d: %s.\n",
            err, dmErrorStr(err));
        goto error_exit;
    }

    // Final initializations
    if ((err = engine.demoInit()) != DMERR_OK)
        goto error_exit;

#if defined(DM_DEBUG) && defined(DM_USE_JSS)
    if (engine.optDebug)
    {
        Uint8 *ptr;
        int left = engine.dev->outFreq * engine.demoDuration;
        dmPrint(1, "DEBUG mode enabled, pre-rendering audio.");
        engine.audioBufSize = jvmGetSampleSize(engine.dev) * engine.dev->outFreq * engine.demoDuration;
        if ((engine.audioBuf = dmMalloc(engine.audioBufSize)) == NULL)
        {
            dmError("Could not allocate audio stream buffer of %d bytes.\n",
                engine.audioBufSize);
            goto error_exit;
        }
        
        ptr = engine.audioBuf;
        while (left > 0)
        {
            int length = left;
            if (length > 16*1024)
                length = 16*1024;

            jvmRenderAudio(engine.dev, ptr, length);
            ptr += jvmGetSampleSize(engine.dev) * length;
            left -= length;
        }
    }
#endif

    dmPrint(1, "Starting up.\n");

    SDL_LockAudio();
    SDL_PauseAudio(0);
    SDL_UnlockAudio();

    engine.startTime = SDL_GetTicks();

    while (!engine.exitFlag)
    {
        while (SDL_PollEvent(&engine.event))
        switch (engine.event.type)
        {
            case SDL_KEYDOWN:
                switch (engine.event.key.keysym.sym)
                {
                    case SDLK_ESCAPE:
                        engine.exitFlag = TRUE;
                        break;
                    
                    case SDLK_SPACE:
                        engine.pauseFlag = !engine.pauseFlag;
                        break;

#ifdef DM_DEBUG
                    case SDLK_LEFT:  engineAdjustTime(-500); break;
                    case SDLK_RIGHT: engineAdjustTime( 500); break;
                    case SDLK_UP:    engineAdjustTime( 1000); break;
                    case SDLK_DOWN:  engineAdjustTime(-1000); break;
#endif
                    case SDLK_RETURN:
                        if (engine.event.key.keysym.mod & KMOD_ALT)
                        {
                        engine.optVFlags ^= SDL_FULLSCREEN;
                        SDL_FreeSurface(engine.screen);
                        engine.screen = SDL_SetVideoMode(engine.optScrWidth, engine.optScrHeight, engine.optBitDepth, engine.optVFlags);
                        if (engine.screen == NULL)
                        {
                            dmError("Can't SDL_SetVideoMode(): %s\n", SDL_GetError());
                            goto error_exit;
                        }
                        }
                        break;

                    default:
                        break;
                }

                break;

            case SDL_VIDEOEXPOSE:
                break;

            case SDL_QUIT:
                engine.exitFlag = TRUE;
                break;
        }

        // Draw frame
        frame.startTime = SDL_GetTicks();
        if (engine.pauseFlag != engine.paused)
        {
            engine.paused = engine.pauseFlag;
            engine.pauseTime = engineGetTick();
        }
        
        if (engine.paused)
        {
            engine.startTime = frame.startTime - engine.pauseTime;
        }

        if (SDL_MUSTLOCK(engine.screen) != 0 && SDL_LockSurface(engine.screen) != 0)
        {
            dmError("Can't lock surface.\n");
            goto error_exit;
        }

        // Call main tick
        if ((err = engine.demoRender()) != DMERR_OK)
            goto error_exit;

        // Flip screen
        if (SDL_MUSTLOCK(engine.screen) != 0)
            SDL_UnlockSurface(engine.screen);

        SDL_Flip(engine.screen);
        SDL_Delay(engine.paused ? 100 : 20);

        // Get frame time, etc
        frame.endTime = SDL_GetTicks();
        engine.currFrame++;
        engine.totalFrameTime += frame.endTime - frame.startTime;
    }

    // Print benchmark results
    engine.endTime = SDL_GetTicks();
    dmPrint(1, "%d frames in %d ms, fps = %1.3f\n",
        engine.currFrame, engine.endTime - engine.startTime,
        (float) (engine.currFrame * 1000.0f) / (float) engine.totalFrameTime);


error_exit:

    dmPrint(1, "Shutting down.\n");
    SDL_ShowCursor(SDL_ENABLE);

    if (engine.screen)
        SDL_FreeSurface(engine.screen);

    SDL_LockAudio();
    SDL_PauseAudio(1);
#ifdef DM_USE_JSS
    jmpClose(engine.plr);
    jvmClose(engine.dev);
    jssClose();
#endif
    SDL_UnlockAudio();

    dmres_close();    

    if (engine.demoShutdown != NULL)
        engine.demoShutdown();

    if (initSDL)
        SDL_Quit();

    if (engine.demoQuit != NULL)
        engine.demoQuit();
    
    return 0;
}