view src/xs_sidplay2.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 828dce1195e6
line wrap: on
line source

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

   libSIDPlay2 support

   Programmed and designed by Matti 'ccr' Hamalainen <ccr@tnsp.org>
   (C) Copyright 1999-2012 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_SIDPLAY2

#include "xs_sidplay2.h"
#include "xs_slsup.h"
#include "xs_config.h"

#include <sidplay/sidplay2.h>
#if G_BYTE_ORDER == G_BIG_ENDIAN
#  define SID2_NATIVE_UNSIGNED SID2_BIG_UNSIGNED
#  define SID2_NATIVE_SIGNED SID2_BIG_SIGNED
#elif G_BYTE_ORDER == G_LITTLE_ENDIAN
#  define SID2_NATIVE_UNSIGNED SID2_LITTLE_UNSIGNED
#  define SID2_NATIVE_SIGNED SID2_LITTLE_SIGNED
#else
#  error Unsupported endianess!
#endif


class XSSIDPlay2 {
public:
    sidplay2 emu;
    sid2_config_t config;
    SidTune tune;

    XSSIDPlay2(void);
    virtual ~XSSIDPlay2(void);
};


#ifdef HAVE_RESID_BUILDER
#  include <sidplay/builders/resid.h>
#endif

#ifdef HAVE_HARDSID_BUILDER
#  include <sidplay/builders/hardsid.h>
#endif


XSSIDPlay2::XSSIDPlay2(void) : tune(0)
{
}


XSSIDPlay2::~XSSIDPlay2(void)
{
    emu.load(NULL);
}


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


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

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

    // Old libSIDPlay2 does not support PSIDv3, afaik
    if (!strncmp(probe, "PSID", 4) && probe[4] == 0 &&
        probe[5] >= 1 && probe[5] <= 2)
        return TRUE;
    else
    if (!strncmp(probe, "RSID", 4) &&
        probe[4] == 0 && probe[5] == 2)
        return TRUE;
    else
        return FALSE;
}


/* Initialize SIDPlay2
 */
gboolean xs_sidplay2_init(XSEngineState * state)
{


    return TRUE;
}


/* Close SIDPlay2 engine
 */
void xs_sidplay2_close(XSEngineState * state)
{
    XSSIDPlay2 *engine = (XSSIDPlay2 *) state->internal;

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

    xs_sidplay2_delete(state);
}


/* Initialize current song and sub-tune
 */
gboolean xs_sidplay2_initsong(XSEngineState * state)
{
    XSSIDPlay2 *engine = (XSSIDPlay2 *) state->internal;

    if (!engine)
        return FALSE;

    if (!engine->tune.selectSong(state->currSong))
    {
        xs_error("[SIDPlay2] tune.selectSong() failed\n");
        return FALSE;
    }

    if (engine->emu.load(&(engine->tune)) < 0)
    {
        xs_error("[SIDPlay2] emu.load() failed\n");
        return FALSE;
    }

    if (engine->emu.config(engine->config) < 0)
    {
        xs_error("[SIDPlay2] Emulator engine configuration failed!\n");
        return FALSE;
    }

    return TRUE;
}


/* Emulate and render audio data to given buffer
 */
guint xs_sidplay2_fillbuffer(XSEngineState * state, gchar * audioBuffer, guint audioBufSize)
{
    XSSIDPlay2 *engine = (XSSIDPlay2 *) state->internal;

    if (!engine)
        return 0;

    return engine->emu.play(audioBuffer, audioBufSize);
}


/* Load a given SID-tune file
 */
