scummvm/engines/xeen/sound_driver.cpp

259 lines
7.2 KiB
C++

/* 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.
*
*/
#include "common/md5.h"
#include "common/config-manager.h"
#include "xeen/sound_driver.h"
#include "xeen/xeen.h"
#include "xeen/files.h"
namespace Xeen {
SoundDriver::SoundDriver() : _musicPlaying(false), _fxPlaying(false),
_musCountdownTimer(0), _fxCountdownTimer(0), _musDataPtr(nullptr),
_fxDataPtr(nullptr), _fxStartPtr(nullptr), _musStartPtr(nullptr),
_exclude7(false), _frameCtr(0) {
_channels.resize(CHANNEL_COUNT);
}
SoundDriver::~SoundDriver() {
_musicPlaying = _fxPlaying = false;
_musCountdownTimer = _fxCountdownTimer = 0;
}
void SoundDriver::execute() {
bool isFX = false;
const byte *srcP = nullptr;
const byte *startP = nullptr;
// Single iteration loop to avoid use of GOTO
do {
if (_musicPlaying) {
startP = _musStartPtr;
srcP = _musDataPtr;
isFX = false;
if (_musCountdownTimer == 0 || --_musCountdownTimer == 0)
break;
}
if (_fxPlaying) {
startP = _fxStartPtr;
srcP = _fxDataPtr;
isFX = true;
if (_fxCountdownTimer == 0 || --_fxCountdownTimer == 0)
break;
}
pausePostProcess();
return;
} while (0);
++_frameCtr;
debugC(3, kDebugSound, "\nSoundDriver frame - #%x", _frameCtr);
// Main loop
bool breakFlag = false;
while (!breakFlag) {
debugCN(3, kDebugSound, "MUSCODE %.4x - %.2x ", (uint)(srcP - startP), (uint)*srcP);
byte nextByte = *srcP++;
int cmd = (nextByte >> 4) & 15;
int param = (nextByte & 15);
CommandFn fn = isFX ? FX_COMMANDS[cmd] : MUSIC_COMMANDS[cmd];
breakFlag = (this->*fn)(srcP, param);
}
}
bool SoundDriver::musCallSubroutine(const byte *&srcP, byte param) {
debugC(3, kDebugSound, "musCallSubroutine");
if (_musSubroutines.size() < 16) {
const byte *returnP = srcP + 2;
srcP = _musStartPtr + READ_LE_UINT16(srcP);
_musSubroutines.push(Subroutine(returnP, srcP));
}
return false;
}
bool SoundDriver::musSetCountdown(const byte *&srcP, byte param) {
// Set the countdown timer
if (!param)
param = *srcP++;
_musCountdownTimer = param;
_musDataPtr = srcP;
debugC(3, kDebugSound, "musSetCountdown %d", param);
// Do paused handling and break out of processing loop
pausePostProcess();
return true;
}
bool SoundDriver::cmdNoOperation(const byte *&srcP, byte param) {
debugC(3, kDebugSound, "cmdNoOperation");
return false;
}
bool SoundDriver::musSkipWord(const byte *&srcP, byte param) {
debugC(3, kDebugSound, "musSkipWord");
srcP += 2;
return false;
}
bool SoundDriver::cmdFreezeFrequency(const byte *&srcP, byte param) {
debugC(3, kDebugSound, "cmdFreezeFrequency %d", param);
_channels[param]._changeFrequency = false;
return false;
}
bool SoundDriver::cmdChangeFrequency(const byte *&srcP, byte param) {
debugC(3, kDebugSound, "cmdChangeFrequency %d", param);
if (param != 7 || !_exclude7) {
_channels[param]._freqCtrChange = (int8)*srcP++;
_channels[param]._freqCtr = 0xFF;
_channels[param]._changeFrequency = true;
_channels[param]._freqChange = (int16)READ_BE_UINT16(srcP);
srcP += 2;
} else {
srcP += 3;
}
return false;
}
bool SoundDriver::musEndSubroutine(const byte *&srcP, byte param) {
debugC(3, kDebugSound, "musEndSubroutine %d", param);
if (param != 15) {
// Music has ended, so flag it stopped
_musicPlaying = false;
return true;
}
// Returning from subroutine, or looping back to start of music
srcP = _musSubroutines.empty() ? _musStartPtr : _musSubroutines.pop()._returnP;
return false;
}
bool SoundDriver::fxCallSubroutine(const byte *&srcP, byte param) {
debugC(3, kDebugSound, "fxCallSubroutine");
if (_fxSubroutines.size() < 16) {
const byte *startP = srcP + 2;
srcP = _musStartPtr + READ_LE_UINT16(srcP);
_fxSubroutines.push(Subroutine(startP, srcP));
}
return false;
}
bool SoundDriver::fxSetCountdown(const byte *&srcP, byte param) {
// Set the countdown timer
if (!param)
param = *srcP++;
_fxCountdownTimer = param;
_fxDataPtr = srcP;
debugC(3, kDebugSound, "fxSetCountdown %d", param);
// Do paused handling and break out of processing loop
pausePostProcess();
return true;
}
bool SoundDriver::fxEndSubroutine(const byte *&srcP, byte param) {
debugC(3, kDebugSound, "fxEndSubroutine %d", param);
if (param != 15) {
// FX has ended, so flag it stopped
_fxPlaying = false;
return true;
}
srcP = _fxSubroutines.empty() ? _fxStartPtr : _fxSubroutines.pop()._returnP;
return false;
}
void SoundDriver::playFX(uint effectId, const byte *data) {
if (!_fxPlaying || effectId < 7 || effectId >= 11) {
_fxDataPtr = _fxStartPtr = data;
_fxCountdownTimer = 0;
_channels[7]._changeFrequency = _channels[8]._changeFrequency = false;
resetFX();
_fxPlaying = true;
}
debugC(1, kDebugSound, "Starting FX %d", effectId);
}
void SoundDriver::stopFX() {
resetFX();
_fxPlaying = false;
_fxStartPtr = _fxDataPtr = nullptr;
}
void SoundDriver::playSong(const byte *data) {
_musDataPtr = _musStartPtr = data;
_musSubroutines.clear();
_musCountdownTimer = 0;
_musicPlaying = true;
debugC(1, kDebugSound, "Starting song");
}
int SoundDriver::songCommand(uint commandId, byte musicVolume, byte sfxVolume) {
if (commandId == STOP_SONG) {
_musicPlaying = false;
} else if (commandId == RESTART_SONG) {
_musicPlaying = true;
_musDataPtr = nullptr;
_musSubroutines.clear();
}
return 0;
}
const CommandFn SoundDriver::MUSIC_COMMANDS[16] = {
&SoundDriver::musCallSubroutine, &SoundDriver::musSetCountdown,
&SoundDriver::musSetInstrument, &SoundDriver::cmdNoOperation,
&SoundDriver::musSetPitchWheel, &SoundDriver::musSkipWord,
&SoundDriver::musSetPanning, &SoundDriver::cmdNoOperation,
&SoundDriver::musFade, &SoundDriver::musStartNote,
&SoundDriver::musSetVolume, &SoundDriver::musInjectMidi,
&SoundDriver::musPlayInstrument, &SoundDriver::cmdFreezeFrequency,
&SoundDriver::cmdChangeFrequency, &SoundDriver::musEndSubroutine
};
const CommandFn SoundDriver::FX_COMMANDS[16] = {
&SoundDriver::fxCallSubroutine, &SoundDriver::fxSetCountdown,
&SoundDriver::fxSetInstrument, &SoundDriver::fxSetVolume,
&SoundDriver::fxMidiReset, &SoundDriver::fxMidiDword,
&SoundDriver::fxSetPanning, &SoundDriver::fxChannelOff,
&SoundDriver::fxFade, &SoundDriver::fxStartNote,
&SoundDriver::cmdNoOperation, &SoundDriver::fxInjectMidi,
&SoundDriver::fxPlayInstrument, &SoundDriver::cmdFreezeFrequency,
&SoundDriver::cmdChangeFrequency, &SoundDriver::fxEndSubroutine
};
} // End of namespace Xeen