    /***************************************************************************
                          player.h  -  description
                             -------------------
    begin                : Fri Jun 9 2000
    copyright            : (C) 2000 by Simon White
    email                : s_a_white@email.com
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/
/***************************************************************************
 *  $Log: player.h,v $
 *  Revision 1.39  2002/12/03 23:25:53  s_a_white
 *  Prevent PSID digis from playing in real C64 mode.
 *
 *  Revision 1.38  2002/11/27 00:16:51  s_a_white
 *  Make sure driver info gets reset and exported properly.
 *
 *  Revision 1.37  2002/11/20 21:44:34  s_a_white
 *  Initial support for external DMA to steal cycles away from the CPU.
 *
 *  Revision 1.36  2002/11/19 22:55:50  s_a_white
 *  PSIDv2NG/RSID changes to deal with spec updates for recommended
 *  implementation.
 *
 *  Revision 1.35  2002/11/01 17:36:01  s_a_white
 *  Frame based support for old sidplay1 modes.
 *
 *  Revision 1.34  2002/10/02 19:45:23  s_a_white
 *  RSID support.
 *
 *  Revision 1.33  2002/09/12 21:01:31  s_a_white
 *  Added support for simulating the random delay before the user loads a
 *  program on a real C64.
 *
 *  Revision 1.32  2002/09/09 18:01:30  s_a_white
 *  Prevent m_info driver details getting modified when C64 crashes.
 *
 *  Revision 1.31  2002/07/21 19:39:41  s_a_white
 *  Proper error handling of reloc info overlapping load image.
 *
 *  Revision 1.30  2002/07/20 08:34:52  s_a_white
 *  Remove unnecessary and pointless conts.
 *
 *  Revision 1.29  2002/07/17 21:48:10  s_a_white
 *  PSIDv2NG reloc exclude region extension.
 *
 *  Revision 1.28  2002/04/14 21:46:50  s_a_white
 *  PlaySID reads fixed to come from RAM only.
 *
 *  Revision 1.27  2002/03/12 18:43:59  s_a_white
 *  Tidy up handling of envReset on illegal CPU instructions.
 *
 *  Revision 1.26  2002/03/03 22:01:58  s_a_white
 *  New clock speed & sid model interface.
 *
 *  Revision 1.25  2002/01/29 21:50:33  s_a_white
 *  Auto switching to a better emulation mode.  m_tuneInfo reloaded after a
 *  config.  Initial code added to support more than two sids.
 *
 *  Revision 1.24  2002/01/28 19:32:01  s_a_white
 *  PSID sample improvements.
 *
 *  Revision 1.23  2001/12/13 08:28:08  s_a_white
 *  Added namespace support to fix problems with xsidplay.
 *
 *  Revision 1.22  2001/10/18 22:34:04  s_a_white
 *  GCC3 fixes.
 *
 *  Revision 1.21  2001/10/02 18:26:36  s_a_white
 *  Removed ReSID support and updated for new scheduler.
 *
 *  Revision 1.20  2001/09/20 19:32:39  s_a_white
 *  Support for a null sid emulation to use when a builder create call fails.
 *
 *  Revision 1.19  2001/09/17 19:02:38  s_a_white
 *  Now uses fixed point maths for sample output and rtc.
 *
 *  Revision 1.18  2001/09/01 11:15:46  s_a_white
 *  Fixes sidplay1 environment modes.
 *
 *  Revision 1.17  2001/08/10 21:01:06  s_a_white
 *  Fixed RTC initialisation order warning.
 *
 *  Revision 1.16  2001/08/10 20:03:19  s_a_white
 *  Added RTC reset.
 *
 *  Revision 1.15  2001/07/25 17:01:13  s_a_white
 *  Support for new configuration interface.
 *
 *  Revision 1.14  2001/07/14 16:46:16  s_a_white
 *  Sync with sidbuilder class project.
 *
 *  Revision 1.13  2001/07/14 12:50:58  s_a_white
 *  Support for credits and debuging.  External filter selection removed.  RTC
 *  and samples obtained in a more efficient way.  Support for component
 *  and sidbuilder classes.
 *
 *  Revision 1.12  2001/04/23 17:09:56  s_a_white
 *  Fixed video speed selection using unforced/forced and NTSC clockSpeeds.
 *
 *  Revision 1.11  2001/03/22 22:45:20  s_a_white
 *  Re-ordered initialisations to match defintions.
 *
 *  Revision 1.10  2001/03/21 23:28:12  s_a_white
 *  Support new component names.
 *
 *  Revision 1.9  2001/03/21 22:32:55  s_a_white
 *  Filter redefinition support.  VIC & NMI support added.
 *
 *  Revision 1.8  2001/03/08 22:48:33  s_a_white
 *  Sid reset on player destruction removed.  Now handled locally by the sids.
 *
 *  Revision 1.7  2001/03/01 23:46:37  s_a_white
 *  Support for sample mode to be selected at runtime.
 *
 *  Revision 1.6  2001/02/28 18:52:55  s_a_white
 *  Removed initBank* related stuff.
 *
 *  Revision 1.5  2001/02/21 21:41:51  s_a_white
 *  Added seperate ram bank to hold C64 player.
 *
 *  Revision 1.4  2001/02/07 20:56:46  s_a_white
 *  Samples now delayed until end of simulated frame.
 *
 *  Revision 1.3  2001/01/23 21:26:28  s_a_white
 *  Only way to load a tune now is by passing in a sidtune object.  This is
 *  required for songlength database support.
 *
 *  Revision 1.2  2001/01/07 15:58:37  s_a_white
 *  SID2_LIB_API now becomes a core define (SID_API).
 *
 *  Revision 1.1  2000/12/12 19:15:40  s_a_white
 *  Renamed from sidplayer
 *
 ***************************************************************************/

