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