Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,70 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifdef ENABLE_EOB
#ifndef KYRA_SOUND_AUDIOMASTER2_H
#define KYRA_SOUND_AUDIOMASTER2_H
namespace Audio {
class Mixer;
}
namespace Common {
class SeekableReadStream;
class String;
}
namespace Kyra {
class AudioMaster2Internal;
class AudioMaster2 {
public:
AudioMaster2(Audio::Mixer *mixer);
~AudioMaster2();
bool init();
bool loadRessourceFile(Common::SeekableReadStream *data);
bool startSound(const Common::String &name);
bool stopSound(const Common::String &name);
void flushResource(const Common::String &name);
void flushAllResources();
void fadeOut(int delay);
bool isFading();
int getPlayDuration();
void setMusicVolume(int volume);
void setSoundEffectVolume(int volume);
private:
AudioMaster2Internal *_am2i;
};
} // End of namespace Kyra
#endif
#endif

View File

@@ -0,0 +1,324 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "kyra/resource/resource.h"
#include "audio/audiostream.h"
#include "common/util.h"
namespace Kyra {
// Thanks to Torbjorn Andersson (eriktorbjorn) for his aud player on which
// this code is based on
// TODO: cleanup of whole AUDStream
class AUDStream : public Audio::SeekableAudioStream {
public:
AUDStream(Common::SeekableReadStream* stream);
~AUDStream() override;
int readBuffer(int16* buffer, const int numSamples) override;
bool isStereo() const override { return false; }
bool endOfData() const override { return _endOfData; }
int getRate() const override { return _rate; }
bool seek(const Audio::Timestamp& where) override;
Audio::Timestamp getLength() const override { return _length; }
private:
Common::SeekableReadStream* _stream;
uint32 _streamStart;
bool _endOfData;
int _rate;
uint _processedSize;
uint _totalSize;
Audio::Timestamp _length;
int _bytesLeft;
byte* _outBuffer;
int _outBufferOffset;
uint _outBufferSize;
byte* _inBuffer;
uint _inBufferSize;
int readChunk(int16* buffer, const int maxSamples);
static const int8 WSTable2Bit[];
static const int8 WSTable4Bit[];
};
const int8 AUDStream::WSTable2Bit[] = { -2, -1, 0, 1 };
const int8 AUDStream::WSTable4Bit[] = {
-9, -8, -6, -5, -4, -3, -2, -1,
0, 1, 2, 3, 4, 5, 6, 8
};
AUDStream::AUDStream(Common::SeekableReadStream *stream) : _stream(stream), _endOfData(true), _rate(0),
_processedSize(0), _totalSize(0), _length(0, 1), _bytesLeft(0), _outBuffer(nullptr),
_outBufferOffset(0), _outBufferSize(0), _inBuffer(nullptr), _inBufferSize(0), _streamStart(0) {
if (_stream->size() < 8) {
warning("No AUD file: too short");
return;
}
_rate = _stream->readUint16LE();
_totalSize = _stream->readUint32LE();
// TODO?: add checks
int flags = _stream->readByte(); // flags
int type = _stream->readByte(); // type
_streamStart = stream->pos();
debugC(5, kDebugLevelSound, "AUD Info: rate: %d, totalSize: %d, flags: %d, type: %d, streamStart: %d", _rate, _totalSize, flags, type, _streamStart);
_length = Audio::Timestamp(0, _rate);
for (uint32 i = 0; i < _totalSize;) {
uint16 size = _stream->readUint16LE();
uint16 outSize = _stream->readUint16LE();
_length = _length.addFrames(outSize);
stream->seek(size + 4, SEEK_CUR);
i += size + 8;
}
stream->seek(_streamStart, SEEK_SET);
if (type == 1 && !flags)
_endOfData = false;
else
warning("No AUD file (rate: %d, size: %d, flags: 0x%X, type: %d)", _rate, _totalSize, flags, type);
}
AUDStream::~AUDStream() {
delete[] _outBuffer;
delete[] _inBuffer;
delete _stream;
}
int AUDStream::readBuffer(int16 *buffer, const int numSamples) {
int samplesRead = 0, samplesLeft = numSamples;
while (samplesLeft > 0 && !_endOfData) {
int samples = readChunk(buffer, samplesLeft);
samplesRead += samples;
samplesLeft -= samples;
buffer += samples;
}
return samplesRead;
}
inline int16 clip8BitSample(int16 sample) {
return CLIP<int16>(sample, 0, 255);
}
int AUDStream::readChunk(int16 *buffer, const int maxSamples) {
int samplesProcessed = 0;
// if no bytes of the old chunk are left, read the next one
if (_bytesLeft <= 0) {
if (_processedSize >= _totalSize) {
_endOfData = true;
return 0;
}
uint16 size = _stream->readUint16LE();
uint16 outSize = _stream->readUint16LE();
uint32 id = _stream->readUint32LE();
assert(id == 0x0000DEAF);
_processedSize += 8 + size;
_outBufferOffset = 0;
if (size == outSize) {
if (outSize > _outBufferSize) {
_outBufferSize = outSize;
delete[] _outBuffer;
_outBuffer = new uint8[_outBufferSize];
assert(_outBuffer);
}
_bytesLeft = size;
_stream->read(_outBuffer, _bytesLeft);
} else {
_bytesLeft = outSize;
if (outSize > _outBufferSize) {
_outBufferSize = outSize;
delete[] _outBuffer;
_outBuffer = new uint8[_outBufferSize];
assert(_outBuffer);
}
if (size > _inBufferSize) {
_inBufferSize = size;
delete[] _inBuffer;
_inBuffer = new uint8[_inBufferSize];
assert(_inBuffer);
}
if (_stream->read(_inBuffer, size) != size) {
_endOfData = true;
return 0;
}
int16 curSample = 0x80;
byte code = 0;
int8 count = 0;
uint16 input = 0;
int j = 0;
int i = 0;
while (outSize > 0) {
input = _inBuffer[i++] << 2;
code = (input >> 8) & 0xFF;
count = (input & 0xFF) >> 2;
switch (code) {
case 2:
if (count & 0x20) {
/* NOTE: count is signed! */
count <<= 3;
curSample += (count >> 3);
_outBuffer[j++] = curSample & 0xFF;
outSize--;
} else {
for (; count >= 0; count--) {
_outBuffer[j++] = _inBuffer[i++];
outSize--;
}
curSample = _inBuffer[i - 1];
}
break;
case 1:
for (; count >= 0; count--) {
code = _inBuffer[i++];
curSample += WSTable4Bit[code & 0x0F];
curSample = clip8BitSample(curSample);
_outBuffer[j++] = curSample;
curSample += WSTable4Bit[code >> 4];
curSample = clip8BitSample(curSample);
_outBuffer[j++] = curSample;
outSize -= 2;
}
break;
case 0:
for (; count >= 0; count--) {
code = (uint8)_inBuffer[i++];
curSample += WSTable2Bit[code & 0x03];
curSample = clip8BitSample(curSample);
_outBuffer[j++] = curSample & 0xFF;
curSample += WSTable2Bit[(code >> 2) & 0x03];
curSample = clip8BitSample(curSample);
_outBuffer[j++] = curSample & 0xFF;
curSample += WSTable2Bit[(code >> 4) & 0x03];
curSample = clip8BitSample(curSample);
_outBuffer[j++] = curSample & 0xFF;
curSample += WSTable2Bit[(code >> 6) & 0x03];
curSample = clip8BitSample(curSample);
_outBuffer[j++] = curSample & 0xFF;
outSize -= 4;
}
break;
default:
for (; count >= 0; count--) {
_outBuffer[j++] = curSample & 0xFF;
outSize--;
}
}
}
}
}
// copies the chunk data to the output buffer
if (_bytesLeft > 0) {
int samples = MIN(_bytesLeft, maxSamples);
samplesProcessed += samples;
_bytesLeft -= samples;
while (samples--) {
int16 sample = (_outBuffer[_outBufferOffset++] << 8) ^ 0x8000;
*buffer++ = sample;
}
}
return samplesProcessed;
}
bool AUDStream::seek(const Audio::Timestamp &where) {
const uint32 seekSample = Audio::convertTimeToStreamPos(where, getRate(), isStereo()).totalNumberOfFrames();
_stream->seek(_streamStart);
_processedSize = 0;
_bytesLeft = 0;
_endOfData = false;
uint32 curSample = 0;
while (!endOfData()) {
uint16 size = _stream->readUint16LE();
uint16 outSize = _stream->readUint16LE();
if (curSample + outSize > seekSample) {
_stream->seek(-4, SEEK_CUR);
uint32 samples = seekSample - curSample;
int16 *temp = new int16[samples];
assert(temp);
readChunk(temp, samples);
delete[] temp;
curSample += samples;
break;
} else {
curSample += outSize;
_processedSize += 8 + size;
_stream->seek(size + 4, SEEK_CUR);
}
}
_endOfData = (_processedSize >= _totalSize);
return (curSample == seekSample);
}
Audio::SeekableAudioStream* makeAUDStream(Common::SeekableReadStream* stream, DisposeAfterUse::Flag disposeAfterUse) {
return new AUDStream(stream);
}
} // End of namespace Kyra

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,71 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifdef ENABLE_EOB
#ifndef KYRA_SOUND_CAPCOM98_H
#define KYRA_SOUND_CAPCOM98_H
#include "audio/mididrv.h"
#include "common/scummsys.h"
namespace Audio {
class Mixer;
}
namespace Kyra {
class CapcomPC98AudioDriverInternal;
class CapcomPC98AudioDriver {
public:
CapcomPC98AudioDriver(Audio::Mixer *mixer, MidiDriver::DeviceHandle dev);
~CapcomPC98AudioDriver();
bool isUsable() const;
// All data passed to the following functions has to be maintained by the caller.
void reset();
void loadFMInstruments(const uint8 *data);
void startSong(const uint8 *data, uint8 volume, bool loop);
void stopSong();
void startSoundEffect(const uint8 *data, uint8 volume);
void stopSoundEffect();
int checkSoundMarker() const;
bool songIsPlaying() const;
bool soundEffectIsPlaying() const;
void fadeOut();
void allNotesOff();
void setMusicVolume(int volume);
void setSoundEffectVolume(int volume);
private:
CapcomPC98AudioDriverInternal *_drv;
};
} // End of namespace Kyra
#endif
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,98 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef KYRA_SOUND_HALESTORM_H
#define KYRA_SOUND_HALESTORM_H
#include "common/scummsys.h"
namespace Audio {
class Mixer;
}
namespace Common {
class SeekableReadStream;
}
namespace Kyra {
class HSSoundSystem;
class HalestormLoader {
public:
virtual ~HalestormLoader() {}
virtual Common::SeekableReadStream *getResource(uint16 id, uint32 type) = 0;
};
class HalestormDriver {
public:
HalestormDriver(HalestormLoader *res, Audio::Mixer *mixer);
~HalestormDriver();
enum InterpolationMode {
kNone = 0,
kSimple = 1,
kTable = 2
};
// The original driver would generate 8-bit sound, which makes
// sense, since most of the hardware had support for only that.
// However, the internal quality of the intermediate pcm stream
// (before the downsampling to 8-bit in the final pass) is a bit
// higher (up to 12 bits, depending on the channel use). I have
// added an "output16bit" option which will output the unmodified
// intermediate data (but converting it from unsigned to signed).
bool init(bool hiQuality, InterpolationMode imode, int numChanSfx, bool output16bit);
void registerSamples(const uint16 *resList, bool registerOnly);
void releaseSamples();
int changeSystemVoices(int numChanMusicTotal, int numChanMusicPoly, int numChanSfx);
void startSoundEffect(int id, int rate = 0);
void enqueueSoundEffect(int id, int rate, int note);
void stopSoundEffect(int id);
void stopAllSoundEffects();
enum HSCommands {
kSongPlayOnce = 0,
kSongPlayLoop = 1,
kSongAbort = 2,
kSongIsPlaying = 3,
kSongFadeOut = 10,
kSongFadeIn = 11,
kSongFadeGetState = 12,
kSongFadeReset = 13,
kSetRateAndIntrplMode = 14
};
int doCommand(int cmd, ...);
void setMusicVolume(int volume);
void setSoundEffectVolume(int volume);
private:
HSSoundSystem *_hs;
};
} // End of namespace Kyra
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,89 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifdef ENABLE_EOB
#ifndef KYRA_SOUND_MLALF98_H
#define KYRA_SOUND_MLALF98_H
#include "common/scummsys.h"
#include "common/array.h"
namespace Common {
class SeekableReadStream;
}
namespace Audio {
class Mixer;
}
namespace Kyra {
class MLALF98Internal;
class MLALF98 {
public:
enum EmuType {
kType9801_26 = 1,
kType9801_86 = 2
};
struct ADPCMData {
ADPCMData() : smpStart(0), smpEnd(0), unk4(0), unk5(0), volume(0), unk7(0) {}
uint16 smpStart;
uint16 smpEnd;
uint8 unk4;
uint8 unk5;
uint8 volume;
uint8 unk7;
};
typedef Common::Array<ADPCMData> ADPCMDataArray;
public:
MLALF98(Audio::Mixer *mixer, EmuType emuType);
~MLALF98();
// The caller has to dispose of the stream. The stream can be discarded
// immediately after calling the respective loader function.
void loadMusicData(Common::SeekableReadStream *data);
void loadSoundEffectData(Common::SeekableReadStream *data);
void loadExtData(ADPCMDataArray &data);
void startMusic(int track);
void fadeOutMusic();
void startSoundEffect(int track);
void allChannelsOff();
void resetExtUnit();
void setMusicVolume(int volume);
void setSoundEffectVolume(int volume);
private:
MLALF98Internal *_drv;
};
} // End of namespace Kyra
#endif
#endif

View File

@@ -0,0 +1,89 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef KYRA_SOUND_PCDRIVER_H
#define KYRA_SOUND_PCDRIVER_H
#include "kyra/resource/resource.h"
namespace Audio {
class Mixer;
}
namespace Kyra {
class PCSoundDriver {
public:
PCSoundDriver() : _soundData(0), _soundDataSize(0) {}
virtual ~PCSoundDriver() {}
virtual void initDriver() = 0;
virtual void setSoundData(uint8 *data, uint32 size) = 0;
virtual void startSound(int track, int volume) = 0;
virtual bool isChannelPlaying(int channel) const = 0;
virtual void stopAllChannels() = 0;
virtual int getSoundTrigger() const { return 0; }
virtual void resetSoundTrigger() {}
virtual void setMusicVolume(uint8 volume) = 0;
virtual void setSfxVolume(uint8 volume) = 0;
// AdLiB (Kyra 1) specific
virtual void setSyncJumpMask(uint16) {}
protected:
uint8 *getProgram(int progId) {
// Safety check: invalid progId would crash.
if (progId < 0 || progId >= (int32)_soundDataSize / 2)
return nullptr;
const uint16 offset = READ_LE_UINT16(_soundData + 2 * progId);
// In case an invalid offset is specified we return nullptr to
// indicate an error. 0xFFFF seems to indicate "this is not a valid
// program/instrument". However, 0 is also invalid because it points
// inside the offset table itself. We also ignore any offsets outside
// of the actual data size.
// The original does not contain any safety checks and will simply
// read outside of the valid sound data in case an invalid offset is
// encountered.
if (offset == 0 || offset >= _soundDataSize) {
return nullptr;
} else {
return _soundData + offset;
}
}
uint8 *_soundData;
uint32 _soundDataSize;
public:
static PCSoundDriver *createAdLib(Audio::Mixer *mixer, int version);
#ifdef ENABLE_EOB
static PCSoundDriver *createPCSpk(Audio::Mixer *mixer, bool pcJRMode);
#endif
};
} // End of namespace Kyra
#endif

View File