#ifndef _player_h_
#define _player_h_

#include <string.h>

#include "config.h"
#include "sidplay2.h"
#include "sidenv.h"
#include "c64env.h"
#include "c64/c64xsid.h"
#include "c64/c64cia.h"
#include "c64/c64vic.h"

#include "mos6510/mos6510.h"
#include "sid6526/sid6526.h"
#include "nullsid.h"
#define  SID2_MAX_SIDS 2

SIDPLAY2_NAMESPACE_START

class Player: private C64Environment, c64env
{
private:
    static const double CLOCK_FREQ_NTSC;
    static const double CLOCK_FREQ_PAL;
    static const double VIC_FREQ_PAL;
    static const double VIC_FREQ_NTSC;

    static const char  *TXT_PAL_VBI;
    static const char  *TXT_PAL_VBI_FIXED;
    static const char  *TXT_PAL_CIA;
    static const char  *TXT_PAL_UNKNOWN;
    static const char  *TXT_NTSC_VBI;
    static const char  *TXT_NTSC_VBI_FIXED;
    static const char  *TXT_NTSC_CIA;
    static const char  *TXT_NTSC_UNKNOWN;
    static const char  *TXT_NA;

    static const char  *ERR_CONF_WHILST_ACTIVE;
    static const char  *ERR_UNSUPPORTED_FREQ;
    static const char  *ERR_UNSUPPORTED_PRECISION;
    static const char  *ERR_MEM_ALLOC;
    static const char  *ERR_UNSUPPORTED_MODE;
    static const char  *credit[10]; // 10 credits max

    static const char  *ERR_PSIDDRV_NO_SPACE; 
    static const char  *ERR_PSIDDRV_RELOC;

    EventScheduler m_scheduler;

    //SID6510  cpu(6510, "Main CPU");
    SID6510 sid6510;
    MOS6510 mos6510;
    MOS6510 *cpu;
    // Sid objects to use.
    NullSID nullsid;
    c64xsid xsid;
    c64cia1 cia;
    c64cia2 cia2;
    SID6526 sid6526;
    c64vic  vic;
    sidemu *sid[SID2_MAX_SIDS];

