view src/xs_sidplay1.cpp @ 957:0e60e5d56fdd

Change how the backend emulator library is initialized for libSIDPlay2 and FP, as it seems the engine configuration has some persistence despite reconfiguration between loaded files if same engine object is retained. This caused, for example, 2SID stereo tunes being played "mono" if played after a normal 1-SID tune. Duh.
author Matti Hamalainen <ccr@tnsp.org>
date Tue, 20 Nov 2012 22:13:48 +0200
parents a5b118c853f5
children be2a8436461a
line wrap: on
line source

/*
   XMMS-SID - SIDPlay input plugin for X MultiMedia System (XMMS)

   libSIDPlay1 support

   Programmed and designed by Matti 'ccr' Hamalainen <ccr@tnsp.org>
   (C) Copyright 1999-2007 Tecnic Software productions (TNSP)

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License along
   with this program; if not, write to the Free Software Foundation, Inc.,
   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "xmms-sid.h"

#ifdef HAVE_SIDPLAY1

#include "xs_sidplay1.h"
#include "xs_slsup.h"
#include "xs_config.h"

#include <sidplay/player.h>
#include <sidplay/myendian.h>
#include <sidplay/fformat.h>


/* Maximum audio frequency supported by libSIDPlay v1 */
#define SIDPLAY1_MAX_FREQ    (48000)


typedef struct {
    emuEngine *emu;
    emuConfig currConfig;
    sidTune *tune;
} XSSIDPlay1;