@@ -0,0 +1,389 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifdef ENABLE_EOB
#include "kyra/sound/drivers/pc_base.h"
#include "audio/audiostream.h"
#include "common/mutex.h"
namespace Kyra {
class PCSpeakerDriver : public PCSoundDriver, public Audio::AudioStream {
public:
PCSpeakerDriver(Audio::Mixer *mixer, bool pcJRMode);
~PCSpeakerDriver() override;
void initDriver() override;
void setSoundData(uint8 *data, uint32 size) override;
void startSound(int id, int) override;
bool isChannelPlaying(int channel) const override;
void stopAllChannels() override;
void setMusicVolume(uint8 volume) override;
void setSfxVolume(uint8) override {}
void update();
// AudioStream interface
int readBuffer(int16 *buffer, const int numSamples) override;
bool isStereo() const override { return false; }
int getRate() const override { return _outputRate; }
bool endOfData() const override { return false; }
private:
void noteOn(int chan, uint16 period);
void chanOff(int chan);
void generateSamples(int16 *buffer, int numSamples);
struct Channel {
Channel(uint8 attnDB) : curSample(32767.0 / pow(2.0, (double)attnDB / 6.0)),
dataPtr(0), timer(0), timerScale(0), repeatCounter1(0), repeatCounter2(0), period(-1), samplesLeft(0) {}
const uint8 *dataPtr;
int16 timer;
uint8 timerScale;
uint8 repeatCounter1;
uint8 repeatCounter2;
int32 period;
int32 curSample;
uint32 samplesLeft;
};
Channel **_channels;
int _numChannels;
const uint8 *_newTrackData;
const uint8 *_trackData;
Common::Mutex _mutex;
Audio::Mixer *_mixer;
Audio::SoundHandle _handle;
uint _outputRate;
int _samplesUpdateIntv;
int _samplesUpdateIntvRem;
int _samplesUpdateTmr;
int _samplesUpdateTmrRem;
int _masterVolume;
bool _ready;
const int _clock;
const int _updateRate;
const bool _pcJR;
const int _periodDiv;
const int _levelAdjust;
const uint16 * const _periodsTable;
static const uint16 _periodsPCSpk[96];
static const uint16 _periodsPCjr[96];
};
PCSpeakerDriver::PCSpeakerDriver(Audio::Mixer *mixer, bool pcJRMode) : PCSoundDriver(), _mixer(mixer), _samplesUpdateIntv(0), _samplesUpdateIntvRem(0),
_outputRate(0), _samplesUpdateTmr(0), _samplesUpdateTmrRem(0), _newTrackData(0), _trackData(0), _pcJR(pcJRMode), _numChannels(pcJRMode ? 3 : 1), _channels(0),
_clock(pcJRMode ? 111860 : 1193180), _updateRate(292), _masterVolume(63), _periodsTable(pcJRMode ? _periodsPCjr : _periodsPCSpk), _periodDiv(2),
_levelAdjust(pcJRMode ? 1 : 0), _ready(false) {
_outputRate = _mixer->getOutputRate();
_samplesUpdateIntv = _outputRate / _updateRate;
_samplesUpdateIntvRem = _outputRate % _updateRate;
_channels = new Channel*[_numChannels];
assert(_channels);
for (int i = 0; i < _numChannels; ++i) {
_channels[i] = new Channel(i * 10);
assert(_channels[i]);
}
}
PCSpeakerDriver::~PCSpeakerDriver() {
_ready = false;
_mixer->stopHandle(_handle);
if (_channels) {
for (int i = 0; i < _numChannels; ++i)
delete _channels[i];
delete[] _channels;
}
}
void PCSpeakerDriver::initDriver() {
if (_ready)
return;
_mixer->playStream(Audio::Mixer::kPlainSoundType, &_handle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
_ready = true;
}
void PCSpeakerDriver::setSoundData(uint8 *data, uint32 size) {
Common::StackLock lock(_mutex);
if (!_ready)
return;
_soundData = data;
_soundDataSize = size;
}
void PCSpeakerDriver::startSound(int id, int) {
Common::StackLock lock(_mutex);
if (!_ready)
return;
_newTrackData = getProgram(id & 0x7F);
}
bool PCSpeakerDriver::isChannelPlaying(int channel) const {
Common::StackLock lock(_mutex);
if (!_ready)
return false;
return _trackData;
}
void PCSpeakerDriver::stopAllChannels() {
Common::StackLock lock(_mutex);
if (!_ready)
return;
for (int i = 0; i < _numChannels; ++i)
chanOff(i);
_trackData = 0;
}
void PCSpeakerDriver::setMusicVolume(uint8 volume) {
Common::StackLock lock(_mutex);
_masterVolume = volume >> 2;
}
void PCSpeakerDriver::update() {
Common::StackLock lock(_mutex);
if (!_ready)
return;
if (_newTrackData) {
_trackData = _newTrackData;
_newTrackData = 0;
for (int i = _numChannels - 1; i >= 0; --i) {
_channels[i]->dataPtr = _trackData;
_channels[i]->timer = i * 35;
_channels[i]->timerScale = 1;
}
}
for (int i = _numChannels - 1; i >= 0; --i) {
const uint8 *pos = _channels[i]->dataPtr;
if (!pos)
continue;
for (bool runloop = true; runloop; ) {
if (--_channels[i]->timer > -1)
break;
_channels[i]->timer = 0;
int8 cmd = (int8)*pos++;
if (cmd >= 0) {
if (cmd > 95)
cmd = 0;
noteOn(i, _periodsTable[cmd]);
uint8 nextTimer = 1 + *pos++;
_channels[i]->timer = _channels[i]->timerScale * nextTimer;
} else {
switch (cmd) {
case -23: {
uint16 ts = _channels[i]->timerScale + *pos++;
_channels[i]->timerScale = (uint8)MIN<uint16>(ts, 0xFF);
} break;
case -24: {
int16 ts = _channels[i]->timerScale - *pos++;
_channels[i]->timerScale = (uint8)MAX<int16>(ts, 1);
} break;
case -26: {
uint16 prd = _clock / READ_LE_UINT16(pos);
if (_pcJR && prd >= 0x400)
prd = 0x3FF;
pos += 2;
noteOn(i, prd);
uint8 nextTimer = 1 + *pos++;
_channels[i]->timer = _channels[i]->timerScale * nextTimer;
} break;
case -30: {
_channels[i]->timerScale = *pos++;
if (!_channels[i]->timerScale)
_channels[i]->timerScale = 1;
} break;
case -46: {
if (--_channels[i]->repeatCounter2)
pos -= *pos;
else
pos += 2;
} break;
case -47: {
_channels[i]->repeatCounter2 = *pos++;
if (!_channels[i]->repeatCounter2)
_channels[i]->repeatCounter2 = 1;
} break;
case -50: {
if (--_channels[i]->repeatCounter1)
pos -= *pos;
else
pos += 2;
} break;
case -51: {
_channels[i]->repeatCounter1 = *pos++;
if (!_channels[i]->repeatCounter1)
_channels[i]->repeatCounter1 = 1;
} break;
default:
chanOff(i);
pos = 0;
runloop = false;
}
}
}
_channels[i]->dataPtr = pos;
}
}
int PCSpeakerDriver::readBuffer(int16 *buffer, const int numSamples) {
Common::StackLock lock(_mutex);
if (!_ready)
return 0;
int render = 0;
for (int samplesLeft = numSamples; samplesLeft; samplesLeft -= render) {
if (_samplesUpdateTmr <= 0) {
_samplesUpdateTmr += _samplesUpdateIntv;
update();
}
_samplesUpdateTmrRem += _samplesUpdateIntvRem;
while (_samplesUpdateTmrRem >= _updateRate) {
_samplesUpdateTmr++;
_samplesUpdateTmrRem -= _updateRate;
}
render = MIN<int>(_samplesUpdateTmr, samplesLeft);
_samplesUpdateTmr -= render;
generateSamples(buffer, render);
buffer += render;
}
return numSamples;
}
void PCSpeakerDriver::noteOn(int chan, uint16 period) {
if (chan >= _numChannels)
return;
if (period == 0) {
chanOff(chan);
return;
}
uint32 p = (_outputRate << 10) / ((_clock << 10) / period);
if (_channels[chan]->period == -1 || _channels[chan]->samplesLeft == 0)
_channels[chan]->samplesLeft = p / _periodDiv;
_channels[chan]->period = p & 0xFFFF;
}
void PCSpeakerDriver::chanOff(int chan) {
if (chan >= _numChannels)
return;
_channels[chan]->period = -1;
}
void PCSpeakerDriver::generateSamples(int16 *buffer, int numSamples) {
int render = 0;
for (int samplesLeft = numSamples; samplesLeft; samplesLeft -= render) {
render = samplesLeft;
for (int i = _numChannels - 1; i >= 0; --i)
if (_channels[i]->period != -1)
render = MIN<int>(render, _channels[i]->samplesLeft);
int32 smp = 0;
for (int i = _numChannels - 1; i >= 0; --i)
if (_channels[i]->period != -1)
smp += _channels[i]->curSample;
smp = (smp * _masterVolume) >> (8 + _levelAdjust);
Common::fill<int16*, int16>(buffer, &buffer[render], smp);
buffer += render;
for (int i = _numChannels - 1; i >= 0; --i) {
if (_channels[i]->period == -1)
continue;
_channels[i]->samplesLeft -= render;
if (_channels[i]->samplesLeft == 0) {
_channels[i]->samplesLeft = _channels[i]->period / _periodDiv;
_channels[i]->curSample = ~_channels[i]->curSample;
}
}
}
}
const uint16 PCSpeakerDriver::_periodsPCSpk[96] = {
0x0000, 0xfdff, 0xefa2, 0xe241, 0xd582, 0xc998, 0xbe3d, 0xb38a,
0xa97c, 0x9ff2, 0x96fc, 0x8e89, 0x8683, 0x7ef7, 0x77d9, 0x7121,
0x6ac7, 0x64c6, 0x5f1f, 0x59ca, 0x54be, 0x4ffd, 0x4b7e, 0x4742,
0x4342, 0x3f7b, 0x3bdb, 0x388f, 0x3562, 0x3263, 0x2f8f, 0x2ce4,
0x2a5f, 0x27fe, 0x25c0, 0x23a1, 0x21a1, 0x1fbe, 0x1df6, 0x1c48,
0x1ab1, 0x1932, 0x17c8, 0x1672, 0x1530, 0x13ff, 0x12e0, 0x11d1,
0x10d1, 0x0fdf, 0x0efb, 0x0e24, 0x0d59, 0x0c99, 0x0be4, 0x0b39,
0x0a98, 0x0a00, 0x0970, 0x08e8, 0x0868, 0x07f0, 0x077e, 0x0712,
0x06ac, 0x064c, 0x05f2, 0x059d, 0x054c, 0x0500, 0x04b8, 0x0474,
0x0434, 0x03f8, 0x03bf, 0x0382, 0x0356, 0x0326, 0x02f9, 0x02ce,
0x02a6, 0x0280, 0x025c, 0x023a, 0x021a, 0x01fc, 0x01df, 0x01c4,
0x01ab, 0x0193, 0x017c, 0x0167, 0x0153, 0x0140, 0x012e, 0x011d
};
const uint16 PCSpeakerDriver::_periodsPCjr[96] = {
0x0000, 0x03ff, 0x03ff, 0x03ff, 0x03ff, 0x03ff, 0x03ff, 0x03ff,
0x03ff, 0x03ff, 0x03ff, 0x03ff, 0x03ff, 0x03ff, 0x03ff, 0x03ff,
0x03ff, 0x03ff, 0x03ff, 0x03ff, 0x03ff, 0x03ff, 0x03ff, 0x03ff,
0x03ff, 0x03ff, 0x03ff, 0x03ff, 0x03ff, 0x03ff, 0x03ff, 0x03ff,
0x03f9, 0x03c0, 0x038a, 0x0357, 0x0327, 0x02fa, 0x02cf, 0x02a7,
0x0281, 0x025d, 0x023b, 0x021b, 0x01fc, 0x01e0, 0x01c5, 0x01ac,
0x0194, 0x017d, 0x0168, 0x0153, 0x0140, 0x012e, 0x011d, 0x010d,
0x00fe, 0x00f0, 0x00e2, 0x00d6, 0x00ca, 0x00be, 0x00b4, 0x00aa,
0x00a0, 0x0097, 0x008f, 0x0087, 0x007f, 0x0078, 0x0071, 0x006b,
0x0065, 0x005f, 0x005a, 0x0054, 0x0050, 0x004c, 0x0047, 0x0043,
0x0040, 0x003c, 0x0039, 0x0035, 0x0032, 0x0030, 0x002d, 0x002a,
0x0028, 0x0026, 0x0024, 0x0022, 0x0020, 0x001e, 0x001c, 0x001b
};
PCSoundDriver *PCSoundDriver::createPCSpk(Audio::Mixer *mixer, bool pcJRMode) {
return new PCSpeakerDriver(mixer, pcJRMode);
}
} // End of namespace Kyra
#endif

View File

@@ -0,0 +1,362 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "kyra/sound/sound_intern.h"
#include "audio/mixer.h"
#include "audio/softsynth/pcspk.h"
namespace Kyra {
MidiDriver_PCSpeaker::MidiDriver_PCSpeaker(Audio::Mixer *mixer)
: MidiDriver_Emulated(mixer), _rate(mixer->getOutputRate()) {
_timerValue = 0;
memset(_channel, 0, sizeof(_channel));
memset(_note, 0, sizeof(_note));
for (int i = 0; i < 2; ++i)
_note[i].hardwareChannel = 0xFF;
_speaker = new Audio::PCSpeakerStream(_rate);
assert(_speaker);
_mixer->playStream(Audio::Mixer::kMusicSoundType, &_mixerSoundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
_countdown = 0xFFFF;
_hardwareChannel[0] = 0xFF;
_modulationFlag = false;
}
MidiDriver_PCSpeaker::~MidiDriver_PCSpeaker() {
_mixer->stopHandle(_mixerSoundHandle);
delete _speaker;
_speaker = nullptr;
}
void MidiDriver_PCSpeaker::send(uint32 data) {
Common::StackLock lock(_mutex);
uint8 channel = data & 0x0F;
uint8 param1 = (data >> 8) & 0xFF;
uint8 param2 = (data >> 16) & 0xFF;
uint8 flags = 0x00;
if (channel > 1)
return;
switch (data & 0xF0) {
case 0x80: // note off
noteOff(channel, param1);
return;
case 0x90: // note on
if (param2)
noteOn(channel, param1);
else
noteOff(channel, param1);
return;
case 0xB0: // controller
switch (param1) {
case 0x01: // modulation
_channel[channel].modulation = param2;
break;
case 0x40: // hold
_channel[channel].hold = param2;
if (param2 < 0x40)
resetController(channel);
return;
case 0x70: // voice protect
_channel[channel].voiceProtect = param2;
return;
case 0x79: // all notes off
_channel[channel].hold = 0;
resetController(channel);
_channel[channel].modulation = 0;
_channel[channel].pitchBendLow = 0;
_channel[channel].pitchBendHigh = 0x40;
flags = 0x01;
break;
default:
return;
}
break;
case 0xE0: // pitch bend
flags = 0x01;
_channel[channel].pitchBendLow = param1;
_channel[channel].pitchBendHigh = param2;
break;
default:
return;
}
for (int i = 0; i < 2; ++i) {
if (_note[i].enabled && _note[i].midiChannel == channel) {
_note[i].flags |= flags;
setupTone(i);
}
}
}
void MidiDriver_PCSpeaker::resetController(int channel) {
for (int i = 0; i < 2; ++i) {
if (_note[i].enabled && _note[i].midiChannel == channel && _note[i].processHold)
noteOff(channel, _note[i].note);
}
}
void MidiDriver_PCSpeaker::noteOn(int channel, int note) {
int n = 0;
while (n < 2 && _note[n].enabled)
++n;
if (n >= 2)
return;
_note[n].midiChannel = channel;
_note[n].note = note;
_note[n].enabled = true;
_note[n].processHold = false;
_note[n].hardwareFlags = 0x20;
_note[n].priority = 0x7FFF;
_note[n].flags = 0x01;
turnNoteOn(n);
}
void MidiDriver_PCSpeaker::turnNoteOn(int note) {
if (_hardwareChannel[0] == 0xFF) {
_note[note].hardwareChannel = 0;
++_channel[_note[note].midiChannel].noteCount;
_hardwareChannel[0] = _note[note].midiChannel;
_note[note].flags = 0x01;
setupTone(note);
} else {
overwriteNote(note);
}
}
void MidiDriver_PCSpeaker::overwriteNote(int note) {
int totalNotes = 0;
for (int i = 0; i < 2; ++i) {
if (_note[i].enabled) {
++totalNotes;
const int channel = _note[i].midiChannel;
uint16 priority = 0xFFFF;
if (_channel[channel].voiceProtect < 0x40)
priority = _note[i].priority;
if (_channel[channel].noteCount > priority)
priority = 0;
else
priority -= _channel[channel].noteCount;
_note[i].precedence = priority;
}
}
if (totalNotes <= 1)
return;
do {
uint16 maxValue = 0;
uint16 minValue = 0xFFFF;
int newNote = 0;
for (int i = 0; i < 2; ++i) {
if (_note[i].enabled) {
if (_note[i].hardwareChannel == 0xFF) {
if (_note[i].precedence >= maxValue) {
maxValue = _note[i].precedence;
newNote = i;
}
} else {
if (_note[i].precedence <= minValue) {
minValue = _note[i].precedence;
note = i;
}
}
}
}
if (maxValue < minValue)
return;
turnNoteOff(_note[note].hardwareChannel);
_note[note].enabled = false;
_note[newNote].hardwareChannel = _note[note].hardwareChannel;
++_channel[_note[newNote].midiChannel].noteCount;
_hardwareChannel[_note[note].hardwareChannel] = _note[newNote].midiChannel;
_note[newNote].flags = 0x01;
setupTone(newNote);
} while (--totalNotes);
}
void MidiDriver_PCSpeaker::noteOff(int channel, int note) {
for (int i = 0; i < 2; ++i) {
if (_note[i].enabled && _note[i].note == note && _note[i].midiChannel == channel) {
if (_channel[i].hold < 0x40) {
turnNoteOff(i);
_note[i].enabled = false;
} else {
_note[i].processHold = true;
}
}
}
}
void MidiDriver_PCSpeaker::turnNoteOff(int note) {
if (_note[note].hardwareChannel != 0xFF) {
_note[note].hardwareFlags &= 0xDF;
_note[note].flags |= 1;
setupTone(note);
--_channel[_note[note].midiChannel].noteCount;
_hardwareChannel[_note[note].hardwareChannel] = 0xFF;
_note[note].hardwareChannel = 0xFF;
}
}
void MidiDriver_PCSpeaker::setupTone(int note) {
if (_note[note].hardwareChannel == 0xFF)
return;
if (!(_note[note].flags & 0x01))
return;
if (!(_note[note].hardwareFlags & 0x20)) {
_speaker->stop();
} else {
const int midiChannel = _note[note].midiChannel;
uint16 pitchBend = (_channel[midiChannel].pitchBendHigh << 7) | _channel[midiChannel].pitchBendLow;
int noteValue = _note[note].note;
noteValue -= 24;
do {
noteValue += 12;
} while (noteValue < 0);
noteValue += 12;
do {
noteValue -= 12;
} while (noteValue > 95);
int16 modulation = _note[note].modulation;
int tableIndex = MAX(noteValue - 12, 0);
uint16 note1 = (_noteTable2[tableIndex] << 8) | _noteTable1[tableIndex];
tableIndex = MIN(noteValue + 12, 95);
uint16 note2 = (_noteTable2[tableIndex] << 8) | _noteTable1[tableIndex];
uint16 note3 = (_noteTable2[noteValue] << 8) | _noteTable1[noteValue];
int32 countdown = pitchBend - 0x2000;
countdown += modulation;
if (countdown >= 0)
countdown *= (note2 - note3);
else
countdown *= (note3 - note1);
countdown /= 0x2000;
countdown += note3;
countdown = uint16(countdown & 0xFFFF);
if (countdown != _countdown)
_countdown = countdown;
_speaker->play(Audio::PCSpeaker::kWaveFormSquare, 1193180 / _countdown, -1);
}
_note[note].flags &= 0xFE;
}
void MidiDriver_PCSpeaker::generateSamples(int16 *buffer, int numSamples) {
Common::StackLock lock(_mutex);
_speaker->readBuffer(buffer, numSamples);
}
void MidiDriver_PCSpeaker::onTimer() {
/*Common::StackLock lock(_mutex);
_timerValue += 20;
if (_timerValue < 120)
return;
_timerValue -= 120;
_modulationFlag = !_modulationFlag;
for (int i = 0; i < 2; ++i) {
if (_note[i].enabled) {
uint16 modValue = 5 * _channel[_note[i].midiChannel].modulation;
if (_modulationFlag)
modValue = -modValue;
_note[i].modulation = modValue;
_note[i].flags |= 1;
setupTone(i);
}
}*/
}
const uint8 MidiDriver_PCSpeaker::_noteTable1[] = {
0x88, 0xB5, 0x4E, 0x40, 0x41, 0xCD, 0xC4, 0x3D,
0x43, 0x7C, 0x2A, 0xD6, 0x88, 0xB5, 0xFF, 0xD1,
0x20, 0xA7, 0xE2, 0x1E, 0xCE, 0xBE, 0xF2, 0x8A,
0x44, 0x41, 0x7F, 0xE8, 0x90, 0x63, 0x63, 0x8F,
0xE7, 0x5F, 0x01, 0xBD, 0xA2, 0xA0, 0xBF, 0xF4,
0x48, 0xB1, 0x31, 0xC7, 0x70, 0x2F, 0xFE, 0xE0,
0xD1, 0xD0, 0xDE, 0xFB, 0x24, 0x58, 0x98, 0xE3,
0x39, 0x97, 0xFF, 0x6F, 0xE8, 0x68, 0xEF, 0x7D,
0x11, 0xAC, 0x4C, 0xF1, 0x9C, 0x4B, 0xFF, 0xB7,
0x74, 0x34, 0xF7, 0xBE, 0x88, 0x56, 0x26, 0xF8,
0xCE, 0xA5, 0x7F, 0x5B, 0x3A, 0x1A, 0xFB, 0xDF,
0xC4, 0xAB, 0x93, 0x7C, 0x67, 0x52, 0x3F, 0x2D
};
const uint8 MidiDriver_PCSpeaker::_noteTable2[] = {
0x8E, 0x86, 0xFD, 0xF0, 0xE2, 0xD5, 0xC9, 0xBE,
0xB3, 0xA9, 0xA0, 0x96, 0x8E, 0x86, 0x7E, 0x77,
0x71, 0x6A, 0x64, 0x5F, 0x59, 0x54, 0x4F, 0x4B,
0x47, 0x43, 0x3F, 0x3B, 0x38, 0x35, 0x32, 0x2F,
0x2C, 0x2A, 0x28, 0x25, 0x23, 0x21, 0x1F, 0x1D,
0x1C, 0x1A, 0x19, 0x17, 0x16, 0x15, 0x13, 0x12,
0x11, 0x10, 0x0F, 0x0E, 0x0E, 0x0D, 0x0C, 0x0B,
0x0B, 0x0A, 0x09, 0x09, 0x08, 0x08, 0x07, 0x07,
0x07, 0x06, 0x06, 0x05, 0x05, 0x05, 0x04, 0x04,
0x04, 0x04, 0x03, 0x03, 0x03, 0x03, 0x03, 0x02,
0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01
};
} // End of namespace Kyra

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,64 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifdef ENABLE_EOB
#ifndef KYRA_SOUND_SEGACD_H
#define KYRA_SOUND_SEGACD_H
#include "common/scummsys.h"
#include "common/array.h"
namespace Audio {
class Mixer;
}
namespace Kyra {
class SegaAudioDriverInternal;
class SegaAudioDriver {
public:
SegaAudioDriver(Audio::Mixer *mixer);
~SegaAudioDriver();
enum PrioFlags {
kPrioHigh = 0x10,
kPrioLow = 0x20
};
void startFMSound(const uint8 *trackData, uint8 volume, PrioFlags prioFlags);
void loadPCMData(uint16 address, const uint8 *data, uint16 dataLen);
void startPCMSound(uint8 channel, uint8 dataStart, uint16 loopStart = 0xFF00, uint16 rate = 0x300, uint8 pan = 0xFF, uint8 vol = 0xFF);
void setMusicVolume(int volume);
void setSoundEffectVolume(int volume);
private:
SegaAudioDriverInternal *_drv;
};
} // End of namespace Kyra
#endif
#endif

View File

@@ -0,0 +1,367 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "kyra/sound/sound.h"
#include "kyra/resource/resource.h"
#include "audio/mixer.h"
#include "audio/audiostream.h"
#include "audio/decoders/flac.h"
#include "audio/decoders/mp3.h"
#include "audio/decoders/raw.h"
#include "audio/decoders/voc.h"
#include "audio/decoders/vorbis.h"
namespace Kyra {
Sound::Sound(KyraEngine_v1 *vm, Audio::Mixer *mixer)
: _vm(vm), _mixer(mixer), _soundChannels(), _musicEnabled(1),
_sfxEnabled(true) {
}
Sound::~Sound() {
}
Sound::kType Sound::getSfxType() const {
return getMusicType();
}
bool Sound::isPlaying() const {
return false;
}
bool Sound::isVoicePresent(const char *file) const {
Common::String filename;
for (int i = 0; _supportedCodecs[i].fileext; ++i) {
filename = file;
filename += _supportedCodecs[i].fileext;
if (_vm->resource()->exists(filename.c_str()))
return true;
}
return false;
}
int32 Sound::voicePlay(const char *file, Audio::SoundHandle *handle, uint8 volume, uint8 priority, bool isSfx) {
Audio::SeekableAudioStream *audioStream = getVoiceStream(file);
if (!audioStream) {
return 0;
}
int playTime = audioStream->getLength().msecs();
playVoiceStream(audioStream, handle, volume, priority, isSfx);
return playTime;
}
Audio::SeekableAudioStream *Sound::getVoiceStream(const char *file) const {
Common::Path filename;
Audio::SeekableAudioStream *audioStream = nullptr;
for (int i = 0; _supportedCodecs[i].fileext; ++i) {
filename = file;
filename.appendInPlace(_supportedCodecs[i].fileext);
Common::SeekableReadStream *stream = _vm->resource()->createReadStream(filename);
if (!stream)
continue;
audioStream = _supportedCodecs[i].streamFunc(stream, DisposeAfterUse::YES);
break;
}
if (!audioStream) {
warning("Couldn't load sound file '%s'", file);
return nullptr;
} else {
return audioStream;
}
}
bool Sound::playVoiceStream(Audio::AudioStream *stream, Audio::SoundHandle *handle, uint8 volume, uint8 priority, bool isSfx) {
int h = 0;
while (h < kNumChannelHandles && _mixer->isSoundHandleActive(_soundChannels[h].handle))
++h;
if (h >= kNumChannelHandles) {
h = 0;
while (h < kNumChannelHandles && _soundChannels[h].priority > priority)
++h;
if (h < kNumChannelHandles)
voiceStop(&_soundChannels[h].handle);
}
if (h >= kNumChannelHandles) {
// When we run out of handles we need to destroy the stream object,
// this is to avoid memory leaks in some scenes where too many sfx
// are started.
// See bug #5886 "LOL-CD: Memory leak in caves level 3".
delete stream;
return false;
}
_mixer->playStream(isSfx ? Audio::Mixer::kSFXSoundType : Audio::Mixer::kSpeechSoundType, &_soundChannels[h].handle, stream, -1, volume);
_soundChannels[h].priority = priority;
if (handle)
*handle = _soundChannels[h].handle;
return true;
}
void Sound::voiceStop(const Audio::SoundHandle *handle) {
if (!handle) {
for (int h = 0; h < kNumChannelHandles; ++h) {
if (_mixer->isSoundHandleActive(_soundChannels[h].handle))
_mixer->stopHandle(_soundChannels[h].handle);
}
} else {
_mixer->stopHandle(*handle);
}
}
bool Sound::voiceIsPlaying(const Audio::SoundHandle *handle) const {
if (!handle) {
for (int h = 0; h < kNumChannelHandles; ++h) {
if (_mixer->isSoundHandleActive(_soundChannels[h].handle))
return true;
}
} else {
return _mixer->isSoundHandleActive(*handle);
}
return false;
}
bool Sound::allVoiceChannelsPlaying() const {
for (int i = 0; i < kNumChannelHandles; ++i)
if (!_mixer->isSoundHandleActive(_soundChannels[i].handle))
return false;
return true;
}
#pragma mark -
MixedSoundDriver::MixedSoundDriver(KyraEngine_v1 *vm, Audio::Mixer *mixer, Sound *music, Sound *sfx)
: Sound(vm, mixer), _music(music), _sfx(sfx) {
}
MixedSoundDriver::~MixedSoundDriver() {
delete _music;
delete _sfx;
}
Sound::kType MixedSoundDriver::getMusicType() const {
return _music->getMusicType();
}
Sound::kType MixedSoundDriver::getSfxType() const {
return _sfx->getSfxType();
}
bool MixedSoundDriver::init() {
return (_music->init() && _sfx->init());
}
void MixedSoundDriver::process() {
_music->process();
_sfx->process();
}
void MixedSoundDriver::updateVolumeSettings() {
_music->updateVolumeSettings();
_sfx->updateVolumeSettings();
}
void MixedSoundDriver::initAudioResourceInfo(int set, void *info) {
_music->initAudioResourceInfo(set, info);
_sfx->initAudioResourceInfo(set, info);
}
void MixedSoundDriver::selectAudioResourceSet(int set) {
_music->selectAudioResourceSet(set);
_sfx->selectAudioResourceSet(set);
}
bool MixedSoundDriver::hasSoundFile(uint file) const {
return _music->hasSoundFile(file) && _sfx->hasSoundFile(file);
}
void MixedSoundDriver::loadSoundFile(uint file) {
_music->loadSoundFile(file);
_sfx->loadSoundFile(file);
}
void MixedSoundDriver::loadSoundFile(const Common::Path &file) {
_music->loadSoundFile(file);
_sfx->loadSoundFile(file);
}
void MixedSoundDriver::loadSfxFile(const Common::Path &file) {
_sfx->loadSoundFile(file);
}
void MixedSoundDriver::playTrack(uint8 track) {
_music->playTrack(track);
}
void MixedSoundDriver::haltTrack() {
_music->haltTrack();
}
bool MixedSoundDriver::isPlaying() const {
return _music->isPlaying() || _sfx->isPlaying();
}
void MixedSoundDriver::playSoundEffect(uint16 track, uint8 volume) {
_sfx->playSoundEffect(track, volume);
}
void MixedSoundDriver::stopAllSoundEffects() {
_sfx->stopAllSoundEffects();
}
void MixedSoundDriver::beginFadeOut() {
_music->beginFadeOut();
}
void MixedSoundDriver::pause(bool paused) {
_music->pause(paused);
_sfx->pause(paused);
}
#pragma mark -
void KyraEngine_v1::snd_playTheme(int file, int track) {
if (_curMusicTheme == file)
return;
_curSfxFile = _curMusicTheme = file;
_sound->loadSoundFile(_curMusicTheme);
// Kyrandia 2 uses a special file for
// MIDI sound effects, so we load
// this here
if (_flags.gameID == GI_KYRA2)
_sound->loadSfxFile("K2SFX");
if (track != -1)
_sound->playTrack(track);
}
void KyraEngine_v1::snd_playSoundEffect(int track, int volume) {
_sound->playSoundEffect(track, volume);
}
void KyraEngine_v1::snd_playWanderScoreViaMap(int command, int restart) {
if (restart)
_lastMusicCommand = -1;
// no track mapping given
// so don't do anything here
if (!_trackMap || !_trackMapSize)
return;
if (_flags.platform == Common::kPlatformDOS) {
assert(command * 2 + 1 < _trackMapSize);
if (_curMusicTheme != _trackMap[command * 2]) {
if (_trackMap[command * 2] != -1 && _trackMap[command * 2] != -2)
snd_playTheme(_trackMap[command * 2], -1);
}
if (command != 1) {
if (_lastMusicCommand != command) {
_sound->haltTrack();
_sound->playTrack(_trackMap[command * 2 + 1]);
}
} else {
_sound->beginFadeOut();
}
} else if (_flags.platform == Common::kPlatformFMTowns || _flags.platform == Common::kPlatformPC98) {
if (command == -1) {
_sound->haltTrack();
} else {
assert(command * 2 + 1 < _trackMapSize);
if (_trackMap[command * 2] != -2 && command != _lastMusicCommand) {
_sound->haltTrack();
_sound->playTrack(command);
}
}
} else if (_flags.platform == Common::kPlatformMacintosh || _flags.platform == Common::kPlatformAmiga) {
if (_curMusicTheme != 1)
snd_playTheme(1, -1);
assert(command < _trackMapSize);
if (_lastMusicCommand == -1 || _trackMap[_lastMusicCommand] != _trackMap[command])
_sound->playTrack(_trackMap[command]);
}
_lastMusicCommand = command;
}
void KyraEngine_v1::snd_stopVoice() {
_sound->voiceStop(&_speechHandle);
}
bool KyraEngine_v1::snd_voiceIsPlaying() {
return _sound->voiceIsPlaying(&_speechHandle);
}
// static res
namespace {
// A simple wrapper to create VOC streams the way like creating MP3, OGG/Vorbis and FLAC streams.
// Possible TODO: Think of making this complete and moving it to sound/voc.cpp ?
Audio::SeekableAudioStream *makeVOCStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse) {
Audio::SeekableAudioStream *as = Audio::makeVOCStream(stream, Audio::FLAG_UNSIGNED, disposeAfterUse);
return as;
}
} // end of anonymous namespace
const Sound::SpeechCodecs Sound::_supportedCodecs[] = {
{ ".VOC", makeVOCStream },
{ "", makeVOCStream },
#ifdef USE_MAD
{ ".VO3", Audio::makeMP3Stream },
{ ".MP3", Audio::makeMP3Stream },
#endif // USE_MAD
#ifdef USE_VORBIS
{ ".VOG", Audio::makeVorbisStream },
{ ".OGG", Audio::makeVorbisStream },
#endif // USE_VORBIS
#ifdef USE_FLAC
{ ".VOF", Audio::makeFLACStream },
{ ".FLA", Audio::makeFLACStream },
#endif // USE_FLAC
{ nullptr, nullptr }
};
} // End of namespace Kyra

365
engines/kyra/sound/sound.h Normal file
View File

@@ -0,0 +1,365 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef KYRA_SOUND_H
#define KYRA_SOUND_H
#include "kyra/kyra_v1.h"
#include "common/scummsys.h"
#include "common/str.h"
#include "audio/mixer.h"
namespace Audio {
class AudioStream;
class SeekableAudioStream;
} // End of namespace Audio
namespace Kyra {
// Helper structs to format the data passed to the various initAudioResourceInfo() implementations
struct SoundResourceInfo_PC {
SoundResourceInfo_PC(const char *const *files, int numFiles) : fileList(files), fileListSize(numFiles) {}
const char *const *fileList;
uint fileListSize;
};
struct SoundResourceInfo_Towns {
SoundResourceInfo_Towns(const char *const *files, int numFiles, const int32 *cdaTbl, int cdaTblSize) : fileList(files), fileListSize(numFiles), cdaTable(cdaTbl), cdaTableSize(cdaTblSize) {}
const char *const *fileList;
uint fileListSize;
const int32 *cdaTable;
uint cdaTableSize;
};
struct SoundResourceInfo_PC98 {
SoundResourceInfo_PC98(const char *fileNamePattern) : pattern(fileNamePattern) {}
const char *pattern;
};
struct SoundResourceInfo_TownsPC98V2 {
SoundResourceInfo_TownsPC98V2(const char *const *files, int numFiles, const char *fileNamePattern, const uint16 *cdaTbl, int cdaTblSize) : fileList(files), fileListSize(numFiles), pattern(fileNamePattern), cdaTable(cdaTbl), cdaTableSize(cdaTblSize) {}
const char *const *fileList;
uint fileListSize;
const char *pattern;
const uint16 *cdaTable;
uint cdaTableSize;
};
struct SoundResourceInfo_TownsEoB {
SoundResourceInfo_TownsEoB(const char *const *filelist, uint numfiles, const uint8 *pcmdata, uint pcmdataSize, int pcmvolume) : fileList(filelist), numFiles(numfiles), pcmData(pcmdata), pcmDataSize(pcmdataSize), pcmVolume(pcmvolume) {}
const uint8 *pcmData;
uint pcmDataSize;
int pcmVolume;
const char *const *fileList;
uint numFiles;
};
struct SoundResourceInfo_AmigaEoB {
SoundResourceInfo_AmigaEoB(const char *const *files, int numFiles, const char *const *soundmap, int numSounds) : fileList(files), fileListSize(numFiles), soundList(soundmap), soundListSize(numSounds) {}
const char *const *fileList;
uint fileListSize;
const char *const *soundList;
uint soundListSize;
};
/**
* Analog audio output device API for Kyrandia games.
* It contains functionality to play music tracks,
* sound effects and voices.
*/
class Sound {
public:
Sound(KyraEngine_v1 *vm, Audio::Mixer *mixer);
virtual ~Sound();
enum kType {
kAdLib,
kMidiMT32,
kMidiGM,
kTowns,
kPC98,
kPCSpkr,
kPCjr,
kAmiga,
kSegaCD,
kMac
};
virtual kType getMusicType() const = 0;
virtual kType getSfxType() const;
// This is obviously not a real implementation. The original allows disabling digital sfx (or simply not having a suitable sound card),
// thus falling back to the music driver's sfx. We never supported this, since we don't even have a setting for it in the launcher.
// Currently, the only purpose of this function is fixing KyraEngine_HoF::o2_playFireflyScore() (bug #10877: "Sound issues in the Legend of Kyrandia 2").
virtual bool useDigitalSfx() const { return true; }
/**
* Initializes the output device.
*
* @return true on success, else false
*/
virtual bool init() = 0;
/**
* Updates the device, this is needed for some devices.
*/
virtual void process() {}
/**
* Updates internal volume settings according to ConfigManager.
*/
virtual void updateVolumeSettings() {}
/**
* Assigns static resource data with information on how to load
* audio resources to
*
* @param set value defined in AudioResourceSet enum
* info various types of resource info data (file list, file name pattern, struct, etc. - depending on the inheriting driver type)
*/
virtual void initAudioResourceInfo(int set, void *info) = 0;
/**
* Select audio resource set.
*
* @param set value defined in AudioResourceSet enum
*/
virtual void selectAudioResourceSet(int set) = 0;
/**
* Checks if a given sound file is present.
*
* @param track track number
* @return true if available, false otherwise
*/
virtual bool hasSoundFile(uint file) const = 0;
/**
* Load a specifc sound file for use of
* playing music and sound effects.
*/
virtual void loadSoundFile(uint file) = 0;
/**
* Load a sound file for playing music
* (and sometimes sound effects) from.
*/
virtual void loadSoundFile(const Common::Path &file) = 0;
/**
* Unload a specifc sound file that has been loaded before.
*/
virtual void unloadSoundFile(const Common::String &file) {}
/**
* Load a sound file for playing sound
* effects from.
*/
virtual void loadSfxFile(const Common::Path &file) {}
/**
* Plays the specified track.
*
* @param track track number
*/
virtual void playTrack(uint8 track) = 0;
/**
* Stop playback of the current track.
*/
virtual void haltTrack() = 0;
/**
* Plays the specified sound effect.
*
* @param track sound effect id
*/
virtual void playSoundEffect(uint16 track, uint8 volume = 0xFF) = 0;
/**
* Stop playback of all sfx tracks.
*/
virtual void stopAllSoundEffects() {}
/**
* Checks if the sound driver plays any sound.
*
* @return true if playing, false otherwise
*/
virtual bool isPlaying() const;
/**
* Starts fading out the volume.
*
* This keeps fading out the output until
* it is silenced, but does not change
* the volume set by setVolume! It will
* automatically reset the volume when
* playing a new track or sound effect.
*/
virtual void beginFadeOut() = 0;
virtual void beginFadeOut(int) { beginFadeOut(); }
/**
* Stops all audio playback when paused. Continues after end of pause.
*/
virtual void pause(bool paused) {}
virtual void enableMusic(int enable) { _musicEnabled = enable; }
int musicEnabled() const { return _musicEnabled; }
void enableSFX(bool enable) { _sfxEnabled = enable; }
bool sfxEnabled() const { return _sfxEnabled; }
/**
* Checks whether a voice file with the given name is present
*
* @param file file name
* @return true if available, false otherwise
*/
bool isVoicePresent(const char *file) const;
/**
* Plays the specified voice file.
*
* Also before starting to play the
* specified voice file, it stops the
* current voice.
*
* @param file file to be played
* @param volume volume of the voice file
* @param isSfx marks file as sfx instead of voice
* @param handle store a copy of the sound handle
* @return playtime of the voice file (-1 marks unknown playtime)
*/
virtual int32 voicePlay(const char *file, Audio::SoundHandle *handle = 0, uint8 volume = 255, uint8 priority = 255, bool isSfx = false);
Audio::SeekableAudioStream *getVoiceStream(const char *file) const;
bool playVoiceStream(Audio::AudioStream *stream, Audio::SoundHandle *handle = 0, uint8 volume = 255, uint8 priority = 255, bool isSfx = false);
/**
* Checks if a voice is being played.
*
* @return true when playing, else false
*/
bool voiceIsPlaying(const Audio::SoundHandle *handle = 0) const;
/**
* Checks if all voice handles are used.
*
* @return false when a handle is free, else true
*/
bool allVoiceChannelsPlaying() const;
/**
* Checks how long a voice has been playing
*
* @return time in milliseconds
*/
uint32 voicePlayedTime(const Audio::SoundHandle &handle) const {
return _mixer->getSoundElapsedTime(handle);
}
/**
* Stops playback of the current voice.
*/
void voiceStop(const Audio::SoundHandle *handle = 0);
/**
* Receive notifications from a song at certain points.
*/
virtual int checkTrigger() { return 0; }
/**
* Reset sound trigger.
*/
virtual void resetTrigger() {}
protected:
enum {
kNumChannelHandles = 4
};
struct SoundChannel {
SoundChannel() : handle(), priority(0) {}
Audio::SoundHandle handle;
int priority;
};
SoundChannel _soundChannels[kNumChannelHandles];
int _musicEnabled;
bool _sfxEnabled;
KyraEngine_v1 *_vm;
Audio::Mixer *_mixer;
private:
struct SpeechCodecs {
const char *fileext;
Audio::SeekableAudioStream *(*streamFunc)(
Common::SeekableReadStream *stream,
DisposeAfterUse::Flag disposeAfterUse);
};
static const SpeechCodecs _supportedCodecs[];
};
class MixedSoundDriver : public Sound {
public:
MixedSoundDriver(KyraEngine_v1 *vm, Audio::Mixer *mixer, Sound *music, Sound *sfx);
~MixedSoundDriver() override;
kType getMusicType() const override;
kType getSfxType() const override;
bool init() override;
void process() override;
void updateVolumeSettings() override;
void initAudioResourceInfo(int set, void *info) override;
void selectAudioResourceSet(int set) override;
bool hasSoundFile(uint file) const override;
void loadSoundFile(uint file) override;
void loadSoundFile(const Common::Path &file) override;
void loadSfxFile(const Common::Path &file) override;
void playTrack(uint8 track) override;
void haltTrack() override;
bool isPlaying() const override;
void playSoundEffect(uint16 track, uint8 volume = 0xFF) override;
void stopAllSoundEffects() override;
void beginFadeOut() override;
void pause(bool paused) override;
private:
Sound *_music, *_sfx;
};
} // End of namespace Kyra
#endif

View File

@@ -0,0 +1,239 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifdef ENABLE_EOB
#include "kyra/sound/sound_intern.h"
#include "kyra/resource/resource.h"
#include "kyra/sound/drivers/audiomaster2.h"
#include "common/config-manager.h"
#include "common/memstream.h"
namespace Kyra {
SoundAmiga_EoB::SoundAmiga_EoB(KyraEngine_v1 *vm, Audio::Mixer *mixer) : Sound(vm, mixer),
_vm(vm), _driver(0), _currentResourceSet(-1), _ready(false) {
_fileBuffer = new uint8[64000];
memset(_resInfo, 0, sizeof(_resInfo));
}
SoundAmiga_EoB::~SoundAmiga_EoB() {
delete _driver;
delete[] _fileBuffer;
for (int i = 0; i < 3; i++)
initAudioResourceInfo(i, 0);
}
Sound::kType SoundAmiga_EoB::getMusicType() const {
return kAmiga;
}
bool SoundAmiga_EoB::init() {
_driver = new AudioMaster2(_mixer);
if (!_driver->init())
return false;
_ready = true;
return true;
}
void SoundAmiga_EoB::initAudioResourceInfo(int set, void *info) {
delete _resInfo[set];
_resInfo[set] = info ? new SoundResourceInfo_AmigaEoB(*(SoundResourceInfo_AmigaEoB*)info) : 0;
}
void SoundAmiga_EoB::selectAudioResourceSet(int set) {
if (set == _currentResourceSet || !_ready)
return;
_driver->flushAllResources();
if (!_resInfo[set])
return;
for (uint i = 0; i < _resInfo[set]->fileListSize; ++i)
loadSoundFile(_resInfo[set]->fileList[i]);
_currentResourceSet = set;
}
void SoundAmiga_EoB::loadSoundFile(const Common::Path &file) {
if (!_ready)
return;
Common::SeekableReadStream *in = _vm->resource()->createReadStream(file);
debugC(6, kDebugLevelSound, "SoundAmiga_EoB::loadSoundFile(): Attempting to load sound file '%s'...%s", file.toString().c_str(), in ? "SUCCESS" : "FILE NOT FOUND");
if (!in)
return;
// This value can deviate up to 5 bytes from the real size in EOB II Amiga.
// The original simply tries to read 64000 bytes from the file (ignoring this
// value). We do the same.
// EOB I strangely always seems to have correct values.
uint16 readSize = in->readUint16LE() - 10;
uint8 cmp = in->readByte();
in->seek(1, SEEK_CUR);
uint32 outSize = in->readUint32LE();
in->seek(2, SEEK_CUR);
readSize = in->read(_fileBuffer, 64000);
delete in;
if (cmp == 0 && readSize < outSize)
outSize = readSize;
uint8 *buf = new uint8[outSize];
if (cmp == 0) {
memcpy(buf, _fileBuffer, outSize);
} else if (cmp == 3) {
Screen::decodeFrame3(_fileBuffer, buf, outSize, true);
} else if (cmp == 4) {
Screen::decodeFrame4(_fileBuffer, buf, outSize);
} else {
error("SoundAmiga_EoB::loadSoundFile(): Failed to load sound file '%s'", file.toString().c_str());
}
Common::MemoryReadStream soundFile(buf, outSize);
if (!_driver->loadRessourceFile(&soundFile))
error("SoundAmiga_EoB::loadSoundFile(): Failed to load sound file '%s'", file.toString().c_str());
delete[] buf;
}
void SoundAmiga_EoB::unloadSoundFile(const Common::String &file) {
if (!_ready)
return;
debugC(5, kDebugLevelSound, "SoundAmiga_EoB::unloadSoundFile(): Attempting to free resource '%s'...%s", file.c_str(), _driver->stopSound(file) ? "SUCCESS" : "FAILURE");
_driver->flushResource(file);
}
void SoundAmiga_EoB::playTrack(uint8 track) {
if (!_musicEnabled || !_ready)
return;
Common::String newSound;
if (_vm->game() == GI_EOB1) {
if (_currentResourceSet == kMusicIntro) {
if (track == 1)
newSound = "NEWINTRO1.SMUS";
else if (track == 20)
newSound = "CHARGEN1.SMUS";
} else if (_currentResourceSet == kMusicFinale) {
newSound = "FINALE.SMUS";
}
} else if (_vm->game() == GI_EOB2) {
if (_currentResourceSet == kMusicIntro) {
if (track > 11 && track < 16) {
const char *const songs[] = { "INTRO1A.SMUS", "CHARGEN3.SMUS", "INTRO1B.SMUS", "INTRO1C.SMUS" };
newSound = songs[track - 12];
}
} else if (_currentResourceSet == kMusicFinale) {
if (track > 0 && track < 4) {
const char *const songs[] = { "FINALE1B.SMUS", "FINALE1C.SMUS", "FINALE1D.SMUS" };
newSound = songs[track - 1];
}
}
}
if (!newSound.empty() && _ready) {
_driver->startSound(newSound);
_lastSound = Common::move(newSound);
}
}
void SoundAmiga_EoB::haltTrack() {
if (!_lastSound.empty())
_driver->stopSound(_lastSound);
_lastSound.clear();
}
void SoundAmiga_EoB::playSoundEffect(uint16 track, uint8 volume) {
if (_currentResourceSet == -1 || !_sfxEnabled || !_ready)
return;
if (_vm->game() == GI_EOB2 && _currentResourceSet == kMusicIntro && track == 14) {
_driver->startSound("TELEPORT.SAM");
return;
}
if (!_resInfo[_currentResourceSet]->soundList || track >= 120 || !_sfxEnabled)
return;
if (_vm->game() == GI_EOB2 && track == 2) {
beginFadeOut(60);
return;
}
Common::String newSound = _resInfo[_currentResourceSet]->soundList[track];
const char *suffix = (_vm->game() == GI_EOB1) ? "1.SAM" : ((track > 51 && track < 68) ? ".SMUS" : ".SAM");
if (!newSound.empty()) {
if (volume == 255) {
if (_driver->startSound(newSound + suffix)) {
_lastSound = newSound + suffix;
return;
} else {
volume = 1;
}
}
if (volume > 0 && volume < 5)
newSound = Common::String::format("%s%d", newSound.c_str(), volume);
if (!_driver->startSound(newSound)) {
// WORKAROUND for wrongly named resources. This applies to at least 'BLADE' in the EOB II dungeons (instead of 'BLADE1').
newSound = _resInfo[_currentResourceSet]->soundList[track];
if (_driver->startSound(newSound))
debugC(5, kDebugLevelSound, "SoundAmiga_EoB::playSoundEffect(): Triggered workaround for wrongly named resource: '%s'", newSound.c_str());
}
_lastSound = Common::move(newSound);
}
}
void SoundAmiga_EoB::beginFadeOut(int delay) {
_driver->fadeOut(delay);
while (_driver->isFading() && !_vm->shouldQuit())
_vm->delay(5);
haltTrack();
}
void SoundAmiga_EoB::updateVolumeSettings() {
if (!_driver || !_ready)
return;
bool mute = false;
if (ConfMan.hasKey("mute"))
mute = ConfMan.getBool("mute");
_driver->setMusicVolume((mute ? 0 : ConfMan.getInt("music_volume")));
_driver->setSoundEffectVolume((mute ? 0 : ConfMan.getInt("sfx_volume")));
}
int SoundAmiga_EoB::checkTrigger() {
return _driver->getPlayDuration();
}
} // End of namespace Kyra
#endif

View File

@@ -0,0 +1,231 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "kyra/sound/sound_intern.h"
#include "kyra/resource/resource.h"
#include "audio/mixer.h"
#include "audio/mods/maxtrax.h"
namespace Kyra {
SoundAmiga_LoK::SoundAmiga_LoK(KyraEngine_v1 *vm, Audio::Mixer *mixer)
: Sound(vm, mixer),
_driver(0),
_musicHandle(),
_fileLoaded(kFileNone),
_tableSfxIntro(0),
_tableSfxGame(0),
_tableSfxIntro_Size(0),
_tableSfxGame_Size(0) {
}
SoundAmiga_LoK::~SoundAmiga_LoK() {
_mixer->stopHandle(_musicHandle);
delete _driver;
}
bool SoundAmiga_LoK::init() {
_driver = new Audio::MaxTrax(_mixer->getOutputRate(), true);
_tableSfxIntro = _vm->staticres()->loadAmigaSfxTable(k1AmigaIntroSFXTable, _tableSfxIntro_Size);
_tableSfxGame = _vm->staticres()->loadAmigaSfxTable(k1AmigaGameSFXTable, _tableSfxGame_Size);
return _driver != 0 && _tableSfxIntro && _tableSfxGame;
}
void SoundAmiga_LoK::initAudioResourceInfo(int set, void *info) {
// See comment below
}
void SoundAmiga_LoK::selectAudioResourceSet(int set) {
// It seems that loadSoundFile() is doing what would normally be done in here.
// As long as this driver is only required for one single target (Kyra 1 Amiga)
// this doesn't matter much.
}
bool SoundAmiga_LoK::hasSoundFile(uint file) const {
if (file < 3)
return true;
return false;
}
void SoundAmiga_LoK::loadSoundFile(uint file) {
debugC(5, kDebugLevelSound, "SoundAmiga_LoK::loadSoundFile(%d)", file);
static const char *const tableFilenames[3][2] = {
{ "introscr.mx", "introinst.mx" },
{ "kyramusic.mx", 0 },
{ "finalescr.mx", "introinst.mx" }
};
assert(file < ARRAYSIZE(tableFilenames));
if (_fileLoaded == (FileType)file)
return;
const char *scoreName = tableFilenames[file][0];
const char *sampleName = tableFilenames[file][1];
bool loaded = false;
Common::SeekableReadStream *scoreIn = _vm->resource()->createReadStream(scoreName);
if (sampleName) {
Common::SeekableReadStream *sampleIn = _vm->resource()->createReadStream(sampleName);
if (scoreIn && sampleIn) {
_fileLoaded = kFileNone;
loaded = _driver->load(*scoreIn, true, false);
loaded = loaded && _driver->load(*sampleIn, false, true);
} else
warning("SoundAmiga_LoK: missing atleast one of those music files: %s, %s", scoreName, sampleName);
delete sampleIn;
} else {
if (scoreIn) {
_fileLoaded = kFileNone;
loaded = _driver->load(*scoreIn);
} else
warning("SoundAmiga_LoK: missing music file: %s", scoreName);
}
delete scoreIn;
if (loaded)
_fileLoaded = (FileType)file;
}
void SoundAmiga_LoK::playTrack(uint8 track) {
debugC(5, kDebugLevelSound, "SoundAmiga_LoK::playTrack(%d)", track);
static const byte tempoIntro[] = { 0x46, 0x55, 0x3C, 0x41 };
static const byte tempoFinal[] = { 0x78, 0x50 };
static const byte tempoIngame[] = {
0x64, 0x64, 0x64, 0x64, 0x64, 0x73, 0x4B, 0x64,
0x64, 0x64, 0x55, 0x9C, 0x6E, 0x91, 0x78, 0x84,
0x32, 0x64, 0x64, 0x6E, 0x3C, 0xD8, 0xAF
};
static const byte loopIngame[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x01, 0x01,
0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00
};
int score = -1;
bool loop = false;
byte volume = 0x40;
byte tempo = 0;
switch (_fileLoaded) {
case kFileIntro:
if (track >= 2 && track < ARRAYSIZE(tempoIntro) + 2) {
score = track - 2;
tempo = tempoIntro[score];
}
break;
case kFileGame:
if (track >= 11 && track < ARRAYSIZE(tempoIngame) + 11) {
score = track - 11;
loop = loopIngame[score] != 0;
tempo = tempoIngame[score];
}
break;
case kFileFinal:
// score 0 gets started immediately after loading the music-files with different tempo.
// we need to define a track-value for the fake call of this function
if (track >= 2 && track < ARRAYSIZE(tempoFinal) + 2) {
score = track - 2;
loop = true;
tempo = tempoFinal[score];
}
break;
default:
return;
}
if (score >= 0) {
if (_musicEnabled && _driver->playSong(score, loop)) {
_driver->setVolume(volume);
_driver->setTempo(tempo << 4);
if (!_mixer->isSoundHandleActive(_musicHandle))
_mixer->playStream(Audio::Mixer::kPlainSoundType, &_musicHandle, _driver, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO);
}
} else if (track == 0)
_driver->stopMusic();
else if (track == 1)
beginFadeOut();
}
void SoundAmiga_LoK::haltTrack() {
debugC(5, kDebugLevelSound, "SoundAmiga_LoK::haltTrack()");
_driver->stopMusic();
}
void SoundAmiga_LoK::beginFadeOut() {
debugC(5, kDebugLevelSound, "SoundAmiga_LoK::beginFadeOut()");
for (int i = 0x3F; i >= 0; --i) {
_driver->setVolume((byte)i);
_vm->delay(_vm->tickLength());
}
_driver->stopMusic();
_vm->delay(_vm->tickLength());
_driver->setVolume(0x40);
}
void SoundAmiga_LoK::playSoundEffect(uint16 track, uint8) {
debugC(5, kDebugLevelSound, "SoundAmiga_LoK::playSoundEffect(%d)", track);
const AmigaSfxTable *sfx = 0;
bool pan = false;
switch (_fileLoaded) {
case kFileFinal:
case kFileIntro:
// We only allow playing of sound effects, which are included in the table.
if (track < _tableSfxIntro_Size) {
sfx = &_tableSfxIntro[track];
pan = (sfx->pan != 0);
}
break;
case kFileGame:
if (0x61 <= track && track <= 0x63)
playTrack(track - 0x4F);
if (track >= _tableSfxGame_Size)
return;
if (_tableSfxGame[track].note) {
sfx = &_tableSfxGame[track];
pan = (sfx->pan != 0) && (sfx->pan != 2);
}
break;
default:
return;
}
if (_sfxEnabled && sfx) {
const bool success = _driver->playNote(sfx->note, sfx->patch, sfx->duration, sfx->volume, pan);
if (success && !_mixer->isSoundHandleActive(_musicHandle))
_mixer->playStream(Audio::Mixer::kPlainSoundType, &_musicHandle, _driver, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO);
}
}
} // End of namespace Kyra

View File

@@ -0,0 +1,247 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "kyra/sound/sound_digital_mr.h"
#include "kyra/resource/resource.h"
#include "kyra/engine/kyra_mr.h"
#include "audio/audiostream.h"
#include "audio/decoders/mp3.h"
#include "audio/decoders/vorbis.h"
#include "audio/decoders/flac.h"
#include "common/util.h"
namespace Kyra {
class KyraAudioStream : public Audio::SeekableAudioStream {
public:
KyraAudioStream(Audio::SeekableAudioStream *impl) : _impl(impl), _rate(impl->getRate()), _fadeSamples(0), _fadeCount(0), _fading(0), _endOfData(false) {}
~KyraAudioStream() override { delete _impl; _impl = nullptr; }
int readBuffer(int16 *buffer, const int numSamples) override;
bool isStereo() const override { return _impl->isStereo(); }
bool endOfData() const override { return _impl->endOfData() | _endOfData; }
int getRate() const override { return _rate; }
void setRate(int newRate) { _rate = newRate; }
void beginFadeOut(uint32 millis);
bool seek(const Audio::Timestamp &where) override { return _impl->seek(where); }
Audio::Timestamp getLength() const override { return _impl->getLength(); }
private:
Audio::SeekableAudioStream *_impl;
int _rate;
int32 _fadeSamples;
int32 _fadeCount;
int _fading;
bool _endOfData;
};
void KyraAudioStream::beginFadeOut(uint32 millis) {
_fadeSamples = (millis * getRate()) / 1000;
if (_fading == 0)
_fadeCount = _fadeSamples;
_fading = -1;
}
int KyraAudioStream::readBuffer(int16 *buffer, const int numSamples) {
int samplesRead = _impl->readBuffer(buffer, numSamples);
if (_fading) {
int samplesProcessed = 0;
for (; samplesProcessed < samplesRead; ++samplesProcessed) {
// To help avoid overflows for long fade times, we divide both
// _fadeSamples and _fadeCount when calculating the new sample.
int32 div = _fadeSamples / 256;
if (_fading) {
*buffer = (*buffer * (_fadeCount / 256)) / div;
++buffer;
_fadeCount += _fading;
if (_fadeCount < 0) {
_fadeCount = 0;
_endOfData = true;
} else if (_fadeCount > _fadeSamples) {
_fadeCount = _fadeSamples;
_fading = 0;
}
}
}
if (_endOfData) {
memset(buffer, 0, (samplesRead - samplesProcessed) * sizeof(int16));
samplesRead = samplesProcessed;
}
}
return samplesRead;
}
SoundDigital_MR::SoundDigital_MR(KyraEngine_MR *vm, Audio::Mixer *mixer) : _vm(vm), _mixer(mixer) {
for (uint i = 0; i < ARRAYSIZE(_sounds); ++i)
_sounds[i].stream = nullptr;
}
SoundDigital_MR::~SoundDigital_MR() {
for (int i = 0; i < ARRAYSIZE(_sounds); ++i)
stopSound(i);
}
int SoundDigital_MR::playSound(const char *filename, uint8 priority, Audio::Mixer::SoundType type, int volume, bool loop, int channel) {
Sound *use = nullptr;
if (channel != -1 && channel < ARRAYSIZE(_sounds)) {
stopSound(channel);
use = &_sounds[channel];
} else {
for (channel = 0; !use && channel < ARRAYSIZE(_sounds); ++channel) {
if (!isPlaying(channel)) {
stopSound(channel);
use = &_sounds[channel];
break;
}
}
for (channel = 0; !use && channel < ARRAYSIZE(_sounds); ++channel) {
if (strcmp(_sounds[channel].filename, filename) == 0) {
stopSound(channel);
use = &_sounds[channel];
break;
}
}
for (channel = 0; !use && channel < ARRAYSIZE(_sounds); ++channel) {
if (_sounds[channel].priority <= priority) {
stopSound(channel);
use = &_sounds[channel];
break;
}
}
if (!use) {
warning("no free sound channel");
return -1;
}
}
Common::SeekableReadStream *stream = nullptr;
int usedCodec = -1;
for (int i = 0; _supportedCodecs[i].fileext; ++i) {
Common::Path file(filename);
file.appendInPlace(_supportedCodecs[i].fileext);
if (!_vm->resource()->exists(file))
continue;
stream = _vm->resource()->createReadStream(file);
usedCodec = i;
}
if (!stream) {
warning("Couldn't find soundfile '%s'", filename);
return -1;
}
Common::strlcpy(use->filename, filename, sizeof(use->filename));
use->priority = priority;
debugC(5, kDebugLevelSound, "playSound: \"%s\"", use->filename);
Audio::SeekableAudioStream *audioStream = _supportedCodecs[usedCodec].streamFunc(stream, DisposeAfterUse::YES);
if (!audioStream) {
warning("Couldn't create audio stream for file '%s'", filename);
return -1;
}
use->stream = new KyraAudioStream(audioStream);
assert(use->stream);
if (use->stream->endOfData()) {
delete use->stream;
use->stream = nullptr;
return -1;
}
if (volume > 255)
volume = 255;
volume = (volume * Audio::Mixer::kMaxChannelVolume) / 255;
if (type == Audio::Mixer::kSpeechSoundType && _vm->heliumMode())
use->stream->setRate(32765);
_mixer->playStream(type, &use->handle, makeLoopingAudioStream(use->stream, loop ? 0 : 1), -1, volume);
return use - _sounds;
}
bool SoundDigital_MR::isPlaying(int channel) {
if (channel == -1)
return false;
assert(channel >= 0 && channel < ARRAYSIZE(_sounds));
if (!_sounds[channel].stream)
return false;
return _mixer->isSoundHandleActive(_sounds[channel].handle);
}
void SoundDigital_MR::stopSound(int channel) {
if (channel == -1)
return;
assert(channel >= 0 && channel < ARRAYSIZE(_sounds));
_mixer->stopHandle(_sounds[channel].handle);
_sounds[channel].stream = nullptr;
}
void SoundDigital_MR::stopAllSounds() {
for (int i = 0; i < ARRAYSIZE(_sounds); ++i) {
if (isPlaying(i))
stopSound(i);
}
}
void SoundDigital_MR::beginFadeOut(int channel, int ticks) {
if (isPlaying(channel))
_sounds[channel].stream->beginFadeOut(ticks * _vm->tickLength());
}
// static res
Audio::SeekableAudioStream *makeAUDStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse);
const SoundDigital_MR::AudioCodecs SoundDigital_MR::_supportedCodecs[] = {
#ifdef USE_FLAC
{ ".FLA", Audio::makeFLACStream },
#endif // USE_FLAC
#ifdef USE_VORBIS
{ ".OGG", Audio::makeVorbisStream },
#endif // USE_VORBIS
#ifdef USE_MAD
{ ".MP3", Audio::makeMP3Stream },
#endif // USE_MAD
{ ".AUD", makeAUDStream },
{ nullptr, nullptr }
};
} // End of namespace Kyra