    class EventMixer: public Event
    {
    private:
        Player &m_player;
        void event (void) { m_player.mixer (); }

    public:
        EventMixer (Player *player)
        :Event("Mixer"),
         m_player(*player) {}
    } mixerEvent;
    friend class EventMixer;

    class EventRTC: public Event
    {
    private:
        EventContext &m_eventContext;
        event_clock_t m_seconds;
        event_clock_t m_period;
        event_clock_t m_clk;

        void event (void)
        {   // Fixed point 25.7 (approx 2 dp)
            event_clock_t cycles;
            m_clk += m_period;
            cycles = m_clk >> 7;
            m_clk &= 0x7F;
            m_seconds++;
            m_eventContext.schedule (this, cycles);
        }

    public:
        EventRTC (EventContext *context)
        :Event("RTC"),
         m_eventContext(*context),
         m_seconds(0)
        {;}

        event_clock_t getTime () const {return m_seconds;}

        void reset (void)
        {   // Fixed point 25.7
            m_seconds = 0;
            m_clk     = m_period & 0x7F;
            m_eventContext.schedule (this, m_period >> 7);
        }

        void clock (float64_t period)
        {   // Fixed point 25.7
            m_period = (event_clock_t) (period / 10.0 * (float64_t) (1 << 7));
            reset ();
        }   
    } rtc;

    // User Configuration Settings
    SidTuneInfo   m_tuneInfo;
    SidTune      *m_tune;
    uint8_t      *m_ram, *m_rom;
    sid2_info_t   m_info;
    sid2_config_t m_cfg;

    const char     *m_errorString;
    float64_t       m_fastForwardFactor;
    uint_least32_t  m_mileage;
    int_least32_t   m_leftVolume;
    int_least32_t   m_rightVolume;
    volatile sid2_player_t m_playerState;
    volatile bool   m_running;
    int             m_rand;

    // Mixer settings
    event_clock_t  m_sampleClock;
    event_clock_t  m_samplePeriod;
    uint_least32_t m_sampleCount;
    uint_least32_t m_sampleIndex;
    char          *m_sampleBuffer;

    // C64 environment settings
    uint8_t        m_bankReg;
    uint8_t        m_playBank;
    uint_least16_t m_sidAddress[2];

    // temp stuff -------------
    bool   isKernal;
    bool   isBasic;
    bool   isIO;
    inline void evalBankSelect (uint8_t data);
    void   c64_initialise      (void);
    // ------------------------

private:
    float64_t clockSpeed     (sid2_clock_t clock, sid2_clock_t defaultClock,
                              bool forced);
    int       environment    (sid2_env_t env);
    void      fakeIRQ        (void);
    int       initialise     (void);
    void      nextSequence   (void);
    void      mixer          (void);
    void      mixerReset     (void);
    void      mileageCorrect (void);
    int       sidCreate      (sidbuilder *builder, sid2_model_t model,
                              sid2_model_t defaultModel);
    void      sidSamples     (bool enable);
    void      reset          ();
    uint8_t   iomap          (uint_least16_t addr);

    uint8_t readMemByte_player    (uint_least16_t addr);
    uint8_t readMemByte_plain     (uint_least16_t addr);
    uint8_t readMemByte_io        (uint_least16_t addr);
    uint8_t readMemByte_sidplaytp (uint_least16_t addr);
    uint8_t readMemByte_sidplaybs (uint_least16_t addr);
    void    writeMemByte_plain    (uint_least16_t addr, uint8_t data);
    void    writeMemByte_playsid  (uint_least16_t addr, uint8_t data);
    void    writeMemByte_sidplay  (uint_least16_t addr, uint8_t data);

    // Use pointers to please requirements of all the provided
    // environments.
    uint8_t (Player::*m_readMemByte)    (uint_least16_t);
    void    (Player::*m_writeMemByte)   (uint_least16_t, uint8_t);
    uint8_t (Player::*m_readMemDataByte)(uint_least16_t);