gboolean xs_sidplay2_load(XSEngineState * state, gchar * filename)
{
    XSSIDPlay2 *engine;
    guint8 *buf = NULL;
    size_t bufSize;
    gint i;

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

    /* Allocate internal structures */
    engine = new XSSIDPlay2();
    state->internal = engine;
    if (!engine)
    {
        xs_error("Allocating XSSIDPlay2 compound backend object failed.\n");
        goto error;
    }

    /* Get current configuration */
    XSDEBUG("SIDPlay2 emulation configuration\n");
    engine->config = engine->emu.config();

    /* Configure channels and stuff */
    switch (state->audioChannels)
    {
        case XS_CHN_AUTOPAN:
            engine->config.playback = sid2_stereo;
            break;

        case XS_CHN_STEREO:
            engine->config.playback = sid2_stereo;
            break;

        case XS_CHN_MONO:
        default:
            engine->config.playback = sid2_mono;
            state->audioChannels = XS_CHN_MONO;
            break;
    }


    /* Memory mode settings */
    switch (xs_cfg.memoryMode)
    {
        case XS_MPU_BANK_SWITCHING:
            engine->config.environment = sid2_envBS;
            break;

        case XS_MPU_TRANSPARENT_ROM:
            engine->config.environment = sid2_envTP;
            break;

        case XS_MPU_PLAYSID_ENVIRONMENT:
            engine->config.environment = sid2_envPS;
            break;

        case XS_MPU_REAL:
        default:
            engine->config.environment = sid2_envR;
            xs_cfg.memoryMode = XS_MPU_REAL;
            break;
    }


    /* Audio parameters sanity checking and setup */
    engine->config.precision = state->audioBitsPerSample;
    engine->config.frequency = state->audioFrequency;


    switch (state->audioBitsPerSample)
    {
        case XS_RES_8BIT:
            state->audioFormat = FMT_U8;
            engine->config.sampleFormat = SID2_LITTLE_UNSIGNED;
            break;

        case XS_RES_16BIT:
        default:
            switch (state->audioFormat)
            {
                case FMT_U16_LE:
                    engine->config.sampleFormat = SID2_LITTLE_UNSIGNED;
                    break;

                case FMT_U16_BE:
                    engine->config.sampleFormat = SID2_BIG_UNSIGNED;
                    break;

                case FMT_U16_NE:
                    engine->config.sampleFormat = SID2_NATIVE_UNSIGNED;
                    break;

                case FMT_S16_LE:
                    engine->config.sampleFormat = SID2_LITTLE_SIGNED;
                    break;

                case FMT_S16_BE:
                    engine->config.sampleFormat = SID2_BIG_SIGNED;
                    break;

                default:
                    state->audioFormat = FMT_S16_NE;
                    engine->config.sampleFormat = SID2_NATIVE_SIGNED;
                    break;
            }
            break;
    }
    
    /* Clockspeed settings */
    switch (xs_cfg.clockSpeed)
    {
        case XS_CLOCK_NTSC:
            engine->config.clockDefault = SID2_CLOCK_NTSC;
            break;

        default:
        case XS_CLOCK_PAL:
            engine->config.clockDefault = SID2_CLOCK_PAL;
            xs_cfg.clockSpeed = XS_CLOCK_PAL;
            break;
    }


    /* Configure rest of the emulation */
    if (xs_cfg.forceSpeed)
    { 
        engine->config.clockForced = true;
        engine->config.clockSpeed = engine->config.clockDefault;
    }
    else
    {
        engine->config.clockForced = false;
        engine->config.clockSpeed = SID2_CLOCK_CORRECT;
    }
    

    if (xs_cfg.sid2OptLevel < 0 || xs_cfg.sid2OptLevel > SID2_MAX_OPTIMISATION)
    {
        xs_error("Invalid sid2OptLevel=%d, falling back to %d.\n",
            xs_cfg.sid2OptLevel, SID2_DEFAULT_OPTIMISATION);
        
        xs_cfg.sid2OptLevel = SID2_DEFAULT_OPTIMISATION;
    }
    engine->config.optimisation = xs_cfg.sid2OptLevel;

    engine->config.sidDefault = xs_cfg.mos8580 ? SID2_MOS8580 : SID2_MOS6581;
    engine->config.sidModel   = xs_cfg.forceModel ? engine->config.sidDefault : SID2_MODEL_CORRECT;
    engine->config.sidSamples = TRUE;


    /* Initialize builder object */
    XSDEBUG("init builder #%i, maxsids=%i\n", xs_cfg.sid2Builder, (engine->emu.info()).maxsids);

    switch (xs_cfg.sid2Builder)
    {
#ifdef HAVE_RESID_BUILDER
        case XS_BLD_RESID:
            {
                ReSIDBuilder *rs = new ReSIDBuilder("ReSID builder");
                if (rs)
                {
                    engine->config.sidEmulation = rs;
                    if (!*rs) return FALSE;
                    rs->create((engine->emu.info()).maxsids);
                    if (!*rs) return FALSE;
                }
            }
            break;
#endif

#ifdef HAVE_HARDSID_BUILDER
        case XS_BLD_HARDSID:
            {
                HardSIDBuilder *hs = new HardSIDBuilder("HardSID builder (FP)");
                engine->config.sidEmulation = (sidbuilder *) hs;
                if (hs)
                {
                    hs->create((engine->emu.info()).maxsids);
                    if (!*hs)
                    {
                        xs_error("hardSID->create() failed.\n");
                        return FALSE;
                    }
                }
            }
            break;
#endif
        
        default:
            xs_error("[SIDPlay2] Invalid or unsupported builder selected.\n");
            return FALSE;
    }

    if (!engine->config.sidEmulation)
    {
        xs_error("[SIDPlay2] Could not initialize SIDBuilder object.\n");
        return FALSE;
    }

    XSDEBUG("%s\n", engine->config.sidEmulation->credits());


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

    engine->tune.read(buf, bufSize);
    if (!engine->tune)
    {
        xs_error("Could not load file '%s': %s\n",
            filename, (engine->tune.getInfo()).statusString);
        goto error;
    }

    res = TRUE;

error:
    g_free(buf);
    return res;
}


/* Delete INTERNAL information
 */
void xs_sidplay2_delete(XSEngineState * state)
{
    XSSIDPlay2 *engine = (XSSIDPlay2 *) state->internal;

    if (engine)
        delete engine;
    state->internal = NULL;
}


/* Hardware backend flushing
 */
void xs_sidplay2_flush(XSEngineState * state)
{
    XSSIDPlay2 *engine = (XSSIDPlay2 *) state->internal;

#ifdef HAVE_HARDSID_BUILDER
    if (xs_cfg.sid2Builder == XS_BLD_HARDSID)
    {
        ((HardSIDBuilder *) engine->config.sidEmulation)->flush();
    }
#endif
}


/* 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_sidplay2_updateinfo()
 */
XSTuneInfo *xs_sidplay2_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_sidplay2_updateinfo(XSEngineState *state)
{
    XSSIDPlay2 *engine;
    SidTuneInfo info;
    
    /* Check if we have required structures initialized */
    if (!state || !state->tuneInfo || !state->internal)
        return FALSE;

    engine = (XSSIDPlay2 *) 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_SIDPLAY2
 */