View File

@@ -0,0 +1,118 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef KYRA_SOUND_DIGITAL_MR_H
#define KYRA_SOUND_DIGITAL_MR_H
#include "audio/mixer.h"
namespace Common {
class SeekableReadStream;
} // End of namespace Common
namespace Audio {
class SeekableAudioStream;
} // End of namespace Audio
namespace Kyra {
// Digital Audio
class KyraAudioStream;
class KyraEngine_MR;
/**
* Digital audio output device.
*
* This is just used for Kyrandia 3.
*/
class SoundDigital_MR {
public:
SoundDigital_MR(KyraEngine_MR *vm, Audio::Mixer *mixer);
~SoundDigital_MR();
/**
* Plays a sound.
*
* @param filename file to be played
* @param priority priority of the sound
* @param type type
* @param volume channel volume
* @param loop true if the sound should loop (endlessly)
* @param channel tell the sound player to use a specific channel for playback
*
* @return channel playing the sound
*/
int playSound(const char *filename, uint8 priority, Audio::Mixer::SoundType type, int volume = 255, bool loop = false, int channel = -1);
/**
* Checks if a given channel is playing a sound.
*
* @param channel channel number to check
* @return true if playing, else false
*/
bool isPlaying(int channel);
/**
* Stop the playback of a sound in the given
* channel.
*
* @param channel channel number
*/
void stopSound(int channel);
/**
* Stops playback of all sounds.
*/
void stopAllSounds();
/**
* Makes the sound in a given channel
* fading out.
*
* @param channel channel number
* @param ticks fadeout time
*/
void beginFadeOut(int channel, int ticks);
private:
KyraEngine_MR *_vm;
Audio::Mixer *_mixer;
struct Sound {
Audio::SoundHandle handle;
char filename[16];
uint8 priority;
KyraAudioStream *stream;
} _sounds[4];
struct AudioCodecs {
const char *fileext;
Audio::SeekableAudioStream *(*streamFunc)(
Common::SeekableReadStream *stream,
DisposeAfterUse::Flag disposeAfterUse);
};
static const AudioCodecs _supportedCodecs[];
};
} // End of namespace Kyra
#endif

