#include "cpc_player.h" #include "mixer.h" #include "util.h" static const char *_tunes[] = { "Options.Cpc", "Jungle.Cpc", "Subway.Cpc", "TVshow.Cpc", "City.Cpc", "Alien.Cpc" }; CpcPlayer::CpcPlayer(Mixer *mixer, FileSystem *fs) : _mix(mixer), _fs(fs) { } CpcPlayer::~CpcPlayer() { } bool CpcPlayer::playTrack(int num) { _compression[0] = 0; const int tuneNum = num - 2; if (tuneNum >= 0 && tuneNum < ARRAYSIZE(_tunes) && _f.open(_tunes[tuneNum], "rb", _fs)) { _pos = 0; _sampleL = _sampleR = 0; while (nextChunk()) { if (_compression[0]) { _restartPos = _nextPos; _mix->setPremixHook(mixCallback, this); return true; } } } return false; } void CpcPlayer::stopTrack() { _f.close(); } void CpcPlayer::pauseTrack() { _mix->setPremixHook(0, 0); } void CpcPlayer::resumeTrack() { _mix->setPremixHook(mixCallback, this); } bool CpcPlayer::nextChunk() { bool found = false; while (!_f.ioErr() && !found) { const uint32_t pos = _pos; char tag[4]; _f.read(tag, sizeof(tag)); const uint32_t len = _f.readUint32BE(); _nextPos = pos + len; if (memcmp(tag, "SNDS", 4) == 0) { _f.readUint32BE(); _f.readUint32BE(); char type[4]; _f.read(type, sizeof(type)); if (memcmp(type, "SHDR", 4) == 0) { uint8_t buf[24]; _f.read(buf, sizeof(buf)); const uint32_t rate = _f.readUint32BE(); const uint32_t channels = _f.readUint32BE(); if (channels != 2 || rate != _mix->getSampleRate()) { warning("Unsupported CPC tune channels %d rate %d", channels, rate); break; } _f.read(_compression, sizeof(_compression) - 1); _compression[sizeof(_compression) - 1] = 0; if (strcmp(_compression, "SDX2") != 0) { warning("Unsupported CPC compression '%s'", _compression); break; } found = true; } else if (memcmp(type, "SSMP", 4) == 0) { _samplesLeft = _f.readUint32BE(); found = true; break; } else { warning("Unhandled SNDS chunk '%c%c%c%c'", type[0], type[1], type[2], type[3]); } } else if (memcmp(tag, "SHDR", 4) == 0 || memcmp(tag, "FILL", 4) == 0 || memcmp(tag, "CTRL", 4) == 0) { // ignore } else { warning("Unhandled chunk '%c%c%c%c' size %d", tag[0], tag[1], tag[2], tag[3], len); } _f.seek(_nextPos); _pos = _nextPos; } return found; } static int16_t decodeSDX2(int16_t prev, int8_t data) { const int sqr = data * ABS(data) * 2; return (data & 1) != 0 ? prev + sqr : sqr; } int8_t CpcPlayer::readSampleData() { if (_samplesLeft <= 0) { _f.seek(_nextPos); _pos = _nextPos; if (!nextChunk()) { // rewind _f.seek(_restartPos); nextChunk(); _sampleL = _sampleR = 0; } } const int8_t data = _f.readByte(); --_samplesLeft; return data; } bool CpcPlayer::mix(int16_t *buf, int len) { for (int i = 0; i < len; ++i) { _sampleL = decodeSDX2(_sampleL, readSampleData()); _sampleR = decodeSDX2(_sampleR, readSampleData()); *buf++ = _sampleL; *buf++ = _sampleR; } return true; } bool CpcPlayer::mixCallback(void *param, int16_t *buf, int len) { return ((CpcPlayer *)param)->mix(buf, len); }