aboutsummaryrefslogtreecommitdiff
path: root/engines/scumm/players/player_sid.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engines/scumm/players/player_sid.cpp')
-rw-r--r--engines/scumm/players/player_sid.cpp1384
1 files changed, 1384 insertions, 0 deletions
diff --git a/engines/scumm/players/player_sid.cpp b/engines/scumm/players/player_sid.cpp
new file mode 100644
index 0000000000..1b97ad16d4
--- /dev/null
+++ b/engines/scumm/players/player_sid.cpp
@@ -0,0 +1,1384 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * 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.
+ *
+ */
+
+#ifndef DISABLE_SID
+
+#include "engines/engine.h"
+#include "scumm/players/player_sid.h"
+#include "scumm/scumm.h"
+#include "audio/mixer.h"
+
+namespace Scumm {
+
+/*
+ * The player's update() routine is called once per (NTSC/PAL) frame as it is
+ * called by the VIC Rasterline interrupt handler which is in turn called
+ * approx. 50 (PAL) or 60 (NTSC) times per second.
+ * The SCUMM V0/V1 music playback routines or sound data have not been adjusted
+ * to PAL systems. As a consequence, music is played audibly (-16%) slower
+ * on PAL systems.
+ * In addition, the SID oscillator frequency depends on the video clock too.
+ * As SCUMM games use an NTSC frequency table for both NTSC and PAL versions
+ * all tone frequencies on PAL systems are slightly (-4%) lower than on NTSC ones.
+ *
+ * For more info on the SID chip see:
+ * - http://www.dopeconnection.net/C64_SID.htm (German)
+ * For more info on the VIC chip see:
+ * - http://www.htu.tugraz.at/~herwig/c64/man-vic.php (German)
+ * - http://www.c64-wiki.de/index.php/VIC (German)
+ */
+
+struct TimingProps {
+ double clockFreq;
+ int cyclesPerFrame;
+};
+
+static const TimingProps timingProps[2] = {
+ { 17734472.0 / 18, 312 * 63 }, // PAL: 312*63 cycles/frame @ 985248 Hz (~50Hz)
+ { 14318180.0 / 14, 263 * 65 } // NTSC: 263*65 cycles/frame @ 1022727 Hz (~60Hz)
+};
+
+static const uint8 BITMASK[7] = {
+ 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40
+};
+static const uint8 BITMASK_INV[7] = {
+ 0xFE, 0xFD, 0xFB, 0xF7, 0xEF, 0xDF, 0xBF
+};
+
+static const int SID_REG_OFFSET[7] = {
+ 0, 7, 14, 21, 2, 9, 16
+};
+
+// NTSC frequency table (also used for PAL versions).
+// FREQ_TBL[i] = tone_freq[i] * 2^24 / clockFreq
+static const uint16 FREQ_TBL[97] = {
+ 0x0000, 0x010C, 0x011C, 0x012D, 0x013E, 0x0151, 0x0166, 0x017B,
+ 0x0191, 0x01A9, 0x01C3, 0x01DD, 0x01FA, 0x0218, 0x0238, 0x025A,
+ 0x027D, 0x02A3, 0x02CC, 0x02F6, 0x0323, 0x0353, 0x0386, 0x03BB,
+ 0x03F4, 0x0430, 0x0470, 0x04B4, 0x04FB, 0x0547, 0x0598, 0x05ED,
+ 0x0647, 0x06A7, 0x070C, 0x0777, 0x07E9, 0x0861, 0x08E1, 0x0968,
+ 0x09F7, 0x0A8F, 0x0B30, 0x0BDA, 0x0C8F, 0x0D4E, 0x0E18, 0x0EEF,
+ 0x0FD2, 0x10C3, 0x11C3, 0x12D1, 0x13EF, 0x151F, 0x1660, 0x17B5,
+ 0x191E, 0x1A9C, 0x1C31, 0x1DDF, 0x1FA5, 0x2187, 0x2386, 0x25A2,
+ 0x27DF, 0x2A3E, 0x2CC1, 0x2F6B, 0x323C, 0x3539, 0x3863, 0x3BBE,
+ 0x3F4B, 0x430F, 0x470C, 0x4B45, 0x4FBF, 0x547D, 0x5983, 0x5ED6,
+ 0x6479, 0x6A73, 0x70C7, 0x777C, 0x7E97, 0x861E, 0x8E18, 0x968B,
+ 0x9F7E, 0xA8FA, 0xB306, 0xBDAC, 0xC8F3, 0xD4E6, 0xE18F, 0xEEF8,
+ 0xFD2E
+};
+
+static const int SONG_CHANNEL_OFFSET[3] = { 6, 8, 10 };
+static const int RES_ID_CHANNEL[3] = { 3, 4, 5 };
+
+#define LOBYTE_(a) ((a) & 0xFF)
+#define HIBYTE_(a) (((a) >> 8) & 0xFF)
+
+#define GETBIT(var, pos) ((var) & (1<<(pos)))
+
+void Player_SID::handleMusicBuffer() { // $33cd
+ int channel = 2;
+ while (channel >= 0) {
+ if ((statusBits1A & BITMASK[channel]) == 0 ||
+ (busyChannelBits & BITMASK[channel]) != 0) {
+ --channel;
+ continue;
+ }
+
+ if (setupSongFileData() == 1)
+ return;
+
+ uint8* l_chanFileDataPtr = chanFileData[channel];
+
+ uint16 l_freq = 0;
+ bool l_keepFreq = false;
+
+ int y = 0;
+ uint8 curByte = l_chanFileDataPtr[y++];
+
+ // freq or 0/0xFF
+ if (curByte == 0) {
+ func_3674(channel);
+ if (!isMusicPlaying)
+ return;
+ continue;
+ } else if (curByte == 0xFF) {
+ l_keepFreq = true;
+ } else {
+ l_freq = FREQ_TBL[curByte];
+ }
+
+ uint8 local1 = 0;
+ curByte = l_chanFileDataPtr[y++];
+ bool isLastCmdByte = (curByte & 0x80) != 0;
+ uint16 curStepSum = stepTbl[curByte & 0x7f];
+
+ for (int i = 0; !isLastCmdByte && (i < 2); ++i) {
+ curByte = l_chanFileDataPtr[y++];
+ isLastCmdByte = (curByte & 0x80) != 0;
+ if (curByte & 0x40) {
+ // note: bit used in zak theme (95) only (not used/handled in MM)
+ _music_timer = curByte & 0x3f;
+ } else {
+ local1 = curByte & 0x3f;
+ }
+ }
+
+ chanFileData[channel] += y;
+ chanDataOffset[channel] += y;
+
+ uint8 *l_chanBuf = getResource(RES_ID_CHANNEL[channel]);
+
+ if (local1 != 0) {
+ // TODO: signed or unsigned?
+ uint16 offset = READ_LE_UINT16(&actSongFileData[local1*2 + 12]);
+ l_chanFileDataPtr = actSongFileData + offset;
+
+ // next five bytes: freqDelta, attack, sustain and phase bit
+ for (int i = 0; i < 5; ++i) {
+ l_chanBuf[15 + i] = l_chanFileDataPtr[i];
+ }
+ phaseBit[channel] = l_chanFileDataPtr[4];
+
+ for (int i = 0; i < 17; ++i) {
+ l_chanBuf[25 + i] = l_chanFileDataPtr[5 + i];
+ }
+ }
+
+ if (l_keepFreq) {
+ if (!releasePhase[channel]) {
+ l_chanBuf[10] &= 0xfe; // release phase
+ }
+ releasePhase[channel] = true;
+ } else {
+ if (releasePhase[channel]) {
+ l_chanBuf[19] = phaseBit[channel];
+ l_chanBuf[10] |= 0x01; // attack phase
+ }
+ l_chanBuf[11] = LOBYTE_(l_freq);
+ l_chanBuf[12] = HIBYTE_(l_freq);
+ releasePhase[channel] = false;
+ }
+
+ // set counter value for frequency update (freqDeltaCounter)
+ l_chanBuf[13] = LOBYTE_(curStepSum);
+ l_chanBuf[14] = HIBYTE_(curStepSum);
+
+ _soundQueue[channel] = RES_ID_CHANNEL[channel];
+ processSongData(channel);
+ _soundQueue[channel+4] = RES_ID_CHANNEL[channel];
+ processSongData(channel+4);
+ --channel;
+ }
+}
+
+int Player_SID::setupSongFileData() { // $36cb
+ // no song playing
+ // TODO: remove (never NULL)
+ if (_music == NULL) {
+ for (int i = 2; i >= 0; --i) {
+ if (songChannelBits & BITMASK[i]) {
+ func_3674(i);
+ }
+ }
+ return 1;
+ }
+
+ // no new song
+ songFileOrChanBufData = _music;
+ if (_music == actSongFileData) {
+ return 0;
+ }
+
+ // new song selected
+ actSongFileData = _music;
+ for (int i = 0; i < 3; ++i) {
+ chanFileData[i] = _music + chanDataOffset[i];
+ }
+
+ return -1;
+}
+
+//x:0..2
+void Player_SID::func_3674(int channel) { // $3674
+ statusBits1B &= BITMASK_INV[channel];
+ if (statusBits1B == 0) {
+ isMusicPlaying = false;
+ unlockCodeLocation();
+ safeUnlockResource(resID_song);
+ for (int i = 0; i < 3; ++i) {
+ safeUnlockResource(RES_ID_CHANNEL[i]);
+ }
+ }
+
+ chanPrio[channel] = 2;
+
+ statusBits1A &= BITMASK_INV[channel];
+ phaseBit[channel] = 0;
+
+ func_4F45(channel);
+}
+
+void Player_SID::resetPlayerState() { // $48f7
+ for (int i = 6; i >= 0; --i)
+ releaseChannel(i);
+
+ isMusicPlaying = false;
+ unlockCodeLocation(); // does nothing
+ statusBits1B = 0;
+ statusBits1A = 0;
+ freeChannelCount = 3;
+ swapPrepared = false;
+ filterSwapped = false;
+ pulseWidthSwapped = false;
+ //var5163 = 0;
+}
+
+void Player_SID::resetSID() { // $48D8
+ SIDReg24 = 0x0f;
+
+ SID_Write( 4, 0);
+ SID_Write(11, 0);
+ SID_Write(18, 0);
+ SID_Write(23, 0);
+ SID_Write(21, 0);
+ SID_Write(22, 0);
+ SID_Write(24, SIDReg24);
+
+ resetPlayerState();
+}
+
+void Player_SID::update() { // $481B
+ if (initializing)
+ return;
+
+ if (_soundInQueue) {
+ for (int i = 6; i >= 0; --i) {
+ if (_soundQueue[i] != -1)
+ processSongData(i);
+ }
+ _soundInQueue = false;
+ }
+
+ // no sound
+ if (busyChannelBits == 0)
+ return;
+
+ for (int i = 6; i >= 0; --i) {
+ if (busyChannelBits & BITMASK[i]) {
+ updateFreq(i);
+ }
+ }
+
+ // seems to be used for background (prio=1?) sounds.
+ // If a bg sound cannot be played because all SID
+ // voices are used by higher priority sounds, the
+ // bg sound's state is updated here so it will be at
+ // the correct state when a voice is available again.
+ if (swapPrepared) {
+ swapVars(0, 0);
+ swapVarLoaded = true;
+ updateFreq(0);
+ swapVars(0, 0);
+ if (pulseWidthSwapped) {
+ swapVars(4, 1);
+ updateFreq(4);
+ swapVars(4, 1);
+ }
+ swapVarLoaded = false;
+ }
+
+ for (int i = 6; i >= 0; --i) {
+ if (busyChannelBits & BITMASK[i])
+ setSIDWaveCtrlReg(i);
+ };
+
+ if (isMusicPlaying) {
+ handleMusicBuffer();
+ }
+
+ return;
+}
+
+// channel: 0..6
+void Player_SID::processSongData(int channel) { // $4939
+ // always: _soundQueue[channel] != -1
+ // -> channelMap[channel] != -1
+ channelMap[channel] = _soundQueue[channel];
+ _soundQueue[channel] = -1;
+ songPosUpdateCounter[channel] = 0;
+
+ isVoiceChannel = (channel < 3);
+
+ songFileOrChanBufOffset[channel] = vec6[channel];
+
+ setupSongPtr(channel);
+
+ //vec5[channel] = songFileOrChanBufData; // not used
+
+ if (songFileOrChanBufData == NULL) { // chanBuf (4C1C)
+ /*
+ // TODO: do we need this?
+ LOBYTE_(vec20[channel]) = 0;
+ LOBYTE_(songPosPtr[channel]) = LOBYTE_(songFileOrChanBufOffset[channel]);
+ */
+ releaseResourceUnk(channel);
+ return;
+ }
+
+ vec20[channel] = songFileOrChanBufData; // chanBuf (4C1C)
+ songPosPtr[channel] = songFileOrChanBufData + songFileOrChanBufOffset[channel]; // chanBuf (4C1C)
+ uint8* ptr1 = songPosPtr[channel];
+
+ int y = -1;
+ if (channel < 4) {
+ ++y;
+ if (channel == 3) {
+ readSetSIDFilterAndProps(&y, ptr1);
+ } else if (statusBits1A & BITMASK[channel]) {
+ ++y;
+ } else { // channel = 0/1/2
+ waveCtrlReg[channel] = ptr1[y];
+
+ ++y;
+ if (ptr1[y] & 0x0f) {
+ // filter on for voice channel
+ SIDReg23 |= BITMASK[channel];
+ } else {
+ // filter off for voice channel
+ SIDReg23 &= BITMASK_INV[channel];
+ }
+ SID_Write(23, SIDReg23);
+ }
+ }
+
+ saveSongPos(y, channel);
+ busyChannelBits |= BITMASK[channel];
+ readSongChunk(channel);
+}
+
+void Player_SID::readSetSIDFilterAndProps(int *offset, uint8* dataPtr) { // $49e7
+ SIDReg23 |= dataPtr[*offset];
+ SID_Write(23, SIDReg23);
+ ++*offset;
+ SIDReg24 = dataPtr[*offset];
+ SID_Write(24, SIDReg24);
+}
+
+void Player_SID::saveSongPos(int y, int channel) {
+ ++y;
+ songPosPtr[channel] += y;
+ songFileOrChanBufOffset[channel] += y;
+}
+
+// channel: 0..6
+void Player_SID::updateFreq(int channel) {
+ isVoiceChannel = (channel < 3);
+
+ --freqDeltaCounter[channel];
+ if (freqDeltaCounter[channel] < 0) {
+ readSongChunk(channel);
+ } else {
+ freqReg[channel] += freqDelta[channel];
+ }
+ setSIDFreqAS(channel);
+}
+
+void Player_SID::resetFreqDelta(int channel) {
+ freqDeltaCounter[channel] = 0;
+ freqDelta[channel] = 0;
+}
+
+void Player_SID::readSongChunk(int channel) { // $4a6b
+ while (true) {
+ if (setupSongPtr(channel) == 1) {
+ // do something with code resource
+ releaseResourceUnk(1);
+ return;
+ }
+
+ uint8* ptr1 = songPosPtr[channel];
+
+ //curChannelActive = true;
+
+ uint8 l_cmdByte = ptr1[0];
+ if (l_cmdByte == 0) {
+ //curChannelActive = false;
+ songPosUpdateCounter[channel] = 0;
+
+ var481A = -1;
+ releaseChannel(channel);
+ return;
+ }
+
+ //vec19[channel] = l_cmdByte;
+
+ // attack (1) / release (0) phase
+ if (isVoiceChannel) {
+ if (GETBIT(l_cmdByte, 0))
+ waveCtrlReg[channel] |= 0x01; // start attack phase
+ else
+ waveCtrlReg[channel] &= 0xfe; // start release phase
+ }
+
+ // channel finished bit
+ if (GETBIT(l_cmdByte, 1)) {
+ var481A = -1;
+ releaseChannel(channel);
+ return;
+ }
+
+ int y = 0;
+
+ // frequency
+ if (GETBIT(l_cmdByte, 2)) {
+ y += 2;
+ freqReg[channel] = READ_LE_UINT16(&ptr1[y-1]);
+ if (!GETBIT(l_cmdByte, 6)) {
+ y += 2;
+ freqDeltaCounter[channel] = READ_LE_UINT16(&ptr1[y-1]);
+ y += 2;
+ freqDelta[channel] = READ_LE_UINT16(&ptr1[y-1]);
+ } else {
+ resetFreqDelta(channel);
+ }
+ } else {
+ resetFreqDelta(channel);
+ }
+
+ // attack / release
+ if (isVoiceChannel && GETBIT(l_cmdByte, 3)) {
+ // start release phase
+ waveCtrlReg[channel] &= 0xfe;
+ setSIDWaveCtrlReg(channel);
+
+ ++y;
+ attackReg[channel] = ptr1[y];
+ ++y;
+ sustainReg[channel] = ptr1[y];
+
+ // set attack (1) or release (0) phase
+ waveCtrlReg[channel] |= (l_cmdByte & 0x01);
+ }
+
+ if (GETBIT(l_cmdByte, 4)) {
+ ++y;
+ uint8 curByte = ptr1[y];
+
+ // pulse width
+ if (isVoiceChannel && GETBIT(curByte, 0)) {
+ int reg = SID_REG_OFFSET[channel+4];
+
+ y += 2;
+ SID_Write(reg, ptr1[y-1]);
+ SID_Write(reg+1, ptr1[y]);
+ }
+
+ if (GETBIT(curByte, 1)) {
+ ++y;
+ readSetSIDFilterAndProps(&y, ptr1);
+
+ y += 2;
+ SID_Write(21, ptr1[y-1]);
+ SID_Write(22, ptr1[y]);
+ }
+
+ if (GETBIT(curByte, 2)) {
+ resetFreqDelta(channel);
+
+ y += 2;
+ freqDeltaCounter[channel] = READ_LE_UINT16(&ptr1[y-1]);
+ }
+ }
+
+ // set waveform (?)
+ if (GETBIT(l_cmdByte, 5)) {
+ ++y;
+ waveCtrlReg[channel] = (waveCtrlReg[channel] & 0x0f) | ptr1[y];
+ }
+
+ // song position
+ if (GETBIT(l_cmdByte, 7)) {
+ if (songPosUpdateCounter[channel] == 1) {
+ y += 2;
+ --songPosUpdateCounter[channel];
+ saveSongPos(y, channel);
+ } else {
+ // looping / skipping / ...
+ ++y;
+ songPosPtr[channel] -= ptr1[y];
+ songFileOrChanBufOffset[channel] -= ptr1[y];
+
+ ++y;
+ if (songPosUpdateCounter[channel] == 0) {
+ songPosUpdateCounter[channel] = ptr1[y];
+ } else {
+ --songPosUpdateCounter[channel];
+ }
+ }
+ } else {
+ saveSongPos(y, channel);
+ return;
+ }
+ }
+}
+
+/**
+ * Sets frequency, attack and sustain register
+ */
+void Player_SID::setSIDFreqAS(int channel) { // $4be6
+ if (swapVarLoaded)
+ return;
+ int reg = SID_REG_OFFSET[channel];
+ SID_Write(reg, LOBYTE_(freqReg[channel])); // freq/pulseWidth voice 1/2/3
+ SID_Write(reg+1, HIBYTE_(freqReg[channel]));
+ if (channel < 3) {
+ SID_Write(reg+5, attackReg[channel]); // attack
+ SID_Write(reg+6, sustainReg[channel]); // sustain
+ }
+}
+
+void Player_SID::setSIDWaveCtrlReg(int channel) { // $4C0D
+ if (channel < 3) {
+ int reg = SID_REG_OFFSET[channel];
+ SID_Write(reg+4, waveCtrlReg[channel]);
+ }
+}
+
+// channel: 0..6
+int Player_SID::setupSongPtr(int channel) { // $4C1C
+ //resID:5,4,3,songid
+ int resID = channelMap[channel];
+
+ // TODO: when does this happen, only if resID == 0?
+ if (getResource(resID) == NULL) {
+ releaseResourceUnk(resID);
+ if (resID == bgSoundResID) {
+ bgSoundResID = 0;
+ bgSoundActive = false;
+ swapPrepared = false;
+ pulseWidthSwapped = false;
+ }
+ return 1;
+ }
+
+ songFileOrChanBufData = getResource(resID); // chanBuf (4C1C)
+ if (songFileOrChanBufData == vec20[channel]) {
+ return 0;
+ } else {
+ vec20[channel] = songFileOrChanBufData;
+ songPosPtr[channel] = songFileOrChanBufData + songFileOrChanBufOffset[channel];
+ return -1;
+ }
+}
+
+// ignore: no effect
+// chanResIndex: 3,4,5 or 58
+void Player_SID::unlockResource(int chanResIndex) { // $4CDA
+ if ((resStatus[chanResIndex] & 0x7F) != 0)
+ --resStatus[chanResIndex];
+}
+
+void Player_SID::countFreeChannels() { // $4f26
+ freeChannelCount = 0;
+ for (int i = 0; i < 3; ++i) {
+ if (GETBIT(usedChannelBits, i) == 0)
+ ++freeChannelCount;
+ }
+}
+
+void Player_SID::func_4F45(int channel) { // $4F45
+ if (swapVarLoaded) {
+ if (channel == 0) {
+ swapPrepared = false;
+ resetSwapVars();
+ }
+ pulseWidthSwapped = false;
+ } else {
+ if (channel == 3) {
+ filterUsed = false;
+ }
+
+ if (chanPrio[channel] == 1) {
+ if (var481A == 1)
+ prepareSwapVars(channel);
+ else if (channel < 3)
+ clearSIDWaveform(channel);
+ } else if (channel < 3 && bgSoundActive && swapPrepared &&
+ !(filterSwapped && filterUsed))
+ {
+ busyChannelBits |= BITMASK[channel];
+ useSwapVars(channel);
+ waveCtrlReg[channel] |= 0x01;
+ setSIDWaveCtrlReg(channel);
+
+ safeUnlockResource(channelMap[channel]);
+ return;
+ }
+
+ chanPrio[channel] = 0;
+ usedChannelBits &= BITMASK_INV[channel];
+ countFreeChannels();
+ }
+
+ int resIndex = channelMap[channel];
+ channelMap[channel] = 0;
+ safeUnlockResource(resIndex);
+}
+
+// chanResIndex: 3,4,5 or 58
+void Player_SID::safeUnlockResource(int resIndex) { // $4FEA
+ if (!isMusicPlaying) {
+ unlockResource(resIndex);
+ }
+}
+
+void Player_SID::releaseResource(int resIndex) { // $5031
+ releaseResChannels(resIndex);
+ if (resIndex == bgSoundResID && var481A == -1) {
+ safeUnlockResource(resIndex);
+
+ bgSoundResID = 0;
+ bgSoundActive = false;
+ swapPrepared = false;
+ pulseWidthSwapped = false;
+
+ resetSwapVars();
+ }
+}
+
+void Player_SID::releaseResChannels(int resIndex) { // $5070
+ for (int i = 3; i >= 0; --i) {
+ if (resIndex == channelMap[i]) {
+ releaseChannel(i);
+ }
+ }
+}
+
+void Player_SID::stopSound_intern(int soundResID) { // $5093
+ for (int i = 0; i < 7; ++i) {
+ if (soundResID == _soundQueue[i]) {
+ _soundQueue[i] = -1;
+ }
+ }
+ var481A = -1;
+ releaseResource(soundResID);
+}
+
+void Player_SID::stopMusic_intern() { // $4CAA
+ statusBits1B = 0;
+ isMusicPlaying = false;
+
+ if (resID_song != 0) {
+ unlockResource(resID_song);
+ }
+
+ chanPrio[0] = 2;
+ chanPrio[1] = 2;
+ chanPrio[2] = 2;
+
+ statusBits1A = 0;
+ phaseBit[0] = 0;
+ phaseBit[1] = 0;
+ phaseBit[2] = 0;
+}
+
+void Player_SID::releaseResourceUnk(int resIndex) { // $50A4
+ var481A = -1;
+ releaseResource(resIndex);
+}
+
+// a: 0..6
+void Player_SID::releaseChannel(int channel) {
+ stopChannel(channel);
+ if (channel >= 4) {
+ return;
+ }
+ if (channel < 3) {
+ SIDReg23Stuff = SIDReg23;
+ clearSIDWaveform(channel);
+ }
+ func_4F45(channel);
+ if (channel >= 3) {
+ return;
+ }
+ if ((SIDReg23 != SIDReg23Stuff) &&
+ (SIDReg23 & 0x07) == 0)
+ {
+ if (filterUsed) {
+ func_4F45(3);
+ stopChannel(3);
+ }
+ }
+
+ stopChannel(channel + 4);
+}
+
+void Player_SID::clearSIDWaveform(int channel) {
+ if (!isMusicPlaying && var481A == -1) {
+ waveCtrlReg[channel] &= 0x0e;
+ setSIDWaveCtrlReg(channel);
+ }
+}
+
+void Player_SID::stopChannel(int channel) {
+ songPosUpdateCounter[channel] = 0;
+ // clear "channel" bit
+ busyChannelBits &= BITMASK_INV[channel];
+ if (channel >= 4) {
+ // pulsewidth = 0
+ channelMap[channel] = 0;
+ }
+}
+
+// channel: 0..6, swapIndex: 0..2
+void Player_SID::swapVars(int channel, int swapIndex) { // $51a5
+ if (channel < 3) {
+ SWAP(attackReg[channel], swapAttack[swapIndex]);
+ SWAP(sustainReg[channel], swapSustain[swapIndex]);
+ }
+ //SWAP(vec5[channel], swapVec5[swapIndex]); // not used
+ //SWAP(vec19[channel], swapVec19[swapIndex]); // not used
+
+ SWAP(chanPrio[channel], swapSongPrio[swapIndex]);
+ SWAP(channelMap[channel], swapVec479C[swapIndex]);
+ SWAP(songPosUpdateCounter[channel], swapSongPosUpdateCounter[swapIndex]);
+ SWAP(waveCtrlReg[channel], swapWaveCtrlReg[swapIndex]);
+ SWAP(songPosPtr[channel], swapSongPosPtr[swapIndex]);
+ SWAP(freqReg[channel], swapFreqReg[swapIndex]);
+ SWAP(freqDeltaCounter[channel], swapVec11[swapIndex]);
+ SWAP(freqDelta[channel], swapVec10[swapIndex]);
+ SWAP(vec20[channel], swapVec20[swapIndex]);
+ SWAP(songFileOrChanBufOffset[channel], swapVec8[swapIndex]);
+}
+
+void Player_SID::resetSwapVars() { // $52d0
+ for (int i = 0; i < 2; ++i) {
+ swapAttack[i] = 0;
+ swapSustain[i] = 0;
+ }
+ for (int i = 0; i < 3; ++i) {
+ swapVec5[i] = 0;
+ swapSongPrio[i] = 0;
+ swapVec479C[i] = 0;
+ swapVec19[i] = 0;
+ swapSongPosUpdateCounter[i] = 0;
+ swapWaveCtrlReg[i] = 0;
+ swapSongPosPtr[i] = 0;
+ swapFreqReg[i] = 0;
+ swapVec11[i] = 0;
+ swapVec10[i] = 0;
+ swapVec20[i] = 0;
+ swapVec8[i] = 0;
+ }
+}
+
+void Player_SID::prepareSwapVars(int channel) { // $52E5
+ if (channel >= 4)
+ return;
+
+ if (channel < 3) {
+ if (!keepSwapVars) {
+ resetSwapVars();
+ }
+ swapVars(channel, 0);
+ if (busyChannelBits & BITMASK[channel+4]) {
+ swapVars(channel+4, 1);
+ pulseWidthSwapped = true;
+ }
+ } else if (channel == 3) {
+ SIDReg24_HiNibble = SIDReg24 & 0x70;
+ resetSwapVars();
+ keepSwapVars = true;
+ swapVars(3, 2);
+ filterSwapped = true;
+ }
+ swapPrepared = true;
+}
+
+void Player_SID::useSwapVars(int channel) { // $5342
+ if (channel >= 3)
+ return;
+
+ swapVars(channel, 0);
+ setSIDFreqAS(channel);
+ if (pulseWidthSwapped) {
+ swapVars(channel+4, 1);
+ setSIDFreqAS(channel+4);
+ }
+ if (filterSwapped) {
+ swapVars(3, 2);
+
+ // resonating filter freq. or voice-to-filter mapping?
+ SIDReg23 = (SIDReg23Stuff & 0xf0) | BITMASK[channel];
+ SID_Write(23, SIDReg23);
+
+ // filter props
+ SIDReg24 = (SIDReg24 & 0x0f) | SIDReg24_HiNibble;
+ SID_Write(24, SIDReg24);
+
+ // filter freq.
+ SID_Write(21, LOBYTE_(freqReg[3]));
+ SID_Write(22, HIBYTE_(freqReg[3]));
+ } else {
+ SIDReg23 = SIDReg23Stuff & BITMASK_INV[channel];
+ SID_Write(23, SIDReg23);
+ }
+
+ swapPrepared = false;
+ pulseWidthSwapped = false;
+ keepSwapVars = false;
+ SIDReg24_HiNibble = 0;
+ filterSwapped = false;
+}
+
+// ignore: no effect
+// resIndex: 3,4,5 or 58
+void Player_SID::lockResource(int resIndex) { // $4ff4
+ if (!isMusicPlaying)
+ ++resStatus[resIndex];
+}
+
+void Player_SID::reserveChannel(int channel, uint8 prioValue, int chanResIndex) { // $4ffe
+ if (channel == 3) {
+ filterUsed = true;
+ } else if (channel < 3) {
+ usedChannelBits |= BITMASK[channel];
+ countFreeChannels();
+ }
+
+ chanPrio[channel] = prioValue;
+ lockResource(chanResIndex);
+}
+
+// ignore: no effect
+void Player_SID::unlockCodeLocation() { // $513e
+ resStatus[1] &= 0x80;
+ resStatus[2] &= 0x80;
+}
+
+// ignore: no effect
+void Player_SID::lockCodeLocation() { // $514f
+ resStatus[1] |= 0x01;
+ resStatus[2] |= 0x01;
+}
+
+void Player_SID::initMusic(int songResIndex) { // $7de6
+ unlockResource(resID_song);
+
+ resID_song = songResIndex;
+ _music = getResource(resID_song);
+ if (_music == NULL) {
+ return;
+ }
+
+ // song base address
+ uint8* songFileDataPtr = _music;
+ actSongFileData = _music;
+
+ initializing = true;
+ _soundInQueue = false;
+ isMusicPlaying = false;
+
+ unlockCodeLocation();
+ resetPlayerState();
+
+ lockResource(resID_song);
+ buildStepTbl(songFileDataPtr[5]);
+
+ // fetch sound
+ songChannelBits = songFileDataPtr[4];
+ for (int i = 2; i >= 0; --i) {
+ if ((songChannelBits & BITMASK[i]) != 0) {
+ func_7eae(i, songFileDataPtr);
+ }
+ }
+
+ isMusicPlaying = true;
+ lockCodeLocation();
+
+ SIDReg23 &= 0xf0;
+ SID_Write(23, SIDReg23);
+
+ handleMusicBuffer();
+
+ initializing = false;
+ _soundInQueue = true;
+}
+
+// params:
+// channel: channel 0..2
+void Player_SID::func_7eae(int channel, uint8* songFileDataPtr) {
+ int pos = SONG_CHANNEL_OFFSET[channel];
+ chanDataOffset[channel] = READ_LE_UINT16(&songFileDataPtr[pos]);
+ chanFileData[channel] = songFileDataPtr + chanDataOffset[channel];
+
+ //vec5[channel+4] = vec5[channel] = CHANNEL_BUFFER_ADDR[RES_ID_CHANNEL[channel]]; // not used
+ vec6[channel+4] = 0x0019;
+ vec6[channel] = 0x0008;
+
+ func_819b(channel);
+
+ waveCtrlReg[channel] = 0;
+}
+
+void Player_SID::func_819b(int channel) {
+ reserveChannel(channel, 127, RES_ID_CHANNEL[channel]);
+
+ statusBits1B |= BITMASK[channel];
+ statusBits1A |= BITMASK[channel];
+}
+
+void Player_SID::buildStepTbl(int step) { // $82B4
+ stepTbl[0] = 0;
+ stepTbl[1] = step - 2;
+ for (int i = 2; i < 33; ++i) {
+ stepTbl[i] = stepTbl[i-1] + step;
+ }
+}
+
+int Player_SID::reserveSoundFilter(uint8 value, uint8 chanResIndex) { // $4ED0
+ int channel = 3;
+ reserveChannel(channel, value, chanResIndex);
+ return channel;
+}
+
+int Player_SID::reserveSoundVoice(uint8 value, uint8 chanResIndex) { // $4EB8
+ for (int i = 2; i >= 0; --i) {
+ if ((usedChannelBits & BITMASK[i]) == 0) {
+ reserveChannel(i, value, chanResIndex);
+ return i;
+ }
+ }
+ return 0;
+}
+
+void Player_SID::findLessPrioChannels(uint8 soundPrio) { // $4ED8
+ minChanPrio = 127;
+
+ chansWithLowerPrioCount = 0;
+ for (int i = 2; i >= 0; --i) {
+ if (usedChannelBits & BITMASK[i]) {
+ if (chanPrio[i] < soundPrio)
+ ++chansWithLowerPrioCount;
+ if (chanPrio[i] < minChanPrio) {
+ minChanPrio = chanPrio[i];
+ minChanPrioIndex = i;
+ }
+ }
+ }
+
+ if (chansWithLowerPrioCount == 0)
+ return;
+
+ if (soundPrio >= chanPrio[3]) {
+ actFilterHasLowerPrio = true;
+ } else {
+ /* TODO: is this really a no-op?
+ if (minChanPrioIndex < chanPrio[3])
+ minChanPrioIndex = minChanPrioIndex;
+ */
+
+ actFilterHasLowerPrio = false;
+ }
+}
+
+void Player_SID::releaseResourceBySound(int resID) { // $5088
+ var481A = 1;
+ releaseResource(resID);
+}
+
+void Player_SID::readVec6Data(int x, int *offset, uint8 *songFilePtr, int chanResID) { // $4E99
+ //vec5[x] = songFilePtr;
+ vec6[x] = songFilePtr[*offset];
+ *offset += 2;
+ _soundQueue[x] = chanResID;
+}
+
+int Player_SID::initSound(int soundResID) { // $4D0A
+ initializing = true;
+
+ if (isMusicPlaying && (statusBits1A & 0x07) == 0x07) {
+ initializing = false;
+ return -2;
+ }
+
+ uint8 *songFilePtr = getResource(soundResID);
+ if (songFilePtr == NULL) {
+ initializing = false;
+ return 1;
+ }
+
+ uint8 soundPrio = songFilePtr[4];
+ // for (mostly but not always looped) background sounds
+ if (soundPrio == 1) {
+ bgSoundResID = soundResID;
+ bgSoundActive = true;
+ }
+
+ uint8 requestedChannels = 0;
+ if ((songFilePtr[5] & 0x40) == 0) {
+ ++requestedChannels;
+ if (songFilePtr[5] & 0x02)
+ ++requestedChannels;
+ if (songFilePtr[5] & 0x08)
+ ++requestedChannels;
+ }
+
+ bool filterNeeded = (songFilePtr[5] & 0x20) != 0;
+ bool filterBlocked = (filterUsed && filterNeeded);
+ if (filterBlocked || (freeChannelCount < requestedChannels)) {
+ findLessPrioChannels(soundPrio);
+
+ if ((freeChannelCount + chansWithLowerPrioCount < requestedChannels) ||
+ (filterBlocked && !actFilterHasLowerPrio)) {
+ initializing = false;
+ return -1;
+ }
+
+ if (filterBlocked) {
+ if (soundPrio < chanPrio[3]) {
+ initializing = false;
+ return -1;
+ }
+
+ uint8 l_resID = channelMap[3];
+ releaseResourceBySound(l_resID);
+ }
+
+ while ((freeChannelCount < requestedChannels) || (filterNeeded && filterUsed)) {
+ findLessPrioChannels(soundPrio);
+ if (minChanPrio >= soundPrio) {
+ initializing = false;
+ return -1;
+ }
+
+ uint8 l_resID = channelMap[minChanPrioIndex];
+ releaseResourceBySound(l_resID);
+ }
+ }
+
+ int x;
+ uint8 soundByte5 = songFilePtr[5];
+ if (soundByte5 & 0x40)
+ x = reserveSoundFilter(soundPrio, soundResID);
+ else
+ x = reserveSoundVoice(soundPrio, soundResID);
+
+ uint8 var4CF3 = x;
+ int y = 6;
+ if (soundByte5 & 0x01) {
+ x += 4;
+ readVec6Data(x, &y, songFilePtr, soundResID);
+ }
+ if (soundByte5 & 0x02) {
+ x = reserveSoundVoice(soundPrio, soundResID);
+ readVec6Data(x, &y, songFilePtr, soundResID);
+ }
+ if (soundByte5 & 0x04) {
+ x += 4;
+ readVec6Data(x, &y, songFilePtr, soundResID);
+ }
+ if (soundByte5 & 0x08) {
+ x = reserveSoundVoice(soundPrio, soundResID);
+ readVec6Data(x, &y, songFilePtr, soundResID);
+ }
+ if (soundByte5 & 0x10) {
+ x += 4;
+ readVec6Data(x, &y, songFilePtr, soundResID);
+ }
+ if (soundByte5 & 0x20) {
+ x = reserveSoundFilter(soundPrio, soundResID);
+ readVec6Data(x, &y, songFilePtr, soundResID);
+ }
+
+ //vec5[var4CF3] = songFilePtr;
+ vec6[var4CF3] = y;
+ _soundQueue[var4CF3] = soundResID;
+
+ initializing = false;
+ _soundInQueue = true;
+
+ return soundResID;
+}
+
+void Player_SID::unused1() { // $50AF
+ var481A = -1;
+ if (bgSoundResID != 0) {
+ releaseResourceUnk(bgSoundResID);
+ }
+}
+
+///////////////////////////
+///////////////////////////
+
+#define ZEROMEM(a) memset(a, 0, sizeof(a))
+
+Player_SID::Player_SID(ScummEngine *scumm, Audio::Mixer *mixer) {
+ /*
+ * clear memory
+ */
+
+ resID_song = 0;
+ statusBits1A = 0;
+ statusBits1B = 0;
+ busyChannelBits = 0;
+ SIDReg23 = 0;
+ SIDReg23Stuff = 0;
+ SIDReg24 = 0;
+ bgSoundResID = 0;
+ freeChannelCount = 0;
+ usedChannelBits = 0;
+ var481A = 0;
+ songChannelBits = 0;
+ //var5163 = 0;
+ SIDReg24_HiNibble = 0;
+ chansWithLowerPrioCount = 0;
+ minChanPrio = 0;
+ minChanPrioIndex = 0;
+
+ _music = NULL;
+ songFileOrChanBufData = NULL;
+ actSongFileData = NULL;
+
+ initializing = false;
+ _soundInQueue = false;
+ isVoiceChannel = false;
+ isMusicPlaying = false;
+ swapVarLoaded = false;
+ bgSoundActive = false;
+ filterUsed = false;
+ pulseWidthSwapped = false;
+ swapPrepared = false;
+ filterSwapped = false;
+ keepSwapVars = false;
+ actFilterHasLowerPrio = false;
+
+ ZEROMEM(chanFileData);
+ ZEROMEM(chanDataOffset);
+ ZEROMEM(songPosPtr);
+ ZEROMEM(freqReg);
+ ZEROMEM(vec6);
+ ZEROMEM(songFileOrChanBufOffset);
+ ZEROMEM(freqDelta);
+ ZEROMEM(freqDeltaCounter);
+ ZEROMEM(swapSongPosPtr);
+ ZEROMEM(swapVec5);
+ ZEROMEM(swapVec8);
+ ZEROMEM(swapVec10);
+ ZEROMEM(swapFreqReg);
+ ZEROMEM(swapVec11);
+ ZEROMEM(vec20);
+ ZEROMEM(swapVec20);
+ ZEROMEM(resStatus);
+ ZEROMEM(attackReg);
+ ZEROMEM(sustainReg);
+ ZEROMEM(phaseBit);
+ ZEROMEM(releasePhase);
+ ZEROMEM(_soundQueue);
+ ZEROMEM(channelMap);
+ ZEROMEM(songPosUpdateCounter);
+ ZEROMEM(chanPrio);
+ ZEROMEM(waveCtrlReg);
+ ZEROMEM(swapAttack);
+ ZEROMEM(swapSustain);
+ ZEROMEM(swapSongPrio);
+ ZEROMEM(swapVec479C);
+ ZEROMEM(swapVec19);
+ ZEROMEM(swapSongPosUpdateCounter);
+ ZEROMEM(swapWaveCtrlReg);
+ ZEROMEM(stepTbl);
+
+ /*
+ * initialize data
+ */
+
+ const uint8 chanBuffer_const[3][45] = {
+ {
+ 0x00,0x00,0x00,0x00,0x7f,0x01,0x19,0x00,
+ 0x00,0x00,0x2d,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0xf0,0x40,0x10,0x04,0x00,0x00,
+ 0x00,0x04,0x27,0x03,0xff,0xff,0x01,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00
+ },
+ {
+ 0x00,0x00,0x00,0x00,0x7f,0x01,0x19,0x00,
+ 0x00,0x00,0x2d,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0xf0,0x20,0x10,0x04,0x00,0x00,
+ 0x00,0x04,0x27,0x03,0xff,0xff,0x02,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00
+ },
+ {
+ 0x00,0x00,0x00,0x00,0x7f,0x01,0x19,0x00,
+ 0x00,0x00,0x2d,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0xf0,0x20,0x10,0x04,0x00,0x00,
+ 0x00,0x04,0x27,0x03,0xff,0xff,0x02,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00
+ }
+ };
+ memcpy(chanBuffer, chanBuffer_const, sizeof(chanBuffer_const));
+
+ for (int i = 0; i < 7; ++i) {
+ _soundQueue[i] = -1;
+ };
+
+ _music_timer = 0;
+
+ _mixer = mixer;
+ _sampleRate = _mixer->getOutputRate();
+ _vm = scumm;
+
+ // sound speed is slightly different on NTSC and PAL machines
+ // as the SID clock depends on the frame rate.
+ // ScummVM does not distinguish between NTSC and PAL targets
+ // so we use the NTSC timing here as the music was composed for
+ // NTSC systems (music on PAL systems is slower).
+ _videoSystem = NTSC;
+ _cpuCyclesLeft = 0;
+
+ initSID();
+ resetSID();
+
+ _mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
+}
+
+Player_SID::~Player_SID() {
+ _mixer->stopHandle(_soundHandle);
+ delete _sid;
+}
+
+uint8 *Player_SID::getResource(int resID) {
+ switch (resID) {
+ case 0:
+ return NULL;
+ case 3:
+ case 4:
+ case 5:
+ return chanBuffer[resID-3];
+ default:
+ return _vm->getResourceAddress(rtSound, resID);
+ }
+}
+
+int Player_SID::readBuffer(int16 *buffer, const int numSamples) {
+ int samplesLeft = numSamples;
+
+ Common::StackLock lock(_mutex);
+
+ while (samplesLeft > 0) {
+ // update SID status after each frame
+ if (_cpuCyclesLeft <= 0) {
+ update();
+ _cpuCyclesLeft = timingProps[_videoSystem].cyclesPerFrame;
+ }
+ // fetch samples
+ int sampleCount = _sid->updateClock(_cpuCyclesLeft, (short *)buffer, samplesLeft);
+ samplesLeft -= sampleCount;
+ buffer += sampleCount;
+ }
+
+ return numSamples;
+}
+
+void Player_SID::SID_Write(int reg, uint8 data) {
+ _sid->write(reg, data);
+}
+
+void Player_SID::initSID() {
+ _sid = new Resid::SID();
+ _sid->set_sampling_parameters(
+ timingProps[_videoSystem].clockFreq,
+ _sampleRate);
+ _sid->enable_filter(true);
+
+ _sid->reset();
+ // Synchronize the waveform generators (must occur after reset)
+ _sid->write( 4, 0x08);
+ _sid->write(11, 0x08);
+ _sid->write(18, 0x08);
+ _sid->write( 4, 0x00);
+ _sid->write(11, 0x00);
+ _sid->write(18, 0x00);
+}
+
+void Player_SID::startSound(int nr) {
+ byte *data = _vm->getResourceAddress(rtSound, nr);
+ assert(data);
+
+ // WORKAROUND:
+ // sound[4] contains either a song prio or a music channel usage byte.
+ // As music channel usage is always 0x07 for all music files and
+ // prio 7 is never used in any sound file use this byte for auto-detection.
+ bool isMusic = (data[4] == 0x07);
+
+ Common::StackLock lock(_mutex);
+
+ if (isMusic) {
+ initMusic(nr);
+ } else {
+ stopSound_intern(nr);
+ initSound(nr);
+ }
+}
+
+void Player_SID::stopSound(int nr) {
+ if (nr == -1)
+ return;
+
+ Common::StackLock lock(_mutex);
+ stopSound_intern(nr);
+}
+
+void Player_SID::stopAllSounds() {
+ Common::StackLock lock(_mutex);
+ resetPlayerState();
+}
+
+int Player_SID::getSoundStatus(int nr) const {
+ int result = 0;
+
+ //Common::StackLock lock(_mutex);
+
+ if (resID_song == nr && isMusicPlaying) {
+ result = 1;
+ }
+
+ for (int i = 0; (i < 4) && (result == 0); ++i) {
+ if (nr == _soundQueue[i] || nr == channelMap[i]) {
+ result = 1;
+ }
+ }
+
+ return result;
+}
+
+int Player_SID::getMusicTimer() {
+ int result = _music_timer;
+ _music_timer = 0;
+ return result;
+}
+
+} // End of namespace Scumm
+
+#endif