View File

@@ -0,0 +1,631 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef KYRA_SOUND_INTERN_H
#define KYRA_SOUND_INTERN_H
#include "kyra/sound/sound.h"
#include "kyra/sound/sound_pc_v1.h"
#include "audio/midiparser.h"
#include "audio/miles.h"
#include "audio/softsynth/emumidi.h"
#include "audio/softsynth/fmtowns_pc98/towns_audio.h"
#include "common/mutex.h"
class EuphonyPlayer;
class TownsPC98_AudioDriver;
namespace Audio {
class PCSpeakerStream;
class MaxTrax;
} // End of namespace Audio
namespace Common {
class MacResManager;
} // End of namespace Common
namespace Kyra {
class MidiOutput;
/**
* MIDI output device.
*
* This device supports both MT-32 MIDI, as used in
* Kyrandia 1 and 2, and GM MIDI, as used in Kyrandia 2.
*/
class SoundMidiPC : public Sound {
public:
SoundMidiPC(KyraEngine_v1 *vm, Audio::Mixer *mixer, MidiDriver *driver, kType type);
~SoundMidiPC() override;
kType getMusicType() const override { return _type; }
bool init() override;
void updateVolumeSettings() override;
void initAudioResourceInfo(int set, void *info) override;
void selectAudioResourceSet(int set) override;
bool hasSoundFile(uint file) const override;
void loadSoundFile(uint file) override;
void loadSoundFile(const Common::Path &file) override;
void loadSfxFile(const Common::Path &file) override;
void playTrack(uint8 track) override;
void haltTrack() override;
bool isPlaying() const override;
void playSoundEffect(uint16 track, uint8 volume = 0xFF) override;
void stopAllSoundEffects() override;
void beginFadeOut() override;
void pause(bool paused) override;
private:
static void onTimer(void *data);
// Our channel handling
int _musicVolume, _sfxVolume;
uint32 _fadeStartTime;
bool _fadeMusicOut;
// Midi file related
Common::Path _mFileName, _sFileName;
byte *_musicFile, *_sfxFile;
MidiParser *_music;
MidiParser *_sfx[3];
const SoundResourceInfo_PC *res() const {return _resInfo[_currentResourceSet]; }
SoundResourceInfo_PC *_resInfo[3];
int _currentResourceSet;
// misc
kType _type;
Common::Path getFileName(const Common::Path &str);
bool _nativeMT32;
MidiDriver *_driver;
Audio::MidiDriver_Miles_Midi *_output;
Common::Mutex _mutex;
};
class SoundTowns_LoK : public Sound {
public:
SoundTowns_LoK(KyraEngine_v1 *vm, Audio::Mixer *mixer);
~SoundTowns_LoK() override;
kType getMusicType() const override { return kTowns; }
bool init() override;
void process() override;
void initAudioResourceInfo(int set, void *info) override;
void selectAudioResourceSet(int set) override;
bool hasSoundFile(uint file) const override;
void loadSoundFile(uint file) override;
void loadSoundFile(const Common::Path &) override {}
void playTrack(uint8 track) override;
void haltTrack() override;
void playSoundEffect(uint16 track, uint8 volume = 0xFF) override;
void stopAllSoundEffects() override;
void beginFadeOut() override;
void updateVolumeSettings() override;
void enableMusic(int enable) override;
private:
bool loadInstruments();
void playEuphonyTrack(uint32 offset, int loop);
void fadeOutSoundEffects();
int _lastTrack;
Audio::SoundHandle _sfxHandle;
uint8 *_musicTrackData;
uint _sfxFileIndex;
uint8 *_sfxFileData;
uint8 _sfxChannel;
EuphonyPlayer *_player;
bool _cdaPlaying;
const SoundResourceInfo_Towns *res() const {return _resInfo[_currentResourceSet]; }
SoundResourceInfo_Towns *_resInfo[3];
int _currentResourceSet;
const uint8 *_musicFadeTable;
const uint8 *_sfxBTTable;
const uint8 *_sfxWDTable;
};
class SoundPC98_LoK : public Sound {
public:
SoundPC98_LoK(KyraEngine_v1 *vm, Audio::Mixer *mixer);
~SoundPC98_LoK() override;
kType getMusicType() const override { return kPC98; }
bool init() override;
void initAudioResourceInfo(int set, void *info) override;
void selectAudioResourceSet(int set) override;
bool hasSoundFile(uint file) const override;
void loadSoundFile(uint file) override;
void loadSoundFile(const Common::Path &file) override;
void playTrack(uint8 track) override;
void haltTrack() override;
void beginFadeOut() override;
int32 voicePlay(const char *file, Audio::SoundHandle *handle, uint8 volume, uint8 priority, bool isSfx) override { return -1; }
void playSoundEffect(uint16 track, uint8 volume = 0xFF) override;
void updateVolumeSettings() override;
private:
int _lastTrack;
uint8 *_musicTrackData;
uint8 *_sfxTrackData;
TownsPC98_AudioDriver *_driver;
const char *resPattern() {return _resInfo[_currentResourceSet]->c_str(); }
Common::String *_resInfo[3];
int _currentResourceSet;
};
class SoundTownsPC98_v2 : public Sound {
public:
SoundTownsPC98_v2(KyraEngine_v1 *vm, Audio::Mixer *mixer);
~SoundTownsPC98_v2() override;
kType getMusicType() const override { return _vm->gameFlags().platform == Common::kPlatformFMTowns ? kTowns : kPC98; }
bool init() override;
void process() override;
void initAudioResourceInfo(int set, void *info) override;
void selectAudioResourceSet(int set) override;
bool hasSoundFile(uint file) const override;
void loadSoundFile(uint file) override {}
void loadSoundFile(const Common::Path &file) override;
void playTrack(uint8 track) override;
void haltTrack() override;
void beginFadeOut() override;
int32 voicePlay(const char *file, Audio::SoundHandle *handle, uint8 volume = 255, uint8 priority = 255, bool isSfx = true) override;
void playSoundEffect(uint16 track, uint8 volume = 0xFF) override;
void updateVolumeSettings() override;
private:
Audio::AudioStream *_currentSFX;
int _lastTrack;
bool _useFmSfx;
uint8 *_musicTrackData;
uint8 *_sfxTrackData;
TownsPC98_AudioDriver *_driver;
const SoundResourceInfo_TownsPC98V2 *res() const { return _resInfo[_currentResourceSet]; }
SoundResourceInfo_TownsPC98V2 *_resInfo[3];
int _currentResourceSet;
};
// PC Speaker MIDI driver
class MidiDriver_PCSpeaker : public MidiDriver_Emulated {
public:
MidiDriver_PCSpeaker(Audio::Mixer *mixer);
~MidiDriver_PCSpeaker() override;
// MidiDriver interface
void close() override {}
void send(uint32 data) override;
MidiChannel *allocateChannel() override { return 0; }
MidiChannel *getPercussionChannel() override { return 0; }
// MidiDriver_Emulated interface
void generateSamples(int16 *buffer, int numSamples) override;
// AudioStream interface
bool isStereo() const override { return false; }
int getRate() const override { return _rate; }
private:
Common::Mutex _mutex;
Audio::PCSpeakerStream *_speaker;
int _rate;
struct Channel {
uint8 pitchBendLow, pitchBendHigh;
uint8 hold;
uint8 modulation;
uint8 voiceProtect;
uint8 noteCount;
} _channel[2];
void resetController(int channel);
struct Note {
bool enabled;
uint8 hardwareChannel;
uint8 midiChannel;
uint8 note;
bool processHold;
uint8 flags;
uint8 hardwareFlags;
uint16 priority;
int16 modulation;
uint16 precedence;
} _note[2];
void noteOn(int channel, int note);
void noteOff(int channel, int note);
void turnNoteOn(int note);
void overwriteNote(int note);
void turnNoteOff(int note);
void setupTone(int note);
uint16 _countdown;
uint8 _hardwareChannel[1];
bool _modulationFlag;
uint8 _timerValue;
void onTimer() override;
static const uint8 _noteTable1[];
static const uint8 _noteTable2[];
};
// for StaticResource (maybe we can find a nicer way to handle it)
struct AmigaSfxTable {
uint8 note;
uint8 patch;
uint16 duration;
uint8 volume;
uint8 pan;
};
class SoundAmiga_LoK : public Sound {
public:
SoundAmiga_LoK(KyraEngine_v1 *vm, Audio::Mixer *mixer);
~SoundAmiga_LoK() override;
kType getMusicType() const override { return kAmiga; } //FIXME
bool init() override;
void initAudioResourceInfo(int set, void *info) override;
void selectAudioResourceSet(int set) override;
bool hasSoundFile(uint file) const override;
void loadSoundFile(uint file) override;
void loadSoundFile(const Common::Path &) override {}
void playTrack(uint8 track) override;
void haltTrack() override;
void beginFadeOut() override;
int32 voicePlay(const char *file, Audio::SoundHandle *handle, uint8 volume, uint8 priority, bool isSfx) override { return -1; }
void playSoundEffect(uint16 track, uint8 volume = 0xFF) override;
protected:
Audio::MaxTrax *_driver;
Audio::SoundHandle _musicHandle;
enum FileType { kFileNone = -1, kFileIntro = 0, kFileGame = 1, kFileFinal = 2 } _fileLoaded;
const AmigaSfxTable *_tableSfxIntro;
int _tableSfxIntro_Size;
const AmigaSfxTable *_tableSfxGame;
int _tableSfxGame_Size;
};
class SoundMacRes;
class HalestormDriver;
class SoundMac : public Sound {
public:
SoundMac(KyraEngine_v1 *vm, Audio::Mixer *mixer);
~SoundMac() override;
kType getMusicType() const override;
bool init() override { return init(musicEnabled() == 1); }
bool init(bool hiQuality);
void initAudioResourceInfo(int, void*) override {}
void selectAudioResourceSet(int set) override;
bool hasSoundFile(uint) const override { return true; }
void loadSoundFile(uint) override {}
void loadSoundFile(const Common::Path &) override {}
void playTrack(uint8 track) override;
void haltTrack() override;
void playSoundEffect(uint16 track, uint8) override;
bool isPlaying() const override;
void beginFadeOut() override;
void updateVolumeSettings() override;
void enableMusic(int enable) override;
private:
void setQuality(bool hi);
SoundMacRes *_res;
HalestormDriver *_driver;
const int _talkieFlag;
bool _ready;
const uint16 *_resIDMusic;
int _currentResourceSet;
static const uint16 _resIDMusicIntro[4];
static const uint16 _resIDMusicIngame[35];
static const uint8 _musicLoopTable[35];
static const uint16 _resIDSfxIntro[2][39];
static const uint16 _resIDSfxIngame[2][39];
struct SoundEffectDef {
uint8 note;
uint8 number;
uint16 rate;
uint8 unk;
};
static const SoundEffectDef _soundEffectDefsIntro[16];
static const SoundEffectDef _soundEffectDefsIngame[120];
};
#ifdef ENABLE_EOB
class SoundTowns_Darkmoon : public Sound, public TownsAudioInterfacePluginDriver {
public:
SoundTowns_Darkmoon(KyraEngine_v1 *vm, Audio::Mixer *mixer);
~SoundTowns_Darkmoon() override;
kType getMusicType() const override { return kTowns; }
bool init() override;
void timerCallback(int timerId) override;
void initAudioResourceInfo(int set, void *info) override;
void selectAudioResourceSet(int set) override;
bool hasSoundFile(uint file) const override;
void loadSoundFile(uint file) override;
void loadSoundFile(const Common::Path &name) override;
void playTrack(uint8 track) override;
void haltTrack() override;
bool isPlaying() const override;
void playSoundEffect(uint16 track, uint8 volume = 0xFF) override;
void stopAllSoundEffects() override;
void beginFadeOut() override;
void updateVolumeSettings() override;
int checkTrigger() override;
void resetTrigger() override;
private:
struct SoundTableEntry {
int8 type;
int32 para1;
int16 para2;
} _soundTable[120];
const char *const *_fileList;
uint _fileListLen;
uint8 _lastSfxChan;
uint8 _lastEnvChan;
uint8 *_pcmData;
uint32 _pcmDataSize;
uint8 _pcmVol;
int _timer;
int _timerSwitch;
SoundResourceInfo_TownsEoB *_resource[3];
TownsAudioInterface *_intf;
};
class AudioMaster2;
class SoundAmiga_EoB: public Sound {
public:
SoundAmiga_EoB(KyraEngine_v1 *vm, Audio::Mixer *mixer);
~SoundAmiga_EoB() override;
kType getMusicType() const override;
bool init() override;
void initAudioResourceInfo(int set, void *info) override;
void selectAudioResourceSet(int set) override;
bool hasSoundFile(uint file) const override { return false; }
void loadSoundFile(uint) override {}
void loadSoundFile(const Common::Path &file) override;
void unloadSoundFile(const Common::String &file) override;
void playTrack(uint8 track) override;
void haltTrack() override;
void playSoundEffect(uint16 track, uint8 volume = 0xFF) override;
void beginFadeOut() override { beginFadeOut(160); }
void beginFadeOut(int delay) override;
void updateVolumeSettings() override;
int checkTrigger() override;
private:
uint8 *_fileBuffer;
KyraEngine_v1 *_vm;
AudioMaster2 *_driver;
SoundResourceInfo_AmigaEoB *_resInfo[3];
Common::String _lastSound;
int _currentResourceSet;
bool _ready;
};
class MLALF98;
class SoundPC98_EoB : public Sound {
public:
SoundPC98_EoB(KyraEngine_v1 *vm, Audio::Mixer *mixer);
~SoundPC98_EoB() override;
kType getMusicType() const override;
bool init() override;
void initAudioResourceInfo(int set, void *info) override;
void selectAudioResourceSet(int set) override;
bool hasSoundFile(uint file) const override { return false; }
void loadSoundFile(uint file) override;
void loadSoundFile(const Common::Path &file) override {}
void loadSfxFile(const Common::Path &file) override;
void playTrack(uint8 track) override;
void haltTrack() override;
void playSoundEffect(uint16 track, uint8) override;
void beginFadeOut() override {}
void updateVolumeSettings() override;
private:
KyraEngine_v1 *_vm;
MLALF98 *_driver;
SoundResourceInfo_PC *_resInfo[3];
int _currentResourceSet;
uint32 _sfxDelay;
bool _ready;
};
class CapcomPC98AudioDriver;
class SoundPC98_Darkmoon : public Sound {
public:
SoundPC98_Darkmoon(KyraEngine_v1 *vm, MidiDriver::DeviceHandle dev, Audio::Mixer *mixer);
~SoundPC98_Darkmoon() override;
kType getMusicType() const override;
kType getSfxType() const override;
bool init() override;
void initAudioResourceInfo(int set, void *info) override;
void selectAudioResourceSet(int set) override;
bool hasSoundFile(uint file) const override { return true; }
void loadSoundFile(uint file) override;
void loadSoundFile(const Common::Path &name) override;
void playTrack(uint8 track) override;
void haltTrack() override;
bool isPlaying() const override;
void playSoundEffect(uint16 track, uint8 volume = 0xFF) override;
void stopAllSoundEffects() override;
void beginFadeOut() override;
void pause(bool paused) override;
void updateVolumeSettings() override;
int checkTrigger() override;
void resetTrigger() override {} // This sound class is for EOB II only, this method is not needed there.
private:
void restartBackgroundMusic();
const uint8 *getData(uint16 track) const;
KyraEngine_v1 *_vm;
CapcomPC98AudioDriver *_driver;
uint8 *_soundData, *_fileBuffer;
int _lastTrack;
const SoundResourceInfo_PC *res() const {return _resInfo[_currentResourceSet]; }
SoundResourceInfo_PC *_resInfo[3];
int _currentResourceSet;
Common::Path _soundFileLoaded;
MidiDriver::DeviceHandle _dev;
kType _drvType;
bool _ready;
};
class SegaAudioDriver;
class SoundSegaCD_EoB : public Sound {
public:
SoundSegaCD_EoB(KyraEngine_v1 *vm, Audio::Mixer *mixer);
~SoundSegaCD_EoB() override;
kType getMusicType() const override;
bool init() override;
void initAudioResourceInfo(int, void*) override {}
void selectAudioResourceSet(int) override {}
bool hasSoundFile(uint file) const override { return false; }
void loadSoundFile(uint file) override {}
void loadSoundFile(const Common::Path &file) override {}
void playTrack(uint8 track) override;
void haltTrack() override;
void playSoundEffect(uint16 track, uint8 volume) override;
bool isPlaying() const override;
void beginFadeOut() override {}
void updateVolumeSettings() override;
private:
void loadPCMData();
void loadFMData();
KyraEngine_v1 *_vm;
SegaAudioDriver *_driver;
uint8 _pcmOffsets[8];
uint16 _fmOffsets[140];
const uint8 *_fmData;
int _lastSoundEffect;
bool _ready;
static const uint8 _fmTrackMap[140];
};
#endif
} // End of namespace Kyra
#endif

View File

@@ -0,0 +1,97 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "kyra/engine/kyra_lok.h"
#include "kyra/sound/sound.h"
#include "common/system.h"
namespace Kyra {
void KyraEngine_LoK::snd_playSoundEffect(int track, int volume) {
if (_flags.platform == Common::kPlatformPC98) {
if (track < 16 || track > 119)
track = 58;
else
track -= 16;
} else if (_flags.platform == Common::kPlatformMacintosh && track >= 97 && track <= 99) {
_sound->playTrack(track - 79);
_lastMusicCommand = -1;
return;
} else if (_flags.platform == Common::kPlatformFMTowns && track == 49) {
snd_playWanderScoreViaMap(56, 1);
return;
}
KyraEngine_v1::snd_playSoundEffect(track);
}
void KyraEngine_LoK::snd_playWanderScoreViaMap(int command, int restart) {
if (restart)
_lastMusicCommand = -1;
if (_flags.platform == Common::kPlatformFMTowns) {
if (command >= 35 && command <= 38) {
snd_playSoundEffect(command - 20);
} else if (command >= 2) {
if (_lastMusicCommand != command)
// the original does -2 here we handle this inside _sound->playTrack()
_sound->playTrack(command);
} else {
_sound->beginFadeOut();
}
_lastMusicCommand = command;
} else if (_flags.platform == Common::kPlatformPC98) {
if (command == 1) {
_sound->beginFadeOut();
} else if ((command >= 2 && command < 53) || command == 55) {
if (_lastMusicCommand != command)
_sound->playTrack(command);
} else {
_sound->haltTrack();
}
_lastMusicCommand = command;
} else {
KyraEngine_v1::snd_playWanderScoreViaMap(command, restart);
}
}
void KyraEngine_LoK::snd_playVoiceFile(int id) {
Common::String vocFile = Common::String::format("%03d", id);
_speechPlayTime = _sound->voicePlay(vocFile.c_str(), &_speechHandle);
}
void KyraEngine_LoK::snd_voiceWaitForFinish(bool ingame) {
while (_sound->voiceIsPlaying() && !skipFlag()) {
if (ingame)
delay(10, true);
else
_system->delayMillis(10);
}
}
uint32 KyraEngine_LoK::snd_getVoicePlayTime() {
if (!snd_voiceIsPlaying())
return 0;
return (_speechPlayTime != -1 ? _speechPlayTime : 0);
}
} // End of namespace Kyra

View File

