542 lines
14 KiB
Plaintext
542 lines
14 KiB
Plaintext
/*
|
|
* This file is part of libsidplayfp, a SID player engine.
|
|
*
|
|
* Copyright 2011-2016 Leandro Nini <drfiemost@users.sourceforge.net>
|
|
* Copyright 2007-2010 Antti Lankila
|
|
* Copyright 2000-2001 Simon White
|
|
*
|
|
* 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 "player.h"
|
|
|
|
#include "sidplayfp/SidTune.h"
|
|
#include "sidplayfp/sidbuilder.h"
|
|
|
|
#include "sidemu.h"
|
|
#include "psiddrv.h"
|
|
#include "romCheck.h"
|
|
|
|
#include "sidcxx11.h"
|
|
|
|
namespace libsidplayfp
|
|
{
|
|
|
|
// Speed strings
|
|
const char TXT_PAL_VBI[] = "50 Hz VBI (PAL)";
|
|
const char TXT_PAL_VBI_FIXED[] = "60 Hz VBI (PAL FIXED)";
|
|
const char TXT_PAL_CIA[] = "CIA (PAL)";
|
|
const char TXT_PAL_UNKNOWN[] = "UNKNOWN (PAL)";
|
|
const char TXT_NTSC_VBI[] = "60 Hz VBI (NTSC)";
|
|
const char TXT_NTSC_VBI_FIXED[] = "50 Hz VBI (NTSC FIXED)";
|
|
const char TXT_NTSC_CIA[] = "CIA (NTSC)";
|
|
const char TXT_NTSC_UNKNOWN[] = "UNKNOWN (NTSC)";
|
|
|
|
// Error Strings
|
|
const char ERR_NA[] = "NA";
|
|
const char ERR_UNSUPPORTED_FREQ[] = "SIDPLAYER ERROR: Unsupported sampling frequency.";
|
|
const char ERR_UNSUPPORTED_SID_ADDR[] = "SIDPLAYER ERROR: Unsupported SID address.";
|
|
const char ERR_UNSUPPORTED_SIZE[] = "SIDPLAYER ERROR: Size of music data exceeds C64 memory.";
|
|
const char ERR_INVALID_PERCENTAGE[] = "SIDPLAYER ERROR: Percentage value out of range.";
|
|
|
|
/**
|
|
* Configuration error exception.
|
|
*/
|
|
class configError
|
|
{
|
|
private:
|
|
const char* m_msg;
|
|
|
|
public:
|
|
configError(const char* msg) : m_msg(msg) {}
|
|
const char* message() const { return m_msg; }
|
|
};
|
|
|
|
Player::Player() :
|
|
// Set default settings for system
|
|
m_tune(nullptr),
|
|
m_errorString(ERR_NA),
|
|
m_isPlaying(STOPPED)
|
|
{
|
|
#ifdef PC64_TESTSUITE
|
|
m_c64.setTestEnv(this);
|
|
#endif
|
|
|
|
m_c64.setRoms(nullptr, nullptr, nullptr);
|
|
config(m_cfg);
|
|
|
|
// Get component credits
|
|
m_info.m_credits.push_back(m_c64.cpuCredits());
|
|
m_info.m_credits.push_back(m_c64.ciaCredits());
|
|
m_info.m_credits.push_back(m_c64.vicCredits());
|
|
}
|
|
|
|
template<class T>
|
|
inline void checkRom(const uint8_t* rom, std::string &desc)
|
|
{
|
|
if (rom != nullptr)
|
|
{
|
|
T romCheck(rom);
|
|
desc.assign(romCheck.info());
|
|
}
|
|
else
|
|
desc.clear();
|
|
}
|
|
|
|
void Player::setRoms(const uint8_t* kernal, const uint8_t* basic, const uint8_t* character)
|
|
{
|
|
checkRom<kernalCheck>(kernal, m_info.m_kernalDesc);
|
|
checkRom<basicCheck>(basic, m_info.m_basicDesc);
|
|
checkRom<chargenCheck>(character, m_info.m_chargenDesc);
|
|
|
|
m_c64.setRoms(kernal, basic, character);
|
|
}
|
|
|
|
bool Player::fastForward(unsigned int percent)
|
|
{
|
|
if (!m_mixer.setFastForward(percent / 100))
|
|
{
|
|
m_errorString = ERR_INVALID_PERCENTAGE;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void Player::initialise()
|
|
{
|
|
m_isPlaying = STOPPED;
|
|
|
|
m_c64.reset();
|
|
|
|
const SidTuneInfo* tuneInfo = m_tune->getInfo();
|
|
|
|
const uint_least32_t size = static_cast<uint_least32_t>(tuneInfo->loadAddr()) + tuneInfo->c64dataLen() - 1;
|
|
if (size > 0xffff)
|
|
{
|
|
throw configError(ERR_UNSUPPORTED_SIZE);
|
|
}
|
|
|
|
psiddrv driver(m_tune->getInfo());
|
|
if (!driver.drvReloc())
|
|
{
|
|
throw configError(driver.errorString());
|
|
}
|
|
|
|
m_info.m_driverAddr = driver.driverAddr();
|
|
m_info.m_driverLength = driver.driverLength();
|
|
|
|
driver.install(m_c64.getMemInterface(), videoSwitch);
|
|
|
|
if (!m_tune->placeSidTuneInC64mem(m_c64.getMemInterface()))
|
|
{
|
|
throw configError(m_tune->statusString());
|
|
}
|
|
|
|
m_c64.resetCpu();
|
|
}
|
|
|
|
bool Player::load(SidTune *tune)
|
|
{
|
|
m_tune = tune;
|
|
|
|
if (tune != nullptr)
|
|
{
|
|
// Must re-configure on fly for stereo support!
|
|
if (!config(m_cfg, true))
|
|
{
|
|
// Failed configuration with new tune, reject it
|
|
m_tune = nullptr;
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void Player::mute(unsigned int sidNum, unsigned int voice, bool enable)
|
|
{
|
|
sidemu *s = m_mixer.getSid(sidNum);
|
|
if (s != nullptr)
|
|
s->voice(voice, enable);
|
|
}
|
|
|
|
void Player::run(unsigned int events)
|
|
{
|
|
for (unsigned int i = 0; m_isPlaying && i < events; i++)
|
|
m_c64.getEventScheduler()->clock();
|
|
}
|
|
|
|
uint_least32_t Player::play(short *buffer, uint_least32_t count)
|
|
{
|
|
// Make sure a tune is loaded
|
|
if (m_tune == nullptr)
|
|
return 0;
|
|
|
|
// Start the player loop
|
|
if (m_isPlaying == STOPPED)
|
|
m_isPlaying = PLAYING;
|
|
|
|
if (m_isPlaying == PLAYING)
|
|
{
|
|
m_mixer.begin(buffer, count);
|
|
|
|
if (m_mixer.getSid(0) != nullptr)
|
|
{
|
|
if (count && buffer != nullptr)
|
|
{
|
|
// Clock chips and mix into output buffer
|
|
while (m_isPlaying && m_mixer.notFinished())
|
|
{
|
|
run(sidemu::OUTPUTBUFFERSIZE);
|
|
|
|
m_mixer.clockChips();
|
|
m_mixer.doMix();
|
|
}
|
|
count = m_mixer.samplesGenerated();
|
|
}
|
|
else
|
|
{
|
|
// Clock chips and discard buffers
|
|
int size = m_c64.getMainCpuSpeed() / m_cfg.frequency;
|
|
while (m_isPlaying && --size)
|
|
{
|
|
run(sidemu::OUTPUTBUFFERSIZE);
|
|
|
|
m_mixer.clockChips();
|
|
m_mixer.resetBufs();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Clock the machine
|
|
int size = m_c64.getMainCpuSpeed() / m_cfg.frequency;
|
|
while (m_isPlaying && --size)
|
|
{
|
|
run(sidemu::OUTPUTBUFFERSIZE);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (m_isPlaying == STOPPING)
|
|
{
|
|
try
|
|
{
|
|
initialise();
|
|
}
|
|
catch (configError const &) {}
|
|
m_isPlaying = STOPPED;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
void Player::stop()
|
|
{
|
|
if (m_tune != nullptr && m_isPlaying == PLAYING)
|
|
{
|
|
m_isPlaying = STOPPING;
|
|
}
|
|
}
|
|
|
|
bool Player::config(const SidConfig &cfg, bool force)
|
|
{
|
|
// Check if configuration have been changed or forced
|
|
if (!force && !m_cfg.compare(cfg))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Check for base sampling frequency
|
|
if (cfg.frequency < 8000)
|
|
{
|
|
m_errorString = ERR_UNSUPPORTED_FREQ;
|
|
return false;
|
|
}
|
|
|
|
// Only do these if we have a loaded tune
|
|
if (m_tune != nullptr)
|
|
{
|
|
const SidTuneInfo* tuneInfo = m_tune->getInfo();
|
|
|
|
try
|
|
{
|
|
sidRelease();
|
|
|
|
std::vector<unsigned int> addresses;
|
|
const uint_least16_t secondSidAddress = tuneInfo->sidChipBase(1) != 0 ?
|
|
tuneInfo->sidChipBase(1) :
|
|
cfg.secondSidAddress;
|
|
if (secondSidAddress != 0)
|
|
addresses.push_back(secondSidAddress);
|
|
|
|
const uint_least16_t thirdSidAddress = tuneInfo->sidChipBase(2) != 0 ?
|
|
tuneInfo->sidChipBase(2) :
|
|
cfg.thirdSidAddress;
|
|
if (thirdSidAddress != 0)
|
|
addresses.push_back(thirdSidAddress);
|
|
|
|
// SID emulation setup (must be performed before the
|
|
// environment setup call)
|
|
sidCreate(cfg.sidEmulation, cfg.defaultSidModel, cfg.forceSidModel, addresses);
|
|
|
|
// Determine clock speed
|
|
const c64::model_t model = c64model(cfg.defaultC64Model, cfg.forceC64Model);
|
|
|
|
m_c64.setModel(model);
|
|
|
|
sidParams(m_c64.getMainCpuSpeed(), cfg.frequency, cfg.samplingMethod, cfg.fastSampling);
|
|
|
|
// Configure, setup and install C64 environment/events
|
|
initialise();
|
|
}
|
|
catch (configError const &e)
|
|
{
|
|
m_errorString = e.message();
|
|
m_cfg.sidEmulation = 0;
|
|
if (&m_cfg != &cfg)
|
|
{
|
|
config(m_cfg);
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
const bool isStereo = cfg.playback == SidConfig::STEREO;
|
|
m_info.m_channels = isStereo ? 2 : 1;
|
|
|
|
m_mixer.setStereo(isStereo);
|
|
m_mixer.setVolume(cfg.leftVolume, cfg.rightVolume);
|
|
|
|
// Update Configuration
|
|
m_cfg = cfg;
|
|
|
|
return true;
|
|
}
|
|
|
|
// Clock speed changes due to loading a new song
|
|
c64::model_t Player::c64model(SidConfig::c64_model_t defaultModel, bool forced)
|
|
{
|
|
const SidTuneInfo* tuneInfo = m_tune->getInfo();
|
|
|
|
SidTuneInfo::clock_t clockSpeed = tuneInfo->clockSpeed();
|
|
|
|
c64::model_t model;
|
|
|
|
// Use preferred speed if forced or if song speed is unknown
|
|
if (forced || clockSpeed == SidTuneInfo::CLOCK_UNKNOWN || clockSpeed == SidTuneInfo::CLOCK_ANY)
|
|
{
|
|
switch (defaultModel)
|
|
{
|
|
case SidConfig::PAL:
|
|
clockSpeed = SidTuneInfo::CLOCK_PAL;
|
|
model = c64::PAL_B;
|
|
videoSwitch = 1;
|
|
break;
|
|
case SidConfig::DREAN:
|
|
clockSpeed = SidTuneInfo::CLOCK_PAL;
|
|
model = c64::PAL_N;
|
|
videoSwitch = 1; // TODO verify
|
|
break;
|
|
case SidConfig::NTSC:
|
|
clockSpeed = SidTuneInfo::CLOCK_NTSC;
|
|
model = c64::NTSC_M;
|
|
videoSwitch = 0;
|
|
break;
|
|
case SidConfig::OLD_NTSC:
|
|
clockSpeed = SidTuneInfo::CLOCK_NTSC;
|
|
model = c64::OLD_NTSC_M;
|
|
videoSwitch = 0;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch (clockSpeed)
|
|
{
|
|
default:
|
|
case SidTuneInfo::CLOCK_PAL:
|
|
model = c64::PAL_B;
|
|
videoSwitch = 1;
|
|
break;
|
|
case SidTuneInfo::CLOCK_NTSC:
|
|
model = c64::NTSC_M;
|
|
videoSwitch = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
switch (clockSpeed)
|
|
{
|
|
case SidTuneInfo::CLOCK_PAL:
|
|
if (tuneInfo->songSpeed() == SidTuneInfo::SPEED_CIA_1A)
|
|
m_info.m_speedString = TXT_PAL_CIA;
|
|
else if (tuneInfo->clockSpeed() == SidTuneInfo::CLOCK_NTSC)
|
|
m_info.m_speedString = TXT_PAL_VBI_FIXED;
|
|
else
|
|
m_info.m_speedString = TXT_PAL_VBI;
|
|
break;
|
|
case SidTuneInfo::CLOCK_NTSC:
|
|
if (tuneInfo->songSpeed() == SidTuneInfo::SPEED_CIA_1A)
|
|
m_info.m_speedString = TXT_NTSC_CIA;
|
|
else if (tuneInfo->clockSpeed() == SidTuneInfo::CLOCK_PAL)
|
|
m_info.m_speedString = TXT_NTSC_VBI_FIXED;
|
|
else
|
|
m_info.m_speedString = TXT_NTSC_VBI;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return model;
|
|
}
|
|
|
|
/**
|
|
* Get the SID model.
|
|
*
|
|
* @param sidModel the tune requested model
|
|
* @param defaultModel the default model
|
|
* @param forced true if the default model shold be forced in spite of tune model
|
|
*/
|
|
SidConfig::sid_model_t getSidModel(SidTuneInfo::model_t sidModel, SidConfig::sid_model_t defaultModel, bool forced)
|
|
{
|
|
SidTuneInfo::model_t tuneModel = sidModel;
|
|
|
|
// Use preferred speed if forced or if song speed is unknown
|
|
if (forced || tuneModel == SidTuneInfo::SIDMODEL_UNKNOWN || tuneModel == SidTuneInfo::SIDMODEL_ANY)
|
|
{
|
|
switch (defaultModel)
|
|
{
|
|
case SidConfig::MOS6581:
|
|
tuneModel = SidTuneInfo::SIDMODEL_6581;
|
|
break;
|
|
case SidConfig::MOS8580:
|
|
tuneModel = SidTuneInfo::SIDMODEL_8580;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
SidConfig::sid_model_t newModel;
|
|
|
|
switch (tuneModel)
|
|
{
|
|
default:
|
|
case SidTuneInfo::SIDMODEL_6581:
|
|
newModel = SidConfig::MOS6581;
|
|
break;
|
|
case SidTuneInfo::SIDMODEL_8580:
|
|
newModel = SidConfig::MOS8580;
|
|
break;
|
|
}
|
|
|
|
return newModel;
|
|
}
|
|
|
|
void Player::sidRelease()
|
|
{
|
|
m_c64.clearSids();
|
|
|
|
for (unsigned int i = 0; ; i++)
|
|
{
|
|
sidemu *s = m_mixer.getSid(i);
|
|
if (s == nullptr)
|
|
break;
|
|
|
|
if (sidbuilder *b = s->builder())
|
|
{
|
|
b->unlock(s);
|
|
}
|
|
}
|
|
|
|
m_mixer.clearSids();
|
|
}
|
|
|
|
void Player::sidCreate(sidbuilder *builder, SidConfig::sid_model_t defaultModel,
|
|
bool forced, const std::vector<unsigned int> &extraSidAddresses)
|
|
{
|
|
if (builder != nullptr)
|
|
{
|
|
const SidTuneInfo* tuneInfo = m_tune->getInfo();
|
|
|
|
// Setup base SID
|
|
const SidConfig::sid_model_t userModel = getSidModel(tuneInfo->sidModel(0), defaultModel, forced);
|
|
sidemu *s = builder->lock(m_c64.getEventScheduler(), userModel);
|
|
if (!builder->getStatus())
|
|
{
|
|
throw configError(builder->error());
|
|
}
|
|
|
|
m_c64.setBaseSid(s);
|
|
m_mixer.addSid(s);
|
|
|
|
// Setup extra SIDs if needed
|
|
if (extraSidAddresses.size() != 0)
|
|
{
|
|
// If bits 6-7 are set to Unknown then the second SID will be set to the same SID
|
|
// model as the first SID.
|
|
defaultModel = userModel;
|
|
|
|
const unsigned int extraSidChips = extraSidAddresses.size();
|
|
|
|
for (unsigned int i = 0; i < extraSidChips; i++)
|
|
{
|
|
const SidConfig::sid_model_t userModel = getSidModel(tuneInfo->sidModel(i+1), defaultModel, forced);
|
|
|
|
sidemu *s = builder->lock(m_c64.getEventScheduler(), userModel);
|
|
if (!builder->getStatus())
|
|
{
|
|
throw configError(builder->error());
|
|
}
|
|
|
|
if (!m_c64.addExtraSid(s, extraSidAddresses[i]))
|
|
throw configError(ERR_UNSUPPORTED_SID_ADDR);
|
|
|
|
m_mixer.addSid(s);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Player::sidParams(double cpuFreq, int frequency,
|
|
SidConfig::sampling_method_t sampling, bool fastSampling)
|
|
{
|
|
for (unsigned int i = 0; ; i++)
|
|
{
|
|
sidemu *s = m_mixer.getSid(i);
|
|
if (s == nullptr)
|
|
break;
|
|
|
|
s->sampling((float)cpuFreq, frequency, sampling, fastSampling);
|
|
}
|
|
}
|
|
|
|
#ifdef PC64_TESTSUITE
|
|
void Player::load(const char *file)
|
|
{
|
|
std::string name(PC64_TESTSUITE);
|
|
name.append(file);
|
|
name.append(".prg");
|
|
|
|
m_tune->load(name.c_str());
|
|
m_tune->selectSong(0);
|
|
initialise();
|
|
}
|
|
#endif
|
|
|
|
}
|