view src/xs_sidplayfp.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 32435407eb9c
line wrap: on
line source

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

   libSIDPlay2-FP 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_SIDPLAYFP

#include "xs_sidplayfp.h"
#include "xs_slsup.h"
#include "xs_config.h"


#ifdef HAVE_SIDPLAYFP_V1
#  include <sidplayfp/SidTune.h>
#  include <sidplayfp/sidplayfp.h>
#  include <sidplayfp/event.h>
#  include <sidplayfp/SidConfig.h>
#  include <sidplayfp/SidInfo.h>
#else
#  include <sidplayfp/sidplay2.h>
#  include <sidplayfp/SidTuneMod.h>
#  include <sidplayfp/event.h>
#endif


class XSSIDPlayFP {
public:
#ifdef HAVE_SIDPLAYFP_V1
    sidplayfp emu;
    SidConfig config;
#else
    sidplay2 emu;
    sid2_config_t config;
#endif
    SidTune tune;

    XSSIDPlayFP(void);
    virtual ~XSSIDPlayFP(void);
};

static guint8 *xs_rom_imagedata[XS_C64_ROM_IMAGES];

#ifdef HAVE_RESID_FP_BUILDER
#  include <sidplayfp/builders/residfp.h>
#endif

#ifdef HAVE_RESID_FP_BUILDER
#  include <sidplayfp/builders/resid.h>
#endif

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


XSSIDPlayFP::XSSIDPlayFP(void) : tune(0)
{
    emu.load(NULL);
}


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


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


/* Return song information
 */
#ifdef HAVE_SIDPLAYFP_V1
#define sid2_mono              SidConfig::MONO
#define sid2_stereo            SidConfig::STEREO

#define SID2_INTERPOLATE       SidConfig::INTERPOLATE
#define SID2_RESAMPLE_INTERPOLATE SidConfig::RESAMPLE_INTERPOLATE

#define SID2_MOS8580           SidConfig::MOS8580
#define SID2_MOS6581           SidConfig::MOS6581

#define SID2_CLOCK_PAL         SidConfig::CLOCK_PAL
#define SID2_CLOCK_NTSC        SidConfig::CLOCK_NTSC

#define SIDTUNE_CLOCK_UNKNOWN  SidTuneInfo::CLOCK_UNKNOWN
#define SIDTUNE_CLOCK_PAL      SidTuneInfo::CLOCK_PAL
#define SIDTUNE_CLOCK_NTSC     SidTuneInfo::CLOCK_NTSC
#define SIDTUNE_CLOCK_ANY      SidTuneInfo::CLOCK_ANY
#define SIDTUNE_SPEED_VBI      SidTuneInfo::SPEED_VBI
#define SIDTUNE_SPEED_CIA_1A   SidTuneInfo::SPEED_CIA_1A
#endif


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

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

    // Basically support all variants ..
    if (!strncmp(probe, "PSID", 4) && probe[4] == 0 &&
        probe[5] >= 1 && probe[5] <= 3)
        return TRUE;
    else
    if (!strncmp(probe, "RSID", 4) &&
        probe[4] == 0 && probe[5] >= 2 && probe[5] <= 3)
        return TRUE;
    else
        return FALSE;
}


/* Initialize SIDPlayFP
 */
gboolean xs_sidplayfp_init(XSEngineState * state)
{
    assert(state);

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

    memset(xs_rom_imagedata, 0, sizeof(xs_rom_imagedata));
    if (!xs_load_rom_images(xs_rom_imagedata))
        return FALSE;
    
    return TRUE;
}


/* Close SIDPlayFP engine
 */