@@ -0,0 +1,302 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifdef ENABLE_LOL
#include "kyra/engine/lol.h"
#include "kyra/sound/sound.h"
#include "kyra/resource/resource.h"
#include "common/system.h"
#include "audio/audiostream.h"
namespace Kyra {
bool LoLEngine::snd_playCharacterSpeech(int id, int8 speaker, int) {
if (!speechEnabled())
return false;
if (speaker < 65) {
if (_characters[speaker].flags & 1)
speaker = (int)_characters[speaker].name[0];
else
speaker = 0;
}
if (_lastSpeechId == id && speaker == _lastSpeaker)
return true;
_lastSpeechId = id;
_lastSpeaker = speaker;
_nextSpeechId = _nextSpeaker = -1;
Common::String pattern1;
Common::String file1;
Common::String file2;
Common::String file3;
SpeechList newSpeechList;
Common::String pattern2 = Common::String::format("%02d", id & 0x4000 ? 0 : _curTlkFile);
if (id & 0x4000) {
pattern1 = Common::String::format("%03X", id & 0x3FFF);
} else if (id < 1000) {
pattern1 = Common::String::format("%03d", id);
} else {
file3 = Common::String::format("@%04d%c.%s", id - 1000, (char)speaker, pattern2.c_str());
if (_sound->isVoicePresent(file3.c_str()))
newSpeechList.push_back(_sound->getVoiceStream(file3.c_str()));
}
if (file3.empty()) {
for (char i = 0; ; i++) {
char symbol = '0' + i;
file1 = Common::String::format("%s%c%c.%s", pattern1.c_str(), (char)speaker, symbol, pattern2.c_str());
file2 = Common::String::format("%s%c%c.%s", pattern1.c_str(), '_', symbol, pattern2.c_str());
if (_sound->isVoicePresent(file1.c_str()))
newSpeechList.push_back(_sound->getVoiceStream(file1.c_str()));
else if (_sound->isVoicePresent(file2.c_str()))
newSpeechList.push_back(_sound->getVoiceStream(file2.c_str()));
else
break;
}
}
if (newSpeechList.empty())
return false;
while (_sound->voiceIsPlaying(&_speechHandle))
delay(_tickLength, true);
while (_sound->allVoiceChannelsPlaying())
delay(_tickLength);
for (SpeechList::iterator i = _speechList.begin(); i != _speechList.end(); ++i)
delete *i;
_speechList.clear();
_speechList = newSpeechList;
_activeVoiceFileTotalTime = 0;
for (SpeechList::iterator i = _speechList.begin(); i != _speechList.end();) {
// Just in case any file loading failed: Remove the bad streams here.
if (!*i)
i = _speechList.erase(i);
else
_activeVoiceFileTotalTime += (*i++)->getLength().msecs();
}
_sound->playVoiceStream(*_speechList.begin(), &_speechHandle);
_speechList.pop_front();
if (!_activeVoiceFileTotalTime)
return false;
_tim->_abortFlag = 0;
return true;
}
int LoLEngine::snd_updateCharacterSpeech() {
if (_sound->voiceIsPlaying(&_speechHandle))
return 2;
if (_speechList.begin() != _speechList.end()) {
_sound->playVoiceStream(*_speechList.begin(), &_speechHandle);
_speechList.pop_front();
return 2;
} else if (_nextSpeechId != -1) {
_lastSpeechId = _lastSpeaker = -1;
_activeVoiceFileTotalTime = 0;
if (snd_playCharacterSpeech(_nextSpeechId, _nextSpeaker, 0))
return 2;
}
_lastSpeechId = _lastSpeaker = -1;
_activeVoiceFileTotalTime = 0;
return 0;
}
void LoLEngine::snd_stopSpeech(bool setFlag) {
if (!_sound->voiceIsPlaying(&_speechHandle))
return;
//_dlgTimer = 0;
_sound->voiceStop(&_speechHandle);
_activeVoiceFileTotalTime = 0;
_nextSpeechId = _nextSpeaker = -1;
for (SpeechList::iterator i = _speechList.begin(); i != _speechList.end(); ++i)
delete *i;
_speechList.clear();
if (setFlag)
_tim->_abortFlag = 1;
}
void LoLEngine::snd_playSoundEffect(int track, int volume) {
if ((track == 1 && (_lastSfxTrack == -1 || _lastSfxTrack == 1)) || shouldQuit())
return;
_lastSfxTrack = track;
if (track == -1 || track >= _ingameSoundIndexSize / 2)
return;
volume &= 0xFF;
int16 prIndex = _ingameSoundIndex[track * 2 + 1];
uint16 priority = (prIndex > 0) ? (prIndex * volume) >> 8 : -prIndex;
static const uint8 volTable1[] = { 223, 159, 95, 47, 15, 0 };
static const uint8 volTable2[] = { 255, 191, 127, 63, 30, 0 };
for (int i = 0; i < 6; i++) {
if (volTable1[i] < volume) {
volume = volTable2[i];
break;
}
}
int16 vocIndex = _ingameSoundIndex[track * 2];
bool hasVocFile = false;
if (vocIndex != -1) {
if (!_ingameSoundList[vocIndex].equalsIgnoreCase("EMPTY"))
hasVocFile = true;
}
if (hasVocFile) {
if (_sound->isVoicePresent(_ingameSoundList[vocIndex].c_str()))
_sound->voicePlay(_ingameSoundList[vocIndex].c_str(), 0, volume, priority, true);
} else if (_flags.platform == Common::kPlatformDOS) {
if (_sound->getSfxType() == Sound::kMidiMT32)
track = (track < _ingameMT32SoundIndexSize) ? (_ingameMT32SoundIndex[track] - 1) : -1;
else if (_sound->getSfxType() == Sound::kMidiGM)
track = (track < _ingameGMSoundIndexSize) ? (_ingameGMSoundIndex[track] - 1) : -1;
else if (_sound->getSfxType() == Sound::kPCSpkr)
track = (track < _ingamePCSpeakerSoundIndexSize) ? (_ingamePCSpeakerSoundIndex[track] - 1) : -1;
if (track == 168)
track = 167;
if (track != -1)
KyraEngine_v1::snd_playSoundEffect(track, volume);
}
}
bool LoLEngine::snd_processEnvironmentalSoundEffect(int soundId, int block) {
if (!KyraRpgEngine::snd_processEnvironmentalSoundEffect(soundId, block))
return false;
if (block != _currentBlock) {
static const int8 blockShiftTable[] = { -32, -31, 1, 33, 32, 31, -1, -33 };
uint16 cbl = _currentBlock;
for (int i = 3; i > 0; i--) {
int dir = calcMonsterDirection(cbl & 0x1F, cbl >> 5, block & 0x1F, block >> 5);
cbl = (cbl + blockShiftTable[dir]) & 0x3FF;
if (cbl == block)
break;
if (testWallFlag(cbl, 0, 1))
_environmentSfxVol >>= 1;
}
}
if (!soundId || _sceneUpdateRequired)
return false;
return snd_processEnvironmentalSoundEffect(0, 0);
}
void LoLEngine::snd_queueEnvironmentalSoundEffect(int soundId, int block) {
if (_envSfxUseQueue && _envSfxNumTracksInQueue < 10) {
_envSfxQueuedTracks[_envSfxNumTracksInQueue] = soundId;
_envSfxQueuedBlocks[_envSfxNumTracksInQueue] = block;
_envSfxNumTracksInQueue++;
} else {
snd_processEnvironmentalSoundEffect(soundId, block);
}
}
void LoLEngine::snd_playQueuedEffects() {
for (int i = 0; i < _envSfxNumTracksInQueue; i++)
snd_processEnvironmentalSoundEffect(_envSfxQueuedTracks[i], _envSfxQueuedBlocks[i]);
_envSfxNumTracksInQueue = 0;
}
void LoLEngine::snd_loadSoundFile(int track) {
if (!_sound->musicEnabled() || _flags.platform != Common::kPlatformDOS)
return;
snd_stopMusic();
int t = (track - 250) * 3;
if (t < 0 || (_curMusicFileIndex == _musicTrackMap[t] && _curMusicFileExt == (char)_musicTrackMap[t + 1]))
return;
_sound->loadSoundFile(Common::Path(Common::String::format("LORE%02d%c", _musicTrackMap[t], (char)_musicTrackMap[t + 1])));
_curMusicFileIndex = _musicTrackMap[t];
_curMusicFileExt = (char)_musicTrackMap[t + 1];
}
int LoLEngine::snd_playTrack(int track) {
if (track == -1)
return _lastMusicTrack;
int res = _lastMusicTrack;
_lastMusicTrack = track;
if (_sound->musicEnabled()) {
if (_flags.platform == Common::kPlatformDOS) {
snd_loadSoundFile(track);
int t = (track - 250) * 3;
_sound->playTrack(_musicTrackMap[t + 2]);
} else {
_sound->playTrack(track - 249);
}
}
return res;
}
int LoLEngine::snd_stopMusic() {
if (_sound->musicEnabled()) {
if (_sound->isPlaying()) {
_sound->beginFadeOut();
_system->delayMillis(3 * _tickLength);
}
_sound->haltTrack();
}
return snd_playTrack(-1);
}
int LoLEngine::convertVolumeToMixer(int value) {
value -= 2;
return (value * Audio::Mixer::kMaxMixerVolume) / 100;
}
int LoLEngine::convertVolumeFromMixer(int value) {
return (value * 100) / Audio::Mixer::kMaxMixerVolume + 2;
}
} // End of namespace Kyra
#endif // ENABLE_LOL

View File

@@ -0,0 +1,386 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "kyra/engine/util.h"
#include "kyra/resource/resource.h"
#include "kyra/sound/sound_intern.h"
#include "kyra/sound/sound_mac_res.h"
#include "kyra/sound/drivers/halestorm.h"
#include "common/config-manager.h"
#include "common/macresman.h"
#include "common/compression/stuffit.h"
#include "audio/mixer.h"
#define HS_16BITOUTPUT false
namespace Kyra {
SoundMacRes::SoundMacRes(KyraEngine_v1 *vm) : _resMan(0), _stuffItArchive(nullptr), _isTalkie(vm->gameFlags().isTalkie) {
_resMan = new Common::MacResManager[2];
if (vm->gameFlags().useInstallerPackage) {
_stuffItArchive = vm->resource()->getCachedArchive("Install Legend of Kyrandia");
if (!_stuffItArchive)
error("SoundMacRes::SoundMacRes(): Failed to load Legend of Kyrandia installer file");
}
}
SoundMacRes::~SoundMacRes() {
delete[] _resMan;
}
bool SoundMacRes::init() {
if (!_resMan)
return false;
_kyraMacExe = _stuffItArchive ? Common::Path("Legend of Kyrandia\xaa") : Util::findMacResourceFile("Legend of Kyrandia");
if (_kyraMacExe.empty() && _isTalkie)
_kyraMacExe = Util::findMacResourceFile("LK");
if (_kyraMacExe.empty()) {
warning("SoundMacRes::init(): Legend of Kyrandia resource fork not found");
return false;
}
// This will also test whether the resource containers are available.
if (!setQuality(true))
return false;
// Test actual resource fork reading...
Common::SeekableReadStream *test = getResource(2, MKTAG('S', 'M', 'O', 'D'));
if (!test) {
warning("SoundMacRes::init(): Resource fork read test failed for 'Legend of Kyrandia' executable");
return false;
}
delete test;
test = getResource(202, MKTAG('S', 'O', 'N', 'G'));
if (!test) {
warning("SoundMacRes::init(): Resource fork read test failed for 'HQ_Music.res'");
return false;
}
delete test;
return true;
}
Common::SeekableReadStream *SoundMacRes::getResource(uint16 id, uint32 type) {
Common::StackLock lock(_mutex);
Common::SeekableReadStream *res = nullptr;
for (int i = 0; i < 2; ++i) {
if ((res = _resMan[i].getResource(type, id)))
break;
}
return res;
}
bool SoundMacRes::setQuality(bool hi) {
Common::StackLock lock(_mutex);
Common::Path s[2];
s[0] = hi ? "HQ_Music.res" : "LQ_Music.res";
s[1] = _kyraMacExe;
int err = 0;
if (_stuffItArchive) {
for (int i = 0; i < 2; ++i)
err |= (_resMan[i].open(s[i], *_stuffItArchive) ? 0 : (1 << i));
} else {
for (int i = 0; i < 2; ++i)
err |= (_resMan[i].open(s[i]) ? 0 : (1 << i));
}
if (err) {
for (int i = 0; i < 2; ++i) {
if (err & (1 << i))
warning("SoundMacRes::setQuality(): Error opening resource container: '%s'", s[i].toString().c_str());
}
return false;
}
return true;
}
SoundMac::SoundMac(KyraEngine_v1 *vm, Audio::Mixer *mixer) : Sound(vm, mixer), _driver(nullptr), _res(nullptr), _currentResourceSet(-1), _resIDMusic(nullptr), _talkieFlag(vm->gameFlags().isTalkie ? 1 : 0), _ready(false) {
}
SoundMac::~SoundMac() {
delete _driver;
delete _res;
}
Sound::kType SoundMac::getMusicType() const {
return kMac;
}
bool SoundMac::init(bool hiQuality) {
if (_ready)
return true;
_res = new SoundMacRes(_vm);
if (!(_res && _res->init()))
return false;
_driver = new HalestormDriver(_res, _mixer);
if (!(_driver && _driver->init(hiQuality, (hiQuality && _talkieFlag) ? HalestormDriver::kSimple : HalestormDriver::kNone, 1 + _talkieFlag, HS_16BITOUTPUT)))
return false;
setQuality(hiQuality);
_ready = true;
updateVolumeSettings();
return true;
}
void SoundMac::selectAudioResourceSet(int set) {
if (set < 0 || set > 2 || set == _currentResourceSet)
return;
_currentResourceSet = set;
if (set == 0)
_resIDMusic = _resIDMusicIntro;
else if (set == 1)
_resIDMusic = _resIDMusicIngame;
}
void SoundMac::playTrack(uint8 track) {
if (!_musicEnabled || !_ready)
return;
int loop = 0;
if (_currentResourceSet == kMusicIntro) {
track -= 2;
assert(track < 4);
} else if (track == 0xff || track == 3) {
return;
} else if (track == 0 || track == 1) {
beginFadeOut();
return;
} else if (_currentResourceSet == kMusicFinale && track == 2) {
_driver->doCommand(HalestormDriver::kSongPlayLoop, 0x12c);
return;
} else if (_currentResourceSet == kMusicFinale && track == 4) {
_driver->doCommand(HalestormDriver::kSongPlayLoop, 0x12d);
return;
} else {
track -= 11;
assert(track < 35);
loop = _musicLoopTable[track];
}
_driver->doCommand(loop ? HalestormDriver::kSongPlayLoop : HalestormDriver::kSongPlayOnce, _resIDMusic[track]);
}
void SoundMac::haltTrack() {
if (_ready)
_driver->doCommand(HalestormDriver::kSongAbort);
}
void SoundMac::playSoundEffect(uint16 track, uint8) {
if (!_sfxEnabled || !_ready)
return;
if (_currentResourceSet == kMusicIntro) {
if (track > 21 && track < 38)
_driver->startSoundEffect(_resIDSfxIntro[_talkieFlag][_soundEffectDefsIntro[track - 22].number]);
} else {
const SoundEffectDef *se = &_soundEffectDefsIngame[track];
if (se->note)
_driver->enqueueSoundEffect(_resIDSfxIngame[_talkieFlag][se->number], se->rate, se->note);
}
}
bool SoundMac::isPlaying() const {
return _ready && _driver->doCommand(HalestormDriver::kSongIsPlaying);
}
void SoundMac::beginFadeOut() {
if (!_ready)
return;
if (!isPlaying())
return;
_driver->doCommand(HalestormDriver::kSongFadeOut, 30);
while (_driver->doCommand(HalestormDriver::kSongFadeGetState) >= 16)
_vm->delay(8);
_driver->doCommand(HalestormDriver::kSongAbort);
_driver->doCommand(HalestormDriver::kSongFadeReset, 256);
}
void SoundMac::updateVolumeSettings() {
if (!_ready)
return;
bool mute = ConfMan.hasKey("mute") ? ConfMan.getBool("mute") : false;
_driver->setMusicVolume(CLIP<int>(mute ? 0 : ConfMan.getInt("music_volume"), 0, Audio::Mixer::kMaxMixerVolume));
_driver->setSoundEffectVolume(CLIP<int>(mute ? 0 : ConfMan.getInt("sfx_volume"), 0, Audio::Mixer::kMaxMixerVolume));
}
void SoundMac::enableMusic(int enable) {
if (enable && enable != _musicEnabled)
setQuality(enable == 1);
_musicEnabled = enable;
}
void SoundMac::setQuality(bool hi) {
static const uint16 resIDs[2][30] = {
{
0x1b5b, 0x1b5c, 0x1b5e, 0x1b62, 0x1b63, 0x1b6b, 0x1b6c, 0x1b6d,
0x1b6e, 0x1b6f, 0x1b70, 0x1b71, 0x1b72, 0x1b73, 0x1b74, 0x1b75,
0x1b76, 0x1b77, 0x1b78, 0x1b79, 0x1b7a, 0x1b7b, 0x1b7c, 0x1b7d,
0x1b7e, 0x1b8a, 0x1bbc, 0x1bbd, 0x1bbe, 0xffff
},
{
0x1b97, 0x1b98, 0x1b9a, 0x1b9e, 0x1b9f, 0x1b6b, 0x1b6c, 0x1b6d,
0x1b6e, 0x1b6f, 0x1b70, 0x1b71, 0x1b72, 0x1b73, 0x1b74, 0x1b75,
0x1b76, 0x1b77, 0x1b78, 0x1b79, 0x1b7a, 0x1b7b, 0x1b7c, 0x1b7d,
0x1b7e, 0x1b8a, 0x1bbc, 0x1bbd, 0x1bbe, 0xffff
}
};
if (!(_driver && _res))
return;
_driver->doCommand(HalestormDriver::kSongAbort);
_driver->stopAllSoundEffects();
_driver->releaseSamples();
_res->setQuality(hi);
if (hi) {
_driver->changeSystemVoices(7 - _talkieFlag, 4, 1 + _talkieFlag);
_driver->doCommand(HalestormDriver::kSetRateAndIntrplMode, 3 + (_talkieFlag << 1));
} else {
_driver->changeSystemVoices(4, 3 + _talkieFlag, 1 + _talkieFlag);
_driver->doCommand(HalestormDriver::kSetRateAndIntrplMode, 2 + _talkieFlag);
}
_driver->registerSamples(resIDs[_talkieFlag], true);
}
const uint16 SoundMac::_resIDMusicIntro[4] {
0x00c8, 0x00c9, 0x00ca, 0x00cb
};
const uint16 SoundMac::_resIDMusicIngame[35] = {
0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b,
0x006c, 0x006d, 0x006e, 0x006f, 0x0070, 0x0071, 0x0072, 0x0073,
0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x01f4,
0x01f5, 0x01f6, 0x01f7, 0x01f8, 0x01f9, 0x01fa, 0x01fb, 0x01fc,
0x01fd, 0x01fe, 0x01ff
};
const uint8 SoundMac::_musicLoopTable[35] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x01, 0x01,
0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x00, 0x00
};
const uint16 SoundMac::_resIDSfxIntro[2][39] = {
{
0x1b58, 0x1b59, 0x1b5a, 0x1b5b, 0x1b5c, 0x1b5d, 0x1b5e, 0x1b5f,
0x1b60, 0x1b61, 0x1b62, 0x1b63, 0x1b64, 0x1b65, 0x1b66, 0x1b67,
0x1b68, 0x1b69, 0x1b6a, 0x1b6d, 0x1b6c, 0x1b7a, 0x1bbc, 0x1bbd,
0x1bbe, 0x1b71, 0x1b72, 0x1b73, 0x1b74, 0x1b75, 0x1b76, 0x1b77,
0x1b78, 0x1b79, 0x1b7a, 0x1b7b, 0x1b7c, 0x1b7d, 0x1b7e
},
{
0x1b94, 0x1b95, 0x1b96, 0x1b97, 0x1b8b, 0x1b99, 0x1b9a, 0x1b9b,
0x1b9c, 0x1b9d, 0x1b9e, 0x1b9f, 0x1ba0, 0x1ba1, 0x1ba2, 0x1ba3,
0x1ba4, 0x1b69, 0x1b6a, 0x1b6d, 0x1b6c, 0x1b7a, 0x1bbc, 0x1bbd,
0x1bbe, 0x1b71, 0x1b72, 0x1b73, 0x1b74, 0x1b75, 0x1b76, 0x1b77,
0x1b78, 0x1b79, 0x1b7a, 0x1b7b, 0x1b7c, 0x1b7d, 0x1b7e
}
};
const uint16 SoundMac::_resIDSfxIngame[2][39] = {
{
0x1b58, 0x1b59, 0x1b5a, 0x1b5b, 0x1b5c, 0x1b5d, 0x1b5e, 0x1b5f,
0x1b60, 0x1b61, 0x1b62, 0x1b63, 0x1b64, 0x1b65, 0x1b66, 0x1b67,
0x1b68, 0x1b69, 0x1b6a, 0x1b6b, 0x1b6c, 0x1b6d, 0x1b6e, 0x1b6f,
0x1b70, 0x1b71, 0x1b72, 0x1b73, 0x1b74, 0x1b75, 0x1b76, 0x1b77,
0x1b78, 0x1b8a, 0x1b7a, 0x1b7b, 0x1b7c, 0x1b7d, 0x1b7e
},
{
0x1b94, 0x1b95, 0x1b96, 0x1b97, 0x1b98, 0x1b99, 0x1b9a, 0x1b9b,
0x1b9c, 0x1b9d, 0x1b9e, 0x1b9f, 0x1ba0, 0x1ba1, 0x1ba2, 0x1ba3,
0x1ba4, 0x1b69, 0x1b6a, 0x1b6b, 0x1b6c, 0x1b6d, 0x1b6e, 0x1b6f,
0x1b70, 0x1b71, 0x1b72, 0x1b73, 0x1b74, 0x1b75, 0x1b76, 0x1b77,
0x1b78, 0x1b8a, 0x1b7a, 0x1b7b, 0x1b7c, 0x1b7d, 0x1b7e
}
};
const SoundMac::SoundEffectDef SoundMac::_soundEffectDefsIntro[16] = {
{ 0x3c, 0x19, 0x252c, 0x6e }, { 0x3c, 0x19, 0x252c, 0x6e }, { 0x3c, 0x19, 0x252c, 0x6e }, { 0x3c, 0x13, 0x1B91, 0x6e },
{ 0x00, 0x00, 0x0000, 0x00 }, { 0x00, 0x00, 0x0000, 0x00 }, { 0x3c, 0x16, 0x2677, 0x6e }, { 0x00, 0x00, 0x0000, 0x00 },
{ 0x00, 0x00, 0x0000, 0x00 }, { 0x00, 0x00, 0x0000, 0x00 }, { 0x3c, 0x17, 0x1198, 0x6e }, { 0x3c, 0x19, 0x252c, 0x6e },
{ 0x3c, 0x18, 0x22d1, 0x6e }, { 0x3c, 0x19, 0x252c, 0x6e }, { 0x45, 0x03, 0x0224, 0x6e }, { 0x3c, 0x16, 0x2677, 0x6e }
};
const SoundMac::SoundEffectDef SoundMac::_soundEffectDefsIngame[120] = {
{ 0x00, 0x00, 0x0000, 0x00 }, { 0x01, 0x00, 0x0000, 0x00 }, { 0x00, 0x00, 0x0000, 0x00 }, { 0x00, 0x00, 0x0000, 0x00 },
{ 0x00, 0x00, 0x0000, 0x00 }, { 0x00, 0x00, 0x0000, 0x00 }, { 0x00, 0x00, 0x0000, 0x00 }, { 0x00, 0x00, 0x0000, 0x00 },
{ 0x00, 0x00, 0x0000, 0x00 }, { 0x00, 0x00, 0x0000, 0x00 }, { 0x00, 0x00, 0x0000, 0x00 }, { 0x00, 0x00, 0x0000, 0x00 },
{ 0x00, 0x00, 0x0000, 0x00 }, { 0x00, 0x00, 0x0000, 0x00 }, { 0x00, 0x00, 0x0000, 0x00 }, { 0x00, 0x00, 0x0000, 0x00 },
{ 0x00, 0x00, 0x0000, 0x00 }, { 0x00, 0x00, 0x0000, 0x00 }, { 0x00, 0x00, 0x0000, 0x00 }, { 0x00, 0x00, 0x0000, 0x00 },
{ 0x3c, 0x13, 0x0156, 0x78 }, { 0x3c, 0x14, 0x272c, 0x78 }, { 0x3c, 0x15, 0x1b91, 0x78 }, { 0x3c, 0x16, 0x1e97, 0x78 },
{ 0x3c, 0x17, 0x122b, 0x78 }, { 0x3c, 0x16, 0x1e97, 0x78 }, { 0x45, 0x03, 0x0224, 0x78 }, { 0x3c, 0x16, 0x1e97, 0x78 },
{ 0x00, 0x00, 0x0000, 0x00 }, { 0x3c, 0x19, 0x252c, 0x78 }, { 0x2c, 0x04, 0x0910, 0x78 }, { 0x3c, 0x19, 0x252c, 0x78 },
{ 0x3c, 0x1a, 0x3aeb, 0x78 }, { 0x25, 0x1b, 0x138b, 0x78 }, { 0x18, 0x03, 0x0f52, 0x78 }, { 0x3e, 0x1c, 0x0622, 0x78 },
{ 0x3b, 0x1c, 0x0754, 0x78 }, { 0x16, 0x03, 0x206f, 0x78 }, { 0x3c, 0x19, 0x252c, 0x78 }, { 0x3c, 0x1d, 0x09ea, 0x78 },
{ 0x00, 0x00, 0x0000, 0x00 }, { 0x00, 0x00, 0x0000, 0x00 }, { 0x00, 0x00, 0x0000, 0x00 }, { 0x3c, 0x14, 0x272c, 0x78 },
{ 0x3c, 0x1e, 0x036e, 0x78 }, { 0x3c, 0x17, 0x122b, 0x78 }, { 0x4e, 0x0b, 0x0991, 0x78 }, { 0x47, 0x1b, 0x02bc, 0x78 },
{ 0x4c, 0x1b, 0x0211, 0x78 }, { 0x00, 0x00, 0x0000, 0x00 }, { 0x3c, 0x13, 0x0156, 0x78 }, { 0x3c, 0x13, 0x0156, 0x78 },
{ 0x3c, 0x1f, 0x0e9e, 0x78 }, { 0x3c, 0x20, 0x010c, 0x78 }, { 0x00, 0x00, 0x0000, 0x00 }, { 0x3c, 0x19, 0x252c, 0x78 },
{ 0x3c, 0x21, 0x0f7c, 0x78 }, { 0x00, 0x00, 0x0000, 0x00 }, { 0x00, 0x00, 0x0000, 0x00 }, { 0x00, 0x00, 0x0000, 0x00 },
{ 0x2a, 0x0b, 0x4c47, 0x78 }, { 0x00, 0x00, 0x0000, 0x00 }, { 0x00, 0x00, 0x0000, 0x00 }, { 0x00, 0x00, 0x0000, 0x00 },
{ 0x00, 0x00, 0x0000, 0x00 }, { 0x00, 0x00, 0x0000, 0x00 }, { 0x3c, 0x1b, 0x0528, 0x78 }, { 0x00, 0x00, 0x0000, 0x00 },
{ 0x00, 0x00, 0x0000, 0x00 }, { 0x2c, 0x04, 0x0910, 0x78 }, { 0x00, 0x00, 0x0000, 0x00 }, { 0x3c, 0x22, 0x0aee, 0x78 },
{ 0x3c, 0x16, 0x1e97, 0x78 }, { 0x3c, 0x15, 0x1b91, 0x78 }, { 0x00, 0x00, 0x0000, 0x00 }, { 0x00, 0x00, 0x0000, 0x00 },
{ 0x00, 0x00, 0x0000, 0x00 }, { 0x3c, 0x14, 0x272c, 0x78 }, { 0x00, 0x00, 0x0000, 0x00 }, { 0x3c, 0x22, 0x0aee, 0x78 },
{ 0x3c, 0x14, 0x272c, 0x78 }, { 0x32, 0x23, 0x1419, 0x9c }, { 0x3c, 0x19, 0x171c, 0x78 }, { 0x3c, 0x14, 0x272c, 0x78 },
{ 0x3e, 0x1c, 0x0622, 0x78 }, { 0x43, 0x13, 0x0201, 0x78 }, { 0x3c, 0x24, 0x1243, 0x5a }, { 0x3e, 0x20, 0x00ee, 0x78 },
{ 0x3c, 0x19, 0x252c, 0x78 }, { 0x29, 0x04, 0x19ea, 0x78 }, { 0x00, 0x00, 0x0000, 0x00 }, { 0x00, 0x00, 0x0000, 0x00 },
{ 0x00, 0x00, 0x0000, 0x00 }, { 0x3c, 0x20, 0x010c, 0x78 }, { 0x3c, 0x25, 0x30b6, 0x78 }, { 0x3c, 0x19, 0x252c, 0x78 },
{ 0x00, 0x00, 0x0000, 0x00 }, { 0x00, 0x00, 0x0000, 0x00 }, { 0x00, 0x00, 0x0000, 0x00 }, { 0x00, 0x00, 0x0000, 0x00 },
{ 0x00, 0x00, 0x0000, 0x00 }, { 0x3c, 0x16, 0x1e97, 0x78 }, { 0x00, 0x00, 0x0000, 0x00 }, { 0x3c, 0x1a, 0x3aeb, 0x78 },
{ 0x1b, 0x04, 0x39f3, 0x78 }, { 0x30, 0x23, 0x1699, 0x50 }, { 0x3c, 0x15, 0x1b91, 0x78 }, { 0x29, 0x06, 0x19ea, 0x50 },
{ 0x3c, 0x19, 0x252c, 0x78 }, { 0x00, 0x00, 0x0000, 0x00 }, { 0x00, 0x00, 0x0000, 0x00 }, { 0x3c, 0x1a, 0x3aeb, 0x78 },
{ 0x3c, 0x19, 0x252c, 0x78 }, { 0x3c, 0x26, 0x0713, 0x78 }, { 0x3c, 0x26, 0x0713, 0x78 }, { 0x3c, 0x14, 0x272c, 0x78 },
{ 0x30, 0x23, 0x1699, 0x50 }, { 0x30, 0x23, 0x1699, 0x50 }, { 0x00, 0x00, 0x0000, 0x00 }, { 0x3c, 0x13, 0x0156, 0x78 }
};
#undef HS_16BITOUTPUT
#undef HS_INTERPOLATION
} // End of namespace Kyra

