scummvm/engines/cine/sound.cpp
Matthew Hoops aa6ff44440 BACKENDS: Only expose one set of functions for AudioCDManager
Engines should only have to call one set of functions and not decide between the two. In fact, the 'emulation' API was documented to just call the 'real CD' API.
2016-03-13 13:57:19 +01:00

1263 lines
34 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/endian.h"
#include "common/file.h"
#include "common/memstream.h"
#include "common/system.h"
#include "common/textconsole.h"
#include "common/timer.h"
#include "common/mutex.h"
#include "common/config-manager.h"
#include "cine/cine.h"
#include "cine/sound.h"
#include "backends/audiocd/audiocd.h"
#include "audio/audiostream.h"
#include "audio/fmopl.h"
#include "audio/mididrv.h"
#include "audio/decoders/raw.h"
#include "audio/mods/soundfx.h"
namespace Cine {
class PCSoundDriver {
public:
typedef void (*UpdateCallback)(void *);
virtual ~PCSoundDriver() {}
virtual void setupChannel(int channel, const byte *data, int instrument, int volume) = 0;
virtual void setChannelFrequency(int channel, int frequency) = 0;
virtual void stopChannel(int channel) = 0;
virtual void playSample(const byte *data, int size, int channel, int volume) = 0;
virtual void stopAll() = 0;
virtual const char *getInstrumentExtension() const { return ""; }
virtual void notifyInstrumentLoad(const byte *data, int size, int channel) {}
virtual void setUpdateCallback(UpdateCallback upCb, void *ref) = 0;
void resetChannel(int channel);
void findNote(int freq, int *note, int *oct) const;
protected:
static const int _noteTable[];
static const int _noteTableCount;
};
const int PCSoundDriver::_noteTable[] = {
0xEEE, 0xE17, 0xD4D, 0xC8C, 0xBD9, 0xB2F, 0xA8E, 0x9F7,
0x967, 0x8E0, 0x861, 0x7E8, 0x777, 0x70B, 0x6A6, 0x647,
0x5EC, 0x597, 0x547, 0x4FB, 0x4B3, 0x470, 0x430, 0x3F4,
0x3BB, 0x385, 0x353, 0x323, 0x2F6, 0x2CB, 0x2A3, 0x27D,
0x259, 0x238, 0x218, 0x1FA, 0x1DD, 0x1C2, 0x1A9, 0x191,
0x17B, 0x165, 0x151, 0x13E, 0x12C, 0x11C, 0x10C, 0x0FD,
0x0EE, 0x0E1, 0x0D4, 0x0C8, 0x0BD, 0x0B2, 0x0A8, 0x09F,
0x096, 0x08E, 0x086, 0x07E, 0x077, 0x070, 0x06A, 0x064,
0x05E, 0x059, 0x054, 0x04F, 0x04B, 0x047, 0x043, 0x03F,
0x03B, 0x038, 0x035, 0x032, 0x02F, 0x02C, 0x02A, 0x027,
0x025, 0x023, 0x021, 0x01F, 0x01D, 0x01C, 0x01A, 0x019,
0x017, 0x016, 0x015, 0x013, 0x012, 0x011, 0x010, 0x00F
};
const int PCSoundDriver::_noteTableCount = ARRAYSIZE(_noteTable);
struct AdLibRegisterSoundInstrument {
uint8 vibrato;
uint8 attackDecay;
uint8 sustainRelease;
uint8 feedbackStrength;
uint8 keyScaling;
uint8 outputLevel;
uint8 freqMod;
};
struct AdLibSoundInstrument {
byte mode;
byte channel;
AdLibRegisterSoundInstrument regMod;
AdLibRegisterSoundInstrument regCar;
byte waveSelectMod;
byte waveSelectCar;
byte amDepth;
};
class AdLibSoundDriver : public PCSoundDriver {
public:
AdLibSoundDriver(Audio::Mixer *mixer);
virtual ~AdLibSoundDriver();
// PCSoundDriver interface
virtual void setUpdateCallback(UpdateCallback upCb, void *ref);
virtual void setupChannel(int channel, const byte *data, int instrument, int volume);
virtual void stopChannel(int channel);
virtual void stopAll();
void initCard();
void onTimer();
void setupInstrument(const byte *data, int channel);
void loadRegisterInstrument(const byte *data, AdLibRegisterSoundInstrument *reg);
virtual void loadInstrument(const byte *data, AdLibSoundInstrument *asi) = 0;
protected:
UpdateCallback _upCb;
void *_upRef;
OPL::OPL *_opl;
Audio::Mixer *_mixer;
byte _vibrato;
int _channelsVolumeTable[4];
AdLibSoundInstrument _instrumentsTable[4];
static const int _freqTable[];
static const int _freqTableCount;
static const int _operatorsTable[];
static const int _operatorsTableCount;
static const int _voiceOperatorsTable[];
static const int _voiceOperatorsTableCount;
};
const int AdLibSoundDriver::_freqTable[] = {
0x157, 0x16C, 0x181, 0x198, 0x1B1, 0x1CB,
0x1E6, 0x203, 0x222, 0x243, 0x266, 0x28A
};
const int AdLibSoundDriver::_freqTableCount = ARRAYSIZE(_freqTable);
const int AdLibSoundDriver::_operatorsTable[] = {
0, 1, 2, 3, 4, 5, 8, 9, 10, 11, 12, 13, 16, 17, 18, 19, 20, 21
};
const int AdLibSoundDriver::_operatorsTableCount = ARRAYSIZE(_operatorsTable);
const int AdLibSoundDriver::_voiceOperatorsTable[] = {
0, 3, 1, 4, 2, 5, 6, 9, 7, 10, 8, 11, 12, 15, 16, 16, 14, 14, 17, 17, 13, 13
};
const int AdLibSoundDriver::_voiceOperatorsTableCount = ARRAYSIZE(_voiceOperatorsTable);
// Future Wars AdLib driver
class AdLibSoundDriverINS : public AdLibSoundDriver {
public:
AdLibSoundDriverINS(Audio::Mixer *mixer) : AdLibSoundDriver(mixer) {}
virtual const char *getInstrumentExtension() const { return ".INS"; }
virtual void loadInstrument(const byte *data, AdLibSoundInstrument *asi);
virtual void setChannelFrequency(int channel, int frequency);
virtual void playSample(const byte *data, int size, int channel, int volume);
};
// Operation Stealth AdLib driver
class AdLibSoundDriverADL : public AdLibSoundDriver {
public:
AdLibSoundDriverADL(Audio::Mixer *mixer) : AdLibSoundDriver(mixer) {}
virtual const char *getInstrumentExtension() const { return ".ADL"; }
virtual void loadInstrument(const byte *data, AdLibSoundInstrument *asi);
virtual void setChannelFrequency(int channel, int frequency);
virtual void playSample(const byte *data, int size, int channel, int volume);
};
// (Future Wars) MIDI driver
class MidiSoundDriverH32 : public PCSoundDriver {
public:
MidiSoundDriverH32(MidiDriver *output);
~MidiSoundDriverH32();
virtual void setUpdateCallback(UpdateCallback upCb, void *ref);
virtual void setupChannel(int channel, const byte *data, int instrument, int volume);
virtual void setChannelFrequency(int channel, int frequency);
virtual void stopChannel(int channel);
virtual void playSample(const byte *data, int size, int channel, int volume);
virtual void stopAll() {}
virtual const char *getInstrumentExtension() const { return ".H32"; }
virtual void notifyInstrumentLoad(const byte *data, int size, int channel);
private:
MidiDriver *_output;
UpdateCallback _callback;
Common::Mutex _mutex;
void writeInstrument(int offset, const byte *data, int size);
void selectInstrument(int channel, int timbreGroup, int timbreNumber, int volume);
};
class PCSoundFxPlayer {
public:
PCSoundFxPlayer(PCSoundDriver *driver);
~PCSoundFxPlayer();
bool load(const char *song);
void play();
void stop();
void fadeOut();
static void updateCallback(void *ref);
enum {
NUM_INSTRUMENTS = 15,
NUM_CHANNELS = 4
};
private:
void update();
void handleEvents();
void handlePattern(int channel, const byte *patternData);
void unload();
bool _playing;
int _currentPos;
int _currentOrder;
int _numOrders;
int _eventsDelay;
int _fadeOutCounter;
int _updateTicksCounter;
int _instrumentsChannelTable[NUM_CHANNELS];
byte *_sfxData;
byte *_instrumentsData[NUM_INSTRUMENTS];
PCSoundDriver *_driver;
Common::Mutex _mutex;
};
void PCSoundDriver::findNote(int freq, int *note, int *oct) const {
if (freq > 0x777)
*oct = 0;
else if (freq > 0x3BB)
*oct = 1;
else if (freq > 0x1DD)
*oct = 2;
else if (freq > 0x0EE)
*oct = 3;
else if (freq > 0x077)
*oct = 4;
else if (freq > 0x03B)
*oct = 5;
else if (freq > 0x01D)
*oct = 6;
else
*oct = 7;
*note = 11;
for (int i = 0; i < 12; ++i) {
if (_noteTable[*oct * 12 + i] <= freq) {
*note = i;
break;
}
}
}
void PCSoundDriver::resetChannel(int channel) {
stopChannel(channel);
stopAll();
}
AdLibSoundDriver::AdLibSoundDriver(Audio::Mixer *mixer)
: _upCb(0), _upRef(0), _mixer(mixer) {
_opl = OPL::Config::create();
if (!_opl || !_opl->init())
error("Failed to create OPL");
memset(_channelsVolumeTable, 0, sizeof(_channelsVolumeTable));
memset(_instrumentsTable, 0, sizeof(_instrumentsTable));
initCard();
_opl->start(new Common::Functor0Mem<void, AdLibSoundDriver>(this, &AdLibSoundDriver::onTimer), 50);
}
AdLibSoundDriver::~AdLibSoundDriver() {
delete _opl;
}
void AdLibSoundDriver::setUpdateCallback(UpdateCallback upCb, void *ref) {
_upCb = upCb;
_upRef = ref;
}
void AdLibSoundDriver::setupChannel(int channel, const byte *data, int instrument, int volume) {
assert(channel < 4);
if (data) {
if (volume > 80) {
volume = 80;
} else if (volume < 0) {
volume = 0;
}
volume += volume / 4;
_channelsVolumeTable[channel] = volume;
setupInstrument(data, channel);
}
}
void AdLibSoundDriver::stopChannel(int channel) {
assert(channel < 4);
AdLibSoundInstrument *ins = &_instrumentsTable[channel];
if (ins->mode != 0 && ins->channel == 6) {
channel = 6;
}
if (ins->mode == 0 || channel == 6) {
_opl->writeReg(0xB0 | channel, 0);
}
if (ins->mode != 0) {
_vibrato &= ~(1 << (10 - ins->channel));
_opl->writeReg(0xBD, _vibrato);
}
}
void AdLibSoundDriver::stopAll() {
int i;
for (i = 0; i < 18; ++i) {
_opl->writeReg(0x40 | _operatorsTable[i], 63);
}
for (i = 0; i < 9; ++i) {
_opl->writeReg(0xB0 | i, 0);
}
_opl->writeReg(0xBD, 0);
}
void AdLibSoundDriver::initCard() {
_vibrato = 0x20;
_opl->writeReg(0xBD, _vibrato);
_opl->writeReg(0x08, 0x40);
static const int oplRegs[] = { 0x40, 0x60, 0x80, 0x20, 0xE0 };
for (int i = 0; i < 9; ++i) {
_opl->writeReg(0xB0 | i, 0);
}
for (int i = 0; i < 9; ++i) {
_opl->writeReg(0xC0 | i, 0);
}
for (int j = 0; j < 5; j++) {
for (int i = 0; i < 18; ++i) {
_opl->writeReg(oplRegs[j] | _operatorsTable[i], 0);
}
}
_opl->writeReg(1, 0x20);
_opl->writeReg(1, 0);
}
void AdLibSoundDriver::onTimer() {
if (_upCb) {
(*_upCb)(_upRef);
}
}
void AdLibSoundDriver::setupInstrument(const byte *data, int channel) {
assert(channel < 4);
AdLibSoundInstrument *ins = &_instrumentsTable[channel];
loadInstrument(data, ins);
int mod, car, tmp;
const AdLibRegisterSoundInstrument *reg;
if (ins->mode != 0) {
mod = _operatorsTable[_voiceOperatorsTable[2 * ins->channel + 0]];
car = _operatorsTable[_voiceOperatorsTable[2 * ins->channel + 1]];
} else {
mod = _operatorsTable[_voiceOperatorsTable[2 * channel + 0]];
car = _operatorsTable[_voiceOperatorsTable[2 * channel + 1]];
}
if (ins->mode == 0 || ins->channel == 6) {
reg = &ins->regMod;
_opl->writeReg(0x20 | mod, reg->vibrato);
if (reg->freqMod) {
tmp = reg->outputLevel & 0x3F;
} else {
tmp = (63 - (reg->outputLevel & 0x3F)) * _channelsVolumeTable[channel];
tmp = 63 - (2 * tmp + 127) / (2 * 127);
}
_opl->writeReg(0x40 | mod, tmp | (reg->keyScaling << 6));
_opl->writeReg(0x60 | mod, reg->attackDecay);
_opl->writeReg(0x80 | mod, reg->sustainRelease);
if (ins->mode != 0) {
_opl->writeReg(0xC0 | ins->channel, reg->feedbackStrength);
} else {
_opl->writeReg(0xC0 | channel, reg->feedbackStrength);
}
_opl->writeReg(0xE0 | mod, ins->waveSelectMod);
}
reg = &ins->regCar;
_opl->writeReg(0x20 | car, reg->vibrato);
tmp = (63 - (reg->outputLevel & 0x3F)) * _channelsVolumeTable[channel];
tmp = 63 - (2 * tmp + 127) / (2 * 127);
_opl->writeReg(0x40 | car, tmp | (reg->keyScaling << 6));
_opl->writeReg(0x60 | car, reg->attackDecay);
_opl->writeReg(0x80 | car, reg->sustainRelease);
_opl->writeReg(0xE0 | car, ins->waveSelectCar);
}
void AdLibSoundDriver::loadRegisterInstrument(const byte *data, AdLibRegisterSoundInstrument *reg) {
reg->vibrato = 0;
if (READ_LE_UINT16(data + 18)) { // amplitude vibrato
reg->vibrato |= 0x80;
}
if (READ_LE_UINT16(data + 20)) { // frequency vibrato
reg->vibrato |= 0x40;
}
if (READ_LE_UINT16(data + 10)) { // sustaining sound
reg->vibrato |= 0x20;
}
if (READ_LE_UINT16(data + 22)) { // envelope scaling
reg->vibrato |= 0x10;
}
reg->vibrato |= READ_LE_UINT16(data + 2) & 0xF; // frequency multiplier
reg->attackDecay = READ_LE_UINT16(data + 6) << 4; // attack rate
reg->attackDecay |= READ_LE_UINT16(data + 12) & 0xF; // decay rate
reg->sustainRelease = READ_LE_UINT16(data + 8) << 4; // sustain level
reg->sustainRelease |= READ_LE_UINT16(data + 14) & 0xF; // release rate
reg->feedbackStrength = READ_LE_UINT16(data + 4) << 1; // feedback
if (READ_LE_UINT16(data + 24) == 0) { // frequency modulation
reg->feedbackStrength |= 1;
}
reg->keyScaling = READ_LE_UINT16(data);
reg->outputLevel = READ_LE_UINT16(data + 16);
reg->freqMod = READ_LE_UINT16(data + 24);
}
void AdLibSoundDriverINS::loadInstrument(const byte *data, AdLibSoundInstrument *asi) {
asi->mode = *data++;
asi->channel = *data++;
loadRegisterInstrument(data, &asi->regMod); data += 26;
loadRegisterInstrument(data, &asi->regCar); data += 26;
asi->waveSelectMod = data[0] & 3; data += 2;
asi->waveSelectCar = data[0] & 3; data += 2;
asi->amDepth = data[0]; data += 2;
}
void AdLibSoundDriverINS::setChannelFrequency(int channel, int frequency) {
assert(channel < 4);
AdLibSoundInstrument *ins = &_instrumentsTable[channel];
if (ins->mode != 0 && ins->channel == 6) {
channel = 6;
}
if (ins->mode == 0 || ins->channel == 6) {
int freq, note, oct;
findNote(frequency, &note, &oct);
if (channel == 6)
oct = 0;
freq = _freqTable[note % 12];
_opl->writeReg(0xA0 | channel, freq);
freq = (oct << 2) | ((freq & 0x300) >> 8);
if (ins->mode == 0) {
freq |= 0x20;
}
_opl->writeReg(0xB0 | channel, freq);
}
if (ins->mode != 0) {
_vibrato |= 1 << (10 - ins->channel);
_opl->writeReg(0xBD, _vibrato);
}
}
void AdLibSoundDriverINS::playSample(const byte *data, int size, int channel, int volume) {
assert(channel < 4);
_channelsVolumeTable[channel] = 127;
resetChannel(channel);
setupInstrument(data + 257, channel);
AdLibSoundInstrument *ins = &_instrumentsTable[channel];
if (ins->mode != 0 && ins->channel == 6) {
channel = 6;
}
if (ins->mode == 0 || channel == 6) {
uint16 note = 12;
int freq = _freqTable[note % 12];
_opl->writeReg(0xA0 | channel, freq);
freq = ((note / 12) << 2) | ((freq & 0x300) >> 8);
if (ins->mode == 0) {
freq |= 0x20;
}
_opl->writeReg(0xB0 | channel, freq);
}
if (ins->mode != 0) {
_vibrato |= 1 << (10 - ins->channel);
_opl->writeReg(0xBD, _vibrato);
}
}
void AdLibSoundDriverADL::loadInstrument(const byte *data, AdLibSoundInstrument *asi) {
asi->mode = *data++;
asi->channel = *data++;
asi->waveSelectMod = *data++ & 3;
asi->waveSelectCar = *data++ & 3;
asi->amDepth = *data++;
++data;
loadRegisterInstrument(data, &asi->regMod); data += 26;
loadRegisterInstrument(data, &asi->regCar); data += 26;
}
void AdLibSoundDriverADL::setChannelFrequency(int channel, int frequency) {
assert(channel < 4);
AdLibSoundInstrument *ins = &_instrumentsTable[channel];
if (ins->mode != 0) {
channel = ins->channel;
if (channel == 9) {
channel = 8;
} else if (channel == 10) {
channel = 7;
}
}
int freq, note, oct;
findNote(frequency, &note, &oct);
if (ins->amDepth) {
note = ins->amDepth;
oct = note / 12;
}
if (note < 0) {
note = 0;
oct = 0;
}
freq = _freqTable[note % 12];
_opl->writeReg(0xA0 | channel, freq);
freq = (oct << 2) | ((freq & 0x300) >> 8);
if (ins->mode == 0) {
freq |= 0x20;
}
_opl->writeReg(0xB0 | channel, freq);
if (ins->mode != 0) {
_vibrato |= 1 << (10 - channel);
_opl->writeReg(0xBD, _vibrato);
}
}
void AdLibSoundDriverADL::playSample(const byte *data, int size, int channel, int volume) {
assert(channel < 4);
_channelsVolumeTable[channel] = 127;
setupInstrument(data, channel);
AdLibSoundInstrument *ins = &_instrumentsTable[channel];
if (ins->mode != 0 && ins->channel == 6) {
_opl->writeReg(0xB0 | channel, 0);
}
if (ins->mode != 0) {
_vibrato &= ~(1 << (10 - ins->channel));
_opl->writeReg(0xBD, _vibrato);
}
if (ins->mode != 0) {
channel = ins->channel;
if (channel == 9) {
channel = 8;
} else if (channel == 10) {
channel = 7;
}
}
uint16 note = 48;
if (ins->amDepth) {
note = ins->amDepth;
}
int freq = _freqTable[note % 12];
_opl->writeReg(0xA0 | channel, freq);
freq = ((note / 12) << 2) | ((freq & 0x300) >> 8);
if (ins->mode == 0) {
freq |= 0x20;
}
_opl->writeReg(0xB0 | channel, freq);
if (ins->mode != 0) {
_vibrato |= 1 << (10 - channel);
_opl->writeReg(0xBD, _vibrato);
}
}
MidiSoundDriverH32::MidiSoundDriverH32(MidiDriver *output)
: _output(output), _callback(0), _mutex() {
}
MidiSoundDriverH32::~MidiSoundDriverH32() {
if (_callback)
g_system->getTimerManager()->removeTimerProc(_callback);
_output->close();
delete _output;
}
void MidiSoundDriverH32::setUpdateCallback(UpdateCallback upCb, void *ref) {
Common::StackLock lock(_mutex);
Common::TimerManager *timer = g_system->getTimerManager();
assert(timer);
if (_callback)
timer->removeTimerProc(_callback);
_callback = upCb;
if (_callback)
timer->installTimerProc(_callback, 1000000 / 50, ref, "MidiSoundDriverH32");
}
void MidiSoundDriverH32::setupChannel(int channel, const byte *data, int instrument, int volume) {
Common::StackLock lock(_mutex);
if (volume < 0 || volume > 100)
volume = 0;
if (!data)
selectInstrument(channel, 0, 0, volume);
// In case the instrument is a builtin instrument select it directly.
else if (data[0] < 0x80)
selectInstrument(channel, data[0] / 0x40, data[0] % 0x40, volume);
// In case we use a custom instrument we need to specify the timbre group
// 2, which means it's a timbre from the timbre memory area.
else
selectInstrument(channel, 2, instrument, volume);
}
void MidiSoundDriverH32::setChannelFrequency(int channel, int frequency) {
Common::StackLock lock(_mutex);
int note, oct;
findNote(frequency, &note, &oct);
note %= 12;
note = oct * 12 + note + 12;
_output->send(0x91 + channel, note, 0x7F);
}
void MidiSoundDriverH32::stopChannel(int channel) {
Common::StackLock lock(_mutex);
_output->send(0xB1 + channel, 0x7B, 0x00);
}
void MidiSoundDriverH32::playSample(const byte *data, int size, int channel, int volume) {
Common::StackLock lock(_mutex);
stopChannel(channel);
volume = volume * 8 / 5;
if (data[0] < 0x80) {
selectInstrument(channel, data[0] / 0x40, data[0] % 0x40, volume);
} else {
writeInstrument(channel * 512 + 0x80000, data + 1, 256);
selectInstrument(channel, 2, channel, volume);
}
_output->send(0x91 + channel, 12, 0x7F);
}
void MidiSoundDriverH32::notifyInstrumentLoad(const byte *data, int size, int channel) {
Common::StackLock lock(_mutex);
// In case we specify a standard instrument or standard rhythm instrument
// do not do anything here. It might be noteworthy that the instrument
// selection client code does not support rhythm instruments!
if (data[0] < 0x80 || data[0] > 0xC0)
return;
writeInstrument(channel * 512 + 0x80000, data + 1, size - 1);
}
void MidiSoundDriverH32::writeInstrument(int offset, const byte *data, int size) {
byte sysEx[254];
sysEx[0] = 0x41;
sysEx[1] = 0x10;
sysEx[2] = 0x16;
sysEx[3] = 0x12;
sysEx[4] = (offset >> 16) & 0xFF;
sysEx[5] = (offset >> 8) & 0xFF;
sysEx[6] = (offset >> 0) & 0xFF;
int copySize = MIN(246, size);
memcpy(&sysEx[7], data, copySize);
byte checkSum = 0;
for (int i = 0; i < copySize + 3; ++i)
checkSum += sysEx[4 + i];
sysEx[7 + copySize] = 0x80 - (checkSum & 0x7F);
_output->sysEx(sysEx, copySize + 8);
}
void MidiSoundDriverH32::selectInstrument(int channel, int timbreGroup, int timbreNumber, int volume) {
const int offset = channel * 16 + 0x30000; // 0x30000 is the start of the patch temp area
byte sysEx[24] = {
0x41, 0x10, 0x16, 0x12,
0x00, 0x00, 0x00, // offset
0x00, // Timbre group _ timbreGroup * 64 + timbreNumber should be the
0x00, // Timbre number / MT-32 instrument in case timbreGroup is 0 or 1.
0x18, // Key shift (= 0)
0x32, // Fine tune (= 0)
0x0C, // Bender Range
0x03, // Assign Mode
0x01, // Reverb Switch (= enabled)
0x00, // dummy
0x00, // Output level
0x07, // Panpot (= balanced)
0x00, // dummy
0x00, // dummy
0x00, // dummy
0x00, // dummy
0x00, // dummy
0x00, // dummy
0x00 // checksum
};
sysEx[4] = (offset >> 16) & 0xFF;
sysEx[5] = (offset >> 8) & 0xFF;
sysEx[6] = (offset >> 0) & 0xFF;
sysEx[7] = timbreGroup;
sysEx[8] = timbreNumber;
sysEx[15] = volume;
byte checkSum = 0;
for (int i = 4; i < 23; ++i)
checkSum += sysEx[i];
sysEx[23] = 0x80 - (checkSum & 0x7F);
_output->sysEx(sysEx, 24);
}
PCSoundFxPlayer::PCSoundFxPlayer(PCSoundDriver *driver)
: _playing(false), _driver(driver), _mutex() {
memset(_instrumentsData, 0, sizeof(_instrumentsData));
_sfxData = NULL;
_fadeOutCounter = 0;
_driver->setUpdateCallback(updateCallback, this);
}
PCSoundFxPlayer::~PCSoundFxPlayer() {
Common::StackLock lock(_mutex);
_driver->setUpdateCallback(NULL, NULL);
stop();
}
bool PCSoundFxPlayer::load(const char *song) {
debug(9, "PCSoundFxPlayer::load('%s')", song);
/* stop (w/ fade out) the previous song */
while (_fadeOutCounter != 0 && _fadeOutCounter < 100) {
g_system->delayMillis(50);
}
_fadeOutCounter = 0;
Common::StackLock lock(_mutex);
stop();
_sfxData = readBundleSoundFile(song);
if (!_sfxData) {
warning("Unable to load soundfx module '%s'", song);
return 0;
}
for (int i = 0; i < NUM_INSTRUMENTS; ++i) {
_instrumentsData[i] = NULL;
char instrument[64];
memset(instrument, 0, 64); // Clear the data first
memcpy(instrument, _sfxData + 20 + i * 30, 12);
instrument[63] = '\0';
if (instrument[0] != '\0') {
char *dot = strrchr(instrument, '.');
if (dot) {
*dot = '\0';
}
Common::strlcat(instrument, _driver->getInstrumentExtension(), sizeof(instrument));
uint32 instrumentSize;
_instrumentsData[i] = readBundleSoundFile(instrument, &instrumentSize);
if (!_instrumentsData[i]) {
warning("Unable to load soundfx instrument '%s'", instrument);
} else {
_driver->notifyInstrumentLoad(_instrumentsData[i], instrumentSize, i);
}
}
}
return 1;
}
void PCSoundFxPlayer::play() {
debug(9, "PCSoundFxPlayer::play()");
Common::StackLock lock(_mutex);
if (_sfxData) {
for (int i = 0; i < NUM_CHANNELS; ++i) {
_instrumentsChannelTable[i] = -1;
}
_currentPos = 0;
_currentOrder = 0;
_numOrders = _sfxData[470];
_eventsDelay = (252 - _sfxData[471]) * 50 / 1060;
_updateTicksCounter = 0;
_playing = true;
}
}
void PCSoundFxPlayer::stop() {
Common::StackLock lock(_mutex);
if (_playing || _fadeOutCounter != 0) {
_fadeOutCounter = 0;
_playing = false;
for (int i = 0; i < NUM_CHANNELS; ++i) {
_driver->stopChannel(i);
}
_driver->stopAll();
}
unload();
}
void PCSoundFxPlayer::fadeOut() {
Common::StackLock lock(_mutex);
if (_playing) {
_fadeOutCounter = 1;
_playing = false;
}
}
void PCSoundFxPlayer::updateCallback(void *ref) {
((PCSoundFxPlayer *)ref)->update();
}
void PCSoundFxPlayer::update() {
Common::StackLock lock(_mutex);
if (_playing || (_fadeOutCounter != 0 && _fadeOutCounter < 100)) {
++_updateTicksCounter;
if (_updateTicksCounter > _eventsDelay) {
handleEvents();
_updateTicksCounter = 0;
}
}
}
void PCSoundFxPlayer::handleEvents() {
const byte *patternData = _sfxData + 600;
const byte *orderTable = _sfxData + 472;
uint16 patternNum = orderTable[_currentOrder] * 1024;
for (int i = 0; i < 4; ++i) {
handlePattern(i, patternData + patternNum + _currentPos);
patternData += 4;
}
if (_fadeOutCounter != 0 && _fadeOutCounter < 100) {
_fadeOutCounter += 2;
}
_currentPos += 16;
if (_currentPos >= 1024) {
_currentPos = 0;
++_currentOrder;
if (_currentOrder == _numOrders) {
_currentOrder = 0;
}
}
debug(7, "_currentOrder=%d/%d _currentPos=%d", _currentOrder, _numOrders, _currentPos);
}
void PCSoundFxPlayer::handlePattern(int channel, const byte *patternData) {
int instrument = patternData[2] >> 4;
if (instrument != 0) {
--instrument;
if (_instrumentsChannelTable[channel] != instrument || _fadeOutCounter != 0) {
_instrumentsChannelTable[channel] = instrument;
const int volume = _sfxData[instrument] - _fadeOutCounter;
_driver->setupChannel(channel, _instrumentsData[instrument], instrument, volume);
}
}
int16 freq = (int16)READ_BE_UINT16(patternData);
if (freq > 0) {
_driver->stopChannel(channel);
_driver->setChannelFrequency(channel, freq);
}
}
void PCSoundFxPlayer::unload() {
for (int i = 0; i < NUM_INSTRUMENTS; ++i) {
free(_instrumentsData[i]);
_instrumentsData[i] = NULL;
}
free(_sfxData);
_sfxData = NULL;
}
PCSound::PCSound(Audio::Mixer *mixer, CineEngine *vm)
: Sound(mixer, vm), _soundDriver(0) {
_currentMusic = 0;
_currentMusicStatus = 0;
_currentBgSlot = 0;
const MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI | MDT_ADLIB);
const MusicType musicType = MidiDriver::getMusicType(dev);
if (musicType == MT_MT32 || musicType == MT_GM) {
const bool isMT32 = (musicType == MT_MT32 || ConfMan.getBool("native_mt32"));
if (isMT32) {
MidiDriver *driver = MidiDriver::createMidi(dev);
if (driver && driver->open() == 0) {
driver->sendMT32Reset();
_soundDriver = new MidiSoundDriverH32(driver);
} else {
warning("Could not create MIDI output, falling back to AdLib");
}
} else {
warning("General MIDI output devices are not supported, falling back to AdLib");
}
}
if (!_soundDriver) {
if (_vm->getGameType() == GType_FW) {
_soundDriver = new AdLibSoundDriverINS(_mixer);
} else {
_soundDriver = new AdLibSoundDriverADL(_mixer);
}
}
_player = new PCSoundFxPlayer(_soundDriver);
// Ensure the CD is open
if (_vm->getGameType() == GType_FW && (_vm->getFeatures() & GF_CD))
g_system->getAudioCDManager()->open();
}
PCSound::~PCSound() {
delete _player;
delete _soundDriver;
}
static const char *const musicFileNames[11] = {
"DUGGER.DAT",
"SUITE21.DAT",
"FWARS.DAT",
"SUITE23.DAT",
"SUITE22.DAT",
"ESCAL",
"MOINES.DAT",
"MEDIAVAL.DAT",
"SFUTUR",
"ALIENS",
"TELESONG.DAT",
};
static uint8 musicCDTracks[11] = {
20, 21, 22, 23, 24, 25, 26, 27, 28, 30, 22,
};
void PCSound::loadMusic(const char *name) {
debugC(5, kCineDebugSound, "PCSound::loadMusic('%s')", name);
if (_vm->getGameType() == GType_FW && (_vm->getFeatures() & GF_CD)) {
_currentMusic = 0;
_currentMusicStatus = 0;
for (int i = 0; i < 11; i++) {
if (!strcmp((const char *)name, musicFileNames[i])) {
_currentMusic = musicCDTracks[i];
_currentMusicStatus = musicCDTracks[i];
}
}
} else {
_player->load(name);
}
}
void PCSound::playMusic() {
debugC(5, kCineDebugSound, "PCSound::playMusic()");
if (_vm->getGameType() == GType_FW && (_vm->getFeatures() & GF_CD)) {
g_system->getAudioCDManager()->stop();
g_system->getAudioCDManager()->play(_currentMusic - 1, -1, 0, 0);
} else {
_player->play();
}
}
static uint8 bgCDTracks[49] = {
0, 21, 21, 23, 0, 29, 0, 0, 0, 0,
0, 27, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 22, 22, 23, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0
};
void PCSound::setBgMusic(int num) {
debugC(5, kCineDebugSound, "PCSound::setBgMusic(%d)", num);
_currentBgSlot = num;
if (!bgCDTracks[_currentBgSlot])
return;
if ((_currentBgSlot == 1) || (_currentMusicStatus == 0 && _currentMusic != bgCDTracks[_currentBgSlot])) {
_currentMusic = bgCDTracks[_currentBgSlot];
g_system->getAudioCDManager()->stop();
g_system->getAudioCDManager()->play(bgCDTracks[_currentBgSlot] - 1, -1, 0, 0);
}
}
void PCSound::stopMusic() {
debugC(5, kCineDebugSound, "PCSound::stopMusic()");
if (_vm->getGameType() == GType_FW && (_vm->getFeatures() & GF_CD)) {
if (_currentBgSlot != 1)
g_system->getAudioCDManager()->stop();
}
_player->stop();
}
void PCSound::fadeOutMusic() {
debugC(5, kCineDebugSound, "PCSound::fadeOutMusic()");
if (_vm->getGameType() == GType_FW && (_vm->getFeatures() & GF_CD)) {
if (_currentMusicStatus) {
if (_currentBgSlot == 1) {
_currentMusicStatus = 0;
} else {
_currentMusic = 0;
_currentMusicStatus = 0;
g_system->getAudioCDManager()->stop();
if (bgCDTracks[_currentBgSlot]) {
g_system->getAudioCDManager()->play(_currentBgSlot - 1, -1, 0, 0);
}
}
}
}
_player->fadeOut();
}
void PCSound::playSound(int channel, int frequency, const uint8 *data, int size, int volumeStep, int stepCount, int volume, int repeat) {
debugC(5, kCineDebugSound, "PCSound::playSound() channel %d size %d", channel, size);
_soundDriver->playSample(data, size, channel, volume);
}
void PCSound::stopSound(int channel) {
debugC(5, kCineDebugSound, "PCSound::stopSound() channel %d", channel);
_soundDriver->resetChannel(channel);
}
PaulaSound::PaulaSound(Audio::Mixer *mixer, CineEngine *vm)
: Sound(mixer, vm), _sfxTimer(0), _musicTimer(0), _musicFadeTimer(0) {
_moduleStream = 0;
// The original is using the following timer frequency:
// 0.709379Mhz / 8000 = 88.672375Hz
// 1000000 / 88.672375Hz = 11277.46944863us
g_system->getTimerManager()->installTimerProc(&PaulaSound::sfxTimerProc, 11277, this, "PaulaSound::sfxTimerProc");
// The original is using the following timer frequency:
// 0.709379Mhz / 14565 = 48.704359Hz
// 1000000 / 48.704359Hz = 20532.04313806us
g_system->getTimerManager()->installTimerProc(&PaulaSound::musicTimerProc, 20532, this, "PaulaSound::musicTimerProc");
}
PaulaSound::~PaulaSound() {
Common::StackLock sfxLock(_sfxMutex);
g_system->getTimerManager()->removeTimerProc(&PaulaSound::sfxTimerProc);
for (int i = 0; i < NUM_CHANNELS; ++i) {
stopSound(i);
}
Common::StackLock musicLock(_musicMutex);
g_system->getTimerManager()->removeTimerProc(&PaulaSound::musicTimerProc);
stopMusic();
}
void PaulaSound::loadMusic(const char *name) {
debugC(5, kCineDebugSound, "PaulaSound::loadMusic('%s')", name);
for (int i = 0; i < NUM_CHANNELS; ++i) {
stopSound(i);
}
// Fade music out when there is music playing.
_musicMutex.lock();
if (_mixer->isSoundHandleActive(_moduleHandle)) {
// Only start fade out when it is not in progress.
if (!_musicFadeTimer) {
_musicFadeTimer = 1;
}
_musicMutex.unlock();
while (_musicFadeTimer != 64) {
g_system->delayMillis(50);
}
} else {
_musicMutex.unlock();
}
Common::StackLock lock(_musicMutex);
assert(!_mixer->isSoundHandleActive(_moduleHandle));
if (_vm->getGameType() == GType_FW) {
// look for separate files
Common::File f;
if (f.open(name)) {
_moduleStream = Audio::makeSoundFxStream(&f, 0, _mixer->getOutputRate());
}
} else {
// look in bundle files
uint32 size;
byte *buf = readBundleSoundFile(name, &size);
if (buf) {
Common::MemoryReadStream s(buf, size);
_moduleStream = Audio::makeSoundFxStream(&s, readBundleSoundFile, _mixer->getOutputRate());
free(buf);
}
}
}
void PaulaSound::playMusic() {
debugC(5, kCineDebugSound, "PaulaSound::playMusic()");
Common::StackLock lock(_musicMutex);
_mixer->stopHandle(_moduleHandle);
if (_moduleStream) {
_musicFadeTimer = 0;
_mixer->playStream(Audio::Mixer::kMusicSoundType, &_moduleHandle, _moduleStream);
}
}
void PaulaSound::stopMusic() {
debugC(5, kCineDebugSound, "PaulaSound::stopMusic()");
Common::StackLock lock(_musicMutex);
_mixer->stopHandle(_moduleHandle);
}
void PaulaSound::setBgMusic(int num) {
}
void PaulaSound::fadeOutMusic() {
debugC(5, kCineDebugSound, "PaulaSound::fadeOutMusic()");
Common::StackLock lock(_musicMutex);
_musicFadeTimer = 1;
}
void PaulaSound::playSound(int channel, int frequency, const uint8 *data, int size, int volumeStep, int stepCount, int volume, int repeat) {
debugC(5, kCineDebugSound, "PaulaSound::playSound() channel %d size %d", channel, size);
Common::StackLock lock(_sfxMutex);
assert(frequency > 0);
stopSound(channel);
if (size > 0) {
byte *sound = (byte *)malloc(size);
if (sound) {
// Create the audio stream
memcpy(sound, data, size);
// Clear the first and last 16 bits like in the original.
sound[0] = sound[1] = sound[size - 2] = sound[size - 1] = 0;
Audio::SeekableAudioStream *stream = Audio::makeRawStream(sound, size, PAULA_FREQ / frequency, 0);
// Initialize the volume control
_channelsTable[channel].initialize(volume, volumeStep, stepCount);
// Start the sfx
_mixer->playStream(Audio::Mixer::kSFXSoundType, &_channelsTable[channel].handle,
Audio::makeLoopingAudioStream(stream, repeat ? 0 : 1),
-1, volume * Audio::Mixer::kMaxChannelVolume / 63,
_channelBalance[channel]);
}
}
}
void PaulaSound::stopSound(int channel) {
debugC(5, kCineDebugSound, "PaulaSound::stopSound() channel %d", channel);
Common::StackLock lock(_sfxMutex);
_mixer->stopHandle(_channelsTable[channel].handle);
}
void PaulaSound::sfxTimerProc(void *param) {
PaulaSound *sound = (PaulaSound *)param;
sound->sfxTimerCallback();
}
void PaulaSound::sfxTimerCallback() {
Common::StackLock lock(_sfxMutex);
if (_sfxTimer < 6) {
++_sfxTimer;
for (int i = 0; i < NUM_CHANNELS; ++i) {
// Only process active channels
if (!_mixer->isSoundHandleActive(_channelsTable[i].handle)) {
continue;
}
if (_channelsTable[i].curStep) {
--_channelsTable[i].curStep;
} else {
_channelsTable[i].curStep = _channelsTable[i].stepCount;
const int volume = CLIP(_channelsTable[i].volume + _channelsTable[i].volumeStep, 0, 63);
_channelsTable[i].volume = volume;
// Unlike the original we stop silent sounds
if (volume) {
_mixer->setChannelVolume(_channelsTable[i].handle, volume * Audio::Mixer::kMaxChannelVolume / 63);
} else {
_mixer->stopHandle(_channelsTable[i].handle);
}
}
}
} else {
_sfxTimer = 0;
// Possible TODO: The original only ever started sounds here. This
// should not be noticable though. So we do not do it for now.
}
}
void PaulaSound::musicTimerProc(void *param) {
PaulaSound *sound = (PaulaSound *)param;
sound->musicTimerCallback();
}
void PaulaSound::musicTimerCallback() {
Common::StackLock lock(_musicMutex);
++_musicTimer;
if (_musicTimer == 6) {
_musicTimer = 0;
if (_musicFadeTimer) {
++_musicFadeTimer;
if (_musicFadeTimer == 64) {
stopMusic();
} else {
if (_mixer->isSoundHandleActive(_moduleHandle)) {
_mixer->setChannelVolume(_moduleHandle, (64 - _musicFadeTimer) * Audio::Mixer::kMaxChannelVolume / 64);
}
}
}
}
}
const int PaulaSound::_channelBalance[NUM_CHANNELS] = {
// L/R/R/L This is according to the Hardware Reference Manual.
// TODO: It seems the order is swapped for some Amiga models:
// http://www.amiga.org/forums/archive/index.php/t-7862.html
// Maybe we should consider using R/L/L/R to match Amiga 500?
// This also is a bit more drastic to what WineUAE defaults,
// which is only 70% of full panning.
-127, 127, 127, -127
};
} // End of namespace Cine