void xs_sidplayfp_close(XSEngineState * state)
{
    XSSIDPlayFP *engine = (XSSIDPlayFP *) state->internal;

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

    xs_sidplayfp_delete(state);
}


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

    if (!engine)
        return FALSE;

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

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

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

    return TRUE;
}


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

    if (!engine)
        return 0;

    return engine->emu.play((short *) audioBuffer, audioBufSize / sizeof(short)) * sizeof(short);
}


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

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

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

    /* Configure channels and stuff */
    engine->config.playback = (state->audioChannels == XS_CHN_MONO) ? sid2_mono : sid2_stereo;

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

    switch (xs_cfg.residSampling)
    {
        case XS_RESID_RESAMPLE_FIR:
            engine->config.samplingMethod = SID2_RESAMPLE_INTERPOLATE;
            break;

        case XS_RESID_INTERPOLATE:
        default:
            xs_cfg.residSampling = XS_RESID_INTERPOLATE;
            engine->config.samplingMethod = SID2_INTERPOLATE;
            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 */
    engine->config.sidDefault  = xs_cfg.mos8580 ? SID2_MOS8580 : SID2_MOS6581;
    engine->config.clockForced = xs_cfg.forceSpeed;

#ifndef HAVE_SIDPLAYFP_V1
    engine->config.sidSamples  = TRUE;
    engine->config.sidModel    = xs_cfg.forceModel ? engine->config.sidDefault : SID2_MODEL_CORRECT;
    engine->config.clockSpeed  = xs_cfg.forceSpeed ? engine->config.clockDefault : SID2_CLOCK_CORRECT;

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

    switch (xs_cfg.sid2Builder)
    {
#ifdef HAVE_RESID_BUILDER
        case XS_BLD_RESID:
            {
                ReSIDBuilder *rs = new ReSIDBuilder("ReSID builder");
#ifdef HAVE_SIDPLAYFP_V1
                if (rs && rs->getStatus())
                {
                    engine->config.sidEmulation = rs;
                    if (!rs->getStatus()) goto error;
                    rs->create((engine->emu.info()).maxsids());
                    if (!rs->getStatus()) goto error;
                }
#else
                if (rs && *rs)
                {
                    engine->config.sidEmulation = rs;
                    if (!*rs) goto error;
                    rs->create((engine->emu.info()).maxsids);
                    if (!*rs) goto error;
                }
#endif
                rs->bias(0.0f);
            }
            break;
#endif // HAVE_RESID_BUILDER


#ifdef HAVE_RESID_FP_BUILDER
        case XS_BLD_RESID_FP:
            {
                ReSIDfpBuilder *rs = new ReSIDfpBuilder("ReSID builder FP!");
#ifdef HAVE_SIDPLAYFP_V1
                if (rs && rs->getStatus())
                {
                    engine->config.sidEmulation = rs;
                    if (!rs->getStatus()) goto error;
                    rs->create((engine->emu.info()).maxsids());
                    if (!rs->getStatus()) goto error;
                }
#else
                if (rs && *rs)
                {
                    engine->config.sidEmulation = rs;
                    if (!*rs) goto error;
                    rs->create((engine->emu.info()).maxsids);
                    if (!*rs) goto error;
                }
#endif
//                rs->filter6581Curve(0.0);
//                rs->filter8580Curve(0.0);
            }
            break;
#endif

#ifdef HAVE_HARDSID_BUILDER
        case XS_BLD_HARDSID:
            {
                HardSIDBuilder *hs = new HardSIDBuilder("HardSID builder (FP)");
                engine->config.sidEmulation = (sidbuilder *) hs;
#ifdef HAVE_SIDPLAYFP_V1
                if (hs && hs->getStatus())
                {
                    hs->create((engine->emu.info()).maxsids());
                    if (!hs->getStatus()) goto error;
                }
#else
                if (hs && *hs)
                {
                    hs->create((engine->emu.info()).maxsids);
                    if (!*hs) goto error;
                }
#endif
            }
            break;
#endif
        
        default:
            xs_error("[SIDPlayFP] Invalid or unsupported builder selected.\n");
            goto error;
    }


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

    // Setup filter
    engine->config.sidEmulation->filter(xs_cfg.emulateFilters);
#ifdef HAVE_SIDPLAYFP_V1
    if (!engine->config.sidEmulation->getStatus())
#else
    if (!*(engine->config.sidEmulation))
#endif
    {
        xs_error("builder->filter(%d) failed.\n", xs_cfg.emulateFilters);
        goto error;
    }

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

#ifdef HAVE_SIDPLAYFP_V1
    engine->emu.setRoms(xs_rom_imagedata[0], xs_rom_imagedata[1], xs_rom_imagedata[2]);
#endif



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

    engine->tune.read(buf, bufSize);
    
#ifdef HAVE_SIDPLAYFP_V1
    if (!engine->tune.getStatus())
#else
    if (!engine->tune)
#endif
    {
        xs_error("Could not load file '%s'\n", filename);
        goto error;
    }

    g_free(buf);
    return TRUE;

error:
    if (engine)
        delete engine;
    state->internal = NULL;
    g_free(buf);
    return FALSE;
}


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

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


/* Hardware backend flushing
 */
void xs_sidplayfp_flush(XSEngineState * state)
{
    XSSIDPlayFP *engine = (XSSIDPlayFP *) 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_sidplayfp_updateinfo()
 */
XSTuneInfo *xs_sidplayfp_getinfo(const gchar *filename)
{
    XSTuneInfo *res = NULL;
    SidTune *tune = NULL;
    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 */
    {
#ifdef HAVE_SIDPLAYFP_V1
    const SidTuneInfo *info = tune->getInfo();

    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->sidModel1()
        );
#else
    const SidTuneInfo info = tune->getInfo();

    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.sidModel1
        );
#endif
    }

error:
    if (tune)
        delete tune;

    g_free(buf);
    return res;
}


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

    engine = (XSSIDPlayFP *) state->internal;

#ifdef HAVE_SIDPLAYFP_V1
    if (!engine->tune.getStatus())
        return FALSE;

    const SidTuneInfo *info = engine->tune.getInfo();

    state->tuneInfo->sidModel = info->sidModel1();

    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;
    }
#else
    if (!(engine->tune))
        return FALSE;

    const SidTuneInfo info = engine->tune.getInfo();

    state->tuneInfo->sidModel = info.sidModel1;

    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;
    }
#endif

    return TRUE;
}


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