View File

@@ -0,0 +1,59 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef KYRA_SOUND_MACRES_H
#define KYRA_SOUND_MACRES_H
#include "common/scummsys.h"
#include "common/str.h"
#include "common/mutex.h"
#include "kyra/sound/drivers/halestorm.h"
namespace Common {
class Archive;
class MacResManager;
class SeekableReadStream;
}
namespace Kyra {
class KyraEngine_v1;
class SoundMacRes final : public HalestormLoader {
public:
SoundMacRes(KyraEngine_v1 *vm);
~SoundMacRes() override;
bool init();
bool setQuality(bool hi);
Common::SeekableReadStream *getResource(uint16 id, uint32 type) override;
private:
Common::Path _kyraMacExe;
Common::MacResManager *_resMan;
Common::Archive *_stuffItArchive;
Common::Mutex _mutex;
const bool _isTalkie;
};
} // End of namespace Kyra
#endif

View File

@@ -0,0 +1,234 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifdef ENABLE_EOB
#include "kyra/sound/sound_intern.h"
#include "kyra/resource/resource.h"
#include "kyra/sound/drivers/capcom98.h"
#include "common/config-manager.h"
namespace Kyra {
SoundPC98_Darkmoon::SoundPC98_Darkmoon(KyraEngine_v1 *vm, MidiDriver::DeviceHandle dev, Audio::Mixer *mixer) : Sound(vm, mixer),
_vm(vm), _driver(nullptr), _soundData(nullptr), _currentResourceSet(-1), _ready(false), _dev(dev), _drvType(kPC98), _lastTrack(-1) {
memset(&_resInfo, 0, sizeof(_resInfo));
_soundData = new uint8[20600];
memset(_soundData, 0, 20600);
_fileBuffer = new uint8[10500];
memset(_fileBuffer, 0, 10500);
MusicType type = MidiDriver::getMusicType(dev);
if (type == MT_MT32)
_drvType = kMidiMT32;
else if (type == MT_GM)
_drvType = kMidiGM;
}
SoundPC98_Darkmoon::~SoundPC98_Darkmoon() {
delete _driver;
delete[] _soundData;
delete[] _fileBuffer;
for (int i = 0; i < 3; i++)
initAudioResourceInfo(i, nullptr);
}
Sound::kType SoundPC98_Darkmoon::getMusicType() const {
return _drvType;
}
Sound::kType SoundPC98_Darkmoon::getSfxType() const {
return kPC98;
}
bool SoundPC98_Darkmoon::init() {
_driver = new CapcomPC98AudioDriver(_mixer, _dev);
_ready = (_soundData && _driver && _driver->isUsable());
return _ready;
}
void SoundPC98_Darkmoon::initAudioResourceInfo(int set, void *info) {
if (set < kMusicIntro || set > kMusicFinale)
return;
delete _resInfo[set];
_resInfo[set] = info ? new SoundResourceInfo_PC(*(SoundResourceInfo_PC*)info) : nullptr;
}
void SoundPC98_Darkmoon::selectAudioResourceSet(int set) {
if (set < kMusicIntro || set > kMusicFinale || set == _currentResourceSet || !_ready)
return;
if (_resInfo[set])
_currentResourceSet = set;
}
void SoundPC98_Darkmoon::loadSoundFile(uint file) {
if (!_ready)
return;
if (file < res()->fileListSize)
loadSoundFile(res()->fileList[file]);
}
void SoundPC98_Darkmoon::loadSoundFile(const Common::Path &name) {
if (!_ready)
return;
haltTrack();
stopAllSoundEffects();
Common::Path path(name);
path.appendInPlace(_drvType == kPC98 ? ".SDO" : ".SDM");
if (!_ready || _soundFileLoaded == path)
return;
Common::SeekableReadStream *in = _vm->resource()->createReadStream(path);
if (!in)
error("SoundPC98_Darkmoon::loadSoundFile(): Failed to load sound file '%s'", path.toString().c_str());
uint16 sz = in->readUint16LE();
uint8 cmp = in->readByte();
in->seek(1, SEEK_CUR);
uint32 outSize = in->readUint32LE();
if ((cmp == 0 && outSize > 10500) || (cmp != 0 && outSize > 20600))
error("SoundPC98_Darkmoon::loadSoundFile(): Failed to load sound file '%s'", path.toString().c_str());
sz -= in->pos();
in->seek(2, SEEK_CUR);
memset(_fileBuffer, 0, 10500);
uint16 readSize = in->read(_fileBuffer, 10500);
assert(sz == readSize);
delete in;
memset(_soundData, 0, 20600);
if (cmp == 0) {
memcpy(_soundData, _fileBuffer, outSize);
} else if (cmp == 3) {
Screen::decodeFrame3(_fileBuffer, _soundData, outSize, true);
} else if (cmp == 4) {
Screen::decodeFrame4(_fileBuffer, _soundData, outSize);
} else {
error("SoundPC98_Darkmoon::loadSoundFile(): Failed to load sound file '%s'", path.toString().c_str());
}
uint16 instrOffs = READ_LE_UINT16(_soundData);
if (instrOffs >= 20600)
error("SoundPC98_Darkmoon::loadSoundFile(): Failed to load sound file '%s'", path.toString().c_str());
_driver->loadFMInstruments(_soundData + instrOffs);
_driver->reset();
}
void SoundPC98_Darkmoon::playTrack(uint8 track) {
if (track == 0 || track == 2)
_lastTrack = track;
playSoundEffect(track, 127);
}
void SoundPC98_Darkmoon::haltTrack() {
if (!_ready)
return;
_driver->stopSong();
_lastTrack = -1;
}
bool SoundPC98_Darkmoon::isPlaying() const {
return _ready && _driver && _driver->songIsPlaying();
}
void SoundPC98_Darkmoon::playSoundEffect(uint16 track, uint8 vol) {
if (!_ready)
return;
if (track == 0 || track == 2) {
restartBackgroundMusic();
return;
}
const uint8 *data = getData(track);
if (!data)
return;
if (track < 52 || track > 67) {
if (_sfxEnabled)
_driver->startSoundEffect(data, vol);
} else if (_musicEnabled) {
_lastTrack = track;
_driver->startSong(data, vol, false);
}
}
void SoundPC98_Darkmoon::stopAllSoundEffects() {
if (_ready)
_driver->stopSoundEffect();
}
void SoundPC98_Darkmoon::beginFadeOut() {
_driver->fadeOut();
}
void SoundPC98_Darkmoon::pause(bool paused) {
if (_ready && paused)
_driver->allNotesOff();
}
int SoundPC98_Darkmoon::checkTrigger() {
return _driver ? _driver->checkSoundMarker() : 99;
}
void SoundPC98_Darkmoon::restartBackgroundMusic() {
if (_lastTrack == -1) {
haltTrack();
stopAllSoundEffects();
} else {
_lastTrack = -1;
const uint8 *data = getData(0);
if (!data)
return;
if (_musicEnabled)
_driver->startSong(data, 127, true);
}
}
const uint8 *SoundPC98_Darkmoon::getData(uint16 track) const {
if (!_ready || track >= 120)
return nullptr;
uint16 offset = READ_LE_UINT16(&_soundData[(track + 1) << 1]);
return (offset < 20600) ? &_soundData[offset] : nullptr;
}
void SoundPC98_Darkmoon::updateVolumeSettings() {
if (!_driver || !_ready)
return;
bool mute = false;
if (ConfMan.hasKey("mute"))
mute = ConfMan.getBool("mute");
_driver->setMusicVolume((mute ? 0 : ConfMan.getInt("music_volume")));
_driver->setSoundEffectVolume((mute ? 0 : ConfMan.getInt("sfx_volume")));
}
} // End of namespace Kyra
#endif

View File

@@ -0,0 +1,127 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifdef ENABLE_EOB
#include "kyra/sound/sound_intern.h"
#include "kyra/resource/resource.h"
#include "kyra/sound/drivers/mlalf98.h"
#include "common/config-manager.h"
namespace Kyra {
SoundPC98_EoB::SoundPC98_EoB(KyraEngine_v1 *vm, Audio::Mixer *mixer) : Sound(vm, mixer),
_vm(vm), _driver(0), _currentResourceSet(-1), _sfxDelay(0), _ready(false) {
memset(_resInfo, 0, sizeof(_resInfo));
}
SoundPC98_EoB::~SoundPC98_EoB() {
delete _driver;
for (int i = 0; i < 3; i++)
initAudioResourceInfo(i, 0);
}
Sound::kType SoundPC98_EoB::getMusicType() const {
return kPC98;
}
bool SoundPC98_EoB::init() {
_driver = new MLALF98(_mixer, MLALF98::kType9801_86);
_ready = true;
return true;
}
void SoundPC98_EoB::initAudioResourceInfo(int set, void *info) {
delete _resInfo[set];
_resInfo[set] = info ? new SoundResourceInfo_PC(*(SoundResourceInfo_PC*)info) : 0;
}
void SoundPC98_EoB::selectAudioResourceSet(int set) {
if (set == _currentResourceSet || !_ready)
return;
if (!_resInfo[set])
return;
_currentResourceSet = set;
}
void SoundPC98_EoB::loadSoundFile(uint file) {
if (!_ready)
return;
if (file >= _resInfo[_currentResourceSet]->fileListSize)
return;
Common::SeekableReadStream *s = _vm->resource()->createReadStream(_resInfo[_currentResourceSet]->fileList[file]);
_driver->loadMusicData(s);
delete s;
}
void SoundPC98_EoB::loadSfxFile(const Common::Path &file) {
if (!_ready)
return;
Common::SeekableReadStream *s = _vm->resource()->createReadStream(file);
_driver->loadSoundEffectData(s);
delete s;
}
void SoundPC98_EoB::playTrack(uint8 track) {
if (!_musicEnabled || !_ready)
return;
_driver->allChannelsOff();
loadSoundFile(track);
_driver->startMusic(0);
}
void SoundPC98_EoB::haltTrack() {
if (!_ready)
return;
playTrack(0);
}
void SoundPC98_EoB::playSoundEffect(uint16 track, uint8) {
if (_currentResourceSet != kMusicIngame || !_sfxEnabled || !_ready || track >= 120 || (track != 28 && _sfxDelay > _vm->_system->getMillis()))
return;
_driver->startSoundEffect(track);
if (track == 28)
_sfxDelay = _vm->_system->getMillis() + 1440;
}
void SoundPC98_EoB::updateVolumeSettings() {
if (!_driver || !_ready)
return;
bool mute = false;
if (ConfMan.hasKey("mute"))
mute = ConfMan.getBool("mute");
_driver->setMusicVolume((mute ? 0 : ConfMan.getInt("music_volume")));
_driver->setSoundEffectVolume((mute ? 0 : ConfMan.getInt("sfx_volume")));
}
} // End of namespace Kyra
#endif

View File

@@ -0,0 +1,145 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "kyra/sound/sound_intern.h"
#include "kyra/resource/resource.h"
#include "audio/softsynth/fmtowns_pc98/towns_pc98_driver.h"
#include "common/config-manager.h"
namespace Kyra {
SoundPC98_LoK::SoundPC98_LoK(KyraEngine_v1 *vm, Audio::Mixer *mixer) :
Sound(vm, mixer), _musicTrackData(nullptr), _sfxTrackData(nullptr), _lastTrack(-1), _driver(nullptr), _currentResourceSet(0) {
memset(&_resInfo, 0, sizeof(_resInfo));
}
SoundPC98_LoK::~SoundPC98_LoK() {
delete[] _musicTrackData;
delete[] _sfxTrackData;
delete _driver;
for (int i = 0; i < 3; i++)
initAudioResourceInfo(i, nullptr);
}
bool SoundPC98_LoK::init() {
_driver = new TownsPC98_AudioDriver(_mixer, TownsPC98_AudioDriver::kType26);
bool reslt = _driver->init();
updateVolumeSettings();
return reslt;
}
void SoundPC98_LoK::initAudioResourceInfo(int set, void *info) {
if (set >= kMusicIntro && set <= kMusicFinale) {
delete _resInfo[set];
_resInfo[set] = info ? new Common::String(((SoundResourceInfo_PC98*)info)->pattern) : nullptr;
}
}
void SoundPC98_LoK::selectAudioResourceSet(int set) {
if (set >= kMusicIntro && set <= kMusicFinale) {
if (_resInfo[set])
_currentResourceSet = set;
}
}
bool SoundPC98_LoK::hasSoundFile(uint file) const {
return true;
}
void SoundPC98_LoK::loadSoundFile(uint) {
if (_currentResourceSet == kMusicIntro) {
delete[] _sfxTrackData;
_sfxTrackData = nullptr;
int dataSize = 0;
const uint8 *tmp = _vm->staticres()->loadRawData(k1PC98IntroSfx, dataSize);
if (!tmp) {
warning("Could not load static intro sound effects data\n");
return;
}
_sfxTrackData = new uint8[dataSize];
memcpy(_sfxTrackData, tmp, dataSize);
}
}
void SoundPC98_LoK::loadSoundFile(const Common::Path &file) {
delete[] _sfxTrackData;
_sfxTrackData = _vm->resource()->fileData(file, nullptr);
}
void SoundPC98_LoK::playTrack(uint8 track) {
track -= 1;
if (track == _lastTrack && _musicEnabled)
return;
beginFadeOut();
Common::String musicFile = Common::String::format(resPattern(), track);
delete[] _musicTrackData;
_musicTrackData = _vm->resource()->fileData(musicFile.c_str(), nullptr);
if (_musicEnabled)
_driver->loadMusicData(_musicTrackData);
_lastTrack = track;
}
void SoundPC98_LoK::haltTrack() {
_lastTrack = -1;
_driver->reset();
}
void SoundPC98_LoK::beginFadeOut() {
if (!_driver->musicPlaying())
return;
for (int i = 0; i < 20; i++) {
_driver->fadeStep();
_vm->delay(32);
}
haltTrack();
}
void SoundPC98_LoK::playSoundEffect(uint16 track, uint8) {
if (!_sfxTrackData)
return;
_driver->loadSoundEffectData(_sfxTrackData, track);
}
void SoundPC98_LoK::updateVolumeSettings() {
if (!_driver)
return;
bool mute = false;
if (ConfMan.hasKey("mute"))
mute = ConfMan.getBool("mute");
_driver->setMusicVolume((mute ? 0 : ConfMan.getInt("music_volume")));
_driver->setSoundEffectVolume((mute ? 0 : ConfMan.getInt("sfx_volume")));
}
} // End of namespace Kyra

View File

@@ -0,0 +1,272 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "kyra/sound/sound_intern.h"
#include "kyra/resource/resource.h"
#include "audio/softsynth/fmtowns_pc98/towns_pc98_driver.h"
#include "common/config-manager.h"
#include "backends/audiocd/audiocd.h"
#include "audio/audiostream.h"
#include "audio/decoders/raw.h"
namespace Kyra {
SoundTownsPC98_v2::SoundTownsPC98_v2(KyraEngine_v1 *vm, Audio::Mixer *mixer) :
Sound(vm, mixer), _currentSFX(nullptr), _musicTrackData(nullptr), _sfxTrackData(nullptr), _lastTrack(-1), _driver(nullptr), _useFmSfx(false), _currentResourceSet(0) {
memset(&_resInfo, 0, sizeof(_resInfo));
}
SoundTownsPC98_v2::~SoundTownsPC98_v2() {
delete[] _musicTrackData;
delete[] _sfxTrackData;
delete _driver;
for (int i = 0; i < 3; i++)
initAudioResourceInfo(i, nullptr);
}
bool SoundTownsPC98_v2::init() {
_driver = new TownsPC98_AudioDriver(_mixer, _vm->gameFlags().platform == Common::kPlatformPC98 ?
TownsPC98_AudioDriver::kType86 : TownsPC98_AudioDriver::kTypeTowns);
if (_vm->gameFlags().platform == Common::kPlatformFMTowns) {
if (_resInfo[_currentResourceSet])
if (_resInfo[_currentResourceSet]->cdaTableSize) {
if (!_vm->existExtractedCDAudioFiles()
&& !_vm->isDataAndCDAudioReadFromSameCD()) {
_vm->warnMissingExtractedCDAudio();
}
}
// Initialize CD for audio
bool hasRealCD = g_system->getAudioCDManager()->open();
// FIXME: While checking for 'track1.XXX(X)' looks like
// a good idea, we should definitely not be doing this
// here. Basically our filenaming scheme could change
// or we could add support for other audio formats. Also
// this misses the possibility that we play the tracks
// right off CD. So we should find another way to
// check if we have access to CD audio.
Resource *r = _vm->resource();
if (_musicEnabled &&
(hasRealCD || r->exists("track1.mp3") || r->exists("track1.ogg") || r->exists("track1.flac") || r->exists("track1.fla")
|| r->exists("track01.mp3") || r->exists("track01.ogg") || r->exists("track01.flac") || r->exists("track01.fla")))
_musicEnabled = 2;
else
_musicEnabled = 1;
_useFmSfx = false;
} else {
_useFmSfx = true;
}
bool reslt = _driver->init();
updateVolumeSettings();
return reslt;
}
void SoundTownsPC98_v2::initAudioResourceInfo(int set, void *info) {
if (set >= kMusicIntro && set <= kMusicFinale) {
delete _resInfo[set];
_resInfo[set] = info ? new SoundResourceInfo_TownsPC98V2(*(SoundResourceInfo_TownsPC98V2*)info) : nullptr;
}
}
void SoundTownsPC98_v2::selectAudioResourceSet(int set) {
if (set >= kMusicIntro && set <= kMusicFinale) {
if (_resInfo[set])
_currentResourceSet = set;
}
}
bool SoundTownsPC98_v2::hasSoundFile(uint file) const {
if (file < res()->fileListSize)
return (res()->fileList[file] != nullptr);
return false;
}
void SoundTownsPC98_v2::loadSoundFile(const Common::Path &file) {
delete[] _sfxTrackData;
_sfxTrackData = _vm->resource()->fileData(file, nullptr);
}
void SoundTownsPC98_v2::process() {
g_system->getAudioCDManager()->update();
}
void SoundTownsPC98_v2::playTrack(uint8 track) {
if (track == _lastTrack && _musicEnabled)
return;
int trackNum = -1;
if (_vm->gameFlags().platform == Common::kPlatformFMTowns) {
for (uint i = 0; i < res()->cdaTableSize >> 1; i++) {
if (track == (uint8)READ_LE_UINT16(&res()->cdaTable[i * 2])) {
trackNum = (int8)READ_LE_UINT16(&res()->cdaTable[i * 2 + 1]) - 1;
break;
}
}
}
beginFadeOut();
Common::String musicFile = res()->pattern ? Common::String::format(res()->pattern, track) : (res()->fileList ? res()->fileList[track] : nullptr);
if (musicFile.empty())
return;
delete[] _musicTrackData;
_musicTrackData = _vm->resource()->fileData(musicFile.c_str(), nullptr);
_driver->loadMusicData(_musicTrackData, true);
if (_musicEnabled == 2 && trackNum != -1) {
g_system->getAudioCDManager()->play(trackNum+1, _driver->looping() ? -1 : 1, 0, 0);
g_system->getAudioCDManager()->update();
} else if (_musicEnabled) {
_driver->cont();
}
_lastTrack = track;
}
void SoundTownsPC98_v2::haltTrack() {
_lastTrack = -1;
g_system->getAudioCDManager()->stop();
g_system->getAudioCDManager()->update();
_driver->reset();
}
void SoundTownsPC98_v2::beginFadeOut() {
if (!_driver->musicPlaying())
return;
for (int i = 0; i < 20; i++) {
_driver->fadeStep();
_vm->delay(32);
}
haltTrack();
}
int32 SoundTownsPC98_v2::voicePlay(const char *file, Audio::SoundHandle *handle, uint8 volume, uint8 priority, bool) {
static const uint16 rates[] = { 0x10E1, 0x0CA9, 0x0870, 0x0654, 0x0438, 0x032A, 0x021C, 0x0194 };
static const char patternHOF[] = "%s.PCM";
static const char patternLOL[] = "%s.VOC";
int h = 0;
if (_currentSFX) {
while (h < kNumChannelHandles && _mixer->isSoundHandleActive(_soundChannels[h].handle))
h++;
if (h >= kNumChannelHandles) {
h = 0;
while (h < kNumChannelHandles && _soundChannels[h].priority > priority)
++h;
if (h < kNumChannelHandles)
voiceStop(&_soundChannels[h].handle);
}
if (h >= kNumChannelHandles)
return 0;
}
Common::String fileName = Common::String::format( _vm->game() == GI_LOL ? patternLOL : patternHOF, file);
uint8 *data = _vm->resource()->fileData(fileName.c_str(), nullptr);
uint8 *src = data;
if (!src)
return 0;
uint16 sfxRate = rates[READ_LE_UINT16(src)];
src += 2;
bool compressed = (READ_LE_UINT16(src) & 1) ? true : false;
src += 2;
uint32 outsize = READ_LE_UINT32(src);
uint8 *sfx = (uint8 *)malloc(outsize);
uint8 *dst = sfx;
src += 4;
if (compressed) {
for (uint32 i = outsize; i;) {
uint8 cnt = *src++;
if (cnt & 0x80) {
cnt &= 0x7F;
memset(dst, *src++, cnt);
} else {
memcpy(dst, src, cnt);
src += cnt;
}
dst += cnt;
i -= cnt;
}
} else {
memcpy(dst, src, outsize);
}
for (uint32 i = 0; i < outsize; i++) {
uint8 cmd = sfx[i];
if (cmd & 0x80) {
cmd = ~cmd;
} else {
cmd |= 0x80;
if (cmd == 0xFF)
cmd--;
}
if (cmd < 0x80)
cmd = 0x80 - cmd;
sfx[i] = cmd;
}
_currentSFX = Audio::makeRawStream(sfx, outsize, sfxRate * 10, Audio::FLAG_UNSIGNED | Audio::FLAG_LITTLE_ENDIAN);
_mixer->playStream(Audio::Mixer::kSFXSoundType, &_soundChannels[h].handle, _currentSFX, -1, volume);
_soundChannels[h].priority = priority;
if (handle)
*handle = _soundChannels[h].handle;
delete[] data;
return 1;
}
void SoundTownsPC98_v2::playSoundEffect(uint16 track, uint8) {
if (!_useFmSfx || !_sfxTrackData)
return;
_driver->loadSoundEffectData(_sfxTrackData, track);
}
void SoundTownsPC98_v2::updateVolumeSettings() {
if (!_driver)
return;
bool mute = false;
if (ConfMan.hasKey("mute"))
mute = ConfMan.getBool("mute");
_driver->setMusicVolume((mute ? 0 : ConfMan.getInt("music_volume")));
_driver->setSoundEffectVolume((mute ? 0 : ConfMan.getInt("sfx_volume")));
}
} // End of namespace Kyra