/* We need to 'export' all this pseudo-C++ crap */
extern "C" {


/* Check if we can play the given file
 */
gboolean xs_sidplay1_probe(XSFile *f)
{
    gchar probe[16];

    if (f == NULL || !xs_fread_str(f, (guint8 *) probe, sizeof(probe)))
        return FALSE;

    // Old libSIDPlay1 only supports PSIDv1, afaik
    if (!strncmp(probe, "PSID", 4) &&
        probe[4] == 0 && probe[5] == 1)
        return TRUE;
    else
        return FALSE;
}


/* Initialize SIDPlay1
 */
gboolean xs_sidplay1_init(XSEngineState * state)
{
    gint tmpFreq;
    XSSIDPlay1 *engine;
    assert(state);

    XSDEBUG("SIDPlay1 backend initializing.\n");

    /* Allocate internal structures */
    engine = (XSSIDPlay1 *) g_malloc0(sizeof(XSSIDPlay1));
    if (!engine)
        return FALSE;

    /* Initialize engine */
    engine->emu = new emuEngine();
    if (!engine->emu)
    {
        xs_error("[SIDPlay1] Could not initialize emulation engine.\n");
        g_free(engine);
        return FALSE;
    }

    /* Verify endianess */
    if (!engine->emu->verifyEndianess())
    {
        xs_error("[SIDPlay1] Endianess verification failed.\n");
        delete engine->emu;
        g_free(engine);
        return FALSE;
    }

    state->internal = engine;

    XSDEBUG("SIDPlay1 emulation configuration\n");

    /* Get current configuration */
    engine->emu->getConfig(engine->currConfig);

    /* Configure channel parameters */
    switch (state->audioChannels)
    {
        case XS_CHN_AUTOPAN:
            engine->currConfig.channels = SIDEMU_STEREO;
            engine->currConfig.autoPanning = SIDEMU_CENTEREDAUTOPANNING;
            engine->currConfig.volumeControl = SIDEMU_FULLPANNING;
            break;

        case XS_CHN_STEREO:
            engine->currConfig.channels = SIDEMU_STEREO;
            engine->currConfig.autoPanning = SIDEMU_NONE;
            engine->currConfig.volumeControl = SIDEMU_NONE;
            break;

        case XS_CHN_MONO:
        default:
            engine->currConfig.channels = SIDEMU_MONO;
            engine->currConfig.autoPanning = SIDEMU_NONE;
            engine->currConfig.volumeControl = SIDEMU_NONE;
            state->audioChannels = XS_CHN_MONO;
            break;
    }


    /* Memory mode settings */
    switch (xs_cfg.memoryMode)
    {
        case XS_MPU_TRANSPARENT_ROM:
            engine->currConfig.memoryMode = MPU_TRANSPARENT_ROM;
            break;

        case XS_MPU_PLAYSID_ENVIRONMENT:
            engine->currConfig.memoryMode = MPU_PLAYSID_ENVIRONMENT;
            break;

        case XS_MPU_BANK_SWITCHING:
        default:
            engine->currConfig.memoryMode = MPU_BANK_SWITCHING;
            xs_cfg.memoryMode = XS_MPU_BANK_SWITCHING;
            break;
    }


    /* Audio parameters sanity checking and setup */
    engine->currConfig.bitsPerSample = state->audioBitsPerSample;
    tmpFreq = state->audioFrequency;

    if (tmpFreq > SIDPLAY1_MAX_FREQ)
        tmpFreq = SIDPLAY1_MAX_FREQ;

    engine->currConfig.frequency = tmpFreq;

    switch (state->audioBitsPerSample)
    {
        case XS_RES_8BIT:
            switch (state->audioFormat)
            {
                case FMT_S8:
                    state->audioFormat = FMT_S8;
                    engine->currConfig.sampleFormat = SIDEMU_SIGNED_PCM;
                    break;

                case FMT_U8:
                default:
                    state->audioFormat = FMT_U8;
                    engine->currConfig.sampleFormat = SIDEMU_UNSIGNED_PCM;
                    break;
            }
            break;

        case XS_RES_16BIT:
        default:
            switch (state->audioFormat)
            {
                case FMT_U16_NE:
                case FMT_U16_LE:
                case FMT_U16_BE:
                    state->audioFormat = FMT_U16_NE;
                    engine->currConfig.sampleFormat = SIDEMU_UNSIGNED_PCM;
                    break;

                case FMT_S16_NE:
                case FMT_S16_LE:
                case FMT_S16_BE:
                default:
                    state->audioFormat = FMT_S16_NE;
                    engine->currConfig.sampleFormat = SIDEMU_SIGNED_PCM;
                    break;
            }
            break;
    }

    /* Clockspeed settings */
    switch (xs_cfg.clockSpeed)
    {
        case XS_CLOCK_NTSC:
            engine->currConfig.clockSpeed = SIDTUNE_CLOCK_NTSC;
            break;

        case XS_CLOCK_PAL:
        default:
            engine->currConfig.clockSpeed = SIDTUNE_CLOCK_PAL;
            xs_cfg.clockSpeed = XS_CLOCK_PAL;
            break;
    }

    engine->currConfig.forceSongSpeed = xs_cfg.forceSpeed;
    
    
    /* Configure rest of the emulation */
    /* if (xs_cfg.forceModel) */
    engine->currConfig.mos8580 = xs_cfg.mos8580;
    engine->currConfig.emulateFilter = xs_cfg.emulateFilters;
    engine->currConfig.filterFs = xs_cfg.sid1Filter.fs;
    engine->currConfig.filterFm = xs_cfg.sid1Filter.fm;
    engine->currConfig.filterFt = xs_cfg.sid1Filter.ft;


    /* Now set the emulator configuration */
    if (!engine->emu->setConfig(engine->currConfig))
    {
        xs_error("[SIDPlay1] Emulator engine configuration failed!\n");
        return FALSE;
    }
    
    /* Create sidtune object */
    engine->tune = new sidTune(0);
    if (!engine->tune)
    {
        xs_error("[SIDPlay1] Could not initialize SIDTune object.\n");
        return FALSE;
    }
    
    return TRUE;
}


/* Close SIDPlay1 engine
 */
void xs_sidplay1_close(XSEngineState * state)
{
    XSSIDPlay1 *engine;
    assert(state);

    XSDEBUG("SIDPlay1 backend shutdown.\n");

    engine = (XSSIDPlay1 *) state->internal;

    /* Free internals */
    if (engine->emu)
    {
        delete engine->emu;
        engine->emu = NULL;
    }

    if (engine->tune)
    {
        delete engine->tune;
        engine->tune = NULL;
    }

    xs_sidplay1_delete(state);
    
    g_free(engine);
    state->internal = NULL;
}


/* Initialize current song and sub-tune
 */
gboolean xs_sidplay1_initsong(XSEngineState * state)
{
    XSSIDPlay1 *engine;
    assert(state);

    engine = (XSSIDPlay1 *) state->internal;
    if (!engine) return FALSE;

    if (!engine->tune)
    {
        xs_error("[SIDPlay1] SID-tune struct pointer was NULL. This should not happen, report to XMMS-SID author.\n");
        return FALSE;
    }

    if (!engine->tune->getStatus())
    {
        xs_error("[SIDPlay1] SID-tune status check failed. This should not happen, report to XMMS-SID author.\n");
        return FALSE;
    }

    return sidEmuInitializeSong(*engine->emu, *engine->tune, state->currSong);
}


/* Emulate and render audio data to given buffer
 */
guint xs_sidplay1_fillbuffer(XSEngineState * state, gchar * audioBuffer, guint audioBufSize)
{
    XSSIDPlay1 *engine;
    assert(state);

    engine = (XSSIDPlay1 *) state->internal;
    if (!engine) return 0;

    sidEmuFillBuffer(*engine->emu, *engine->tune, audioBuffer, audioBufSize);

    return audioBufSize;
}


/* Load a given SID-tune file
 */
gboolean xs_sidplay1_load(XSEngineState * state, gchar * filename)
{
    XSSIDPlay1 *engine = (XSSIDPlay1 *) state->internal;
    gboolean res = FALSE;
    guint8 *buf = NULL;
    size_t bufSize;

    if (!engine)
        return FALSE;

    if (!xs_fload_buffer(filename, &buf, &bufSize, XS_SIDBUF_SIZE, TRUE))
        goto error;

    if (!engine->tune->load(buf, bufSize))
    {
        xs_error("could not initialize tune from buffer, %p:%d '%s'.\n",
            buf, bufSize, filename);
        goto error;
    }

    res = TRUE;

error:
    g_free(buf);
    return res;
}


/* Delete INTERNAL information
 */
void xs_sidplay1_delete(XSEngineState * state)
{
    (void) state;
}


/* This function gets most of the information, though we do miss some
 * (those variables that are only set by libSIDPlay when tune is initialized).
 * Rest of the information is acquired in xs_sidplay1_updateinfo()
 */
XSTuneInfo *xs_sidplay1_getinfo(const gchar *filename)
{
    XSTuneInfo *res = NULL;
    sidTune *tune = NULL;
    sidTuneInfo info;
    guint8 *buf = NULL;
    size_t bufSize;

    /* Check if the tune exists and is readable */
    if (!xs_fload_buffer(filename, &buf, &bufSize, XS_SIDBUF_SIZE, TRUE))
    {
        XSDEBUG("could not load file '%s'.\n", filename);
        goto error;
    }

    if ((tune = new sidTune(buf, bufSize)) == NULL)
    {
        XSDEBUG("could not initialize tune from '%s'.\n", filename);
        goto error;
    }

    if (!tune->getStatus())
    {
        XSDEBUG("tune->getStatus() returned false for '%s'.\n", filename);
        goto error;
    }

    /* Get general tune information */
    tune->getInfo(info);

    /* Allocate tuneinfo structure and set information */
    res = xs_tuneinfo_new(filename,
        info.songs, info.startSong,
        info.infoString[0], info.infoString[1], info.infoString[2],
        info.loadAddr, info.initAddr, info.playAddr,
        info.dataFileLen, info.formatString,
        info.sidModel
        );
    
error:
    if (tune)
        delete tune;

    g_free(buf);
    return res;
}


/* Updates the information of currently playing tune
 */
gboolean xs_sidplay1_updateinfo(XSEngineState *state)
{
    XSSIDPlay1 *engine;
    sidTuneInfo info;
    
    /* Check if we have required structures initialized */
    if (!state || !state->tuneInfo || !state->internal)
        return FALSE;

    engine = (XSSIDPlay1 *) state->internal;
    if (!(engine->tune))
        return FALSE;

    /* Get general tune information */
    engine->tune->getInfo(info);

    /* NOTICE! Here we assume that libSIDPlay[12] headers define
     * SIDTUNE_SIDMODEL_* similarly to our enums in xs_config.h ...
     */
    state->tuneInfo->sidModel = info.sidModel;

    if (state->currSong > 0 && state->currSong <= state->tuneInfo->nsubTunes)
    {
        gint tmpSpeed = info.clockSpeed;
        switch (info.clockSpeed)
        {
            case SIDTUNE_CLOCK_PAL:      tmpSpeed = XS_CLOCK_PAL; break;
            case SIDTUNE_CLOCK_NTSC:     tmpSpeed = XS_CLOCK_NTSC; break;
            case SIDTUNE_CLOCK_ANY:      tmpSpeed = XS_CLOCK_ANY; break;
            case SIDTUNE_CLOCK_UNKNOWN:
                switch (info.songSpeed)
                {
                    case SIDTUNE_SPEED_VBI:      tmpSpeed = XS_CLOCK_VBI; break;
                    case SIDTUNE_SPEED_CIA_1A:   tmpSpeed = XS_CLOCK_CIA; break;
                    default:                     tmpSpeed = info.songSpeed; break;
                }
                break;
        }
        state->tuneInfo->subTunes[state->currSong - 1].tuneSpeed = tmpSpeed;
    }

    return TRUE;
}

}    /* extern "C" */
#endif    /* HAVE_SIDPLAY1 */