view src/xs_sidplay2.cc @ 776:bb7b3ded919a

Indeed, libsidplay-fp's audio renderer expects buffer of 16-bit shorts and number of samples as input, AND returns number of samples. Thus, we need to divide and multiply the values accordingly. However, it also seems that some internal buffers in resid-fp are hardcoded to certain size, and using too big audio buffers cause overflows .. trying to compensate for that by limiting the amount of audio rendered. Unfortunately there are still memory corruptions.
author Matti Hamalainen <ccr@tnsp.org>
date Tue, 06 Nov 2012 11:59:24 +0200
parents 0e9f2894b0a9
children 8fb51f9ef44e
line wrap: on
line source

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

   libSIDPlay v2 and libSIDPlay v2-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_SIDPLAY2

#include <stdio.h>
#include "xs_sidplay2.h"
#include "xs_config.h"


#ifdef HAVE_SIDPLAY2_FP
#  include <sidplayfp/sidplay2.h>
#  include <sidplayfp/SidTuneMod.h>
#  include <sidplayfp/event.h>
#else
#  include <sidplay/sidplay2.h>
#  include <sidplay/sidlazyiptr.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
#endif


class XSSIDPlay2 {
public:
#ifdef HAVE_SIDPLAY2_FP
    sidplay2 emu;
#else
    SidIPtr<ISidplay2> emu;
#endif
    sid2_config_t config;
    SidTune *tune;

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


#ifdef HAVE_RESID_BUILDER
#  ifdef HAVE_SIDPLAY2_FP
#    include <sidplayfp/builders/residfp.h>
#    include <sidplayfp/builders/resid.h>
#  else
#    include <sidplay/builders/resid.h>
#  endif
#endif

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


XSSIDPlay2::XSSIDPlay2(void)
{
    tune = NULL;
}


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


/* Return song information
 */
#define TFUNCTION   xs_sidplay2_getinfo
#define TFUNCTION2  xs_sidplay2_updateinfo
#define TTUNEINFO   SidTuneInfo
#define TTUNE       SidTune
#define TENGINE     XSSIDPlay2
#include "xs_sidplay.h"


/* Check if we can play the given file
 */
gboolean xs_sidplay2_probe(XSFile *f)
{
    gchar tmpBuf[5];
    
    if (!f) return FALSE;
    
    if (xs_fread(tmpBuf, sizeof(gchar), 4, f) != 4)
        return FALSE;
    
    if (!strncmp(tmpBuf, "PSID", 4) || !strncmp(tmpBuf, "RSID", 4))
        return TRUE;
    else
        return FALSE;
}


/* Initialize SIDPlay2
 */
gboolean xs_sidplay2_init(XSEngineState * state)
{
    XSSIDPlay2 *engine;
#ifndef HAVE_SIDPLAY2_FP
    sid_filter_t tmpFilter;
    xs_sid_filter_t *f;
    gint i;
#endif
    assert(state);

    /* Allocate internal structures */
    engine = new XSSIDPlay2();
    state->internal = engine;
    if (!engine)
        return FALSE;

    /* Get current configuration */
    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 */
#ifdef HAVE_SIDPLAY2_FP
    state->audioBitsPerSample = XS_RES_16BIT;
    engine->config.samplingMethod = SID2_RESAMPLE_INTERPOLATE;
#else
    engine->config.precision = state->audioBitsPerSample;
#endif

    engine->config.frequency = state->audioFrequency;


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

        case XS_RES_16BIT:
        default:
            state->audioFormat = FMT_S16_NE;
#ifndef HAVE_SIDPLAY2_FP
            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:
                    engine->config.sampleFormat = SID2_NATIVE_SIGNED;
                    break;
            }
#endif
            break;
    }
    
#if !defined(HAVE_SIDPLAY2_FP)
    /* Convert filter */
    f = &(xs_cfg.sid2Filter);
    XSDEBUG("using filter '%s', %d points\n", f->name, f->npoints);
    if (f->npoints > XS_SIDPLAY2_NFPOINTS)
    {
        xs_error("[SIDPlay2] Invalid number of filter curve points (%d > %d)\n",
            f->npoints, XS_SIDPLAY2_NFPOINTS);
        f->npoints = XS_SIDPLAY2_NFPOINTS;
    }
    
    tmpFilter.points = f->npoints;
    for (i = 0; i < f->npoints; i++)
    {
        tmpFilter.cutoff[i][0] = f->points[i].x;
        tmpFilter.cutoff[i][1] = f->points[i].y;
    }