View File

@@ -0,0 +1,455 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "kyra/sound/sound_intern.h"
#include "kyra/resource/resource.h"
#include "common/system.h"
#include "common/config-manager.h"
#include "common/translation.h"
#include "gui/message.h"
namespace Kyra {
SoundMidiPC::SoundMidiPC(KyraEngine_v1 *vm, Audio::Mixer *mixer, MidiDriver *driver, kType type) : Sound(vm, mixer) {
_driver = driver;
_output = nullptr;
_musicFile = _sfxFile = nullptr;
_currentResourceSet = 0;
memset(&_resInfo, 0, sizeof(_resInfo));
_music = MidiParser::createParser_XMIDI(MidiParser::defaultXMidiCallback, nullptr, 0);
assert(_music);
_music->property(MidiParser::mpDisableAllNotesOffMidiEvents, true);
_music->property(MidiParser::mpDisableAutoStartPlayback, true);
for (int i = 0; i < 3; ++i) {
_sfx[i] = MidiParser::createParser_XMIDI(MidiParser::defaultXMidiCallback, nullptr, i + 1);
assert(_sfx[i]);
_sfx[i]->property(MidiParser::mpDisableAllNotesOffMidiEvents, true);
_sfx[i]->property(MidiParser::mpDisableAutoStartPlayback, true);
}
_musicVolume = _sfxVolume = 0;
_fadeMusicOut = false;
_fadeStartTime = 0;
_type = type;
assert(_type == kMidiMT32 || _type == kMidiGM || _type == kPCSpkr);
// Only General MIDI isn't a Roland MT-32 MIDI implemenation,
// even the PC Speaker driver is a Roland MT-32 based MIDI implementation.
// Thus we set "_nativeMT32" for all types except Gerneral MIDI to true.
_nativeMT32 = (_type != kMidiGM);
// KYRA1 does not include any General MIDI tracks, thus we have
// to overwrite the internal type with MT32 to get the correct
// file extension.
if (_vm->game() == GI_KYRA1 && _type == kMidiGM)
_type = kMidiMT32;
// Display a warning about possibly wrong sound when the user only has
// a General MIDI device, but the game is setup to use Roland MT32 MIDI.
// (This will only happen in The Legend of Kyrandia 1 though, all other
// supported games include special General MIDI tracks).
if (_type == kMidiMT32 && !_nativeMT32) {
::GUI::MessageDialog dialog(_("You appear to be using a General MIDI device,\n"
"but your game only supports Roland MT32 MIDI.\n"
"We try to map the Roland MT32 instruments to\n"
"General MIDI ones. It is still possible that\n"
"some tracks sound incorrect."));
dialog.runModal();
}
}
SoundMidiPC::~SoundMidiPC() {
Common::StackLock lock(_mutex);
_output->setTimerCallback(nullptr, nullptr);
delete _music;
for (int i = 0; i < 3; ++i)
delete _sfx[i];
_output->stopAllNotes();
delete _output; // This automatically frees _driver (!)
if (_musicFile != _sfxFile)
delete[] _sfxFile;
delete[] _musicFile;
for (int i = 0; i < 3; i++)
initAudioResourceInfo(i, nullptr);
}
bool SoundMidiPC::init() {
_output = Audio::MidiDriver_Miles_MIDI_create(_type == kMidiGM ? MT_GM : MT_MT32, "");
assert(_output);
int returnCode = _output->open(_driver, _nativeMT32);
if (returnCode > 0) {
return false;
}
updateVolumeSettings();
_music->setMidiDriver(_output);
_music->setTempo(_output->getBaseTempo());
_music->setTimerRate(_output->getBaseTempo());
for (int i = 0; i < 3; ++i) {
_sfx[i]->setMidiDriver(_output);
_sfx[i]->setTempo(_output->getBaseTempo());
_sfx[i]->setTimerRate(_output->getBaseTempo());
}
_output->setTimerCallback(this, SoundMidiPC::onTimer);
// Load MT-32 and GM initialization files
const char* midiFile = nullptr;
const char* pakFile = nullptr;
if (_nativeMT32 && _type == kMidiMT32) {
if (_vm->game() == GI_KYRA1) {
midiFile = "INTRO";
} else if (_vm->game() == GI_KYRA2) {
midiFile = "HOF_SYX";
pakFile = "AUDIO.PAK";
} else if (_vm->game() == GI_LOL) {
midiFile = "LOREINTR";
if (_vm->gameFlags().isDemo) {
if (_vm->gameFlags().isTalkie) {
pakFile = "ISTARTUP.PAK";
} else if (_vm->resource()->exists("INTROVOC.PAK")) {
// Intro demo
pakFile = "INTROVOC.PAK";
} else {
// Kyra2 SEQ player based demo
pakFile = "GENERAL.PAK";
midiFile = "LOLSYSEX";
}
} else {
if (_vm->gameFlags().isTalkie)
pakFile = (_vm->_flags.lang == Common::FR_FRA) ? "FRE/STARTUP.PAK" : (_vm->_flags.lang == Common::DE_DEU ? "GER/STARTUP.PAK" : "ENG/STARTUP.PAK");
else
pakFile = "INTROVOC.PAK";
}
}
} else if (_type == kMidiGM && _vm->game() == GI_LOL) {
if (_vm->gameFlags().isDemo && _vm->resource()->exists("INTROVOC.PAK")) {
// Intro demo
midiFile = "LOREINTR";
pakFile = "INTROVOC.PAK";
} else {
midiFile = "LOLSYSEX";
pakFile = "GENERAL.PAK";
}
}
if (!midiFile)
return true;
if (pakFile)
_vm->resource()->loadPakFile(pakFile);
loadSoundFile(midiFile);
playTrack(0);
Common::Event event;
while (isPlaying() && !_vm->shouldQuit()) {
_vm->screen()->updateBackendScreen(true);
_vm->_eventMan->pollEvent(event);
_vm->_system->delayMillis(10);
}
if (pakFile)
_vm->resource()->unloadPakFile(pakFile);
return true;
}
void SoundMidiPC::updateVolumeSettings() {
Common::StackLock lock(_mutex);
if (!_output)
return;
bool mute = false;
if (ConfMan.hasKey("mute"))
mute = ConfMan.getBool("mute");
const int newMusVol = (mute ? 0 : ConfMan.getInt("music_volume"));
_sfxVolume = (mute ? 0 : ConfMan.getInt("sfx_volume"));
_output->setSourceVolume(0, newMusVol);
_musicVolume = newMusVol;
for (int i = 1; i < 4; ++i)
_output->setSourceVolume(i, _sfxVolume);
}
void SoundMidiPC::initAudioResourceInfo(int set, void *info) {
if (set >= kMusicIntro && set <= kMusicFinale) {
delete _resInfo[set];
_resInfo[set] = info ? new SoundResourceInfo_PC(*(SoundResourceInfo_PC*)info) : nullptr;
}
}
void SoundMidiPC::selectAudioResourceSet(int set) {
if (set >= kMusicIntro && set <= kMusicFinale) {
if (_resInfo[set])
_currentResourceSet = set;
}
}
bool SoundMidiPC::hasSoundFile(uint file) const {
if (file < res()->fileListSize)
return (res()->fileList[file] != nullptr);
return false;
}
void SoundMidiPC::loadSoundFile(uint file) {
if (file < res()->fileListSize)
loadSoundFile(res()->fileList[file]);
}
void SoundMidiPC::loadSoundFile(const Common::Path &file) {
Common::StackLock lock(_mutex);
Common::Path path = getFileName(file);
if (_mFileName == path)
return;
if (!_vm->resource()->exists(path))
return;
haltTrack();
if (_vm->game() == GI_KYRA1) {
stopAllSoundEffects();
}
delete[] _musicFile;
uint32 fileSize = 0;
_musicFile = _vm->resource()->fileData(path, &fileSize);
_mFileName = path;
_music->loadMusic(_musicFile, fileSize);
// WORKAROUND The track playing during the character selection screen has a
// bug: towards the end of the track, pitch bend events are sent on two
// channels, but pitch bend is not reset to neutral when the track loops.
// This causes two instruments to be out of tune after the track loops.
// This occurs in both the MT-32 and GM versions, but in the GM version the
// pitch bend is smaller, so it is much less noticeable. It was fixed in the
// CD version for MT-32 by adding pitch bend neutral events to the end of
// the track.
// It is fixed here for the MT-32 floppy version and both GM versions by
// moving the for loop event (indicating the start of the loop) before the
// pitch bend neutral events at the start of the track; position is swapped
// with the first pitch bend neutral event. (The pitch bend neutral events
// are sent in a different order, but that makes no practical difference.)
// The initial pitch bend neutral events are then sent again when the track
// loops.
if (path == "LOREINTR.XMI" && fileSize >= 0x6221 && _musicFile[0x6210] == 0xE1) {
// MT-32 floppy version.
// Overwrite first pitch bend event with for loop event.
_musicFile[0x6210] = 0xB6;
_musicFile[0x6211] = 0x74;
_musicFile[0x6212] = 0x00;
// Write pitch event in the old location of the for loop event.
_musicFile[0x621F] = 0xE1;
_musicFile[0x6220] = 0x00;
_musicFile[0x6221] = 0x40;
} else if (path == "LOREINTR.C55" && fileSize >= 0x216D && _musicFile[0x215C] == 0xE0) {
// GM floppy and CD version.
// Overwrite first pitch bend event with for loop event.
_musicFile[0x215C] = 0xB9;
_musicFile[0x215D] = 0x74;
_musicFile[0x215E] = 0x00;
// Write pitch event in the old location of the for loop event.
_musicFile[0x216B] = 0xE0;
_musicFile[0x216C] = 0x00;
_musicFile[0x216D] = 0x40;
}
// Since KYRA1 uses the same file for SFX and Music
// we setup sfx to play from music file as well
if (_vm->game() == GI_KYRA1) {
for (int i = 0; i < 3; ++i) {
_sfx[i]->loadMusic(_musicFile, fileSize);
}
}
}
void SoundMidiPC::loadSfxFile(const Common::Path &file) {
Common::StackLock lock(_mutex);
// Kyrandia 1 doesn't use a special sfx file
if (_vm->game() == GI_KYRA1)
return;
Common::Path path = getFileName(file);
if (_sFileName == path)
return;
if (!_vm->resource()->exists(path))
return;
stopAllSoundEffects();
delete[] _sfxFile;
uint32 fileSize = 0;
_sfxFile = _vm->resource()->fileData(path, &fileSize);
_sFileName = path;
for (int i = 0; i < 3; ++i) {
_sfx[i]->loadMusic(_sfxFile, fileSize);
_sfx[i]->stopPlaying();
}
}
void SoundMidiPC::playTrack(uint8 track) {
if (!_musicEnabled)
return;
haltTrack();
Common::StackLock lock(_mutex);
_fadeMusicOut = false;
_output->setSourceVolume(0, _musicVolume);
if (_music->setTrack(track))
_music->startPlaying();
}
void SoundMidiPC::haltTrack() {
Common::StackLock lock(_mutex);
_music->stopPlaying();
_output->deinitSource(0);
}
bool SoundMidiPC::isPlaying() const {
Common::StackLock lock(_mutex);
return _music->isPlaying();
}
void SoundMidiPC::playSoundEffect(uint16 track, uint8) {
if (!_sfxEnabled)
return;
Common::StackLock lock(_mutex);
for (int i = 0; i < 3; ++i) {
if (!_sfx[i]->isPlaying()) {
if (_sfx[i]->setTrack(track))
_sfx[i]->startPlaying();
return;
}
}
}
void SoundMidiPC::stopAllSoundEffects() {
Common::StackLock lock(_mutex);
for (int i = 0; i < 3; ++i) {
_sfx[i]->stopPlaying();
_output->deinitSource(i+1);
}
}
void SoundMidiPC::beginFadeOut() {
Common::StackLock lock(_mutex);
_fadeMusicOut = true;
_fadeStartTime = _vm->_system->getMillis();
}
void SoundMidiPC::pause(bool paused) {
Common::StackLock lock(_mutex);
if (paused) {
_music->pausePlaying();
for (int i = 0; i < 3; i++)
_sfx[i]->pausePlaying();
if (_output)
_output->stopAllNotes();
} else {
_music->resumePlaying();
for (int i = 0; i < 3; ++i)
_sfx[i]->resumePlaying();
// Possible TODO (IMHO unnecessary): restore notes and/or update _fadeStartTime
}
}
void SoundMidiPC::onTimer(void *data) {
SoundMidiPC *midi = (SoundMidiPC *)data;
Common::StackLock lock(midi->_mutex);
if (midi->_fadeMusicOut) {
static const uint32 musicFadeTime = 1 * 1000;
if (midi->_fadeStartTime + musicFadeTime > midi->_vm->_system->getMillis()) {
int volume = (byte)((musicFadeTime - (midi->_vm->_system->getMillis() - midi->_fadeStartTime)) * midi->_musicVolume / musicFadeTime);
midi->_output->setSourceVolume(0, volume);
} else {
midi->haltTrack();
midi->stopAllSoundEffects();
midi->_fadeMusicOut = false;
// Restore music volume
midi->_output->setSourceVolume(0, midi->_musicVolume);
}
}
midi->_music->onTimer();
for (int i = 0; i < 3; ++i) {
midi->_sfx[i]->onTimer();
}
}
Common::Path SoundMidiPC::getFileName(const Common::Path &str) {
Common::Path file(str);
if (_type == kMidiMT32)
file.appendInPlace(".XMI");
else if (_type == kMidiGM)
file.appendInPlace(".C55");
else if (_type == kPCSpkr)
file.appendInPlace(".PCS");
if (_vm->resource()->exists(file))
return file;
return str.append(".XMI");
}
} // End of namespace Kyra

View File

@@ -0,0 +1,266 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "kyra/sound/sound_intern.h"
#include "kyra/sound/drivers/pc_base.h"
#include "common/system.h"
#include "common/config-manager.h"
namespace Kyra {
// Kyra 1 sound triggers. Most noticeably, these are used towards the end of
// the game, in the castle, to cycle between different songs. The same music is
// used in other places throughout the game, but the player is less likely to
// spend enough time there to notice.
const int SoundPC_v1::_kyra1SoundTriggers[] = {
0, 4, 5, 3
};
const int SoundPC_v1::_kyra1NumSoundTriggers = ARRAYSIZE(SoundPC_v1::_kyra1SoundTriggers);
SoundPC_v1::SoundPC_v1(KyraEngine_v1 *vm, Audio::Mixer *mixer, kType type)
: Sound(vm, mixer), _driver(nullptr), _trackEntries(), _soundDataPtr(nullptr), _type(type), _version(-1) {
memset(_trackEntries, 0, sizeof(_trackEntries));
_soundTriggers = nullptr;
_numSoundTriggers = 0;
_sfxPlayingSound = -1;
_soundFileLoaded.clear();
_currentResourceSet = 0;
memset(&_resInfo, 0, sizeof(_resInfo));
switch (vm->game()) {
case GI_LOL:
_version = (_vm->gameFlags().isDemo && !_vm->gameFlags().isTalkie) ? 3 : 4;
break;
case GI_KYRA2:
_version = 4;
break;
case GI_KYRA1:
_version = 3;
_soundTriggers = _kyra1SoundTriggers;
_numSoundTriggers = _kyra1NumSoundTriggers;
break;
case GI_EOB2:
_version = 2;
break;
case GI_EOB1:
_version = 1;
break;
default:
break;
}
#ifdef ENABLE_EOB
// Correct the type to someting we support. NullSound is treated as a silent AdLib driver.
if (_type != kAdLib && _type != kPCSpkr && _type != kPCjr)
_type = kAdLib;
_driver = (type == kAdLib) ? (_version > 0 ? PCSoundDriver::createAdLib(mixer, _version) : nullptr) : PCSoundDriver::createPCSpk(mixer, _type == kPCjr);
#else
_type = kAdLib;
if (_version > 0)
_driver = PCSoundDriver::createAdLib(mixer, _version);
#endif
assert(_driver);
}
SoundPC_v1::~SoundPC_v1() {
delete _driver;
delete[] _soundDataPtr;
for (int i = 0; i < 3; i++)
initAudioResourceInfo(i, nullptr);
}
bool SoundPC_v1::init() {
_driver->initDriver();
return true;
}
void SoundPC_v1::process() {
int trigger = _driver->getSoundTrigger();
if (trigger < _numSoundTriggers) {
int soundId = _soundTriggers[trigger];
if (soundId)
playTrack(soundId);
} else {
warning("Unknown sound trigger %d", trigger);
// TODO: At this point, we really want to clear the trigger...
}
}
void SoundPC_v1::updateVolumeSettings() {
bool mute = false;
if (ConfMan.hasKey("mute"))
mute = ConfMan.getBool("mute");
int newMusicVolume = mute ? 0 : ConfMan.getInt("music_volume");
//newMusicVolume = (newMusicVolume * 145) / Audio::Mixer::kMaxMixerVolume + 110;
newMusicVolume = CLIP(newMusicVolume, 0, 255);
int newSfxVolume = mute ? 0 : ConfMan.getInt("sfx_volume");
//newSfxVolume = (newSfxVolume * 200) / Audio::Mixer::kMaxMixerVolume + 55;
newSfxVolume = CLIP(newSfxVolume, 0, 255);
_driver->setMusicVolume(newMusicVolume);
_driver->setSfxVolume(newSfxVolume);
}
void SoundPC_v1::playTrack(uint8 track) {
if (_musicEnabled) {
// WORKAROUND: There is a bug in the Kyra 1 "Pool of Sorrow"
// music which causes the channels to get progressively out of
// sync for each loop. To avoid that, we declare that all four
// of the song channels have to jump "in sync".
if (track == 4 && _soundFileLoaded.equalsIgnoreCase("KYRA1B.ADL"))
_driver->setSyncJumpMask(0x000F);
else
_driver->setSyncJumpMask(0);
play(track, 0xFF);
}
}
void SoundPC_v1::haltTrack() {
play(0, 0);
play(0, 0);
//_vm->_system->delayMillis(3 * 60);
}
bool SoundPC_v1::isPlaying() const {
return _driver->isChannelPlaying(0);
}
void SoundPC_v1::playSoundEffect(uint16 track, uint8 volume) {
if (_sfxEnabled)
play(track, volume);
}
void SoundPC_v1::play(uint8 track, uint8 volume) {
uint16 soundId = 0;
if (_version == 4)
soundId = READ_LE_UINT16(&_trackEntries[track<<1]);
else
soundId = _trackEntries[track];
if ((soundId == 0xFFFF && _version == 4) || (soundId == 0xFF && _version < 4) || !_soundDataPtr)
return;
_driver->startSound(soundId, volume);
}
void SoundPC_v1::beginFadeOut() {
play(_version > 2 ? 1 : 15, 0xFF);
}
int SoundPC_v1::checkTrigger() {
return _driver->getSoundTrigger();
}
void SoundPC_v1::resetTrigger() {
_driver->resetSoundTrigger();
}
void SoundPC_v1::initAudioResourceInfo(int set, void *info) {
if (set >= kMusicIntro && set <= kMusicFinale) {
delete _resInfo[set];
_resInfo[set] = info ? new SoundResourceInfo_PC(*(SoundResourceInfo_PC*)info) : nullptr;
}
}
void SoundPC_v1::selectAudioResourceSet(int set) {
if (set >= kMusicIntro && set <= kMusicFinale) {
if (_resInfo[set])
_currentResourceSet = set;
}
}
bool SoundPC_v1::hasSoundFile(uint file) const {
if (file < res()->fileListSize)
return (res()->fileList[file] != nullptr);
return false;
}
void SoundPC_v1::loadSoundFile(uint file) {
if (_version == 1 && (_type == kPCSpkr || _type == kPCjr))
file += 1;
if (file < res()->fileListSize)
internalLoadFile(res()->fileList[file]);
}
void SoundPC_v1::loadSoundFile(const Common::Path &file) {
internalLoadFile(file);
}
void SoundPC_v1::internalLoadFile(const Common::Path &file) {
Common::Path path(file);
path.appendInPlace((_version == 1) ? ".DAT" : (_type == kPCSpkr ? ".SND" : ".ADL"));
if (_soundFileLoaded == path)
return;
if (_soundDataPtr)
haltTrack();
uint8 *fileData = nullptr; uint32 fileSize = 0;
fileData = _vm->resource()->fileData(path, &fileSize);
if (!fileData) {
warning("Couldn't find music file: '%s'", path.toString().c_str());
return;
}
playSoundEffect(0);
playSoundEffect(0);
_driver->stopAllChannels();
int soundDataSize = fileSize;
uint8 *p = fileData;
if (_version == 4) {
memcpy(_trackEntries, p, 500);
p += 500;
soundDataSize -= 500;
} else {
memcpy(_trackEntries, p, 120);
p += 120;
soundDataSize -= 120;
}
uint8 *oldData = _soundDataPtr;
_soundDataPtr = new uint8[soundDataSize];
assert(_soundDataPtr);
memcpy(_soundDataPtr, p, soundDataSize);
_driver->setSoundData(_soundDataPtr, soundDataSize);
delete[] fileData;
delete[] oldData;
_soundFileLoaded = path;
}
} // End of namespace Kyra

View File

@@ -0,0 +1,105 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef KYRA_SOUND_ADLIB_H
#define KYRA_SOUND_ADLIB_H
#include "kyra/sound/sound.h"
#include "common/mutex.h"
namespace Kyra {
class PCSoundDriver;
/**
* AdLib/PC Speaker (early version) implementation of the
* sound output device.
*
* It uses a special sound file format special to EoB I, II,
* Dune II, Kyrandia 1 and 2 and LoL. EoB I has a slightly
* different (oldest) file format, EoB II, Dune II and
* Kyrandia 1 have the exact same format, Kyrandia 2 and
* LoL have a slightly different format.
*
* For PC Speaker this is a little different. Only the EoB
* games use the old driver with this data file format. The
* newer games use a MIDI-like driver (see pcspeaker_v2.cpp).
*
* See AdLibDriver / PCSpeakerDriver for more information.
* @see AdLibDriver
*/
class SoundPC_v1 : public Sound {
public:
SoundPC_v1(KyraEngine_v1 *vm, Audio::Mixer *mixer, kType type);
~SoundPC_v1() override;
kType getMusicType() const override { return _type; }
bool init() override;
void process() override;
void updateVolumeSettings() override;
void initAudioResourceInfo(int set, void *info) override;
void selectAudioResourceSet(int set) override;
bool hasSoundFile(uint file) const override;
void loadSoundFile(uint file) override;
void loadSoundFile(const Common::Path &file) override;
void playTrack(uint8 track) override;
void haltTrack() override;
bool isPlaying() const override;
void playSoundEffect(uint16 track, uint8 volume = 0xFF) override;
void beginFadeOut() override;
int checkTrigger() override;
void resetTrigger() override;
private:
void internalLoadFile(const Common::Path &file);
void play(uint8 track, uint8 volume);
const SoundResourceInfo_PC *res() const {return _resInfo[_currentResourceSet]; }
SoundResourceInfo_PC *_resInfo[3];
int _currentResourceSet;
PCSoundDriver *_driver;
int _version;
kType _type;
uint8 _trackEntries[500];
uint8 *_soundDataPtr;
int _sfxPlayingSound;
Common::Path _soundFileLoaded;
int _numSoundTriggers;
const int *_soundTriggers;
static const int _kyra1NumSoundTriggers;
static const int _kyra1SoundTriggers[];
};
} // End of namespace Kyra
#endif

View File