    uint8_t  readMemRamByte (uint_least16_t addr)
    {   return m_ram[addr]; }

    // Environment Function entry Points
    void           envReset           (bool safe);
    inline void    envReset           (void) { envReset (true); }
    uint8_t envReadMemByte     (uint_least16_t addr);
    inline void    envWriteMemByte    (uint_least16_t addr, uint8_t data);
    inline bool    envCheckBankJump   (uint_least16_t addr);
    inline uint8_t envReadMemDataByte (uint_least16_t addr);
    inline void    envSleep           (void);

    void   envLoadFile (char *file)
    {
        char name[0x100] = "e:/emulators/c64/games/prgs/";
        strcat (name, file);
        strcat (name, ".sid");
        m_tune->load (name);
        stop ();
    }

    // Rev 2.0.3 Added - New Mixer Routines
    uint_least32_t (Player::*output) (char *buffer);

    // Rev 2.0.4 (saw) - Added to reduce code size
    int_least32_t monoOutGenericLeftIn   (uint_least8_t  bits);
    int_least32_t monoOutGenericStereoIn (uint_least8_t  bits);
    int_least32_t monoOutGenericRightIn  (uint_least8_t bits);

    // 8 bit output
    uint_least32_t monoOut8MonoIn       (char *buffer);
    uint_least32_t monoOut8StereoIn     (char *buffer);
    uint_least32_t monoOut8StereoRIn    (char *buffer);
    uint_least32_t stereoOut8MonoIn     (char *buffer);
    uint_least32_t stereoOut8StereoIn   (char *buffer);

    // Rev 2.0.4 (jp) - Added 16 bit support
    uint_least32_t monoOut16MonoIn      (char *buffer);
    uint_least32_t monoOut16StereoIn    (char *buffer);
    uint_least32_t monoOut16StereoRIn   (char *buffer);
    uint_least32_t stereoOut16MonoIn    (char *buffer);
    uint_least32_t stereoOut16StereoIn  (char *buffer);

    void interruptIRQ (bool state);
    void interruptNMI (void);
    void interruptRST (void);
    void signalBA     (bool state) { cpu->rdySignal (state); }
    void signalAEC    (bool state) { cpu->aecSignal (state); }

    // PSID driver
    int  psidDrvInstall (SidTuneInfo &tuneInfo, sid2_info_t &info);
    void psidRelocAddr  (SidTuneInfo &tuneInfo, int startp, int endp);

public:
    Player ();
    ~Player();

    const sid2_config_t &config (void) const { return m_cfg; }
    const sid2_info_t   &info   (void) const { return m_info; }

    int            config       (const sid2_config_t &cfg);
    int            fastForward  (uint percent);
    int            load         (SidTune *tune);
    uint_least8_t  mileage      (void) const { return m_mileage + time(); }
    void           pause        (void);
    uint_least32_t play         (void *buffer, uint_least32_t length);
    sid2_player_t  state        (void) const { return m_playerState; }
    void           stop         (void);
    uint_least32_t time         (void) const {return rtc.getTime (); }
    void           debug        (bool enable) { cpu->debug (enable); }
    const char    *error        (void) const { return m_errorString; }
};

inline void Player::envSleep (void)
{
    if (m_info.environment != sid2_envR)
    {   // Start the sample sequence
        xsid.suppress (false);
        xsid.suppress (true);
    }
}

inline void Player::interruptIRQ (bool state)
{
    if (state)
    {
        if (m_info.environment == sid2_envR)
            cpu->triggerIRQ ();
        else
            fakeIRQ ();
    }
    else
        cpu->clearIRQ ();
}

inline void Player::interruptNMI ()
{
    cpu->triggerNMI ();
}

inline void Player::interruptRST ()
{
    stop ();
}

SIDPLAY2_NAMESPACE_STOP

#endif // _player_h_
