diff options
Diffstat (limited to 'engines/scumm/players/player_sid.cpp')
-rw-r--r-- | engines/scumm/players/player_sid.cpp | 1384 |
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 |