@@ -0,0 +1,176 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifdef ENABLE_EOB
#include "kyra/sound/drivers/segacd.h"
#include "kyra/sound/sound_intern.h"
#include "kyra/resource/resource.h"
#include "common/config-manager.h"
#include "backends/audiocd/audiocd.h"
namespace Kyra {
SoundSegaCD_EoB::SoundSegaCD_EoB(KyraEngine_v1 *vm, Audio::Mixer *mixer) : Sound(vm, mixer),
_vm(vm), _driver(0), _fmData(0), _lastSoundEffect(-1), _ready(false) {
memset(_pcmOffsets, 0, sizeof(_pcmOffsets));
memset(_fmOffsets, 0, sizeof(_fmOffsets));
}
SoundSegaCD_EoB::~SoundSegaCD_EoB() {
delete _driver;
delete[] _fmData;
}
Sound::kType SoundSegaCD_EoB::getMusicType() const {
return kSegaCD;
}
bool SoundSegaCD_EoB::init() {
_driver = new SegaAudioDriver(_mixer);
g_system->getAudioCDManager()->open();
loadPCMData();
loadFMData();
_ready = true;
return true;
}
void SoundSegaCD_EoB::playTrack(uint8 track) {
if (!_ready)
return;
if (!_musicEnabled) {
haltTrack();
return;
}
int loop = track >> 6;
track &= 0x7F;
g_system->getAudioCDManager()->play(track - 1, loop - 1, 0, 0);
g_system->getAudioCDManager()->update();
}
void SoundSegaCD_EoB::haltTrack() {
if (!_ready)
return;
g_system->getAudioCDManager()->stop();
}
void SoundSegaCD_EoB::playSoundEffect(uint16 track, uint8 volume) {
if (!_sfxEnabled || !_ready)
return;
uint8 flags = track >> 8;
track &= 0xFF;
if (flags & 0x80) {
track--;
assert(track < ARRAYSIZE(_pcmOffsets));
for (uint8 i = 0; i < 8; ++i)
_driver->startPCMSound(i, _pcmOffsets[track]);
} else {
uint8 snd = (flags & 0x40) ? track : _fmTrackMap[track];
if (snd == 0 || snd > 135)
return;
_driver->startFMSound(&_fmData[_fmOffsets[snd - 1]], volume, (SegaAudioDriver::PrioFlags)flags);
_lastSoundEffect = track;
}
}
bool SoundSegaCD_EoB::isPlaying() const {
return g_system->getAudioCDManager()->isPlaying();
}
void SoundSegaCD_EoB::updateVolumeSettings() {
if (!_driver || !_ready)
return;
bool mute = false;
if (ConfMan.hasKey("mute"))
mute = ConfMan.getBool("mute");
_driver->setMusicVolume((mute ? 0 : ConfMan.getInt("music_volume")));
_driver->setSoundEffectVolume((mute ? 0 : ConfMan.getInt("sfx_volume")));
}
void SoundSegaCD_EoB::loadPCMData() {
uint32 dataSize;
uint8 *data1 = _vm->resource()->fileData("PCM", &dataSize);
if (!data1)
error("SoundSegaCD_EoB::loadPCMData: File not found: '%s'", "PCM");
uint8 *data2 = new uint8[0x100];
memset(data2, 0xFF, 0x100);
data2[0] = 0x80;
for (int i = 0; i < ARRAYSIZE(_pcmOffsets); ++i)
_pcmOffsets[i] = data1[(i << 2) + 2];
_driver->loadPCMData(0, data1 + 64, dataSize - 64);
_driver->loadPCMData(0xFF00, data2, 0x100);
delete[] data1;
delete[] data2;
}
void SoundSegaCD_EoB::loadFMData() {
Common::SeekableReadStreamEndian *in = _vm->resource()->createEndianAwareReadStream("FMSE");
if (!in)
error("SoundSegaCD_EoB::loadFMData: File not found: '%s'", "FMSE");
for (int i = 0; i < ARRAYSIZE(_fmOffsets); ++i)
_fmOffsets[i] = (in->readUint32() - ARRAYSIZE(_fmOffsets) * 4) & 0xFFFF;
uint32 dataSize = in->size() - in->pos();
uint8 *data = new uint8[dataSize];
in->read(data, dataSize);
delete[] _fmData;
_fmData = data;
delete in;
}
const uint8 SoundSegaCD_EoB::_fmTrackMap[140] = {
0x00, 0x05, 0x40, 0x01, 0x02, 0x02, 0x06, 0x07, 0x12, 0x0a,
0x11, 0x1f, 0x1e, 0x12, 0x09, 0x0c, 0x0b, 0x17, 0x21, 0x0d,
0x00, 0x14, 0x00, 0x16, 0x00, 0x00, 0x26, 0x0f, 0x13, 0x10,
0x00, 0x00, 0x27, 0x00, 0x1a, 0x28, 0x39, 0x46, 0x33, 0x4a,
0x3b, 0x48, 0x33, 0x47, 0x38, 0x4d, 0x3e, 0x45, 0x36, 0x41,
0x3a, 0x49, 0x46, 0x38, 0x44, 0x37, 0x42, 0x34, 0x4b, 0x3c,
0x41, 0x3b, 0x40, 0x38, 0x47, 0x39, 0x4d, 0x35, 0x4c, 0x3d,
0x4e, 0x3d, 0x42, 0x43, 0x36, 0x32, 0x00, 0x60, 0x1f, 0x82,
0x1c, 0x29, 0x00, 0x00, 0x00, 0x65, 0x24, 0x60, 0x62, 0x6d,
0x00, 0x78, 0x70, 0x74, 0x7e, 0x7d, 0x66, 0x81, 0x6a, 0x67,
0x80, 0x68, 0x64, 0x6c, 0x77, 0x77, 0x77, 0x61, 0x61, 0x61,
0x71, 0x79, 0x7f, 0x73, 0x7a, 0x7b, 0x71, 0x7c, 0x6e, 0x0e,
0x75, 0x76, 0x78, 0x6b, 0x30, 0x2f, 0x03, 0x04, 0x23, 0x2b,
0x5a, 0x1a, 0x1c, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
} // End of namespace Kyra
#endif // ENABLE_EOB

View File

@@ -0,0 +1,293 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifdef ENABLE_EOB
#include "kyra/sound/sound_intern.h"
#include "kyra/resource/resource.h"
#include "common/config-manager.h"
#include "backends/audiocd/audiocd.h"
#include "audio/audiostream.h"
#include "audio/decoders/raw.h"
namespace Kyra {
SoundTowns_Darkmoon::SoundTowns_Darkmoon(KyraEngine_v1 *vm, Audio::Mixer *mixer) : Sound(vm, mixer) {
_intf = new TownsAudioInterface(mixer, this);
_pcmData = 0;
_pcmDataSize = 0;
_pcmVol = 0;
_timer = 0;
_timerSwitch = 0;
_fileList = nullptr;
_fileListLen = 0;
_lastEnvChan = _lastSfxChan = 0;
memset(_resource, 0, sizeof(_resource));
memset(_soundTable, 0, sizeof(_soundTable));
}
SoundTowns_Darkmoon::~SoundTowns_Darkmoon() {
for (int i = 0; i < 3; i++)
initAudioResourceInfo(i, 0);
delete _intf;
delete[] _pcmData;
}
bool SoundTowns_Darkmoon::init() {
if (!_intf->init())
return false;
_intf->callback(21, 255, 1);
_intf->callback(21, 0, 1);
_intf->callback(22, 255, 221);
_intf->callback(70, 0x31);
_intf->callback(33, 1);
_intf->callback(8, 0x47, 127);
_intf->callback(67, 1, 127, 127);
_intf->setSoundEffectChanMask(-1);
_lastSfxChan = 0x46;
_lastEnvChan = 0x40;
updateVolumeSettings();
return true;
}
void SoundTowns_Darkmoon::timerCallback(int timerId) {
switch (timerId) {
case 1:
_timerSwitch = (_timerSwitch + 1) % 4;
if (!_timerSwitch)
_timer++;
break;
default:
break;
}
}
void SoundTowns_Darkmoon::initAudioResourceInfo(int set, void *info) {
delete _resource[set];
_resource[set] = info ? new SoundResourceInfo_TownsEoB(*(SoundResourceInfo_TownsEoB*)info) : 0;
}
void SoundTowns_Darkmoon::selectAudioResourceSet(int set) {
delete[] _pcmData;
if (!_resource[set] || !_resource[kMusicIngame])
return;
_fileList = _resource[set]->fileList;
_fileListLen = _resource[set]->numFiles;
_pcmDataSize = _resource[kMusicIngame]->pcmDataSize;
_pcmData = new uint8[_pcmDataSize];
_pcmVol = _resource[set]->pcmVolume;
memcpy(_pcmData, _resource[kMusicIngame]->pcmData, _pcmDataSize);
if (set == kMusicIngame)
return;
memcpy(_pcmData, _resource[set]->pcmData, _resource[set]->pcmDataSize);
}
bool SoundTowns_Darkmoon::hasSoundFile(uint file) const {
return true;
}
void SoundTowns_Darkmoon::loadSoundFile(uint file) {
if (file < _fileListLen)
loadSoundFile(_fileList[file]);
}
void SoundTowns_Darkmoon::loadSoundFile(const Common::Path &name) {
Common::SeekableReadStream *s = _vm->resource()->createReadStream(name.append(".SDT"));
if (!s)
error("Failed to load sound file '%s.SDT'", name.toString().c_str());
for (int i = 0; i < 120; i++) {
_soundTable[i].type = s->readSByte();
_soundTable[i].para1 = s->readSint32LE();
_soundTable[i].para2 = s->readSint16LE();
}
delete s;
uint32 bytesLeft;
uint8 *pmb = _vm->resource()->fileData(name.append(".PMB"), &bytesLeft);
_vm->delay(300);
if (pmb) {
uint8 *src = pmb + 8;
for (int i = 0; i < 32; i++)
_intf->callback(5, 0x40, i, &src[i << 7]);
_intf->callback(35, -1);
src += 0x1000;
bytesLeft -= 0x1008;
while (bytesLeft) {
_intf->callback(34, src);
uint32 len = READ_LE_UINT16(&src[12]) + 32;
src = src + len;
bytesLeft -= len;
}
delete[] pmb;
} else {
warning("Sound file '%s.PMB' not found.", name.toString().c_str());
// TODO
}
}
void SoundTowns_Darkmoon::playTrack(uint8 track) {
if (track >= 120 || !_sfxEnabled)
return;
uint8 *pcm = 0;
switch (_soundTable[track].type) {
case -1:
if (track == 0)
haltTrack();
else if (track == 2)
beginFadeOut();
break;
case 0:
if (_soundTable[track].para1 == -1 || (uint32)_soundTable[track].para1 > _pcmDataSize)
return;
pcm = _pcmData + _soundTable[track].para1;
WRITE_LE_UINT16(&pcm[24], _soundTable[track].para2 * 98 / 1000);
_intf->callback(39, 0x47);
_intf->callback(37, 0x47, 60, track == 11 ? 127 : _pcmVol, pcm);
break;
case 2:
resetTrigger();
g_system->getAudioCDManager()->play(_soundTable[track].para1 - 1, 1, 0, 0);
break;
case 3:
_lastSfxChan ^= 3;
_intf->callback(39, _lastSfxChan);
_intf->callback(4, _lastSfxChan, _soundTable[track].para1);
_intf->callback(1, _lastSfxChan, _soundTable[track].para2, 127);
break;
default:
break;
}
}
void SoundTowns_Darkmoon::haltTrack() {
_intf->callback(39, 0x47);
_intf->callback(39, 0x46);
_intf->callback(39, 0x45);
g_system->getAudioCDManager()->stop();
}
bool SoundTowns_Darkmoon::isPlaying() const {
return g_system->getAudioCDManager()->isPlaying();
}
void SoundTowns_Darkmoon::playSoundEffect(uint16 track, uint8 volume) {
if (!_sfxEnabled)
return;
if (volume == 255)
return playTrack(track);
uint8 *pcm = 0;
switch (_soundTable[track].type) {
case 0:
if (_soundTable[track].para1 == -1 || (uint32)_soundTable[track].para1 > _pcmDataSize)
return;
pcm = _pcmData + _soundTable[track].para1;
WRITE_LE_UINT16(&pcm[24], _soundTable[track].para2 * 98 / 1000);
_intf->callback(39, 0x47);
_intf->callback(37, 0x47, 60, volume, pcm);
break;
case 3:
_intf->callback(2, _lastEnvChan);
_intf->callback(4, _lastEnvChan, _soundTable[track].para1);
_intf->callback(1, _lastEnvChan, _soundTable[track].para2, volume);
break;
default:
break;
}
if (++_lastEnvChan == 0x43)
_lastEnvChan = 0x40;
}
void SoundTowns_Darkmoon::stopAllSoundEffects() {
_intf->callback(39, 0x42);
_intf->callback(39, 0x41);
_intf->callback(39, 0x40);
}
void SoundTowns_Darkmoon::beginFadeOut() {
for (int vol = 127; vol >= 0; vol -= 2) {
_intf->callback(67, 1, vol, vol);
_vm->delay(16);
}
_intf->callback(67, 1, 0, 0);
_intf->callback(70, 1);
g_system->getAudioCDManager()->stop();
_intf->callback(70, 0x31);
_intf->callback(67, 1, 127, 127);
}
void SoundTowns_Darkmoon::updateVolumeSettings() {
bool mute = (ConfMan.hasKey("mute")) ? ConfMan.getBool("mute") : false;
_intf->setSoundEffectVolume((mute ? 0 : ConfMan.getInt("sfx_volume")));
}
int SoundTowns_Darkmoon::checkTrigger() {
return _timer;
}
void SoundTowns_Darkmoon::resetTrigger() {
_timer = 0;
_timerSwitch = 0;
}
} // End of namespace Kyra
#endif

View File

@@ -0,0 +1,402 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "kyra/sound/sound_intern.h"
#include "kyra/resource/resource.h"
#include "audio/softsynth/fmtowns_pc98/towns_euphony.h"
#include "common/config-manager.h"
#include "backends/audiocd/audiocd.h"
namespace Kyra {
SoundTowns_LoK::SoundTowns_LoK(KyraEngine_v1 *vm, Audio::Mixer *mixer)
: Sound(vm, mixer), _lastTrack(-1), _musicTrackData(nullptr), _sfxFileData(nullptr), _cdaPlaying(false),
_sfxFileIndex((uint)-1), _musicFadeTable(nullptr), _sfxWDTable(nullptr), _sfxBTTable(nullptr), _sfxChannel(0x46), _currentResourceSet(0) {
memset(&_resInfo, 0, sizeof(_resInfo));
_player = new EuphonyPlayer(_mixer);
}
SoundTowns_LoK::~SoundTowns_LoK() {
g_system->getAudioCDManager()->stop();
haltTrack();
delete _player;
delete[] _musicTrackData;
delete[] _sfxFileData;
for (int i = 0; i < 3; i++)
initAudioResourceInfo(i, nullptr);
}
bool SoundTowns_LoK::init() {
if (!_vm->existExtractedCDAudioFiles()
&& !_vm->isDataAndCDAudioReadFromSameCD()) {
_vm->warnMissingExtractedCDAudio();
}
int unused = 0;
_musicFadeTable = _vm->staticres()->loadRawData(k1TownsMusicFadeTable, unused);
_sfxWDTable = _vm->staticres()->loadRawData(k1TownsSFXwdTable, unused);
_sfxBTTable = _vm->staticres()->loadRawData(k1TownsSFXbtTable, unused);
_musicTrackData = new uint8[50570];
if (!_player->init())
return false;
if (!loadInstruments())
return false;
/*_player->driver()->intf()->callback(68);
_player->driver()->intf()->callback(70, 0x33);*/
_player->driver()->setOutputVolume(1, 118, 118);
// Initialize CD for audio
g_system->getAudioCDManager()->open();
return true;
}
void SoundTowns_LoK::process() {
g_system->getAudioCDManager()->update();
}
void SoundTowns_LoK::playTrack(uint8 track) {
if (track < 2)
return;
track -= 2;
uint tTableIndex = 3 * track;
assert(tTableIndex + 2 < res()->cdaTableSize);
int trackNum = (int)READ_LE_UINT32(&res()->cdaTable[tTableIndex + 2]);
int32 loop = (int32)READ_LE_UINT32(&res()->cdaTable[tTableIndex + 1]);
if (track == _lastTrack && _musicEnabled)
return;
beginFadeOut();
if (_musicEnabled == 2 && trackNum != -1) {
_player->driver()->setOutputVolume(1, 118, 118);
g_system->getAudioCDManager()->play(trackNum + 1, loop ? -1 : 1, 0, 0);
g_system->getAudioCDManager()->update();
_cdaPlaying = true;
} else if (_musicEnabled) {
playEuphonyTrack(READ_LE_UINT32(&res()->cdaTable[tTableIndex]), loop);
_cdaPlaying = false;
}
_lastTrack = track;
}
void SoundTowns_LoK::haltTrack() {
_lastTrack = -1;
g_system->getAudioCDManager()->stop();
g_system->getAudioCDManager()->update();
_cdaPlaying = false;
for (int i = 0; i < 6; i++)
_player->driver()->channelVolume(i, 0);
for (int i = 0x40; i < 0x46; i++)
_player->driver()->channelVolume(i, 0);
for (int i = 0; i < 32; i++)
_player->configPart_enable(i, 0);
_player->stop();
}
void SoundTowns_LoK::initAudioResourceInfo(int set, void *info) {
if (set >= kMusicIntro && set <= kMusicFinale) {
delete _resInfo[set];
_resInfo[set] = info ? new SoundResourceInfo_Towns(*(SoundResourceInfo_Towns*)info) : nullptr;
}
}
void SoundTowns_LoK::selectAudioResourceSet(int set) {
if (set >= kMusicIntro && set <= kMusicFinale) {
if (_resInfo[set])
_currentResourceSet = set;
}
}
bool SoundTowns_LoK::hasSoundFile(uint file) const {
if (file < res()->fileListSize)
return (res()->fileList[file] != nullptr);
return false;
}
void SoundTowns_LoK::loadSoundFile(uint file) {
if (_sfxFileIndex == file || file >= res()->fileListSize)
return;
_sfxFileIndex = file;
delete[] _sfxFileData;
_sfxFileData = _vm->resource()->fileData(res()->fileList[file], nullptr);
}
void SoundTowns_LoK::playSoundEffect(uint16 track, uint8) {
if (!_sfxEnabled || !_sfxFileData)
return;
if (track == 0 || track == 10) {
stopAllSoundEffects();
return;
} else if (track == 1) {
fadeOutSoundEffects();
return;
}
uint8 note = 60;
if (_sfxFileIndex == 5) {
if (track == 16) {
note = 62;
track = 15;
} else if (track == 17) {
note = 64;
track = 15;
} else if (track == 18) {
note = 65;
track = 15;
}
}
uint8 *fileBody = _sfxFileData + 0x01B8;
int32 offset = (int32)READ_LE_UINT32(_sfxFileData + (track - 0x0B) * 4);
if (offset == -1)
return;
if (!_player->driver()->soundEffectIsPlaying(_sfxChannel ^ 1)) {
_sfxChannel ^= 1;
} else if (_player->driver()->soundEffectIsPlaying(_sfxChannel)) {
_sfxChannel ^= 1;
_player->driver()->stopSoundEffect(_sfxChannel);
}
uint32 *sfxHeader = (uint32 *)(fileBody + offset);
uint32 sfxHeaderID = READ_LE_UINT32(sfxHeader);
uint32 playbackBufferSize = sfxHeaderID == 1 ? 30704 : READ_LE_UINT32(&sfxHeader[3]);
uint8 *sfxPlaybackBuffer = new uint8[playbackBufferSize + 32];
memcpy(sfxPlaybackBuffer, fileBody + offset, 32);
uint8 *dst = sfxPlaybackBuffer + 32;
memset(dst, 0x80, playbackBufferSize);
uint8 *sfxBody = ((uint8 *)sfxHeader) + 0x20;
if (!sfxHeaderID) {
memcpy(dst, sfxBody, playbackBufferSize);
} else if (sfxHeaderID == 1) {
Screen::decodeFrame4(sfxBody, dst, playbackBufferSize);
} else if (_sfxWDTable) {
uint8 *tgt = dst;
uint32 sfx_BtTable_Offset = 0;
uint32 sfx_WdTable_Offset = 0;
uint32 sfx_WdTable_Number = 5;
uint32 inSize = READ_LE_UINT32(&sfxHeader[1]);
for (uint32 i = 0; i < inSize; i++) {
sfx_WdTable_Offset = (sfx_WdTable_Number * 3 << 9) + sfxBody[i] * 6;
sfx_WdTable_Number = READ_LE_UINT16(_sfxWDTable + sfx_WdTable_Offset);
sfx_BtTable_Offset += (int16)READ_LE_UINT16(_sfxWDTable + sfx_WdTable_Offset + 2);
*tgt++ = _sfxBTTable[((sfx_BtTable_Offset >> 2) & 0xFF)];
sfx_BtTable_Offset += (int16)READ_LE_UINT16(_sfxWDTable + sfx_WdTable_Offset + 4);
*tgt++ = _sfxBTTable[((sfx_BtTable_Offset >> 2) & 0xFF)];
}
}
_player->driver()->channelVolume(_sfxChannel, 127);
_player->driver()->channelPan(_sfxChannel, 0x40);
_player->driver()->channelPitch(_sfxChannel, 0);
_player->driver()->playSoundEffect(_sfxChannel, note, 127, sfxPlaybackBuffer);
delete[] sfxPlaybackBuffer;
}
void SoundTowns_LoK::updateVolumeSettings() {
if (!_player)
return;
bool mute = false;
if (ConfMan.hasKey("mute"))
mute = ConfMan.getBool("mute");
_player->driver()->setMusicVolume((mute ? 0 : ConfMan.getInt("music_volume")));
_player->driver()->setSoundEffectVolume((mute ? 0 : ConfMan.getInt("sfx_volume")));
}
void SoundTowns_LoK::enableMusic(int enable) {
if (enable && enable != _musicEnabled && _lastTrack != -1)
haltTrack();
_musicEnabled = enable;
}
void SoundTowns_LoK::stopAllSoundEffects() {
_player->driver()->channelVolume(0x46, 0);
_player->driver()->channelVolume(0x47, 0);
_player->driver()->stopSoundEffect(0x46);
_player->driver()->stopSoundEffect(0x47);
_sfxChannel = 0x46;
}
void SoundTowns_LoK::beginFadeOut() {
if (_cdaPlaying) {
for (int i = 118; i > 103; i--) {
_player->driver()->setOutputVolume(1, i, i);
_vm->delay(2 * _vm->tickLength());
}
for (int i = 103; i > 83; i -= 2) {
_player->driver()->setOutputVolume(1, i, i);
_vm->delay(2 * _vm->tickLength());
}
for (int i = 83; i > 58; i -= 2) {
_player->driver()->setOutputVolume(1, i, i);
_vm->delay(_vm->tickLength());
}
for (int i = 58; i > 0; i--) {
_player->driver()->setOutputVolume(1, i, i);
_vm->delay(1);
}
_player->driver()->setOutputVolume(1, 0, 0);
} else {
if (_lastTrack == -1 || !_player->isPlaying())
return;
uint32 ticks = 2;
int tickAdv = 0;
uint16 fadeVolCur[12];
uint16 fadeVolStep[12];
for (int i = 0; i < 6; i++) {
fadeVolCur[i] = READ_LE_UINT16(&_musicFadeTable[(_lastTrack * 12 + i) * 2]);
fadeVolStep[i] = fadeVolCur[i] / 50;
fadeVolCur[i + 6] = READ_LE_UINT16(&_musicFadeTable[(_lastTrack * 12 + 6 + i) * 2]);
fadeVolStep[i + 6] = fadeVolCur[i + 6] / 30;
}
for (int i = 0; i < 12; i++) {
for (int ii = 0; ii < 6; ii++)
_player->driver()->channelVolume(ii, fadeVolCur[ii]);
for (int ii = 0x40; ii < 0x46; ii++)
_player->driver()->channelVolume(ii, fadeVolCur[ii - 0x3A]);
for (int ii = 0; ii < 6; ii++) {
fadeVolCur[ii] -= fadeVolStep[ii];
if (fadeVolCur[ii] < 10)
fadeVolCur[ii] = 0;
fadeVolCur[ii + 6] -= fadeVolStep[ii + 6];
if (fadeVolCur[ii + 6] < 10)
fadeVolCur[ii + 6] = 0;
}
if (++tickAdv == 3) {
tickAdv = 0;
ticks += 2;
}
_vm->delay(ticks * _vm->tickLength());
}
}
haltTrack();
}
bool SoundTowns_LoK::loadInstruments() {
uint8 *twm = _vm->resource()->fileData("twmusic.pak", nullptr);
if (!twm)
return false;
Screen::decodeFrame4(twm, _musicTrackData, 50570);
for (int i = 0; i < 128; i++)
_player->driver()->loadInstrument(0, i, &_musicTrackData[i * 48 + 8]);
Screen::decodeFrame4(twm + 3232, _musicTrackData, 50570);
for (int i = 0; i < 32; i++)
_player->driver()->loadInstrument(0x40, i, &_musicTrackData[i * 128 + 8]);
_player->driver()->unloadWaveTable(-1);
uint8 *src = &_musicTrackData[32 * 128 + 8];
for (int i = 0; i < 10; i++) {
_player->driver()->loadWaveTable(src);
src = src + READ_LE_UINT16(&src[12]) + 32;
}
_player->driver()->reserveSoundEffectChannels(2);
delete[] twm;
return true;
}
void SoundTowns_LoK::playEuphonyTrack(uint32 offset, int loop) {
uint8 *twm = _vm->resource()->fileData("twmusic.pak", nullptr);
Screen::decodeFrame4(twm + 19312 + offset, _musicTrackData, 50570);
delete[] twm;
const uint8 *src = _musicTrackData + 852;
for (int i = 0; i < 32; i++)
_player->configPart_enable(i, *src++);
for (int i = 0; i < 32; i++)
_player->configPart_setType(i, *src++);
for (int i = 0; i < 32; i++)
_player->configPart_remap(i, *src++);
for (int i = 0; i < 32; i++)
_player->configPart_adjustVolume(i, *src++);
for (int i = 0; i < 32; i++)
_player->configPart_setTranspose(i, *src++);
src = _musicTrackData + 1748;
for (int i = 0; i < 6; i++)
_player->driver()->assignPartToChannel(i, *src++);
for (int i = 0x40; i < 0x46; i++)
_player->driver()->assignPartToChannel(i, *src++);
uint32 trackSize = READ_LE_UINT32(_musicTrackData + 2048);
uint8 startTick = _musicTrackData[2052];
_player->setTempo(_musicTrackData[2053]);
src = _musicTrackData + 2054;
uint32 l = READ_LE_UINT32(src + trackSize);
trackSize += (l + 4);
l = READ_LE_UINT32(src + trackSize);
trackSize += (l + 4);
_player->setLoopStatus(loop);
_player->startTrack(src, trackSize, startTick);
}
void SoundTowns_LoK::fadeOutSoundEffects() {
for (int i = 127; i > 0; i-= 12) {
_player->driver()->channelVolume(0x46, i);
_player->driver()->channelVolume(0x47, i);
_vm->delay(_vm->tickLength());
}
stopAllSoundEffects();
}
} // End of namespace Kyra