#endif

    /* 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
#ifdef HAVE_SIDPLAY2_FP
        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;
                    rs->bias(0.0f);
                }
            }
            break;

        case XS_BLD_RESID_FP:
            {
                ReSIDfpBuilder *rs = new ReSIDfpBuilder("ReSID builder FP!");
                if (rs)
                {
                    engine->config.sidEmulation = rs;
                    if (!*rs) return FALSE;
                    rs->create((engine->emu.info()).maxsids);
                    if (!*rs) return FALSE;

/*
                    if (m_filter.filterCurve6581)
                        rs->filter6581Curve(m_filter.filterCurve6581);
                    if (m_filter.filterCurve8580)
                        rs->filter8580Curve((double)m_filter.filterCurve8580);
*/
                }
            }
            break;
#else
        case XS_BLD_RESID:
            {
                engine->config.sidEmulation = ReSIDBuilderCreate("ReSID builder");
                SidLazyIPtr<IReSIDBuilder> rs(engine->config.sidEmulation);
                if (rs)
                {
                    engine->config.sidEmulation = rs->iaggregate();
                    rs->create((engine->emu.info()).maxsids);
                    if (!*rs) return FALSE;
                }
            }
            break;
#endif
#endif

#ifdef HAVE_HARDSID_BUILDER
        case XS_BLD_HARDSID:
#ifdef HAVE_SIDPLAY2_FP
            {
                HardSIDBuilder *hs = new HardSIDBuilder("HardSID builder (FP)");
                engine->config.sidEmulation = (sidbuilder *) hs;
                if (hs)
                {
#else
                engine->config.sidEmulation = HardSIDBuilderCreate("HardSID builder");
                SidLazyIPtr<IHardSIDBuilder> hs(engine->config.sidEmulation);
                if (hs)
                {
                    engine->config.sidEmulation = hs->iaggregate();
#endif
                    /* Builder object created, initialize it */
                    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");
            break;
    }

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

    // Setup filter
    engine->config.sidEmulation->filter(xs_cfg.emulateFilters);
    if (!*(engine->config.sidEmulation))
    {
        xs_error("builder->filter(%d) failed.\n", xs_cfg.emulateFilters);
        return FALSE;
    }

#ifdef HAVE_SIDPLAY2_FP
    engine->config.sidEmulation = engine->config.sidEmulation;
    XSDEBUG("%s\n", engine->config.sidEmulation->credits());
#endif

    /* 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;
    }
    

#ifndef HAVE_SIDPLAY2_FP
    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;
#endif

    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;

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

    return TRUE;
}


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

    xs_sidplay2_delete(state);

    if (engine)
    {
#ifdef HAVE_SIDPLAY2_FP
        if (engine->config.sidEmulation)
        {
            sidbuilder *builder = engine->config.sidEmulation;
            engine->config.sidEmulation = NULL;
            engine->emu.config(engine->config);
            delete builder;
        }
#endif

        delete engine;
        engine = NULL;
    }

    state->internal = NULL;
}


/* 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;
    }
    
    state->isInitialized = TRUE;

    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;

    int len = audioBufSize / sizeof(short);
    if (len > 512) len = 512;
    return engine->emu.play((short *) audioBuffer, len) * sizeof(short);
}


/* Load a given SID-tune file
 */
gboolean xs_sidplay2_load(XSEngineState * state, gchar * filename)
{
    XSSIDPlay2 *engine = (XSSIDPlay2 *) state->internal;

    state->isInitialized = FALSE;

    if (!engine)
        return FALSE;

    if (!(engine->tune = new SidTune(filename)))
        return FALSE;

    return TRUE;
}


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

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


/* Hardware backend flushing
 */
void xs_sidplay2_flush(XSEngineState * state)
{
    (void) state;

#ifdef HAVE_HARDSID_BUILDER
    if (xs_cfg.sid2Builder == XS_BLD_HARDSID)
    {
#ifdef HSID_SID2_COM
        IfPtr<HardSIDBuilder> hs(state->config.sidEmulation);
        if (hs)
            hs->flush();
#else
        ((HardSIDBuilder *) state->config.sidEmulation)->flush();
#endif
    }
#endif
}


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