aboutsummaryrefslogtreecommitdiff
path: root/engines/kyra/sound
diff options
context:
space:
mode:
Diffstat (limited to 'engines/kyra/sound')
-rw-r--r--engines/kyra/sound/sound.cpp371
-rw-r--r--engines/kyra/sound/sound.h341
-rw-r--r--engines/kyra/sound/sound_adlib.cpp2518
-rw-r--r--engines/kyra/sound/sound_adlib.h115
-rw-r--r--engines/kyra/sound/sound_amiga.cpp232
-rw-r--r--engines/kyra/sound/sound_digital.cpp544
-rw-r--r--engines/kyra/sound/sound_digital.h119
-rw-r--r--engines/kyra/sound/sound_intern.h407
-rw-r--r--engines/kyra/sound/sound_lok.cpp96
-rw-r--r--engines/kyra/sound/sound_lol.cpp307
-rw-r--r--engines/kyra/sound/sound_midi.cpp814
-rw-r--r--engines/kyra/sound/sound_pcspk.cpp366
-rw-r--r--engines/kyra/sound/sound_towns.cpp749
-rw-r--r--engines/kyra/sound/sound_towns_darkmoon.cpp278
14 files changed, 7257 insertions, 0 deletions
diff --git a/engines/kyra/sound/sound.cpp b/engines/kyra/sound/sound.cpp
new file mode 100644
index 0000000000..39784f4682
--- /dev/null
+++ b/engines/kyra/sound/sound.cpp
@@ -0,0 +1,371 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+
+#include "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::String filename;
+
+ Audio::SeekableAudioStream *audioStream = 0;
+ for (int i = 0; _supportedCodecs[i].fileext; ++i) {
+ filename = file;
+ filename += _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 0;
+ } 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 #3427240 "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(Common::String file) {
+ _music->loadSoundFile(file);
+ _sfx->loadSoundFile(file);
+}
+
+void MixedSoundDriver::loadSfxFile(Common::String 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(uint8 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 (!_disableSound) {
+ // XXX
+ //}
+
+ if (_flags.platform == Common::kPlatformDOS || _flags.platform == Common::kPlatformMacintosh) {
+ 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::kPlatformAmiga) {
+ if (_curMusicTheme != 1)
+ snd_playTheme(1, -1);
+
+ assert(command < _trackMapSize);
+ if (_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
+
+ { 0, 0 }
+};
+
+} // End of namespace Kyra
diff --git a/engines/kyra/sound/sound.h b/engines/kyra/sound/sound.h
new file mode 100644
index 0000000000..3f4216c5fb
--- /dev/null
+++ b/engines/kyra/sound/sound.h
@@ -0,0 +1,341 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef 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 uint8 *pcmdata, uint dataSize, int pcmvolume) : pcmData(pcmdata), pcmDataSize(dataSize), pcmVolume(pcmvolume) {}
+ const uint8 *pcmData;
+ uint pcmDataSize;
+ int pcmVolume;
+};
+
+/**
+ * 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,
+ kAmiga
+ };
+
+ virtual kType getMusicType() const = 0;
+ virtual kType getSfxType() const;
+
+ /**
+ * 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(Common::String file) = 0;
+
+ /**
+ * Load a sound file for playing sound
+ * effects from.
+ */
+ virtual void loadSfxFile(Common::String 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(uint8 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;
+
+ /**
+ * Stops all audio playback when paused. Continues after end of pause.
+ */
+ virtual void pause(bool paused) {}
+
+ 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();
+
+ virtual kType getMusicType() const;
+ virtual kType getSfxType() const;
+
+ virtual bool init();
+ virtual void process();
+
+ virtual void updateVolumeSettings();
+
+ virtual void initAudioResourceInfo(int set, void *info);
+ virtual void selectAudioResourceSet(int set);
+ virtual bool hasSoundFile(uint file) const;
+ virtual void loadSoundFile(uint file);
+ virtual void loadSoundFile(Common::String file);
+
+ virtual void loadSfxFile(Common::String file);
+
+ virtual void playTrack(uint8 track);
+ virtual void haltTrack();
+ virtual bool isPlaying() const;
+
+ virtual void playSoundEffect(uint8 track, uint8 volume = 0xFF);
+
+ virtual void stopAllSoundEffects();
+
+ virtual void beginFadeOut();
+ virtual void pause(bool paused);
+private:
+ Sound *_music, *_sfx;
+};
+
+} // End of namespace Kyra
+
+#endif
diff --git a/engines/kyra/sound/sound_adlib.cpp b/engines/kyra/sound/sound_adlib.cpp
new file mode 100644
index 0000000000..7a87e71982
--- /dev/null
+++ b/engines/kyra/sound/sound_adlib.cpp
@@ -0,0 +1,2518 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * LGPL License
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+
+#include "kyra/sound/sound_intern.h"
+#include "kyra/resource/resource.h"
+
+#include "common/system.h"
+#include "common/mutex.h"
+#include "common/config-manager.h"
+
+#include "audio/fmopl.h"
+
+// Basic AdLib Programming:
+// http://www.gamedev.net/reference/articles/article446.asp
+
+#define CALLBACKS_PER_SECOND 72
+
+namespace Audio {
+class Mixer;
+}
+
+namespace Kyra {
+
+class AdLibDriver {
+public:
+ AdLibDriver(Audio::Mixer *mixer, int version);
+ ~AdLibDriver();
+
+ void initDriver();
+ void setSoundData(uint8 *data, uint32 size);
+ void queueTrack(int track, int volume);
+ bool isChannelPlaying(int channel) const;
+ void stopAllChannels();
+ int getSoundTrigger() const { return _soundTrigger; }
+ void resetSoundTrigger() { _soundTrigger = 0; }
+
+ void callback();
+
+ void setSyncJumpMask(uint16 mask) { _syncJumpMask = mask; }
+
+ void setMusicVolume(uint8 volume);
+ void setSfxVolume(uint8 volume);
+
+private:
+ // These variables have not yet been named, but some of them are partly
+ // known nevertheless:
+ //
+ // pitchBend - Sound-related. Possibly some sort of pitch bend.
+ // unk18 - Sound-effect. Used for secondaryEffect1()
+ // unk19 - Sound-effect. Used for secondaryEffect1()
+ // unk20 - Sound-effect. Used for secondaryEffect1()
+ // unk21 - Sound-effect. Used for secondaryEffect1()
+ // unk22 - Sound-effect. Used for secondaryEffect1()
+ // unk29 - Sound-effect. Used for primaryEffect1()
+ // unk30 - Sound-effect. Used for primaryEffect1()
+ // unk31 - Sound-effect. Used for primaryEffect1()
+ // unk32 - Sound-effect. Used for primaryEffect2()
+ // unk33 - Sound-effect. Used for primaryEffect2()
+ // unk34 - Sound-effect. Used for primaryEffect2()
+ // unk35 - Sound-effect. Used for primaryEffect2()
+ // unk36 - Sound-effect. Used for primaryEffect2()
+ // unk37 - Sound-effect. Used for primaryEffect2()
+ // unk38 - Sound-effect. Used for primaryEffect2()
+ // unk39 - Currently unused, except for updateCallback56()
+ // unk40 - Currently unused, except for updateCallback56()
+ // unk41 - Sound-effect. Used for primaryEffect2()
+
+ struct Channel {
+ bool lock; // New to ScummVM
+ uint8 opExtraLevel2;
+ const uint8 *dataptr;
+ uint8 duration;
+ uint8 repeatCounter;
+ int8 baseOctave;
+ uint8 priority;
+ uint8 dataptrStackPos;
+ const uint8 *dataptrStack[4];
+ int8 baseNote;
+ uint8 unk29;
+ uint8 unk31;
+ uint16 unk30;
+ uint16 unk37;
+ uint8 unk33;
+ uint8 unk34;
+ uint8 unk35;
+ uint8 unk36;
+ uint8 unk32;
+ uint8 unk41;
+ uint8 unk38;
+ uint8 opExtraLevel1;
+ uint8 spacing2;
+ uint8 baseFreq;
+ uint8 tempo;
+ uint8 position;
+ uint8 regAx;
+ uint8 regBx;
+ typedef void (AdLibDriver::*Callback)(Channel&);
+ Callback primaryEffect;
+ Callback secondaryEffect;
+ uint8 fractionalSpacing;
+ uint8 opLevel1;
+ uint8 opLevel2;
+ uint8 opExtraLevel3;
+ uint8 twoChan;
+ uint8 unk39;
+ uint8 unk40;
+ uint8 spacing1;
+ uint8 durationRandomness;
+ uint8 unk19;
+ uint8 unk18;
+ int8 unk20;
+ int8 unk21;
+ uint8 unk22;
+ uint16 offset;
+ uint8 tempoReset;
+ uint8 rawNote;
+ int8 pitchBend;
+ uint8 volumeModifier;
+ };
+
+ void primaryEffect1(Channel &channel);
+ void primaryEffect2(Channel &channel);
+ void secondaryEffect1(Channel &channel);
+
+ void resetAdLibState();
+ void writeOPL(byte reg, byte val);
+ void initChannel(Channel &channel);
+ void noteOff(Channel &channel);
+ void unkOutput2(uint8 num);
+
+ uint16 getRandomNr();
+ void setupDuration(uint8 duration, Channel &channel);
+
+ void setupNote(uint8 rawNote, Channel &channel, bool flag = false);
+ void setupInstrument(uint8 regOffset, const uint8 *dataptr, Channel &channel);
+ void noteOn(Channel &channel);
+
+ void adjustVolume(Channel &channel);
+
+ uint8 calculateOpLevel1(Channel &channel);
+ uint8 calculateOpLevel2(Channel &channel);
+
+ uint16 checkValue(int16 val) {
+ if (val < 0)
+ val = 0;
+ else if (val > 0x3F)
+ val = 0x3F;
+ return val;
+ }
+
+ // The sound data has at least two lookup tables:
+ //
+ // * One for programs, starting at offset 0.
+ // * One for instruments, starting at offset 500.
+
+ uint8 *getProgram(int progId) {
+ 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;
+ }
+ }
+
+ const uint8 *getInstrument(int instrumentId) {
+ return getProgram(_numPrograms + instrumentId);
+ }
+
+ void setupPrograms();
+ void executePrograms();
+
+ struct ParserOpcode {
+ typedef int (AdLibDriver::*POpcode)(const uint8 *&dataptr, Channel &channel, uint8 value);
+ POpcode function;
+ const char *name;
+ };
+
+ void setupParserOpcodeTable();
+ const ParserOpcode *_parserOpcodeTable;
+ int _parserOpcodeTableSize;
+
+ int update_setRepeat(const uint8 *&dataptr, Channel &channel, uint8 value);
+ int update_checkRepeat(const uint8 *&dataptr, Channel &channel, uint8 value);
+ int update_setupProgram(const uint8 *&dataptr, Channel &channel, uint8 value);
+ int update_setNoteSpacing(const uint8 *&dataptr, Channel &channel, uint8 value);
+ int update_jump(const uint8 *&dataptr, Channel &channel, uint8 value);
+ int update_jumpToSubroutine(const uint8 *&dataptr, Channel &channel, uint8 value);
+ int update_returnFromSubroutine(const uint8 *&dataptr, Channel &channel, uint8 value);
+ int update_setBaseOctave(const uint8 *&dataptr, Channel &channel, uint8 value);
+ int update_stopChannel(const uint8 *&dataptr, Channel &channel, uint8 value);
+ int update_playRest(const uint8 *&dataptr, Channel &channel, uint8 value);
+ int update_writeAdLib(const uint8 *&dataptr, Channel &channel, uint8 value);
+ int update_setupNoteAndDuration(const uint8 *&dataptr, Channel &channel, uint8 value);
+ int update_setBaseNote(const uint8 *&dataptr, Channel &channel, uint8 value);
+ int update_setupSecondaryEffect1(const uint8 *&dataptr, Channel &channel, uint8 value);
+ int update_stopOtherChannel(const uint8 *&dataptr, Channel &channel, uint8 value);
+ int update_waitForEndOfProgram(const uint8 *&dataptr, Channel &channel, uint8 value);
+ int update_setupInstrument(const uint8 *&dataptr, Channel &channel, uint8 value);
+ int update_setupPrimaryEffect1(const uint8 *&dataptr, Channel &channel, uint8 value);
+ int update_removePrimaryEffect1(const uint8 *&dataptr, Channel &channel, uint8 value);
+ int update_setBaseFreq(const uint8 *&dataptr, Channel &channel, uint8 value);
+ int update_setupPrimaryEffect2(const uint8 *&dataptr, Channel &channel, uint8 value);
+ int update_setPriority(const uint8 *&dataptr, Channel &channel, uint8 value);
+ int updateCallback23(const uint8 *&dataptr, Channel &channel, uint8 value);
+ int updateCallback24(const uint8 *&dataptr, Channel &channel, uint8 value);
+ int update_setExtraLevel1(const uint8 *&dataptr, Channel &channel, uint8 value);
+ int update_setupDuration(const uint8 *&dataptr, Channel &channel, uint8 value);
+ int update_playNote(const uint8 *&dataptr, Channel &channel, uint8 value);
+ int update_setFractionalNoteSpacing(const uint8 *&dataptr, Channel &channel, uint8 value);
+ int update_setTempo(const uint8 *&dataptr, Channel &channel, uint8 value);
+ int update_removeSecondaryEffect1(const uint8 *&dataptr, Channel &channel, uint8 value);
+ int update_setChannelTempo(const uint8 *&dataptr, Channel &channel, uint8 value);
+ int update_setExtraLevel3(const uint8 *&dataptr, Channel &channel, uint8 value);
+ int update_setExtraLevel2(const uint8 *&dataptr, Channel &channel, uint8 value);
+ int update_changeExtraLevel2(const uint8 *&dataptr, Channel &channel, uint8 value);
+ int update_setAMDepth(const uint8 *&dataptr, Channel &channel, uint8 value);
+ int update_setVibratoDepth(const uint8 *&dataptr, Channel &channel, uint8 value);
+ int update_changeExtraLevel1(const uint8 *&dataptr, Channel &channel, uint8 value);
+ int updateCallback38(const uint8 *&dataptr, Channel &channel, uint8 value);
+ int updateCallback39(const uint8 *&dataptr, Channel &channel, uint8 value);
+ int update_removePrimaryEffect2(const uint8 *&dataptr, Channel &channel, uint8 value);
+ int update_pitchBend(const uint8 *&dataptr, Channel &channel, uint8 value);
+ int update_resetToGlobalTempo(const uint8 *&dataptr, Channel &channel, uint8 value);
+ int update_nop(const uint8 *&dataptr, Channel &channel, uint8 value);
+ int update_setDurationRandomness(const uint8 *&dataptr, Channel &channel, uint8 value);
+ int update_changeChannelTempo(const uint8 *&dataptr, Channel &channel, uint8 value);
+ int updateCallback46(const uint8 *&dataptr, Channel &channel, uint8 value);
+ int update_setupRhythmSection(const uint8 *&dataptr, Channel &channel, uint8 value);
+ int update_playRhythmSection(const uint8 *&dataptr, Channel &channel, uint8 value);
+ int update_removeRhythmSection(const uint8 *&dataptr, Channel &channel, uint8 value);
+ int updateCallback51(const uint8 *&dataptr, Channel &channel, uint8 value);
+ int updateCallback52(const uint8 *&dataptr, Channel &channel, uint8 value);
+ int updateCallback53(const uint8 *&dataptr, Channel &channel, uint8 value);
+ int update_setSoundTrigger(const uint8 *&dataptr, Channel &channel, uint8 value);
+ int update_setTempoReset(const uint8 *&dataptr, Channel &channel, uint8 value);
+ int updateCallback56(const uint8 *&dataptr, Channel &channel, uint8 value);
+private:
+ // These variables have not yet been named, but some of them are partly
+ // known nevertheless:
+ //
+ // _unkValue1 - Unknown. Used for updating _unkValue2
+ // _unkValue2 - Unknown. Used for updating _unkValue4
+ // _unkValue4 - Unknown. Used for updating _unkValue5
+ // _unkValue5 - Unknown. Used for controlling updateCallback24().
+ // _unkValue6 - Unknown. Rhythm section volume?
+ // _unkValue7 - Unknown. Rhythm section volume?
+ // _unkValue8 - Unknown. Rhythm section volume?
+ // _unkValue9 - Unknown. Rhythm section volume?
+ // _unkValue10 - Unknown. Rhythm section volume?
+ // _unkValue11 - Unknown. Rhythm section volume?
+ // _unkValue12 - Unknown. Rhythm section volume?
+ // _unkValue13 - Unknown. Rhythm section volume?
+ // _unkValue14 - Unknown. Rhythm section volume?
+ // _unkValue15 - Unknown. Rhythm section volume?
+ // _unkValue16 - Unknown. Rhythm section volume?
+ // _unkValue17 - Unknown. Rhythm section volume?
+ // _unkValue18 - Unknown. Rhythm section volume?
+ // _unkValue19 - Unknown. Rhythm section volume?
+ // _unkValue20 - Unknown. Rhythm section volume?
+ // _freqTable[] - Probably frequences for the 12-tone scale.
+ // _unkTable2[] - Unknown. Currently only used by updateCallback46()
+ // _unkTable2_1[] - One of the tables in _unkTable2[]
+ // _unkTable2_2[] - One of the tables in _unkTable2[]
+ // _unkTable2_3[] - One of the tables in _unkTable2[]
+
+ int _curChannel;
+ uint8 _soundTrigger;
+
+ uint16 _rnd;
+
+ uint8 _unkValue1;
+ uint8 _unkValue2;
+ uint8 _callbackTimer;
+ uint8 _unkValue4;
+ uint8 _unkValue5;
+ uint8 _unkValue6;
+ uint8 _unkValue7;
+ uint8 _unkValue8;
+ uint8 _unkValue9;
+ uint8 _unkValue10;
+ uint8 _unkValue11;
+ uint8 _unkValue12;
+ uint8 _unkValue13;
+ uint8 _unkValue14;
+ uint8 _unkValue15;
+ uint8 _unkValue16;
+ uint8 _unkValue17;
+ uint8 _unkValue18;
+ uint8 _unkValue19;
+ uint8 _unkValue20;
+
+ OPL::OPL *_adlib;
+
+ uint8 *_soundData;
+ uint32 _soundDataSize;
+
+ struct QueueEntry {
+ QueueEntry() : data(0), id(0), volume(0) {}
+ QueueEntry(uint8 *ptr, uint8 track, uint8 vol) : data(ptr), id(track), volume(vol) {}
+ uint8 *data;
+ uint8 id;
+ uint8 volume;
+ };
+
+ QueueEntry _programQueue[16];
+ int _programStartTimeout;
+ int _programQueueStart, _programQueueEnd;
+ bool _retrySounds;
+
+ void adjustSfxData(uint8 *data, int volume);
+ uint8 *_sfxPointer;
+ int _sfxPriority;
+ int _sfxVelocity;
+
+ Channel _channels[10];
+
+ uint8 _vibratoAndAMDepthBits;
+ uint8 _rhythmSectionBits;
+
+ uint8 _curRegOffset;
+ uint8 _tempo;
+
+ const uint8 *_tablePtr1;
+ const uint8 *_tablePtr2;
+
+ static const uint8 _regOffset[];
+ static const uint16 _freqTable[];
+ static const uint8 *const _unkTable2[];
+ static const uint8 _unkTable2_1[];
+ static const uint8 _unkTable2_2[];
+ static const uint8 _unkTable2_3[];
+ static const uint8 _pitchBendTables[][32];
+
+ uint16 _syncJumpMask;
+
+ Common::Mutex _mutex;
+ Audio::Mixer *_mixer;
+
+ uint8 _musicVolume, _sfxVolume;
+
+ int _numPrograms;
+ int _version;
+};
+
+AdLibDriver::AdLibDriver(Audio::Mixer *mixer, int version) {
+ setupParserOpcodeTable();
+
+ _version = version;
+ _numPrograms = (_version == 1) ? 150 : ((_version == 4) ? 500 : 250);
+
+ _mixer = mixer;
+
+ _adlib = OPL::Config::create();
+ if (!_adlib || !_adlib->init())
+ error("Failed to create OPL");
+
+ memset(_channels, 0, sizeof(_channels));
+ _soundData = 0;
+ _soundDataSize = 0;
+
+ _vibratoAndAMDepthBits = _curRegOffset = 0;
+
+ _curChannel = _rhythmSectionBits = 0;
+ _rnd = 0x1234;
+
+ _tempo = 0;
+ _soundTrigger = 0;
+ _programStartTimeout = 0;
+
+ _callbackTimer = 0xFF;
+ _unkValue1 = _unkValue2 = _unkValue4 = _unkValue5 = 0;
+ _unkValue6 = _unkValue7 = _unkValue8 = _unkValue9 = _unkValue10 = 0;
+ _unkValue11 = _unkValue12 = _unkValue13 = _unkValue14 = _unkValue15 =
+ _unkValue16 = _unkValue17 = _unkValue18 = _unkValue19 = _unkValue20 = 0;
+
+ _tablePtr1 = _tablePtr2 = 0;
+
+ _syncJumpMask = 0;
+
+ _musicVolume = 0;
+ _sfxVolume = 0;
+
+ _sfxPointer = 0;
+
+ _programQueueStart = _programQueueEnd = 0;
+ _retrySounds = false;
+
+ _adlib->start(new Common::Functor0Mem<void, AdLibDriver>(this, &AdLibDriver::callback), CALLBACKS_PER_SECOND);
+}
+
+AdLibDriver::~AdLibDriver() {
+ delete _adlib;
+ _adlib = 0;
+}
+
+void AdLibDriver::setMusicVolume(uint8 volume) {
+ Common::StackLock lock(_mutex);
+
+ _musicVolume = volume;
+
+ for (uint i = 0; i < 6; ++i) {
+ Channel &chan = _channels[i];
+ chan.volumeModifier = volume;
+
+ const uint8 regOffset = _regOffset[i];
+
+ // Level Key Scaling / Total Level
+ writeOPL(0x40 + regOffset, calculateOpLevel1(chan));
+ writeOPL(0x43 + regOffset, calculateOpLevel2(chan));
+ }
+
+ // For now we use the music volume for both sfx and music in Kyra1 and EoB
+ if (_version < 4) {
+ _sfxVolume = volume;
+
+ for (uint i = 6; i < 9; ++i) {
+ Channel &chan = _channels[i];
+ chan.volumeModifier = volume;
+
+ const uint8 regOffset = _regOffset[i];
+
+ // Level Key Scaling / Total Level
+ writeOPL(0x40 + regOffset, calculateOpLevel1(chan));
+ writeOPL(0x43 + regOffset, calculateOpLevel2(chan));
+ }
+ }
+}
+
+void AdLibDriver::setSfxVolume(uint8 volume) {
+ // We only support sfx volume in version 4 games.
+ if (_version < 4)
+ return;
+
+ Common::StackLock lock(_mutex);
+
+ _sfxVolume = volume;
+
+ for (uint i = 6; i < 9; ++i) {
+ Channel &chan = _channels[i];
+ chan.volumeModifier = volume;
+
+ const uint8 regOffset = _regOffset[i];
+
+ // Level Key Scaling / Total Level
+ writeOPL(0x40 + regOffset, calculateOpLevel1(chan));
+ writeOPL(0x43 + regOffset, calculateOpLevel2(chan));
+ }
+}
+
+void AdLibDriver::initDriver() {
+ Common::StackLock lock(_mutex);
+ resetAdLibState();
+}
+
+void AdLibDriver::setSoundData(uint8 *data, uint32 size) {
+ Common::StackLock lock(_mutex);
+
+ // Drop all tracks that are still queued. These would point to the old
+ // sound data.
+ _programQueueStart = _programQueueEnd = 0;
+ memset(_programQueue, 0, sizeof(_programQueue));
+
+ if (_soundData) {
+ delete[] _soundData;
+ _soundData = _sfxPointer = 0;
+ }
+
+ _soundData = data;
+ _soundDataSize = size;
+}
+
+void AdLibDriver::queueTrack(int track, int volume) {
+ Common::StackLock lock(_mutex);
+
+ uint8 *trackData = getProgram(track);
+ if (!trackData)
+ return;
+
+ // Don't drop tracks in EoB. The queue is always full there if a couple of monsters are around.
+ // If we drop the incoming tracks we get no sound effects, but tons of warnings instead.
+ if (_version >= 3 && _programQueueEnd == _programQueueStart && _programQueue[_programQueueEnd].data != 0) {
+ warning("AdLibDriver: Program queue full, dropping track %d", track);
+ return;
+ }
+
+ _programQueue[_programQueueEnd] = QueueEntry(trackData, track, volume);
+ _programQueueEnd = (_programQueueEnd + 1) & 15;
+}
+
+bool AdLibDriver::isChannelPlaying(int channel) const {
+ Common::StackLock lock(_mutex);
+
+ assert(channel >= 0 && channel <= 9);
+ return (_channels[channel].dataptr != 0);
+}
+
+void AdLibDriver::stopAllChannels() {
+ Common::StackLock lock(_mutex);
+
+ for (int channel = 0; channel <= 9; ++channel) {
+ _curChannel = channel;
+
+ Channel &chan = _channels[_curChannel];
+ chan.priority = 0;
+ chan.dataptr = 0;
+
+ if (channel != 9)
+ noteOff(chan);
+ }
+ _retrySounds = false;
+}
+
+// timer callback
+
+void AdLibDriver::callback() {
+ Common::StackLock lock(_mutex);
+ if (_programStartTimeout)
+ --_programStartTimeout;
+ else
+ setupPrograms();
+ executePrograms();
+
+ uint8 temp = _callbackTimer;
+ _callbackTimer += _tempo;
+ if (_callbackTimer < temp) {
+ if (!(--_unkValue2)) {
+ _unkValue2 = _unkValue1;
+ ++_unkValue4;
+ }
+ }
+}
+
+void AdLibDriver::setupPrograms() {
+ // If there is no program queued, we skip this.
+ if (_programQueueStart == _programQueueEnd)
+ return;
+
+ uint8 *ptr = _programQueue[_programQueueStart].data;
+
+ // The AdLib driver (in its old versions used for EOB) is not suitable for modern (fast) CPUs.
+ // The stop sound track (track 0 which has a priority of 50) will often still be busy when the
+ // next sound (with a lower priority) starts which will cause that sound to be skipped. We simply
+ // restart incoming sounds during stop sound execution.
+ // UPDATE: This stilly applies after introduction of the _programQueue.
+ QueueEntry retrySound;
+ if (_version < 3 && _programQueue[_programQueueStart].id == 0)
+ _retrySounds = true;
+ else if (_retrySounds)
+ retrySound = _programQueue[_programQueueStart];
+
+ // Adjust data in case we hit a sound effect.
+ adjustSfxData(ptr, _programQueue[_programQueueStart].volume);
+
+ // Clear the queue entry
+ _programQueue[_programQueueStart].data = 0;
+ _programQueueStart = (_programQueueStart + 1) & 15;
+
+ const int chan = *ptr++;
+ const int priority = *ptr++;
+
+ // Only start this sound if its priority is higher than the one
+ // already playing.
+
+ Channel &channel = _channels[chan];
+
+ if (priority >= channel.priority) {
+ initChannel(channel);
+ channel.priority = priority;
+ channel.dataptr = ptr;
+ channel.tempo = 0xFF;
+ channel.position = 0xFF;
+ channel.duration = 1;
+
+ if (chan <= 5)
+ channel.volumeModifier = _musicVolume;
+ else
+ channel.volumeModifier = _sfxVolume;
+
+ unkOutput2(chan);
+
+ // We need to wait two callback calls till we can start another track.
+ // This is (probably) required to assure that the sfx are started with
+ // the correct priority and velocity.
+ _programStartTimeout = 2;
+
+ retrySound = QueueEntry();
+ }
+
+ if (retrySound.data) {
+ debugC(9, kDebugLevelSound, "AdLibDriver::setupPrograms(): WORKAROUND - Restarting skipped sound %d)", retrySound.id);
+ queueTrack(retrySound.id, retrySound.volume);
+ }
+}
+
+void AdLibDriver::adjustSfxData(uint8 *ptr, int volume) {
+ // Check whether we need to reset the data of an old sfx which has been
+ // started.
+ if (_sfxPointer) {
+ _sfxPointer[1] = _sfxPriority;
+ _sfxPointer[3] = _sfxVelocity;
+ _sfxPointer = 0;
+ }
+
+ // Only music tracks are started on channel 9, thus we need to make sure
+ // we do not have a music track here.
+ if (*ptr == 9)
+ return;
+
+ // Store the pointer so we can reset the data when a new program is started.
+ _sfxPointer = ptr;
+
+ // Store the old values.
+ _sfxPriority = ptr[1];
+ _sfxVelocity = ptr[3];
+
+ // Adjust the values.
+ if (volume != 0xFF) {
+ if (_version >= 3) {
+ int newVal = ((((ptr[3]) + 63) * volume) >> 8) & 0xFF;
+ ptr[3] = -newVal + 63;
+ ptr[1] = ((ptr[1] * volume) >> 8) & 0xFF;
+ } else {
+ int newVal = ((_sfxVelocity << 2) ^ 0xFF) * volume;
+ ptr[3] = (newVal >> 10) ^ 0x3F;
+ ptr[1] = newVal >> 11;
+ }
+ }
+}
+
+// A few words on opcode parsing and timing:
+//
+// First of all, We simulate a timer callback 72 times per second. Each timeout
+// we update each channel that has something to play.
+//
+// Each channel has its own individual tempo, which is added to its position.
+// This will frequently cause the position to "wrap around" but that is
+// intentional. In fact, it's the signal to go ahead and do more stuff with
+// that channel.
+//
+// Each channel also has a duration, indicating how much time is left on the
+// its current task. This duration is decreased by one. As long as it still has
+// not reached zero, the only thing that can happen is that the note is turned
+// off depending on manual or automatic note spacing. Once the duration reaches
+// zero, a new set of musical opcodes are executed.
+//
+// An opcode is one byte, followed by a variable number of parameters. Since
+// most opcodes have at least one one-byte parameter, we read that as well. Any
+// opcode that doesn't have that one parameter is responsible for moving the
+// data pointer back again.
+//
+// If the most significant bit of the opcode is 1, it's a function; call it.
+// The opcode functions return either 0 (continue), 1 (stop) or 2 (stop, and do
+// not run the effects callbacks).
+//
+// If the most significant bit of the opcode is 0, it's a note, and the first
+// parameter is its duration. (There are cases where the duration is modified
+// but that's an exception.) The note opcode is assumed to return 1, and is the
+// last opcode unless its duration is zero.
+//
+// Finally, most of the times that the callback is called, it will invoke the
+// effects callbacks. The final opcode in a set can prevent this, if it's a
+// function and it returns anything other than 1.
+
+void AdLibDriver::executePrograms() {
+ // Each channel runs its own program. There are ten channels: One for
+ // each AdLib channel (0-8), plus one "control channel" (9) which is
+ // the one that tells the other channels what to do.
+
+ // This is where we ensure that channels that are made to jump "in
+ // sync" do so.
+
+ if (_syncJumpMask) {
+ bool forceUnlock = true;
+
+ for (_curChannel = 9; _curChannel >= 0; --_curChannel) {
+ if ((_syncJumpMask & (1 << _curChannel)) == 0)
+ continue;
+
+ if (_channels[_curChannel].dataptr && !_channels[_curChannel].lock)
+ forceUnlock = false;
+ }
+
+ if (forceUnlock) {
+ for (_curChannel = 9; _curChannel >= 0; --_curChannel)
+ if (_syncJumpMask & (1 << _curChannel))
+ _channels[_curChannel].lock = false;
+ }
+ }
+
+ for (_curChannel = 9; _curChannel >= 0; --_curChannel) {
+ int result = 1;
+
+ if (!_channels[_curChannel].dataptr)
+ continue;
+
+ if (_channels[_curChannel].lock && (_syncJumpMask & (1 << _curChannel)))
+ continue;
+
+ Channel &channel = _channels[_curChannel];
+ if (_curChannel == 9)
+ _curRegOffset = 0;
+ else
+ _curRegOffset = _regOffset[_curChannel];
+
+ if (channel.tempoReset)
+ channel.tempo = _tempo;
+
+ uint8 backup = channel.position;
+ channel.position += channel.tempo;
+ if (channel.position < backup) {
+ if (--channel.duration) {
+ if (channel.duration == channel.spacing2)
+ noteOff(channel);
+ if (channel.duration == channel.spacing1 && _curChannel != 9)
+ noteOff(channel);
+ } else {
+ // An opcode is not allowed to modify its own
+ // data pointer except through the 'dataptr'
+ // parameter. To enforce that, we have to work
+ // on a copy of the data pointer.
+ //
+ // This fixes a subtle music bug where the
+ // wrong music would play when getting the
+ // quill in Kyra 1.
+ const uint8 *dataptr = channel.dataptr;
+ while (dataptr) {
+ uint8 opcode = *dataptr++;
+ uint8 param = *dataptr++;
+
+ if (opcode & 0x80) {
+ opcode &= 0x7F;
+ if (opcode >= _parserOpcodeTableSize)
+ opcode = _parserOpcodeTableSize - 1;
+ debugC(9, kDebugLevelSound, "Calling opcode '%s' (%d) (channel: %d)", _parserOpcodeTable[opcode].name, opcode, _curChannel);
+ result = (this->*(_parserOpcodeTable[opcode].function))(dataptr, channel, param);
+ channel.dataptr = dataptr;
+ if (result)
+ break;
+ } else {
+ debugC(9, kDebugLevelSound, "Note on opcode 0x%02X (duration: %d) (channel: %d)", opcode, param, _curChannel);
+ setupNote(opcode, channel);
+ noteOn(channel);
+ setupDuration(param, channel);
+ if (param) {
+ // We need to make sure we are always running the
+ // effects after this. Otherwise some sounds are
+ // wrong. Like the sfx when bumping into a wall in
+ // LoL.
+ result = 1;
+ channel.dataptr = dataptr;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ if (result == 1) {
+ if (channel.primaryEffect)
+ (this->*(channel.primaryEffect))(channel);
+ if (channel.secondaryEffect)
+ (this->*(channel.secondaryEffect))(channel);
+ }
+ }
+}
+
+//
+
+void AdLibDriver::resetAdLibState() {
+ debugC(9, kDebugLevelSound, "resetAdLibState()");
+ _rnd = 0x1234;
+
+ // Authorize the control of the waveforms
+ writeOPL(0x01, 0x20);
+
+ // Select FM music mode
+ writeOPL(0x08, 0x00);
+
+ // I would guess the main purpose of this is to turn off the rhythm,
+ // thus allowing us to use 9 melodic voices instead of 6.
+ writeOPL(0xBD, 0x00);
+
+ int loop = 10;
+ while (loop--) {
+ if (loop != 9) {
+ // Silence the channel
+ writeOPL(0x40 + _regOffset[loop], 0x3F);
+ writeOPL(0x43 + _regOffset[loop], 0x3F);
+ }
+ initChannel(_channels[loop]);
+ }
+}
+
+// Old calling style: output0x388(0xABCD)
+// New calling style: writeOPL(0xAB, 0xCD)
+
+void AdLibDriver::writeOPL(byte reg, byte val) {
+ _adlib->writeReg(reg, val);
+}
+
+void AdLibDriver::initChannel(Channel &channel) {
+ debugC(9, kDebugLevelSound, "initChannel(%lu)", (long)(&channel - _channels));
+ memset(&channel.dataptr, 0, sizeof(Channel) - ((char *)&channel.dataptr - (char *)&channel));
+
+ channel.tempo = 0xFF;
+ channel.priority = 0;
+ // normally here are nullfuncs but we set 0 for now
+ channel.primaryEffect = 0;
+ channel.secondaryEffect = 0;
+ channel.spacing1 = 1;
+ channel.lock = false;
+}
+
+void AdLibDriver::noteOff(Channel &channel) {
+ debugC(9, kDebugLevelSound, "noteOff(%lu)", (long)(&channel - _channels));
+
+ // The control channel has no corresponding AdLib channel
+
+ if (_curChannel >= 9)
+ return;
+
+ // When the rhythm section is enabled, channels 6, 7 and 8 are special.
+
+ if (_rhythmSectionBits && _curChannel >= 6)
+ return;
+
+ // This means the "Key On" bit will always be 0
+ channel.regBx &= 0xDF;
+
+ // Octave / F-Number / Key-On
+ writeOPL(0xB0 + _curChannel, channel.regBx);
+}
+
+void AdLibDriver::unkOutput2(uint8 chan) {
+ debugC(9, kDebugLevelSound, "unkOutput2(%d)", chan);
+
+ // The control channel has no corresponding AdLib channel
+
+ if (chan >= 9)
+ return;
+
+ // I believe this has to do with channels 6, 7, and 8 being special
+ // when AdLib's rhythm section is enabled.
+
+ if (_rhythmSectionBits && chan >= 6)
+ return;
+
+ uint8 offset = _regOffset[chan];
+
+ // The channel is cleared: First the attack/delay rate, then the
+ // sustain level/release rate, and finally the note is turned off.
+
+ writeOPL(0x60 + offset, 0xFF);
+ writeOPL(0x63 + offset, 0xFF);
+
+ writeOPL(0x80 + offset, 0xFF);
+ writeOPL(0x83 + offset, 0xFF);
+
+ writeOPL(0xB0 + chan, 0x00);
+
+ // ...and then the note is turned on again, with whatever value is
+ // still lurking in the A0 + chan register, but everything else -
+ // including the two most significant frequency bit, and the octave -
+ // set to zero.
+ //
+ // This is very strange behavior, and causes problems with the ancient
+ // FMOPL code we borrowed from AdPlug. I've added a workaround. See
+ // audio/softsynth/opl/mame.cpp for more details.
+ //
+ // Fortunately, the more modern DOSBox FMOPL code does not seem to have
+ // any trouble with this.
+
+ writeOPL(0xB0 + chan, 0x20);
+}
+
+// I believe this is a random number generator. It actually does seem to
+// generate an even distribution of almost all numbers from 0 through 65535,
+// though in my tests some numbers were never generated.
+
+uint16 AdLibDriver::getRandomNr() {
+ _rnd += 0x9248;
+ uint16 lowBits = _rnd & 7;
+ _rnd >>= 3;
+ _rnd |= (lowBits << 13);
+ return _rnd;
+}
+
+void AdLibDriver::setupDuration(uint8 duration, Channel &channel) {
+ debugC(9, kDebugLevelSound, "setupDuration(%d, %lu)", duration, (long)(&channel - _channels));
+ if (channel.durationRandomness) {
+ channel.duration = duration + (getRandomNr() & channel.durationRandomness);
+ return;
+ }
+ if (channel.fractionalSpacing)
+ channel.spacing2 = (duration >> 3) * channel.fractionalSpacing;
+ channel.duration = duration;
+}
+
+// This function may or may not play the note. It's usually followed by a call
+// to noteOn(), which will always play the current note.
+
+void AdLibDriver::setupNote(uint8 rawNote, Channel &channel, bool flag) {
+ debugC(9, kDebugLevelSound, "setupNote(%d, %lu)", rawNote, (long)(&channel - _channels));
+
+ if (_curChannel >= 9)
+ return;
+
+ channel.rawNote = rawNote;
+
+ int8 note = (rawNote & 0x0F) + channel.baseNote;
+ int8 octave = ((rawNote + channel.baseOctave) >> 4) & 0x0F;
+
+ // There are only twelve notes. If we go outside that, we have to
+ // adjust the note and octave.
+
+ if (note >= 12) {
+ note -= 12;
+ octave++;
+ } else if (note < 0) {
+ note += 12;
+ octave--;
+ }
+
+ // The calculation of frequency looks quite different from the original
+ // disassembly at a first glance, but when you consider that the
+ // largest possible value would be 0x0246 + 0xFF + 0x47 (and that's if
+ // baseFreq is unsigned), freq is still a 10-bit value, just as it
+ // should be to fit in the Ax and Bx registers.
+ //
+ // If it were larger than that, it could have overflowed into the
+ // octave bits, and that could possibly have been used in some sound.
+ // But as it is now, I can't see any way it would happen.
+
+ uint16 freq = _freqTable[note] + channel.baseFreq;
+
+ // When called from callback 41, the behavior is slightly different:
+ // We adjust the frequency, even when channel.pitchBend is 0.
+
+ if (channel.pitchBend || flag) {
+ const uint8 *table;
+
+ if (channel.pitchBend >= 0) {
+ table = _pitchBendTables[(channel.rawNote & 0x0F) + 2];
+ freq += table[channel.pitchBend];
+ } else {
+ table = _pitchBendTables[channel.rawNote & 0x0F];
+ freq -= table[-channel.pitchBend];
+ }
+ }
+
+ channel.regAx = freq & 0xFF;
+ channel.regBx = (channel.regBx & 0x20) | (octave << 2) | ((freq >> 8) & 0x03);
+
+ // Keep the note on or off
+ writeOPL(0xA0 + _curChannel, channel.regAx);
+ writeOPL(0xB0 + _curChannel, channel.regBx);
+}
+
+void AdLibDriver::setupInstrument(uint8 regOffset, const uint8 *dataptr, Channel &channel) {
+ debugC(9, kDebugLevelSound, "setupInstrument(%d, %p, %lu)", regOffset, (const void *)dataptr, (long)(&channel - _channels));
+
+ if (_curChannel >= 9)
+ return;
+
+ // Amplitude Modulation / Vibrato / Envelope Generator Type /
+ // Keyboard Scaling Rate / Modulator Frequency Multiple
+ writeOPL(0x20 + regOffset, *dataptr++);
+ writeOPL(0x23 + regOffset, *dataptr++);
+
+ uint8 temp = *dataptr++;
+
+ // Feedback / Algorithm
+
+ // It is very likely that _curChannel really does refer to the same
+ // channel as regOffset, but there's only one Cx register per channel.
+
+ writeOPL(0xC0 + _curChannel, temp);
+
+ // The algorithm bit. I don't pretend to understand this fully, but
+ // "If set to 0, operator 1 modulates operator 2. In this case,
+ // operator 2 is the only one producing sound. If set to 1, both
+ // operators produce sound directly. Complex sounds are more easily
+ // created if the algorithm is set to 0."
+
+ channel.twoChan = temp & 1;
+
+ // Waveform Select
+ writeOPL(0xE0 + regOffset, *dataptr++);
+ writeOPL(0xE3 + regOffset, *dataptr++);
+
+ channel.opLevel1 = *dataptr++;
+ channel.opLevel2 = *dataptr++;
+
+ // Level Key Scaling / Total Level
+ writeOPL(0x40 + regOffset, calculateOpLevel1(channel));
+ writeOPL(0x43 + regOffset, calculateOpLevel2(channel));
+
+ // Attack Rate / Decay Rate
+ writeOPL(0x60 + regOffset, *dataptr++);
+ writeOPL(0x63 + regOffset, *dataptr++);
+
+ // Sustain Level / Release Rate
+ writeOPL(0x80 + regOffset, *dataptr++);
+ writeOPL(0x83 + regOffset, *dataptr++);
+}
+
+// Apart from playing the note, this function also updates the variables for
+// primary effect 2.
+
+void AdLibDriver::noteOn(Channel &channel) {
+ debugC(9, kDebugLevelSound, "noteOn(%lu)", (long)(&channel - _channels));
+
+ // The "note on" bit is set, and the current note is played.
+
+ if (_curChannel >= 9)
+ return;
+
+ channel.regBx |= 0x20;
+ writeOPL(0xB0 + _curChannel, channel.regBx);
+
+ int8 shift = 9 - channel.unk33;
+ uint16 temp = channel.regAx | (channel.regBx << 8);
+ channel.unk37 = ((temp & 0x3FF) >> shift) & 0xFF;
+ channel.unk38 = channel.unk36;
+}
+
+void AdLibDriver::adjustVolume(Channel &channel) {
+ debugC(9, kDebugLevelSound, "adjustVolume(%lu)", (long)(&channel - _channels));
+
+ if (_curChannel >= 9)
+ return;
+
+ // Level Key Scaling / Total Level
+
+ writeOPL(0x43 + _regOffset[_curChannel], calculateOpLevel2(channel));
+ if (channel.twoChan)
+ writeOPL(0x40 + _regOffset[_curChannel], calculateOpLevel1(channel));
+}
+
+// This is presumably only used for some sound effects, e.g. Malcolm blowing up
+// the trees in the intro (but not the effect where he "booby-traps" the big
+// tree) and turning Kallak to stone. Related functions and variables:
+//
+// update_setupPrimaryEffect1()
+// - Initializes unk29, unk30 and unk31
+// - unk29 is not further modified
+// - unk30 is not further modified, except by update_removePrimaryEffect1()
+//
+// update_removePrimaryEffect1()
+// - Deinitializes unk30
+//
+// unk29 - determines how often the notes are played
+// unk30 - modifies the frequency
+// unk31 - determines how often the notes are played
+
+void AdLibDriver::primaryEffect1(Channel &channel) {
+ debugC(9, kDebugLevelSound, "Calling primaryEffect1 (channel: %d)", _curChannel);
+
+ if (_curChannel >= 9)
+ return;
+
+ uint8 temp = channel.unk31;
+ channel.unk31 += channel.unk29;
+ if (channel.unk31 >= temp)
+ return;
+
+ // Initialize unk1 to the current frequency
+ int16 unk1 = ((channel.regBx & 3) << 8) | channel.regAx;
+
+ // This is presumably to shift the "note on" bit so far to the left
+ // that it won't be affected by any of the calculations below.
+ int16 unk2 = ((channel.regBx & 0x20) << 8) | (channel.regBx & 0x1C);
+
+ int16 unk3 = (int16)channel.unk30;
+
+ if (unk3 >= 0) {
+ unk1 += unk3;
+ if (unk1 >= 734) {
+ // The new frequency is too high. Shift it down and go
+ // up one octave.
+ unk1 >>= 1;
+ if (!(unk1 & 0x3FF))
+ ++unk1;
+ unk2 = (unk2 & 0xFF00) | ((unk2 + 4) & 0xFF);
+ unk2 &= 0xFF1C;
+ }
+ } else {
+ unk1 += unk3;
+ if (unk1 < 388) {
+ // The new frequency is too low. Shift it up and go
+ // down one octave.
+ unk1 <<= 1;
+ if (!(unk1 & 0x3FF))
+ --unk1;
+ unk2 = (unk2 & 0xFF00) | ((unk2 - 4) & 0xFF);
+ unk2 &= 0xFF1C;
+ }
+ }
+
+ // Make sure that the new frequency is still a 10-bit value.
+ unk1 &= 0x3FF;
+
+ writeOPL(0xA0 + _curChannel, unk1 & 0xFF);
+ channel.regAx = unk1 & 0xFF;
+
+ // Shift down the "note on" bit again.
+ uint8 value = unk1 >> 8;
+ value |= (unk2 >> 8) & 0xFF;
+ value |= unk2 & 0xFF;
+
+ writeOPL(0xB0 + _curChannel, value);
+ channel.regBx = value;
+}
+
+// This is presumably only used for some sound effects, e.g. Malcolm entering
+// and leaving Kallak's hut. Related functions and variables:
+//
+// update_setupPrimaryEffect2()
+// - Initializes unk32, unk33, unk34, unk35 and unk36
+// - unk32 is not further modified
+// - unk33 is not further modified
+// - unk34 is a countdown that gets reinitialized to unk35 on zero
+// - unk35 is based on unk34 and not further modified
+// - unk36 is not further modified
+//
+// noteOn()
+// - Plays the current note
+// - Updates unk37 with a new (lower?) frequency
+// - Copies unk36 to unk38. The unk38 variable is a countdown.
+//
+// unk32 - determines how often the notes are played
+// unk33 - modifies the frequency
+// unk34 - countdown, updates frequency on zero
+// unk35 - initializer for unk34 countdown
+// unk36 - initializer for unk38 countdown
+// unk37 - frequency
+// unk38 - countdown, begins playing on zero
+// unk41 - determines how often the notes are played
+//
+// Note that unk41 is never initialized. Not that it should matter much, but it
+// is a bit sloppy.
+
+void AdLibDriver::primaryEffect2(Channel &channel) {
+ debugC(9, kDebugLevelSound, "Calling primaryEffect2 (channel: %d)", _curChannel);
+
+ if (_curChannel >= 9)
+ return;
+
+ if (channel.unk38) {
+ --channel.unk38;
+ return;
+ }
+
+ uint8 temp = channel.unk41;
+ channel.unk41 += channel.unk32;
+ if (channel.unk41 < temp) {
+ uint16 unk1 = channel.unk37;
+ if (!(--channel.unk34)) {
+ unk1 ^= 0xFFFF;
+ ++unk1;
+ channel.unk37 = unk1;
+ channel.unk34 = channel.unk35;
+ }
+
+ uint16 unk2 = (channel.regAx | (channel.regBx << 8)) & 0x3FF;
+ unk2 += unk1;
+
+ channel.regAx = unk2 & 0xFF;
+ channel.regBx = (channel.regBx & 0xFC) | (unk2 >> 8);
+
+ // Octave / F-Number / Key-On
+ writeOPL(0xA0 + _curChannel, channel.regAx);
+ writeOPL(0xB0 + _curChannel, channel.regBx);
+ }
+}
+
+// I don't know where this is used. The same operation is performed several
+// times on the current channel, using a chunk of the _soundData[] buffer for
+// parameters. The parameters are used starting at the end of the chunk.
+//
+// Since we use _curRegOffset to specify the final register, it's quite
+// unlikely that this function is ever used to play notes. It's probably only
+// used to modify the sound. Another thing that supports this idea is that it
+// can be combined with any of the effects callbacks above.
+//
+// Related functions and variables:
+//
+// update_setupSecondaryEffect1()
+// - Initialies unk18, unk19, unk20, unk21, unk22 and offset
+// - unk19 is not further modified
+// - unk20 is not further modified
+// - unk22 is not further modified
+// - offset is not further modified
+//
+// unk18 - determines how often the operation is performed
+// unk19 - determines how often the operation is performed
+// unk20 - the start index into the data chunk
+// unk21 - the current index into the data chunk
+// unk22 - the operation to perform
+// offset - the offset to the data chunk
+
+void AdLibDriver::secondaryEffect1(Channel &channel) {
+ debugC(9, kDebugLevelSound, "Calling secondaryEffect1 (channel: %d)", _curChannel);
+
+ if (_curChannel >= 9)
+ return;
+
+ uint8 temp = channel.unk18;
+ channel.unk18 += channel.unk19;
+ if (channel.unk18 < temp) {
+ if (--channel.unk21 < 0)
+ channel.unk21 = channel.unk20;
+ writeOPL(channel.unk22 + _curRegOffset, _soundData[channel.offset + channel.unk21]);
+ }
+}
+
+uint8 AdLibDriver::calculateOpLevel1(Channel &channel) {
+ int8 value = channel.opLevel1 & 0x3F;
+
+ if (channel.twoChan) {
+ value += channel.opExtraLevel1;
+ value += channel.opExtraLevel2;
+
+ uint16 level3 = (channel.opExtraLevel3 ^ 0x3F) * channel.volumeModifier;
+ if (level3) {
+ level3 += 0x3F;
+ level3 >>= 8;
+ }
+
+ value += level3 ^ 0x3F;
+ }
+
+ value = CLIP<int8>(value, 0, 0x3F);
+
+ if (!channel.volumeModifier)
+ value = 0x3F;
+
+ // Preserve the scaling level bits from opLevel1
+
+ return checkValue(value) | (channel.opLevel1 & 0xC0);
+}
+
+uint8 AdLibDriver::calculateOpLevel2(Channel &channel) {
+ int8 value = channel.opLevel2 & 0x3F;
+
+ value += channel.opExtraLevel1;
+ value += channel.opExtraLevel2;
+
+ uint16 level3 = (channel.opExtraLevel3 ^ 0x3F) * channel.volumeModifier;
+ if (level3) {
+ level3 += 0x3F;
+ level3 >>= 8;
+ }
+
+ value += level3 ^ 0x3F;
+
+ value = CLIP<int8>(value, 0, 0x3F);
+
+ if (!channel.volumeModifier)
+ value = 0x3F;
+
+ // Preserve the scaling level bits from opLevel2
+
+ return checkValue(value) | (channel.opLevel2 & 0xC0);
+}
+
+// parser opcodes
+
+int AdLibDriver::update_setRepeat(const uint8 *&dataptr, Channel &channel, uint8 value) {
+ channel.repeatCounter = value;
+ return 0;
+}
+
+int AdLibDriver::update_checkRepeat(const uint8 *&dataptr, Channel &channel, uint8 value) {
+ ++dataptr;
+ if (--channel.repeatCounter) {
+ int16 add = READ_LE_UINT16(dataptr - 2);
+ dataptr += add;
+ }
+ return 0;
+}
+
+int AdLibDriver::update_setupProgram(const uint8 *&dataptr, Channel &channel, uint8 value) {
+ if (value == 0xFF)
+ return 0;
+
+ const uint8 *ptr = getProgram(value);
+
+ // In case we encounter an invalid program we simply ignore it and do
+ // nothing instead. The original did not care about invalid programs and
+ // simply tried to play them anyway... But to avoid crashes due we ingore
+ // them.
+ // This, for example, happens in the Lands of Lore intro when Scotia gets
+ // the ring in the intro.
+ if (!ptr) {
+ debugC(3, kDebugLevelSound, "AdLibDriver::update_setupProgram: Invalid program %d specified", value);
+ return 0;
+ }
+
+ uint8 chan = *ptr++;
+ uint8 priority = *ptr++;
+
+ Channel &channel2 = _channels[chan];
+
+ if (priority >= channel2.priority) {
+ // We keep new tracks from being started for two further iterations of
+ // the callback. This assures the correct velocity is used for this
+ // program.
+ _programStartTimeout = 2;
+ initChannel(channel2);
+ channel2.priority = priority;
+ channel2.dataptr = ptr;
+ channel2.tempo = 0xFF;
+ channel2.position = 0xFF;
+ channel2.duration = 1;
+
+ if (chan <= 5)
+ channel2.volumeModifier = _musicVolume;
+ else
+ channel2.volumeModifier = _sfxVolume;
+
+ unkOutput2(chan);
+ }
+
+ return 0;
+}
+
+int AdLibDriver::update_setNoteSpacing(const uint8 *&dataptr, Channel &channel, uint8 value) {
+ channel.spacing1 = value;
+ return 0;
+}
+
+int AdLibDriver::update_jump(const uint8 *&dataptr, Channel &channel, uint8 value) {
+ --dataptr;
+ int16 add = READ_LE_UINT16(dataptr); dataptr += 2;
+ if (_version == 1)
+ dataptr = _soundData + add - 191;
+ else
+ dataptr += add;
+ if (_syncJumpMask & (1 << (&channel - _channels)))
+ channel.lock = true;
+ return 0;
+}
+
+int AdLibDriver::update_jumpToSubroutine(const uint8 *&dataptr, Channel &channel, uint8 value) {
+ --dataptr;
+ int16 add = READ_LE_UINT16(dataptr); dataptr += 2;
+ channel.dataptrStack[channel.dataptrStackPos++] = dataptr;
+ if (_version < 3)
+ dataptr = _soundData + add - 191;
+ else
+ dataptr += add;
+ return 0;
+}
+
+int AdLibDriver::update_returnFromSubroutine(const uint8 *&dataptr, Channel &channel, uint8 value) {
+ dataptr = channel.dataptrStack[--channel.dataptrStackPos];
+ return 0;
+}
+
+int AdLibDriver::update_setBaseOctave(const uint8 *&dataptr, Channel &channel, uint8 value) {
+ channel.baseOctave = value;
+ return 0;
+}
+
+int AdLibDriver::update_stopChannel(const uint8 *&dataptr, Channel &channel, uint8 value) {
+ channel.priority = 0;
+ if (_curChannel != 9)
+ noteOff(channel);
+ dataptr = 0;
+ return 2;
+}
+
+int AdLibDriver::update_playRest(const uint8 *&dataptr, Channel &channel, uint8 value) {
+ setupDuration(value, channel);
+ noteOff(channel);
+ return (value != 0);
+}
+
+int AdLibDriver::update_writeAdLib(const uint8 *&dataptr, Channel &channel, uint8 value) {
+ writeOPL(value, *dataptr++);
+ return 0;
+}
+
+int AdLibDriver::update_setupNoteAndDuration(const uint8 *&dataptr, Channel &channel, uint8 value) {
+ setupNote(value, channel);
+ value = *dataptr++;
+ setupDuration(value, channel);
+ return (value != 0);
+}
+
+int AdLibDriver::update_setBaseNote(const uint8 *&dataptr, Channel &channel, uint8 value) {
+ channel.baseNote = value;
+ return 0;
+}
+
+int AdLibDriver::update_setupSecondaryEffect1(const uint8 *&dataptr, Channel &channel, uint8 value) {
+ channel.unk18 = value;
+ channel.unk19 = value;
+ channel.unk20 = channel.unk21 = *dataptr++;
+ channel.unk22 = *dataptr++;
+ // WORKAROUND: The original code reads a true offset which later gets translated via xlat (in
+ // the current segment). This means that the outcome depends on the sound data offset.
+ // Unfortunately this offset is different in most implementations of the audio driver and
+ // probably also different from the offset assumed by the sequencer.
+ // It seems that the driver assumes an offset of 191 which is wrong for all the game driver
+ // implementations.
+ // This bug has probably not been noticed, since the effect is hardly used and the sounds are
+ // not necessarily worse. I noticed the difference between ScummVM and DOSBox for the EOB II
+ // teleporter sound. I also found the location of the table which is supposed to be used here
+ // (simple enough: it is located at the end of the track after the 0x88 ending opcode).
+ // Teleporters in EOB I and II now sound exactly the same which I am sure was the intended way,
+ // since the sound data is exactly the same.
+ // In DOSBox the teleporters will sound different in EOB I and II, due to different sound
+ // data offsets.
+ channel.offset = READ_LE_UINT16(dataptr) - 191; dataptr += 2;
+ channel.secondaryEffect = &AdLibDriver::secondaryEffect1;
+ return 0;
+}
+
+int AdLibDriver::update_stopOtherChannel(const uint8 *&dataptr, Channel &channel, uint8 value) {
+ Channel &channel2 = _channels[value];
+ channel2.duration = 0;
+ channel2.priority = 0;
+ channel2.dataptr = 0;
+ return 0;
+}
+
+int AdLibDriver::update_waitForEndOfProgram(const uint8 *&dataptr, Channel &channel, uint8 value) {
+ const uint8 *ptr = getProgram(value);
+
+ // Safety check in case an invalid program is specified. This would make
+ // getProgram return a nullptr and thus cause invalid memory reads.
+ if (!ptr) {
+ debugC(3, kDebugLevelSound, "AdLibDriver::update_waitForEndOfProgram: Invalid program %d specified", value);
+ return 0;
+ }
+
+ uint8 chan = *ptr;
+
+ if (!_channels[chan].dataptr)
+ return 0;
+
+ dataptr -= 2;
+ return 2;
+}
+
+int AdLibDriver::update_setupInstrument(const uint8 *&dataptr, Channel &channel, uint8 value) {
+ const uint8 *instrument = getInstrument(value);
+
+ // We add a safety check to avoid setting up invalid instruments. This is
+ // not done in the original. However, to avoid crashes due to invalid
+ // memory reads we simply ignore the request.
+ // This happens, for example, in Hand of Fate when using the swampsnake
+ // potion on Zanthia to scare off the rat in the cave in the first chapter
+ // of the game.
+ if (!instrument) {
+ debugC(3, kDebugLevelSound, "AdLibDriver::update_setupInstrument: Invalid instrument %d specified", value);
+ return 0;
+ }
+
+ setupInstrument(_curRegOffset, instrument, channel);
+ return 0;
+}
+
+int AdLibDriver::update_setupPrimaryEffect1(const uint8 *&dataptr, Channel &channel, uint8 value) {
+ channel.unk29 = value;
+ channel.unk30 = READ_BE_UINT16(dataptr);
+ dataptr += 2;
+ channel.primaryEffect = &AdLibDriver::primaryEffect1;
+ channel.unk31 = 0xFF;
+ return 0;
+}
+
+int AdLibDriver::update_removePrimaryEffect1(const uint8 *&dataptr, Channel &channel, uint8 value) {
+ --dataptr;
+ channel.primaryEffect = 0;
+ channel.unk30 = 0;
+ return 0;
+}
+
+int AdLibDriver::update_setBaseFreq(const uint8 *&dataptr, Channel &channel, uint8 value) {
+ channel.baseFreq = value;
+ return 0;
+}
+
+int AdLibDriver::update_setupPrimaryEffect2(const uint8 *&dataptr, Channel &channel, uint8 value) {
+ channel.unk32 = value;
+ channel.unk33 = *dataptr++;
+ uint8 temp = *dataptr++;
+ channel.unk34 = temp + 1;
+ channel.unk35 = temp << 1;
+ channel.unk36 = *dataptr++;
+ channel.primaryEffect = &AdLibDriver::primaryEffect2;
+ return 0;
+}
+
+int AdLibDriver::update_setPriority(const uint8 *&dataptr, Channel &channel, uint8 value) {
+ channel.priority = value;
+ return 0;
+}
+
+int AdLibDriver::updateCallback23(const uint8 *&dataptr, Channel &channel, uint8 value) {
+ value >>= 1;
+ _unkValue1 = _unkValue2 = value;
+ _callbackTimer = 0xFF;
+ _unkValue4 = _unkValue5 = 0;
+ return 0;
+}
+
+int AdLibDriver::updateCallback24(const uint8 *&dataptr, Channel &channel, uint8 value) {
+ if (_unkValue5) {
+ if (_unkValue4 & value) {
+ _unkValue5 = 0;
+ return 0;
+ }
+ }
+
+ if (!(value & _unkValue4))
+ ++_unkValue5;
+
+ dataptr -= 2;
+ channel.duration = 1;
+ return 2;
+}
+
+int AdLibDriver::update_setExtraLevel1(const uint8 *&dataptr, Channel &channel, uint8 value) {
+ channel.opExtraLevel1 = value;
+ adjustVolume(channel);
+ return 0;
+}
+
+int AdLibDriver::update_setupDuration(const uint8 *&dataptr, Channel &channel, uint8 value) {
+ setupDuration(value, channel);
+ return (value != 0);
+}
+
+int AdLibDriver::update_playNote(const uint8 *&dataptr, Channel &channel, uint8 value) {
+ setupDuration(value, channel);
+ noteOn(channel);
+ return (value != 0);
+}
+
+int AdLibDriver::update_setFractionalNoteSpacing(const uint8 *&dataptr, Channel &channel, uint8 value) {
+ channel.fractionalSpacing = value & 7;
+ return 0;
+}
+
+int AdLibDriver::update_setTempo(const uint8 *&dataptr, Channel &channel, uint8 value) {
+ _tempo = value;
+ return 0;
+}
+
+int AdLibDriver::update_removeSecondaryEffect1(const uint8 *&dataptr, Channel &channel, uint8 value) {
+ --dataptr;
+ channel.secondaryEffect = 0;
+ return 0;
+}
+
+int AdLibDriver::update_setChannelTempo(const uint8 *&dataptr, Channel &channel, uint8 value) {
+ channel.tempo = value;
+ return 0;
+}
+
+int AdLibDriver::update_setExtraLevel3(const uint8 *&dataptr, Channel &channel, uint8 value) {
+ channel.opExtraLevel3 = value;
+ return 0;
+}
+
+int AdLibDriver::update_setExtraLevel2(const uint8 *&dataptr, Channel &channel, uint8 value) {
+ int channelBackUp = _curChannel;
+
+ _curChannel = value;
+ Channel &channel2 = _channels[value];
+ channel2.opExtraLevel2 = *dataptr++;
+ adjustVolume(channel2);
+
+ _curChannel = channelBackUp;
+ return 0;
+}
+
+int AdLibDriver::update_changeExtraLevel2(const uint8 *&dataptr, Channel &channel, uint8 value) {
+ int channelBackUp = _curChannel;
+
+ _curChannel = value;
+ Channel &channel2 = _channels[value];
+ channel2.opExtraLevel2 += *dataptr++;
+ adjustVolume(channel2);
+
+ _curChannel = channelBackUp;
+ return 0;
+}
+
+// Apart from initializing to zero, these two functions are the only ones that
+// modify _vibratoAndAMDepthBits.
+
+int AdLibDriver::update_setAMDepth(const uint8 *&dataptr, Channel &channel, uint8 value) {
+ if (value & 1)
+ _vibratoAndAMDepthBits |= 0x80;
+ else
+ _vibratoAndAMDepthBits &= 0x7F;
+
+ writeOPL(0xBD, _vibratoAndAMDepthBits);
+ return 0;
+}
+
+int AdLibDriver::update_setVibratoDepth(const uint8 *&dataptr, Channel &channel, uint8 value) {
+ if (value & 1)
+ _vibratoAndAMDepthBits |= 0x40;
+ else
+ _vibratoAndAMDepthBits &= 0xBF;
+
+ writeOPL(0xBD, _vibratoAndAMDepthBits);
+ return 0;
+}
+
+int AdLibDriver::update_changeExtraLevel1(const uint8 *&dataptr, Channel &channel, uint8 value) {
+ channel.opExtraLevel1 += value;
+ adjustVolume(channel);
+ return 0;
+}
+
+int AdLibDriver::updateCallback38(const uint8 *&dataptr, Channel &channel, uint8 value) {
+ int channelBackUp = _curChannel;
+
+ _curChannel = value;
+ Channel &channel2 = _channels[value];
+ channel2.duration = channel2.priority = 0;
+ channel2.dataptr = 0;
+ channel2.opExtraLevel2 = 0;
+
+ if (value != 9) {
+ uint8 outValue = _regOffset[value];
+
+ // Feedback strength / Connection type
+ writeOPL(0xC0 + _curChannel, 0x00);
+
+ // Key scaling level / Operator output level
+ writeOPL(0x43 + outValue, 0x3F);
+
+ // Sustain Level / Release Rate
+ writeOPL(0x83 + outValue, 0xFF);
+
+ // Key On / Octave / Frequency
+ writeOPL(0xB0 + _curChannel, 0x00);
+ }
+
+ _curChannel = channelBackUp;
+ return 0;
+}
+
+int AdLibDriver::updateCallback39(const uint8 *&dataptr, Channel &channel, uint8 value) {
+ if (_curChannel >= 9)
+ return 0;
+
+ uint16 unk = *dataptr++;
+ unk |= value << 8;
+ unk &= getRandomNr();
+
+ uint16 unk2 = ((channel.regBx & 0x1F) << 8) | channel.regAx;
+ unk2 += unk;
+ unk2 |= ((channel.regBx & 0x20) << 8);
+
+ // Frequency
+ writeOPL(0xA0 + _curChannel, unk2 & 0xFF);
+
+ // Key On / Octave / Frequency
+ writeOPL(0xB0 + _curChannel, (unk2 & 0xFF00) >> 8);
+
+ return 0;
+}
+
+int AdLibDriver::update_removePrimaryEffect2(const uint8 *&dataptr, Channel &channel, uint8 value) {
+ --dataptr;
+ channel.primaryEffect = 0;
+ return 0;
+}
+
+int AdLibDriver::update_pitchBend(const uint8 *&dataptr, Channel &channel, uint8 value) {
+ channel.pitchBend = value;
+ setupNote(channel.rawNote, channel, true);
+ return 0;
+}
+
+int AdLibDriver::update_resetToGlobalTempo(const uint8 *&dataptr, Channel &channel, uint8 value) {
+ --dataptr;
+ channel.tempo = _tempo;
+ return 0;
+}
+
+int AdLibDriver::update_nop(const uint8 *&dataptr, Channel &channel, uint8 value) {
+ --dataptr;
+ return 0;
+}
+
+int AdLibDriver::update_setDurationRandomness(const uint8 *&dataptr, Channel &channel, uint8 value) {
+ channel.durationRandomness = value;
+ return 0;
+}
+
+int AdLibDriver::update_changeChannelTempo(const uint8 *&dataptr, Channel &channel, uint8 value) {
+ int tempo = channel.tempo + (int8)value;
+
+ if (tempo <= 0)
+ tempo = 1;
+ else if (tempo > 255)
+ tempo = 255;
+
+ channel.tempo = tempo;
+ return 0;
+}
+
+int AdLibDriver::updateCallback46(const uint8 *&dataptr, Channel &channel, uint8 value) {
+ uint8 entry = *dataptr++;
+ _tablePtr1 = _unkTable2[entry++];
+ _tablePtr2 = _unkTable2[entry];
+ if (value == 2) {
+ // Frequency
+ writeOPL(0xA0, _tablePtr2[0]);
+ }
+ return 0;
+}
+
+int AdLibDriver::update_setupRhythmSection(const uint8 *&dataptr, Channel &channel, uint8 value) {
+ int channelBackUp = _curChannel;
+ int regOffsetBackUp = _curRegOffset;
+
+ _curChannel = 6;
+ _curRegOffset = _regOffset[6];
+
+ const uint8 *instrument;
+ instrument = getInstrument(value);
+ if (instrument) {
+ setupInstrument(_curRegOffset, instrument, channel);
+ } else {
+ debugC(3, kDebugLevelSound, "AdLibDriver::update_setupRhythmSection: Invalid instrument %d for channel 6 specified", value);
+ }
+ _unkValue6 = channel.opLevel2;
+
+ _curChannel = 7;
+ _curRegOffset = _regOffset[7];
+
+ instrument = getInstrument(*dataptr++);
+ if (instrument) {
+ setupInstrument(_curRegOffset, instrument, channel);
+ } else {
+ debugC(3, kDebugLevelSound, "AdLibDriver::update_setupRhythmSection: Invalid instrument %d for channel 7 specified", value);
+ }
+ _unkValue7 = channel.opLevel1;
+ _unkValue8 = channel.opLevel2;
+
+ _curChannel = 8;
+ _curRegOffset = _regOffset[8];
+
+ instrument = getInstrument(*dataptr++);
+ if (instrument) {
+ setupInstrument(_curRegOffset, instrument, channel);
+ } else {
+ debugC(3, kDebugLevelSound, "AdLibDriver::update_setupRhythmSection: Invalid instrument %d for channel 8 specified", value);
+ }
+ _unkValue9 = channel.opLevel1;
+ _unkValue10 = channel.opLevel2;
+
+ // Octave / F-Number / Key-On for channels 6, 7 and 8
+
+ _channels[6].regBx = *dataptr++ & 0x2F;
+ writeOPL(0xB6, _channels[6].regBx);
+ writeOPL(0xA6, *dataptr++);
+
+ _channels[7].regBx = *dataptr++ & 0x2F;
+ writeOPL(0xB7, _channels[7].regBx);
+ writeOPL(0xA7, *dataptr++);
+
+ _channels[8].regBx = *dataptr++ & 0x2F;
+ writeOPL(0xB8, _channels[8].regBx);
+ writeOPL(0xA8, *dataptr++);
+
+ _rhythmSectionBits = 0x20;
+
+ _curRegOffset = regOffsetBackUp;
+ _curChannel = channelBackUp;
+ return 0;
+}
+
+int AdLibDriver::update_playRhythmSection(const uint8 *&dataptr, Channel &channel, uint8 value) {
+ // Any instrument that we want to play, and which was already playing,
+ // is temporarily keyed off. Instruments that were off already, or
+ // which we don't want to play, retain their old on/off status. This is
+ // probably so that the instrument's envelope is played from its
+ // beginning again...
+
+ writeOPL(0xBD, (_rhythmSectionBits & ~(value & 0x1F)) | 0x20);
+
+ // ...but since we only set the rhythm instrument bits, and never clear
+ // them (until the entire rhythm section is disabled), I'm not sure how
+ // useful the cleverness above is. We could perhaps simply turn off all
+ // the rhythm instruments instead.
+
+ _rhythmSectionBits |= value;
+
+ writeOPL(0xBD, _vibratoAndAMDepthBits | 0x20 | _rhythmSectionBits);
+ return 0;
+}
+
+int AdLibDriver::update_removeRhythmSection(const uint8 *&dataptr, Channel &channel, uint8 value) {
+ --dataptr;
+ _rhythmSectionBits = 0;
+
+ // All the rhythm bits are cleared. The AM and Vibrato depth bits
+ // remain unchanged.
+
+ writeOPL(0xBD, _vibratoAndAMDepthBits);
+ return 0;
+}
+
+int AdLibDriver::updateCallback51(const uint8 *&dataptr, Channel &channel, uint8 value) {
+ uint8 value2 = *dataptr++;
+
+ if (value & 1) {
+ _unkValue12 = value2;
+
+ // Channel 7, op1: Level Key Scaling / Total Level
+ writeOPL(0x51, checkValue(value2 + _unkValue7 + _unkValue11 + _unkValue12));
+ }
+
+ if (value & 2) {
+ _unkValue14 = value2;
+
+ // Channel 8, op2: Level Key Scaling / Total Level
+ writeOPL(0x55, checkValue(value2 + _unkValue10 + _unkValue13 + _unkValue14));
+ }
+
+ if (value & 4) {
+ _unkValue15 = value2;
+
+ // Channel 8, op1: Level Key Scaling / Total Level
+ writeOPL(0x52, checkValue(value2 + _unkValue9 + _unkValue16 + _unkValue15));
+ }
+
+ if (value & 8) {
+ _unkValue18 = value2;
+
+ // Channel 7, op2: Level Key Scaling / Total Level
+ writeOPL(0x54, checkValue(value2 + _unkValue8 + _unkValue17 + _unkValue18));
+ }
+
+ if (value & 16) {
+ _unkValue20 = value2;
+
+ // Channel 6, op2: Level Key Scaling / Total Level
+ writeOPL(0x53, checkValue(value2 + _unkValue6 + _unkValue19 + _unkValue20));
+ }
+
+ return 0;
+}
+
+int AdLibDriver::updateCallback52(const uint8 *&dataptr, Channel &channel, uint8 value) {
+ uint8 value2 = *dataptr++;
+
+ if (value & 1) {
+ _unkValue11 = checkValue(value2 + _unkValue7 + _unkValue11 + _unkValue12);
+
+ // Channel 7, op1: Level Key Scaling / Total Level
+ writeOPL(0x51, _unkValue11);
+ }
+
+ if (value & 2) {
+ _unkValue13 = checkValue(value2 + _unkValue10 + _unkValue13 + _unkValue14);
+
+ // Channel 8, op2: Level Key Scaling / Total Level
+ writeOPL(0x55, _unkValue13);
+ }
+
+ if (value & 4) {
+ _unkValue16 = checkValue(value2 + _unkValue9 + _unkValue16 + _unkValue15);
+
+ // Channel 8, op1: Level Key Scaling / Total Level
+ writeOPL(0x52, _unkValue16);
+ }
+
+ if (value & 8) {
+ _unkValue17 = checkValue(value2 + _unkValue8 + _unkValue17 + _unkValue18);
+
+ // Channel 7, op2: Level Key Scaling / Total Level
+ writeOPL(0x54, _unkValue17);
+ }
+
+ if (value & 16) {
+ _unkValue19 = checkValue(value2 + _unkValue6 + _unkValue19 + _unkValue20);
+
+ // Channel 6, op2: Level Key Scaling / Total Level
+ writeOPL(0x53, _unkValue19);
+ }
+
+ return 0;
+}
+
+int AdLibDriver::updateCallback53(const uint8 *&dataptr, Channel &channel, uint8 value) {
+ uint8 value2 = *dataptr++;
+
+ if (value & 1) {
+ _unkValue11 = value2;
+
+ // Channel 7, op1: Level Key Scaling / Total Level
+ writeOPL(0x51, checkValue(value2 + _unkValue7 + _unkValue12));
+ }
+
+ if (value & 2) {
+ _unkValue13 = value2;
+
+ // Channel 8, op2: Level Key Scaling / Total Level
+ writeOPL(0x55, checkValue(value2 + _unkValue10 + _unkValue14));
+ }
+
+ if (value & 4) {
+ _unkValue16 = value2;
+
+ // Channel 8, op1: Level Key Scaling / Total Level
+ writeOPL(0x52, checkValue(value2 + _unkValue9 + _unkValue15));
+ }
+
+ if (value & 8) {
+ _unkValue17 = value2;
+
+ // Channel 7, op2: Level Key Scaling / Total Level
+ writeOPL(0x54, checkValue(value2 + _unkValue8 + _unkValue18));
+ }
+
+ if (value & 16) {
+ _unkValue19 = value2;
+
+ // Channel 6, op2: Level Key Scaling / Total Level
+ writeOPL(0x53, checkValue(value2 + _unkValue6 + _unkValue20));
+ }
+
+ return 0;
+}
+
+int AdLibDriver::update_setSoundTrigger(const uint8 *&dataptr, Channel &channel, uint8 value) {
+ _soundTrigger = value;
+ return 0;
+}
+
+int AdLibDriver::update_setTempoReset(const uint8 *&dataptr, Channel &channel, uint8 value) {
+ channel.tempoReset = value;
+ return 0;
+}
+
+int AdLibDriver::updateCallback56(const uint8 *&dataptr, Channel &channel, uint8 value) {
+ channel.unk39 = value;
+ channel.unk40 = *dataptr++;
+ return 0;
+}
+
+// static res
+
+#define COMMAND(x) { &AdLibDriver::x, #x }
+
+void AdLibDriver::setupParserOpcodeTable() {
+ static const ParserOpcode parserOpcodeTable[] = {
+ // 0
+ COMMAND(update_setRepeat),
+ COMMAND(update_checkRepeat),
+ COMMAND(update_setupProgram),
+ COMMAND(update_setNoteSpacing),
+
+ // 4
+ COMMAND(update_jump),
+ COMMAND(update_jumpToSubroutine),
+ COMMAND(update_returnFromSubroutine),
+ COMMAND(update_setBaseOctave),
+
+ // 8
+ COMMAND(update_stopChannel),
+ COMMAND(update_playRest),
+ COMMAND(update_writeAdLib),
+ COMMAND(update_setupNoteAndDuration),
+
+ // 12
+ COMMAND(update_setBaseNote),
+ COMMAND(update_setupSecondaryEffect1),
+ COMMAND(update_stopOtherChannel),
+ COMMAND(update_waitForEndOfProgram),
+
+ // 16
+ COMMAND(update_setupInstrument),
+ COMMAND(update_setupPrimaryEffect1),
+ COMMAND(update_removePrimaryEffect1),
+ COMMAND(update_setBaseFreq),
+
+ // 20
+ COMMAND(update_stopChannel),
+ COMMAND(update_setupPrimaryEffect2),
+ COMMAND(update_stopChannel),
+ COMMAND(update_stopChannel),
+
+ // 24
+ COMMAND(update_stopChannel),
+ COMMAND(update_stopChannel),
+ COMMAND(update_setPriority),
+ COMMAND(update_stopChannel),
+
+ // 28
+ COMMAND(updateCallback23),
+ COMMAND(updateCallback24),
+ COMMAND(update_setExtraLevel1),
+ COMMAND(update_stopChannel),
+
+ // 32
+ COMMAND(update_setupDuration),
+ COMMAND(update_playNote),
+ COMMAND(update_stopChannel),
+ COMMAND(update_stopChannel),
+
+ // 36
+ COMMAND(update_setFractionalNoteSpacing),
+ COMMAND(update_stopChannel),
+ COMMAND(update_setTempo),
+ COMMAND(update_removeSecondaryEffect1),
+
+ // 40
+ COMMAND(update_stopChannel),
+ COMMAND(update_setChannelTempo),
+ COMMAND(update_stopChannel),
+ COMMAND(update_setExtraLevel3),
+
+ // 44
+ COMMAND(update_setExtraLevel2),
+ COMMAND(update_changeExtraLevel2),
+ COMMAND(update_setAMDepth),
+ COMMAND(update_setVibratoDepth),
+
+ // 48
+ COMMAND(update_changeExtraLevel1),
+ COMMAND(update_stopChannel),
+ COMMAND(update_stopChannel),
+ COMMAND(updateCallback38),
+
+ // 52
+ COMMAND(update_stopChannel),
+ COMMAND(updateCallback39),
+ COMMAND(update_removePrimaryEffect2),
+ COMMAND(update_stopChannel),
+
+ // 56
+ COMMAND(update_stopChannel),
+ COMMAND(update_pitchBend),
+ COMMAND(update_resetToGlobalTempo),
+ COMMAND(update_nop),
+
+ // 60
+ COMMAND(update_setDurationRandomness),
+ COMMAND(update_changeChannelTempo),
+ COMMAND(update_stopChannel),
+ COMMAND(updateCallback46),
+
+ // 64
+ COMMAND(update_nop),
+ COMMAND(update_setupRhythmSection),
+ COMMAND(update_playRhythmSection),
+ COMMAND(update_removeRhythmSection),
+
+ // 68
+ COMMAND(updateCallback51),
+ COMMAND(updateCallback52),
+ COMMAND(updateCallback53),
+ COMMAND(update_setSoundTrigger),
+
+ // 72
+ COMMAND(update_setTempoReset),
+ COMMAND(updateCallback56),
+ COMMAND(update_stopChannel)
+ };
+
+ _parserOpcodeTable = parserOpcodeTable;
+ _parserOpcodeTableSize = ARRAYSIZE(parserOpcodeTable);
+}
+#undef COMMAND
+
+// This table holds the register offset for operator 1 for each of the nine
+// channels. To get the register offset for operator 2, simply add 3.
+
+const uint8 AdLibDriver::_regOffset[] = {
+ 0x00, 0x01, 0x02, 0x08, 0x09, 0x0A, 0x10, 0x11,
+ 0x12
+};
+
+//These are the F-Numbers (10 bits) for the notes of the 12-tone scale.
+// However, it does not match the table in the AdLib documentation I've seen.
+
+const uint16 AdLibDriver::_freqTable[] = {
+ 0x0134, 0x0147, 0x015A, 0x016F, 0x0184, 0x019C, 0x01B4, 0x01CE, 0x01E9,
+ 0x0207, 0x0225, 0x0246
+};
+
+// These tables are currently only used by updateCallback46(), which only ever
+// uses the first element of one of the sub-tables.
+
+const uint8 *const AdLibDriver::_unkTable2[] = {
+ AdLibDriver::_unkTable2_1,
+ AdLibDriver::_unkTable2_2,
+ AdLibDriver::_unkTable2_1,
+ AdLibDriver::_unkTable2_2,
+ AdLibDriver::_unkTable2_3,
+ AdLibDriver::_unkTable2_2
+};
+
+const uint8 AdLibDriver::_unkTable2_1[] = {
+ 0x50, 0x50, 0x4F, 0x4F, 0x4E, 0x4E, 0x4D, 0x4D,
+ 0x4C, 0x4C, 0x4B, 0x4B, 0x4A, 0x4A, 0x49, 0x49,
+ 0x48, 0x48, 0x47, 0x47, 0x46, 0x46, 0x45, 0x45,
+ 0x44, 0x44, 0x43, 0x43, 0x42, 0x42, 0x41, 0x41,
+ 0x40, 0x40, 0x3F, 0x3F, 0x3E, 0x3E, 0x3D, 0x3D,
+ 0x3C, 0x3C, 0x3B, 0x3B, 0x3A, 0x3A, 0x39, 0x39,
+ 0x38, 0x38, 0x37, 0x37, 0x36, 0x36, 0x35, 0x35,
+ 0x34, 0x34, 0x33, 0x33, 0x32, 0x32, 0x31, 0x31,
+ 0x30, 0x30, 0x2F, 0x2F, 0x2E, 0x2E, 0x2D, 0x2D,
+ 0x2C, 0x2C, 0x2B, 0x2B, 0x2A, 0x2A, 0x29, 0x29,
+ 0x28, 0x28, 0x27, 0x27, 0x26, 0x26, 0x25, 0x25,
+ 0x24, 0x24, 0x23, 0x23, 0x22, 0x22, 0x21, 0x21,
+ 0x20, 0x20, 0x1F, 0x1F, 0x1E, 0x1E, 0x1D, 0x1D,
+ 0x1C, 0x1C, 0x1B, 0x1B, 0x1A, 0x1A, 0x19, 0x19,
+ 0x18, 0x18, 0x17, 0x17, 0x16, 0x16, 0x15, 0x15,
+ 0x14, 0x14, 0x13, 0x13, 0x12, 0x12, 0x11, 0x11,
+ 0x10, 0x10
+};
+
+// no don't ask me WHY this table exsits!
+const uint8 AdLibDriver::_unkTable2_2[] = {
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+ 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
+ 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
+ 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
+ 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
+ 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
+ 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
+ 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
+ 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
+ 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x6F,
+ 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,
+ 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F,
+ 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
+ 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F
+};
+
+const uint8 AdLibDriver::_unkTable2_3[] = {
+ 0x40, 0x40, 0x40, 0x3F, 0x3F, 0x3F, 0x3E, 0x3E,
+ 0x3E, 0x3D, 0x3D, 0x3D, 0x3C, 0x3C, 0x3C, 0x3B,
+ 0x3B, 0x3B, 0x3A, 0x3A, 0x3A, 0x39, 0x39, 0x39,
+ 0x38, 0x38, 0x38, 0x37, 0x37, 0x37, 0x36, 0x36,
+ 0x36, 0x35, 0x35, 0x35, 0x34, 0x34, 0x34, 0x33,
+ 0x33, 0x33, 0x32, 0x32, 0x32, 0x31, 0x31, 0x31,
+ 0x30, 0x30, 0x30, 0x2F, 0x2F, 0x2F, 0x2E, 0x2E,
+ 0x2E, 0x2D, 0x2D, 0x2D, 0x2C, 0x2C, 0x2C, 0x2B,
+ 0x2B, 0x2B, 0x2A, 0x2A, 0x2A, 0x29, 0x29, 0x29,
+ 0x28, 0x28, 0x28, 0x27, 0x27, 0x27, 0x26, 0x26,
+ 0x26, 0x25, 0x25, 0x25, 0x24, 0x24, 0x24, 0x23,
+ 0x23, 0x23, 0x22, 0x22, 0x22, 0x21, 0x21, 0x21,
+ 0x20, 0x20, 0x20, 0x1F, 0x1F, 0x1F, 0x1E, 0x1E,
+ 0x1E, 0x1D, 0x1D, 0x1D, 0x1C, 0x1C, 0x1C, 0x1B,
+ 0x1B, 0x1B, 0x1A, 0x1A, 0x1A, 0x19, 0x19, 0x19,
+ 0x18, 0x18, 0x18, 0x17, 0x17, 0x17, 0x16, 0x16,
+ 0x16, 0x15
+};
+
+// This table is used to modify the frequency of the notes, depending on the
+// note value and the pitch bend value. In theory, we could very well try to
+// access memory outside this table, but in reality that probably won't happen.
+//
+
+const uint8 AdLibDriver::_pitchBendTables[][32] = {
+ // 0
+ { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x08,
+ 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10,
+ 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x19,
+ 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21 },
+ // 1
+ { 0x00, 0x01, 0x02, 0x03, 0x04, 0x06, 0x07, 0x09,
+ 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11,
+ 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x1A,
+ 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x22, 0x24 },
+ // 2
+ { 0x00, 0x01, 0x02, 0x03, 0x04, 0x06, 0x08, 0x09,
+ 0x0A, 0x0C, 0x0D, 0x0E, 0x0F, 0x11, 0x12, 0x13,
+ 0x14, 0x15, 0x16, 0x17, 0x19, 0x1A, 0x1C, 0x1D,
+ 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x24, 0x25, 0x26 },
+ // 3
+ { 0x00, 0x01, 0x02, 0x03, 0x04, 0x06, 0x08, 0x0A,
+ 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x11, 0x12, 0x13,
+ 0x14, 0x15, 0x16, 0x17, 0x18, 0x1A, 0x1C, 0x1D,
+ 0x1E, 0x1F, 0x20, 0x21, 0x23, 0x25, 0x27, 0x28 },
+ // 4
+ { 0x00, 0x01, 0x02, 0x03, 0x04, 0x06, 0x08, 0x0A,
+ 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x11, 0x13, 0x15,
+ 0x16, 0x17, 0x18, 0x19, 0x1B, 0x1D, 0x1F, 0x20,
+ 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x28, 0x2A },
+ // 5
+ { 0x00, 0x01, 0x02, 0x03, 0x05, 0x07, 0x09, 0x0B,
+ 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x13, 0x15,
+ 0x16, 0x17, 0x18, 0x19, 0x1B, 0x1D, 0x1F, 0x20,
+ 0x21, 0x22, 0x23, 0x25, 0x27, 0x29, 0x2B, 0x2D },
+ // 6
+ { 0x00, 0x01, 0x02, 0x03, 0x05, 0x07, 0x09, 0x0B,
+ 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x13, 0x15,
+ 0x16, 0x17, 0x18, 0x1A, 0x1C, 0x1E, 0x21, 0x24,
+ 0x25, 0x26, 0x27, 0x29, 0x2B, 0x2D, 0x2F, 0x30 },
+ // 7
+ { 0x00, 0x01, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C,
+ 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x13, 0x15, 0x18,
+ 0x19, 0x1A, 0x1C, 0x1D, 0x1F, 0x21, 0x23, 0x25,
+ 0x26, 0x27, 0x29, 0x2B, 0x2D, 0x2F, 0x30, 0x32 },
+ // 8
+ { 0x00, 0x01, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0D,
+ 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x14, 0x17, 0x1A,
+ 0x19, 0x1A, 0x1C, 0x1E, 0x20, 0x22, 0x25, 0x28,
+ 0x29, 0x2A, 0x2B, 0x2D, 0x2F, 0x31, 0x33, 0x35 },
+ // 9
+ { 0x00, 0x01, 0x03, 0x05, 0x07, 0x09, 0x0B, 0x0E,
+ 0x0F, 0x10, 0x12, 0x14, 0x16, 0x18, 0x1A, 0x1B,
+ 0x1C, 0x1D, 0x1E, 0x20, 0x22, 0x24, 0x26, 0x29,
+ 0x2A, 0x2C, 0x2E, 0x30, 0x32, 0x34, 0x36, 0x39 },
+ // 10
+ { 0x00, 0x01, 0x03, 0x05, 0x07, 0x09, 0x0B, 0x0E,
+ 0x0F, 0x10, 0x12, 0x14, 0x16, 0x19, 0x1B, 0x1E,
+ 0x1F, 0x21, 0x23, 0x25, 0x27, 0x29, 0x2B, 0x2D,
+ 0x2E, 0x2F, 0x31, 0x32, 0x34, 0x36, 0x39, 0x3C },
+ // 11
+ { 0x00, 0x01, 0x03, 0x05, 0x07, 0x0A, 0x0C, 0x0F,
+ 0x10, 0x11, 0x13, 0x15, 0x17, 0x19, 0x1B, 0x1E,
+ 0x1F, 0x20, 0x22, 0x24, 0x26, 0x28, 0x2B, 0x2E,
+ 0x2F, 0x30, 0x32, 0x34, 0x36, 0x39, 0x3C, 0x3F },
+ // 12
+ { 0x00, 0x02, 0x04, 0x06, 0x08, 0x0B, 0x0D, 0x10,
+ 0x11, 0x12, 0x14, 0x16, 0x18, 0x1B, 0x1E, 0x21,
+ 0x22, 0x23, 0x25, 0x27, 0x29, 0x2C, 0x2F, 0x32,
+ 0x33, 0x34, 0x36, 0x38, 0x3B, 0x34, 0x41, 0x44 },
+ // 13
+ { 0x00, 0x02, 0x04, 0x06, 0x08, 0x0B, 0x0D, 0x11,
+ 0x12, 0x13, 0x15, 0x17, 0x1A, 0x1D, 0x20, 0x23,
+ 0x24, 0x25, 0x27, 0x29, 0x2C, 0x2F, 0x32, 0x35,
+ 0x36, 0x37, 0x39, 0x3B, 0x3E, 0x41, 0x44, 0x47 }
+};
+
+#pragma mark -
+
+// 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 SoundAdLibPC::_kyra1SoundTriggers[] = {
+ 0, 4, 5, 3
+};
+
+const int SoundAdLibPC::_kyra1NumSoundTriggers = ARRAYSIZE(SoundAdLibPC::_kyra1SoundTriggers);
+
+SoundAdLibPC::SoundAdLibPC(KyraEngine_v1 *vm, Audio::Mixer *mixer)
+ : Sound(vm, mixer), _driver(0), _trackEntries(), _soundDataPtr(0) {
+ memset(_trackEntries, 0, sizeof(_trackEntries));
+
+ _soundTriggers = 0;
+ _numSoundTriggers = 0;
+ _sfxPlayingSound = -1;
+ _soundFileLoaded.clear();
+ _currentResourceSet = 0;
+ memset(&_resInfo, 0, sizeof(_resInfo));
+
+ switch (vm->game()) {
+ case GI_LOL:
+ _version = _vm->gameFlags().isDemo ? 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;
+ }
+
+ _driver = new AdLibDriver(mixer, _version);
+ assert(_driver);
+}
+
+SoundAdLibPC::~SoundAdLibPC() {
+ delete _driver;
+ delete[] _soundDataPtr;
+ for (int i = 0; i < 3; i++)
+ initAudioResourceInfo(i, 0);
+}
+
+bool SoundAdLibPC::init() {
+ _driver->initDriver();
+ return true;
+}
+
+void SoundAdLibPC::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 SoundAdLibPC::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 SoundAdLibPC::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 SoundAdLibPC::haltTrack() {
+ play(0, 0);
+ play(0, 0);
+ //_vm->_system->delayMillis(3 * 60);
+}
+
+bool SoundAdLibPC::isPlaying() const {
+ return _driver->isChannelPlaying(0);
+}
+
+void SoundAdLibPC::playSoundEffect(uint8 track, uint8 volume) {
+ if (_sfxEnabled)
+ play(track, volume);
+}
+
+void SoundAdLibPC::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->queueTrack(soundId, volume);
+}
+
+void SoundAdLibPC::beginFadeOut() {
+ play(_version > 2 ? 1 : 15, 0xFF);
+}
+
+int SoundAdLibPC::checkTrigger() {
+ return _driver->getSoundTrigger();
+}
+
+void SoundAdLibPC::resetTrigger() {
+ _driver->resetSoundTrigger();
+}
+
+void SoundAdLibPC::initAudioResourceInfo(int set, void *info) {
+ if (set >= kMusicIntro && set <= kMusicFinale) {
+ delete _resInfo[set];
+ _resInfo[set] = info ? new SoundResourceInfo_PC(*(SoundResourceInfo_PC*)info) : 0;
+ }
+}
+
+void SoundAdLibPC::selectAudioResourceSet(int set) {
+ if (set >= kMusicIntro && set <= kMusicFinale) {
+ if (_resInfo[set])
+ _currentResourceSet = set;
+ }
+}
+
+bool SoundAdLibPC::hasSoundFile(uint file) const {
+ if (file < res()->fileListSize)
+ return (res()->fileList[file] != 0);
+ return false;
+}
+
+void SoundAdLibPC::loadSoundFile(uint file) {
+ if (file < res()->fileListSize)
+ internalLoadFile(res()->fileList[file]);
+}
+
+void SoundAdLibPC::loadSoundFile(Common::String file) {
+ internalLoadFile(file);
+}
+
+void SoundAdLibPC::internalLoadFile(Common::String file) {
+ file += ((_version == 1) ? ".DAT" : ".ADL");
+ if (_soundFileLoaded == file)
+ return;
+
+ if (_soundDataPtr)
+ haltTrack();
+
+ uint8 *fileData = 0; uint32 fileSize = 0;
+
+ fileData = _vm->resource()->fileData(file.c_str(), &fileSize);
+ if (!fileData) {
+ warning("Couldn't find music file: '%s'", file.c_str());
+ return;
+ }
+
+ playSoundEffect(0);
+ playSoundEffect(0);
+
+ _driver->stopAllChannels();
+ _soundDataPtr = 0;
+
+ 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;
+ }
+
+ _soundDataPtr = new uint8[soundDataSize];
+ assert(_soundDataPtr);
+
+ memcpy(_soundDataPtr, p, soundDataSize);
+
+ delete[] fileData;
+ fileData = p = 0;
+ fileSize = 0;
+
+ _driver->setSoundData(_soundDataPtr, soundDataSize);
+
+ _soundFileLoaded = file;
+}
+
+} // End of namespace Kyra
diff --git a/engines/kyra/sound/sound_adlib.h b/engines/kyra/sound/sound_adlib.h
new file mode 100644
index 0000000000..a5d6fe3659
--- /dev/null
+++ b/engines/kyra/sound/sound_adlib.h
@@ -0,0 +1,115 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * LGPL License
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifndef KYRA_SOUND_ADLIB_H
+#define KYRA_SOUND_ADLIB_H
+
+#include "kyra/sound/sound.h"
+
+#include "common/mutex.h"
+
+namespace Kyra {
+class AdLibDriver;
+
+/**
+ * AdLib implementation of the sound output device.
+ *
+ * It uses a special sound file format special to
+ * Dune II, Kyrandia 1 and 2. While Dune II and
+ * Kyrandia 1 are using exact the same format, the
+ * one of Kyrandia 2 slightly differs.
+ *
+ * See AdLibDriver for more information.
+ * @see AdLibDriver
+ */
+class SoundAdLibPC : public Sound {
+public:
+ SoundAdLibPC(KyraEngine_v1 *vm, Audio::Mixer *mixer);
+ ~SoundAdLibPC();
+
+ virtual kType getMusicType() const { return kAdLib; }
+
+ virtual bool init();
+ virtual void process();
+
+ virtual void updateVolumeSettings();
+
+ virtual void initAudioResourceInfo(int set, void *info);
+ virtual void selectAudioResourceSet(int set);
+ virtual bool hasSoundFile(uint file) const;
+ virtual void loadSoundFile(uint file);
+ virtual void loadSoundFile(Common::String file);
+
+ virtual void playTrack(uint8 track);
+ virtual void haltTrack();
+ virtual bool isPlaying() const;
+
+ virtual void playSoundEffect(uint8 track, uint8 volume = 0xFF);
+
+ virtual void beginFadeOut();
+
+ virtual int checkTrigger();
+ virtual void resetTrigger();
+private:
+ void internalLoadFile(Common::String file);
+
+ void play(uint8 track, uint8 volume);
+
+ const SoundResourceInfo_PC *res() const {return _resInfo[_currentResourceSet]; }
+ SoundResourceInfo_PC *_resInfo[3];
+ int _currentResourceSet;
+
+ AdLibDriver *_driver;
+
+ int _version;
+ uint8 _trackEntries[500];
+ uint8 *_soundDataPtr;
+ int _sfxPlayingSound;
+
+ Common::String _soundFileLoaded;
+
+ int _numSoundTriggers;
+ const int *_soundTriggers;
+
+ static const int _kyra1NumSoundTriggers;
+ static const int _kyra1SoundTriggers[];
+};
+
+} // End of namespace Kyra
+
+#endif
diff --git a/engines/kyra/sound/sound_amiga.cpp b/engines/kyra/sound/sound_amiga.cpp
new file mode 100644
index 0000000000..110400415f
--- /dev/null
+++ b/engines/kyra/sound/sound_amiga.cpp
@@ -0,0 +1,232 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "kyra/sound/sound_intern.h"
+#include "kyra/resource/resource.h"
+
+#include "audio/mixer.h"
+#include "audio/mods/maxtrax.h"
+
+namespace Kyra {
+
+SoundAmiga::SoundAmiga(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::~SoundAmiga() {
+ _mixer->stopHandle(_musicHandle);
+ delete _driver;
+}
+
+bool SoundAmiga::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::initAudioResourceInfo(int set, void *info) {
+ // See comment below
+}
+
+void SoundAmiga::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::hasSoundFile(uint file) const {
+ if (file < 3)
+ return true;
+ return false;
+}
+
+void SoundAmiga::loadSoundFile(uint file) {
+ debugC(5, kDebugLevelSound, "SoundAmiga::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: 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: missing music file: %s", scoreName);
+ }
+ delete scoreIn;
+
+ if (loaded)
+ _fileLoaded = (FileType)file;
+}
+
+void SoundAmiga::playTrack(uint8 track) {
+ debugC(5, kDebugLevelSound, "SoundAmiga::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::haltTrack() {
+ debugC(5, kDebugLevelSound, "SoundAmiga::haltTrack()");
+ _driver->stopMusic();
+}
+
+void SoundAmiga::beginFadeOut() {
+ debugC(5, kDebugLevelSound, "SoundAmiga::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::playSoundEffect(uint8 track, uint8) {
+ debugC(5, kDebugLevelSound, "SoundAmiga::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
diff --git a/engines/kyra/sound/sound_digital.cpp b/engines/kyra/sound/sound_digital.cpp
new file mode 100644
index 0000000000..e3586605e8
--- /dev/null
+++ b/engines/kyra/sound/sound_digital.cpp
@@ -0,0 +1,544 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "kyra/sound/sound_digital.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() { delete _impl; _impl = 0; }
+
+ int readBuffer(int16 *buffer, const int numSamples);
+ bool isStereo() const { return _impl->isStereo(); }
+ bool endOfData() const { return _impl->endOfData() | _endOfData; }
+ int getRate() const { return _rate; }
+
+ void setRate(int newRate) { _rate = newRate; }
+ void beginFadeOut(uint32 millis);
+
+ bool seek(const Audio::Timestamp &where) { return _impl->seek(where); }
+ Audio::Timestamp getLength() const { 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;
+}
+
+// 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();
+
+ int readBuffer(int16 *buffer, const int numSamples);
+
+ bool isStereo() const { return false; }
+ bool endOfData() const { return _endOfData; }
+
+ int getRate() const { return _rate; }
+
+ bool seek(const Audio::Timestamp &where);
+ Audio::Timestamp getLength() const { 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(0),
+ _outBufferOffset(0), _outBufferSize(0), _inBuffer(0), _inBufferSize(0) {
+
+ _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);
+}
+
+#pragma mark -
+
+SoundDigital::SoundDigital(KyraEngine_MR *vm, Audio::Mixer *mixer) : _vm(vm), _mixer(mixer) {
+ for (uint i = 0; i < ARRAYSIZE(_sounds); ++i)
+ _sounds[i].stream = 0;
+}
+
+SoundDigital::~SoundDigital() {
+ for (int i = 0; i < ARRAYSIZE(_sounds); ++i)
+ stopSound(i);
+}
+
+int SoundDigital::playSound(const char *filename, uint8 priority, Audio::Mixer::SoundType type, int volume, bool loop, int channel) {
+ Sound *use = 0;
+ 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 = 0;
+ int usedCodec = -1;
+ for (int i = 0; _supportedCodecs[i].fileext; ++i) {
+ Common::String file = filename;
+ file += _supportedCodecs[i].fileext;
+
+ if (!_vm->resource()->exists(file.c_str()))
+ 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 = 0;
+
+ 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::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::stopSound(int channel) {
+ if (channel == -1)
+ return;
+
+ assert(channel >= 0 && channel < ARRAYSIZE(_sounds));
+ _mixer->stopHandle(_sounds[channel].handle);
+ _sounds[channel].stream = 0;
+}
+
+void SoundDigital::stopAllSounds() {
+ for (int i = 0; i < ARRAYSIZE(_sounds); ++i) {
+ if (isPlaying(i))
+ stopSound(i);
+ }
+}
+
+void SoundDigital::beginFadeOut(int channel, int ticks) {
+ if (isPlaying(channel))
+ _sounds[channel].stream->beginFadeOut(ticks * _vm->tickLength());
+}
+
+// static res
+
+namespace {
+
+Audio::SeekableAudioStream *makeAUDStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse) {
+ return new AUDStream(stream);
+}
+
+} // end of anonymous namespace
+
+const SoundDigital::AudioCodecs SoundDigital::_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 },
+ { 0, 0 }
+};
+
+
+} // End of namespace Kyra
diff --git a/engines/kyra/sound/sound_digital.h b/engines/kyra/sound/sound_digital.h
new file mode 100644
index 0000000000..271dde6a21
--- /dev/null
+++ b/engines/kyra/sound/sound_digital.h
@@ -0,0 +1,119 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef KYRA_SOUND_DIGITAL_H
+#define KYRA_SOUND_DIGITAL_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 {
+public:
+ SoundDigital(KyraEngine_MR *vm, Audio::Mixer *mixer);
+ ~SoundDigital();
+
+ /**
+ * 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
diff --git a/engines/kyra/sound/sound_intern.h b/engines/kyra/sound/sound_intern.h
new file mode 100644
index 0000000000..77436d08ee
--- /dev/null
+++ b/engines/kyra/sound/sound_intern.h
@@ -0,0 +1,407 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef KYRA_SOUND_INTERN_H
+#define KYRA_SOUND_INTERN_H
+
+
+
+#include "kyra/sound/sound.h"
+#include "kyra/sound/sound_adlib.h"
+
+#include "common/mutex.h"
+
+#include "audio/softsynth/fmtowns_pc98/towns_pc98_driver.h"
+#include "audio/softsynth/fmtowns_pc98/towns_euphony.h"
+
+#include "audio/softsynth/emumidi.h"
+#include "audio/midiparser.h"
+
+namespace Audio {
+class PCSpeaker;
+class MaxTrax;
+} // End of namespace Audio
+
+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);
+ virtual ~SoundMidiPC();
+
+ virtual kType getMusicType() const { return _type; }
+
+ virtual bool init();
+
+ virtual void updateVolumeSettings();
+
+ virtual void initAudioResourceInfo(int set, void *info);
+ virtual void selectAudioResourceSet(int set);
+ virtual bool hasSoundFile(uint file) const;
+ virtual void loadSoundFile(uint file);
+ virtual void loadSoundFile(Common::String file);
+ virtual void loadSfxFile(Common::String file);
+
+ virtual void playTrack(uint8 track);
+ virtual void haltTrack();
+ virtual bool isPlaying() const;
+
+ virtual void playSoundEffect(uint8 track, uint8 volume = 0xFF);
+ virtual void stopAllSoundEffects();
+
+ virtual void beginFadeOut();
+
+ virtual void pause(bool paused);
+private:
+ static void onTimer(void *data);
+
+ // Our channel handling
+ int _musicVolume, _sfxVolume;
+
+ uint32 _fadeStartTime;
+ bool _fadeMusicOut;
+
+ // Midi file related
+ Common::String _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::String getFileName(const Common::String &str);
+
+ bool _nativeMT32;
+ MidiDriver *_driver;
+ MidiOutput *_output;
+
+ Common::Mutex _mutex;
+};
+
+class SoundTowns : public Sound {
+public:
+ SoundTowns(KyraEngine_v1 *vm, Audio::Mixer *mixer);
+ virtual ~SoundTowns();
+
+ virtual kType getMusicType() const { return kTowns; }
+
+ virtual bool init();
+ virtual void process();
+
+ virtual void initAudioResourceInfo(int set, void *info);
+ virtual void selectAudioResourceSet(int set);
+ virtual bool hasSoundFile(uint file) const;
+ virtual void loadSoundFile(uint file);
+ virtual void loadSoundFile(Common::String) {}
+
+ virtual void playTrack(uint8 track);
+ virtual void haltTrack();
+
+ virtual void playSoundEffect(uint8 track, uint8 volume = 0xFF);
+ virtual void stopAllSoundEffects();
+
+ virtual void beginFadeOut();
+
+ virtual void updateVolumeSettings();
+
+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 : public Sound {
+public:
+ SoundPC98(KyraEngine_v1 *vm, Audio::Mixer *mixer);
+ virtual ~SoundPC98();
+
+ virtual kType getMusicType() const override { return kPC98; }
+
+ virtual bool init() override;
+
+ virtual void initAudioResourceInfo(int set, void *info) override;
+ virtual void selectAudioResourceSet(int set) override;
+ virtual bool hasSoundFile(uint file) const override;
+ virtual void loadSoundFile(uint file) override;
+ virtual void loadSoundFile(Common::String file) override;
+
+ virtual void playTrack(uint8 track) override;
+ virtual void haltTrack() override;
+ virtual void beginFadeOut() override;
+
+ virtual int32 voicePlay(const char *file, Audio::SoundHandle *handle, uint8 volume, uint8 priority, bool isSfx) override { return -1; }
+ virtual void playSoundEffect(uint8 track, uint8 volume = 0xFF) override;
+
+ virtual 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);
+ virtual ~SoundTownsPC98_v2();
+
+ virtual kType getMusicType() const override { return _vm->gameFlags().platform == Common::kPlatformFMTowns ? kTowns : kPC98; }
+
+ virtual bool init() override;
+ virtual void process() override;
+
+ virtual void initAudioResourceInfo(int set, void *info) override;
+ virtual void selectAudioResourceSet(int set) override;
+ virtual bool hasSoundFile(uint file) const override;
+ virtual void loadSoundFile(uint file) override {}
+ virtual void loadSoundFile(Common::String file) override;
+
+ virtual void playTrack(uint8 track) override;
+ virtual void haltTrack() override;
+ virtual void beginFadeOut() override;
+
+ virtual int32 voicePlay(const char *file, Audio::SoundHandle *handle, uint8 volume = 255, uint8 priority = 255, bool isSfx = true) override;
+ virtual void playSoundEffect(uint8 track, uint8 volume = 0xFF) override;
+
+ virtual 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();
+
+ // MidiDriver interface
+ virtual void close() {}
+
+ virtual void send(uint32 data);
+
+ virtual MidiChannel *allocateChannel() { return 0; }
+ virtual MidiChannel *getPercussionChannel() { return 0; }
+
+ // MidiDriver_Emulated interface
+ void generateSamples(int16 *buffer, int numSamples);
+
+ // AudioStream interface
+ bool isStereo() const { return false; }
+ int getRate() const { return _rate; }
+private:
+ Common::Mutex _mutex;
+ Audio::PCSpeaker *_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();
+
+ 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 : public Sound {
+public:
+ SoundAmiga(KyraEngine_v1 *vm, Audio::Mixer *mixer);
+ virtual ~SoundAmiga();
+
+ virtual kType getMusicType() const override { return kAmiga; } //FIXME
+
+ virtual bool init() override;
+
+ virtual void initAudioResourceInfo(int set, void *info) override;
+ virtual void selectAudioResourceSet(int set) override;
+ virtual bool hasSoundFile(uint file) const override;
+ virtual void loadSoundFile(uint file) override;
+ virtual void loadSoundFile(Common::String) override {}
+
+ virtual void playTrack(uint8 track) override;
+ virtual void haltTrack() override;
+ virtual void beginFadeOut() override;
+
+ virtual int32 voicePlay(const char *file, Audio::SoundHandle *handle, uint8 volume, uint8 priority, bool isSfx) override { return -1; }
+ virtual void playSoundEffect(uint8 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 SoundTowns_Darkmoon : public Sound, public TownsAudioInterfacePluginDriver {
+public:
+ SoundTowns_Darkmoon(KyraEngine_v1 *vm, Audio::Mixer *mixer);
+ virtual ~SoundTowns_Darkmoon();
+
+ virtual kType getMusicType() const { return kTowns; }
+
+ virtual bool init();
+
+ void timerCallback(int timerId);
+
+ virtual void initAudioResourceInfo(int set, void *info);
+ virtual void selectAudioResourceSet(int set);
+ virtual bool hasSoundFile(uint file) const;
+ virtual void loadSoundFile(uint file) {}
+ virtual void loadSoundFile(Common::String name);
+
+ virtual void playTrack(uint8 track);
+ virtual void haltTrack();
+ virtual bool isPlaying() const;
+
+ virtual void playSoundEffect(uint8 track, uint8 volume = 0xFF);
+ virtual void stopAllSoundEffects();
+
+ virtual void beginFadeOut();
+
+ virtual void updateVolumeSettings();
+
+ virtual int checkTrigger();
+
+ virtual void resetTrigger();
+
+private:
+ struct SoundTableEntry {
+ int8 type;
+ int32 para1;
+ int16 para2;
+ } _soundTable[120];
+
+ uint8 _lastSfxChan;
+ uint8 _lastEnvChan;
+ uint8 *_pcmData;
+ uint32 _pcmDataSize;
+ uint8 _pcmVol;
+
+ int _timer;
+ int _timerSwitch;
+
+ SoundResourceInfo_TownsEoB *_pcmResource[3];
+
+ TownsAudioInterface *_intf;
+};
+
+} // End of namespace Kyra
+
+#endif
diff --git a/engines/kyra/sound/sound_lok.cpp b/engines/kyra/sound/sound_lok.cpp
new file mode 100644
index 0000000000..34436637a0
--- /dev/null
+++ b/engines/kyra/sound/sound_lok.cpp
@@ -0,0 +1,96 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "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;
+ }
+
+ 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
diff --git a/engines/kyra/sound/sound_lol.cpp b/engines/kyra/sound/sound_lol.cpp
new file mode 100644
index 0000000000..ae64af91b9
--- /dev/null
+++ b/engines/kyra/sound/sound_lol.cpp
@@ -0,0 +1,307 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#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 (scumm_stricmp(_ingameSoundList[vocIndex], "EMPTY"))
+ hasVocFile = true;
+ }
+
+ if (hasVocFile) {
+ if (_sound->isVoicePresent(_ingameSoundList[vocIndex]))
+ _sound->voicePlay(_ingameSoundList[vocIndex], 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()) {
+ if (_flags.platform == Common::kPlatformDOS) {
+ int t = (track - 250) * 3;
+ if (_curMusicFileIndex != _musicTrackMap[t] || _curMusicFileExt != (char)_musicTrackMap[t + 1]) {
+ snd_stopMusic();
+ _sound->loadSoundFile(Common::String::format("LORE%02d%c", _musicTrackMap[t], (char)_musicTrackMap[t + 1]));
+ _curMusicFileIndex = _musicTrackMap[t];
+ _curMusicFileExt = (char)_musicTrackMap[t + 1];
+ } else {
+ snd_stopMusic();
+ }
+ }
+ }
+}
+
+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
diff --git a/engines/kyra/sound/sound_midi.cpp b/engines/kyra/sound/sound_midi.cpp
new file mode 100644
index 0000000000..c0cf6c1b16
--- /dev/null
+++ b/engines/kyra/sound/sound_midi.cpp
@@ -0,0 +1,814 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "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 {
+
+class MidiOutput : public MidiDriver_BASE {
+public:
+ MidiOutput(OSystem *system, MidiDriver *output, bool isMT32, bool defaultMT32);
+ ~MidiOutput();
+
+ void setSourceVolume(int source, int volume, bool apply=false);
+
+ void initSource(int source);
+ void deinitSource(int source);
+ void stopNotesOnChannel(int channel);
+
+ void setSoundSource(int source) { _curSource = source; }
+
+ // MidiDriver_BASE interface
+ virtual void send(uint32 b);
+ virtual void sysEx(const byte *msg, uint16 length);
+ virtual void metaEvent(byte type, byte *data, uint16 length);
+
+ // TODO: Get rid of the following two methods
+ void setTimerCallback(void *timerParam, void (*timerProc)(void *)) { _output->setTimerCallback(timerParam, timerProc); }
+ uint32 getBaseTempo() { return _output->getBaseTempo(); }
+
+
+private:
+ void sendIntern(const byte event, const byte channel, byte param1, const byte param2);
+ void sendSysEx(const byte p1, const byte p2, const byte p3, const byte *buffer, const int size);
+
+ OSystem *_system;
+ MidiDriver *_output;
+
+ bool _isMT32;
+ bool _defaultMT32;
+
+ struct Controller {
+ byte controller;
+ byte value;
+ };
+
+ enum {
+ kChannelLocked = 0x80,
+ kChannelProtected = 0x40
+ };
+
+ struct Channel {
+ byte flags;
+
+ byte program;
+ int16 pitchWheel;
+
+ byte noteCount;
+
+ Controller controllers[9];
+ } _channels[16];
+
+ int lockChannel();
+ void unlockChannel(int channel);
+
+ int _curSource;
+
+ struct SoundSource {
+ int volume;
+
+ int8 channelMap[16];
+ byte channelProgram[16];
+ int16 channelPW[16];
+ Controller controllers[16][9];
+
+ struct Note {
+ byte channel;
+ byte note;
+ };
+
+ Note notes[32];
+ } _sources[4];
+};
+
+MidiOutput::MidiOutput(OSystem *system, MidiDriver *output, bool isMT32, bool defaultMT32) : _system(system), _output(output) {
+ _isMT32 = isMT32;
+ _defaultMT32 = defaultMT32;
+
+ int ret = _output->open();
+ if (ret != MidiDriver::MERR_ALREADY_OPEN && ret != 0)
+ error("Couldn't open midi driver");
+
+ static const Controller defaultControllers[] = {
+ { 0x07, 0x7F }, { 0x01, 0x00 }, { 0x0A, 0x40 },
+ { 0x0B, 0x7F }, { 0x40, 0x00 }, { 0x72, 0x00 },
+ { 0x6E, 0x00 }, { 0x6F, 0x00 }, { 0x70, 0x00 }
+ };
+
+ static const byte defaultPrograms[] = {
+ 0x44, 0x30, 0x5F, 0x4E, 0x29, 0x03, 0x6E, 0x7A, 0xFF
+ };
+
+ static const byte sysEx1[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
+ static const byte sysEx2[] = { 3, 4, 3, 4, 3, 4, 3, 4, 4 };
+ static const byte sysEx3[] = { 0, 3, 2 };
+
+ if (_isMT32) {
+ sendSysEx(0x7F, 0x00, 0x00, sysEx1, 1);
+ sendSysEx(0x10, 0x00, 0x0D, sysEx1, 9);
+ sendSysEx(0x10, 0x00, 0x04, sysEx2, 9);
+ sendSysEx(0x10, 0x00, 0x01, sysEx3, 3);
+ } else {
+ _output->sendGMReset();
+ }
+
+ memset(_channels, 0, sizeof(_channels));
+ for (int i = 0; i < 16; ++i) {
+ for (int j = 0; j < 9; ++j)
+ _channels[i].controllers[j] = defaultControllers[j];
+ _channels[i].pitchWheel = -1;
+ _channels[i].program = 0xFF;
+ }
+
+ for (int i = 0; i < 9; ++i) {
+ for (int j = 1; j <= 9; ++j)
+ sendIntern(0xB0, j, defaultControllers[i].controller, defaultControllers[i].value);
+ }
+
+ for (int i = 1; i <= 9; ++i) {
+ sendIntern(0xE0, i, 0x00, 0x40);
+ if (defaultPrograms[i - 1] != 0xFF)
+ sendIntern(0xC0, i, defaultPrograms[i - 1], 0x00);
+ }
+
+ for (int i = 0; i < 4; ++i) {
+ _sources[i].volume = 256;
+ initSource(i);
+ }
+}
+
+
+MidiOutput::~MidiOutput() {
+ _output->close();
+ delete _output;
+}
+
+void MidiOutput::send(uint32 b) {
+ const byte event = b & 0xF0;
+ const byte channel = b & 0x0F;
+ byte param1 = (b >> 8) & 0xFF;
+ byte param2 = (b >> 16) & 0xFF;
+
+ if (event == 0xE0) { // Pitch-Wheel
+ _channels[channel].pitchWheel =
+ _sources[_curSource].channelPW[channel] = (param2 << 8) | param1;
+ } else if (event == 0xC0) { // Program change
+ _channels[channel].program =
+ _sources[_curSource].channelProgram[channel] = param1;
+ } else if (event == 0xB0) { // Controller change
+ for (int i = 0; i < 9; ++i) {
+ Controller &cont = _sources[_curSource].controllers[channel][i];
+ if (cont.controller == param1) {
+ cont.value = param2;
+ break;
+ }
+ }
+
+ if (param1 == 0x07) {
+ param2 = (param2 * _sources[_curSource].volume) >> 8;
+ } else if (param1 == 0x6E) { // Lock Channel
+ if (param2 >= 0x40) { // Lock Channel
+ int chan = lockChannel();
+ if (chan < 0)
+ chan = channel;
+ _sources[_curSource].channelMap[channel] = chan;
+ } else { // Unlock Channel
+ stopNotesOnChannel(channel);
+ unlockChannel(_sources[_curSource].channelMap[channel]);
+ _sources[_curSource].channelMap[channel] = channel;
+ }
+ } else if (param1 == 0x6F) { // Protect Channel
+ if (param2 >= 0x40) { // Protect Channel
+ _channels[channel].flags |= kChannelProtected;
+ } else { // Unprotect Channel
+ _channels[channel].flags &= ~kChannelProtected;
+ }
+ } else if (param1 == 0x7B) { // All notes off
+ // FIXME: Since the XMIDI parsers sends this
+ // on track change, we simply ignore it.
+ return;
+ }
+ } else if (event == 0x90 || event == 0x80) { // Note On/Off
+ if (!(_channels[channel].flags & kChannelLocked)) {
+ const bool remove = (event == 0x80) || (param2 == 0x00);
+ int note = -1;
+
+ for (int i = 0; i < 32; ++i) {
+ if (remove) {
+ if (_sources[_curSource].notes[i].channel == channel &&
+ _sources[_curSource].notes[i].note == param1) {
+ note = i;
+ break;
+ }
+ } else {
+ if (_sources[_curSource].notes[i].channel == 0xFF) {
+ note = i;
+ break;
+ }
+ }
+ }
+
+ if (note != -1) {
+ if (remove) {
+ _sources[_curSource].notes[note].channel = 0xFF;
+
+ --_channels[_sources[_curSource].channelMap[channel]].noteCount;
+ } else {
+ _sources[_curSource].notes[note].channel = channel;
+ _sources[_curSource].notes[note].note = param1;
+
+ ++_channels[_sources[_curSource].channelMap[channel]].noteCount;
+ }
+
+ sendIntern(event, _sources[_curSource].channelMap[channel], param1, param2);
+ }
+ }
+ return;
+ }
+
+ if (!(_channels[channel].flags & kChannelLocked))
+ sendIntern(event, _sources[_curSource].channelMap[channel], param1, param2);
+}
+
+void MidiOutput::sendIntern(const byte event, const byte channel, byte param1, const byte param2) {
+ if (event == 0xC0) {
+ // MT32 -> GM conversion
+ if (!_isMT32 && _defaultMT32)
+ param1 = MidiDriver::_mt32ToGm[param1];
+ }
+
+ _output->send(event | channel, param1, param2);
+}
+
+void MidiOutput::sysEx(const byte *msg, uint16 length) {
+ // Wait the time it takes to send the SysEx data
+ uint32 delay = (length + 2) * 1000 / 3125;
+
+ // Plus an additional delay for the MT-32 rev00
+ if (_isMT32)
+ delay += 40;
+
+ _output->sysEx(msg, length);
+ _system->delayMillis(delay);
+}
+
+void MidiOutput::sendSysEx(const byte p1, const byte p2, const byte p3, const byte *buffer, const int size) {
+ int bufferSize = 8 + size;
+ byte *outBuffer = new byte[bufferSize];
+ assert(outBuffer);
+
+ outBuffer[0] = 0x41;
+ outBuffer[1] = 0x10;
+ outBuffer[2] = 0x16;
+ outBuffer[3] = 0x12;
+
+ outBuffer[4] = p1;
+ outBuffer[5] = p2;
+ outBuffer[6] = p3;
+
+ memcpy(outBuffer + 7, buffer, size);
+
+ uint16 checkSum = p1 + p2 + p3;
+ for (int i = 0; i < size; ++i)
+ checkSum += buffer[i];
+ checkSum &= 0x7F;
+ checkSum -= 0x80;
+ checkSum = -checkSum;
+ checkSum &= 0x7F;
+
+ outBuffer[7+size] = checkSum;
+
+ sysEx(outBuffer, bufferSize);
+
+ delete[] outBuffer;
+}
+
+void MidiOutput::metaEvent(byte type, byte *data, uint16 length) {
+ if (type == 0x2F) // End of Track
+ deinitSource(_curSource);
+
+ _output->metaEvent(type, data, length);
+}
+
+void MidiOutput::setSourceVolume(int source, int volume, bool apply) {
+ _sources[source].volume = volume;
+
+ if (apply) {
+ for (int i = 0; i < 16; ++i) {
+ // Controller 0 in the state table should always be '7' aka
+ // volume control
+ byte realVol = (_sources[source].controllers[i][0].value * volume) >> 8;
+ sendIntern(0xB0, i, 0x07, realVol);
+ }
+ }
+}
+
+void MidiOutput::initSource(int source) {
+ memset(_sources[source].notes, -1, sizeof(_sources[source].notes));
+
+ for (int i = 0; i < 16; ++i) {
+ _sources[source].channelMap[i] = i;
+ _sources[source].channelProgram[i] = 0xFF;
+ _sources[source].channelPW[i] = -1;
+
+ for (int j = 0; j < 9; ++j)
+ _sources[source].controllers[i][j] = _channels[i].controllers[j];
+ }
+}
+
+void MidiOutput::deinitSource(int source) {
+ for (int i = 0; i < 16; ++i) {
+ for (int j = 0; j < 9; ++j) {
+ const Controller &cont = _sources[source].controllers[i][j];
+
+ if (cont.controller == 0x40) {
+ if (cont.value >= 0x40)
+ sendIntern(0xB0, i, 0x40, 0);
+ } else if (cont.controller == 0x6E) {
+ if (cont.value >= 0x40) {
+ stopNotesOnChannel(i);
+ unlockChannel(_sources[source].channelMap[i]);
+ _sources[source].channelMap[i] = i;
+ }
+ } else if (cont.controller == 0x6F) {
+ if (cont.value >= 0x40)
+ _channels[i].flags &= ~kChannelProtected;
+ } else if (cont.controller == 0x70) {
+ if (cont.value >= 0x40)
+ sendIntern(0xB0, i, 0x70, 0);
+ }
+ }
+ }
+}
+
+int MidiOutput::lockChannel() {
+ int channel = -1;
+ int notes = 0xFF;
+ byte flags = kChannelLocked | kChannelProtected;
+
+ while (channel == -1) {
+ for (int i = _isMT32 ? 8 : 15; i >= 1; --i) {
+ if (_channels[i].flags & flags)
+ continue;
+ if (_channels[i].noteCount < notes) {
+ channel = i;
+ notes = _channels[i].noteCount;
+ }
+ }
+
+ if (channel == -1) {
+ if (flags & kChannelProtected)
+ flags &= ~kChannelProtected;
+ else
+ break;
+ }
+ }
+
+ if (channel == -1)
+ return -1;
+
+ sendIntern(0xB0, channel, 0x40, 0);
+ stopNotesOnChannel(channel);
+ _channels[channel].noteCount = 0;
+ _channels[channel].flags |= kChannelLocked;
+
+ return channel;
+}
+
+void MidiOutput::unlockChannel(int channel) {
+ if (!(_channels[channel].flags & kChannelLocked))
+ return;
+
+ _channels[channel].flags &= ~kChannelLocked;
+ _channels[channel].noteCount = 0;
+ sendIntern(0xB0, channel, 0x40, 0);
+ sendIntern(0xB0, channel, 0x7B, 0);
+
+ for (int i = 0; i < 9; ++i) {
+ if (_channels[channel].controllers[i].value != 0xFF)
+ sendIntern(0xB0, channel, _channels[channel].controllers[i].controller, _channels[channel].controllers[i].value);
+ }
+
+ if (_channels[channel].program != 0xFF)
+ sendIntern(0xC0, channel, _channels[channel].program, 0);
+
+ if (_channels[channel].pitchWheel != -1)
+ sendIntern(0xE0, channel, _channels[channel].pitchWheel & 0xFF, (_channels[channel].pitchWheel >> 8) & 0xFF);
+}
+
+void MidiOutput::stopNotesOnChannel(int channel) {
+ for (int i = 0; i < 4; ++i) {
+ SoundSource &sound = _sources[i];
+ for (int j = 0; j < 32; ++j) {
+ if (sound.notes[j].channel == channel) {
+ sound.notes[j].channel = 0xFF;
+ sendIntern(0x80, sound.channelMap[channel], sound.notes[j].note, 0);
+ --_channels[sound.channelMap[channel]].noteCount;
+ }
+ }
+ }
+}
+
+#pragma mark -
+
+SoundMidiPC::SoundMidiPC(KyraEngine_v1 *vm, Audio::Mixer *mixer, MidiDriver *driver, kType type) : Sound(vm, mixer) {
+ _driver = driver;
+ _output = 0;
+
+ _musicFile = _sfxFile = 0;
+ _currentResourceSet = 0;
+ memset(&_resInfo, 0, sizeof(_resInfo));
+
+ _music = MidiParser::createParser_XMIDI();
+ assert(_music);
+ for (int i = 0; i < 3; ++i) {
+ _sfx[i] = MidiParser::createParser_XMIDI();
+ assert(_sfx[i]);
+ }
+
+ _musicVolume = _sfxVolume = 0;
+ _fadeMusicOut = false;
+
+ _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(0, 0);
+
+ delete _music;
+ for (int i = 0; i < 3; ++i)
+ delete _sfx[i];
+
+ delete _output; // This automatically frees _driver (!)
+
+ if (_musicFile != _sfxFile)
+ delete[] _sfxFile;
+
+ delete[] _musicFile;
+
+ for (int i = 0; i < 3; i++)
+ initAudioResourceInfo(i, 0);
+}
+
+bool SoundMidiPC::init() {
+ _output = new MidiOutput(_vm->_system, _driver, _nativeMT32, (_type != kMidiGM));
+ assert(_output);
+
+ 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);
+
+ if (_nativeMT32 && _type == kMidiMT32) {
+ const char *midiFile = 0;
+ const char *pakFile = 0;
+ 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().useAltShapeHeader) {
+ // Intro demo
+ pakFile = "INTROVOC.PAK";
+ } else {
+ // Kyra2 SEQ player based demo
+ pakFile = "GENERAL.PAK";
+ midiFile = "LOLSYSEX";
+ }
+ } else {
+ if (_vm->gameFlags().isTalkie)
+ pakFile = "ENG/STARTUP.PAK";
+ else
+ pakFile = "INTROVOC.PAK";
+ }
+ }
+
+ if (!midiFile)
+ return true;
+
+ if (pakFile)
+ _vm->resource()->loadPakFile(pakFile);
+
+ loadSoundFile(midiFile);
+ playTrack(0);
+
+ Common::Event event;
+ while (isPlaying() && !_vm->shouldQuit()) {
+ _vm->_system->updateScreen();
+ _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, newMusVol != _musicVolume);
+ _musicVolume = newMusVol;
+
+ for (int i = 1; i < 4; ++i)
+ _output->setSourceVolume(i, _sfxVolume, false);
+}
+
+void SoundMidiPC::initAudioResourceInfo(int set, void *info) {
+ if (set >= kMusicIntro && set <= kMusicFinale) {
+ delete _resInfo[set];
+ _resInfo[set] = info ? new SoundResourceInfo_PC(*(SoundResourceInfo_PC*)info) : 0;
+ }
+}
+
+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] != 0);
+ return false;
+}
+
+void SoundMidiPC::loadSoundFile(uint file) {
+ if (file < res()->fileListSize)
+ loadSoundFile(res()->fileList[file]);
+}
+
+void SoundMidiPC::loadSoundFile(Common::String file) {
+ Common::StackLock lock(_mutex);
+ file = getFileName(file);
+
+ if (_mFileName == file)
+ return;
+
+ if (!_vm->resource()->exists(file.c_str()))
+ return;
+
+ // When loading a new file we stop all notes
+ // still running on our own, just to prevent
+ // glitches
+ for (int i = 0; i < 16; ++i)
+ _output->stopNotesOnChannel(i);
+
+ delete[] _musicFile;
+ uint32 fileSize = 0;
+ _musicFile = _vm->resource()->fileData(file.c_str(), &fileSize);
+ _mFileName = file;
+
+ _output->setSoundSource(0);
+ _music->loadMusic(_musicFile, fileSize);
+ _music->stopPlaying();
+
+ // 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) {
+ _output->setSoundSource(i+1);
+ _sfx[i]->loadMusic(_musicFile, fileSize);
+ _sfx[i]->stopPlaying();
+ }
+ }
+}
+
+void SoundMidiPC::loadSfxFile(Common::String file) {
+ Common::StackLock lock(_mutex);
+
+ // Kyrandia 1 doesn't use a special sfx file
+ if (_vm->game() == GI_KYRA1)
+ return;
+
+ file = getFileName(file);
+
+ if (_sFileName == file)
+ return;
+
+ if (!_vm->resource()->exists(file.c_str()))
+ return;
+
+ delete[] _sfxFile;
+
+ uint32 fileSize = 0;
+ _sfxFile = _vm->resource()->fileData(file.c_str(), &fileSize);
+ _sFileName = file;
+
+ for (int i = 0; i < 3; ++i) {
+ _output->setSoundSource(i+1);
+ _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, true);
+
+ _output->initSource(0);
+ _output->setSourceVolume(0, _musicVolume, true);
+ _music->setTrack(track);
+}
+
+void SoundMidiPC::haltTrack() {
+ Common::StackLock lock(_mutex);
+
+ _output->setSoundSource(0);
+ _music->stopPlaying();
+ _output->deinitSource(0);
+}
+
+bool SoundMidiPC::isPlaying() const {
+ Common::StackLock lock(_mutex);
+
+ return _music->isPlaying();
+}
+
+void SoundMidiPC::playSoundEffect(uint8 track, uint8) {
+ if (!_sfxEnabled)
+ return;
+
+ Common::StackLock lock(_mutex);
+ for (int i = 0; i < 3; ++i) {
+ if (!_sfx[i]->isPlaying()) {
+ _output->initSource(i+1);
+ _sfx[i]->setTrack(track);
+ return;
+ }
+ }
+}
+
+void SoundMidiPC::stopAllSoundEffects() {
+ Common::StackLock lock(_mutex);
+
+ for (int i = 0; i < 3; ++i) {
+ _output->setSoundSource(i+1);
+ _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->setMidiDriver(0);
+ for (int i = 0; i < 3; i++)
+ _sfx[i]->setMidiDriver(0);
+ for (int i = 0; i < 16; i++)
+ _output->stopNotesOnChannel(i);
+ } else {
+ _music->setMidiDriver(_output);
+ for (int i = 0; i < 3; ++i)
+ _sfx[i]->setMidiDriver(_output);
+ // 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, true);
+ } else {
+ for (int i = 0; i < 16; ++i)
+ midi->_output->stopNotesOnChannel(i);
+ for (int i = 0; i < 4; ++i)
+ midi->_output->deinitSource(i);
+
+ midi->_output->setSoundSource(0);
+ midi->_music->stopPlaying();
+
+ for (int i = 0; i < 3; ++i) {
+ midi->_output->setSoundSource(i+1);
+ midi->_sfx[i]->stopPlaying();
+ }
+
+ midi->_fadeMusicOut = false;
+ }
+ }
+
+ midi->_output->setSoundSource(0);
+ midi->_music->onTimer();
+
+ for (int i = 0; i < 3; ++i) {
+ midi->_output->setSoundSource(i+1);
+ midi->_sfx[i]->onTimer();
+ }
+}
+
+Common::String SoundMidiPC::getFileName(const Common::String &str) {
+ Common::String file = str;
+ if (_type == kMidiMT32)
+ file += ".XMI";
+ else if (_type == kMidiGM)
+ file += ".C55";
+ else if (_type == kPCSpkr)
+ file += ".PCS";
+
+ if (_vm->resource()->exists(file.c_str()))
+ return file;
+
+ return str + ".XMI";
+}
+
+} // End of namespace Kyra
diff --git a/engines/kyra/sound/sound_pcspk.cpp b/engines/kyra/sound/sound_pcspk.cpp
new file mode 100644
index 0000000000..110addefd8
--- /dev/null
+++ b/engines/kyra/sound/sound_pcspk.cpp
@@ -0,0 +1,366 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "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::PCSpeaker(_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 = 0;
+}
+
+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 (channel > 1)
+ return;
+
+ 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
diff --git a/engines/kyra/sound/sound_towns.cpp b/engines/kyra/sound/sound_towns.cpp
new file mode 100644
index 0000000000..621c2f1419
--- /dev/null
+++ b/engines/kyra/sound/sound_towns.cpp
@@ -0,0 +1,749 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "kyra/sound/sound_intern.h"
+#include "kyra/resource/resource.h"
+
+#include "common/config-manager.h"
+#include "common/system.h"
+
+#include "backends/audiocd/audiocd.h"
+
+#include "audio/audiostream.h"
+#include "audio/decoders/raw.h"
+
+namespace Kyra {
+
+SoundTowns::SoundTowns(KyraEngine_v1 *vm, Audio::Mixer *mixer)
+ : Sound(vm, mixer), _lastTrack(-1), _musicTrackData(0), _sfxFileData(0), _cdaPlaying(0),
+ _sfxFileIndex((uint)-1), _musicFadeTable(0), _sfxWDTable(0), _sfxBTTable(0), _sfxChannel(0x46), _currentResourceSet(0) {
+ memset(&_resInfo, 0, sizeof(_resInfo));
+ _player = new EuphonyPlayer(_mixer);
+}
+
+SoundTowns::~SoundTowns() {
+ g_system->getAudioCDManager()->stop();
+ haltTrack();
+ delete _player;
+ delete[] _musicTrackData;
+ delete[] _sfxFileData;
+ for (int i = 0; i < 3; i++)
+ initAudioResourceInfo(i, 0);
+}
+
+bool SoundTowns::init() {
+ _vm->checkCD();
+ 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::process() {
+ g_system->getAudioCDManager()->update();
+}
+
+void SoundTowns::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::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::initAudioResourceInfo(int set, void *info) {
+ if (set >= kMusicIntro && set <= kMusicFinale) {
+ delete _resInfo[set];
+ _resInfo[set] = info ? new SoundResourceInfo_Towns(*(SoundResourceInfo_Towns*)info) : 0;
+ }
+}
+
+void SoundTowns::selectAudioResourceSet(int set) {
+ if (set >= kMusicIntro && set <= kMusicFinale) {
+ if (_resInfo[set])
+ _currentResourceSet = set;
+ }
+}
+
+bool SoundTowns::hasSoundFile(uint file) const {
+ if (file < res()->fileListSize)
+ return (res()->fileList[file] != 0);
+ return false;
+}
+
+void SoundTowns::loadSoundFile(uint file) {
+ if (_sfxFileIndex == file || file >= res()->fileListSize)
+ return;
+ _sfxFileIndex = file;
+ delete[] _sfxFileData;
+ _sfxFileData = _vm->resource()->fileData(res()->fileList[file], 0);
+}
+
+void SoundTowns::playSoundEffect(uint8 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::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::stopAllSoundEffects() {
+ _player->driver()->channelVolume(0x46, 0);
+ _player->driver()->channelVolume(0x47, 0);
+ _player->driver()->stopSoundEffect(0x46);
+ _player->driver()->stopSoundEffect(0x47);
+ _sfxChannel = 0x46;
+}
+
+void SoundTowns::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)
+ 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::loadInstruments() {
+ uint8 *twm = _vm->resource()->fileData("twmusic.pak", 0);
+ 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::playEuphonyTrack(uint32 offset, int loop) {
+ uint8 *twm = _vm->resource()->fileData("twmusic.pak", 0);
+ 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::fadeOutSoundEffects() {
+ for (int i = 127; i > 0; i-= 12) {
+ _player->driver()->channelVolume(0x46, i);
+ _player->driver()->channelVolume(0x47, i);
+ _vm->delay(_vm->tickLength());
+ }
+ stopAllSoundEffects();
+}
+
+SoundPC98::SoundPC98(KyraEngine_v1 *vm, Audio::Mixer *mixer) :
+ Sound(vm, mixer), _musicTrackData(0), _sfxTrackData(0), _lastTrack(-1), _driver(0), _currentResourceSet(0) {
+ memset(&_resInfo, 0, sizeof(_resInfo));
+}
+
+SoundPC98::~SoundPC98() {
+ delete[] _musicTrackData;
+ delete[] _sfxTrackData;
+ delete _driver;
+ for (int i = 0; i < 3; i++)
+ initAudioResourceInfo(i, 0);
+}
+
+bool SoundPC98::init() {
+ _driver = new TownsPC98_AudioDriver(_mixer, TownsPC98_AudioDriver::kType26);
+ bool reslt = _driver->init();
+ updateVolumeSettings();
+
+ // Initialize CD for audio
+ g_system->getAudioCDManager()->open();
+
+ return reslt;
+}
+
+void SoundPC98::initAudioResourceInfo(int set, void *info) {
+ if (set >= kMusicIntro && set <= kMusicFinale) {
+ delete _resInfo[set];
+ _resInfo[set] = info ? new Common::String(((SoundResourceInfo_PC98*)info)->pattern) : 0;
+ }
+}
+
+void SoundPC98::selectAudioResourceSet(int set) {
+ if (set >= kMusicIntro && set <= kMusicFinale) {
+ if (_resInfo[set])
+ _currentResourceSet = set;
+ }
+}
+
+bool SoundPC98::hasSoundFile(uint file) const {
+ return true;
+}
+
+void SoundPC98::loadSoundFile(uint) {
+ if (_currentResourceSet == kMusicIntro) {
+ delete[] _sfxTrackData;
+ _sfxTrackData = 0;
+
+ 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::loadSoundFile(Common::String file) {
+ delete[] _sfxTrackData;
+ _sfxTrackData = _vm->resource()->fileData(file.c_str(), 0);
+}
+
+void SoundPC98::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(), 0);
+ if (_musicEnabled)
+ _driver->loadMusicData(_musicTrackData);
+
+ _lastTrack = track;
+}
+
+void SoundPC98::haltTrack() {
+ _lastTrack = -1;
+ g_system->getAudioCDManager()->stop();
+ g_system->getAudioCDManager()->update();
+ _driver->reset();
+}
+
+void SoundPC98::beginFadeOut() {
+ if (!_driver->musicPlaying())
+ return;
+
+ for (int i = 0; i < 20; i++) {
+ _driver->fadeStep();
+ _vm->delay(32);
+ }
+ haltTrack();
+}
+
+void SoundPC98::playSoundEffect(uint8 track, uint8) {
+ if (!_sfxTrackData)
+ return;
+
+ _driver->loadSoundEffectData(_sfxTrackData, track);
+}
+
+void SoundPC98::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")));
+}
+
+// KYRA 2
+
+SoundTownsPC98_v2::SoundTownsPC98_v2(KyraEngine_v1 *vm, Audio::Mixer *mixer) :
+ Sound(vm, mixer), _currentSFX(0), _musicTrackData(0), _sfxTrackData(0), _lastTrack(-1), _driver(0), _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, 0);
+}
+
+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)
+ _vm->checkCD();
+
+ // 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) : 0;
+ }
+}
+
+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] != 0);
+ return false;
+}
+
+void SoundTownsPC98_v2::loadSoundFile(Common::String file) {
+ delete[] _sfxTrackData;
+ _sfxTrackData = _vm->resource()->fileData(file.c_str(), 0);
+}
+
+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; i++) {
+ if (track == (uint8)READ_LE_UINT16(&res()->cdaTable[i * 2])) {
+ trackNum = (int)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] : 0);
+ if (musicFile.empty())
+ return;
+
+ delete[] _musicTrackData;
+
+ _musicTrackData = _vm->resource()->fileData(musicFile.c_str(), 0);
+ _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(), 0);
+ 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(uint8 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
+
+#undef EUPHONY_FADEOUT_TICKS
diff --git a/engines/kyra/sound/sound_towns_darkmoon.cpp b/engines/kyra/sound/sound_towns_darkmoon.cpp
new file mode 100644
index 0000000000..25fd4142b9
--- /dev/null
+++ b/engines/kyra/sound/sound_towns_darkmoon.cpp
@@ -0,0 +1,278 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "kyra/sound/sound_intern.h"
+#include "kyra/resource/resource.h"
+
+#include "common/config-manager.h"
+#include "common/system.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, false);
+ _pcmData = 0;
+ _pcmVol = 0;
+ _timer = 0;
+ _timerSwitch = 0;
+ memset(_pcmResource, 0, sizeof(_pcmResource));
+}
+
+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 _pcmResource[set];
+ _pcmResource[set] = info ? new SoundResourceInfo_TownsEoB(*(SoundResourceInfo_TownsEoB*)info) : 0;
+}
+
+void SoundTowns_Darkmoon::selectAudioResourceSet(int set) {
+ delete[] _pcmData;
+
+ if (!_pcmResource[set] || !_pcmResource[kMusicIngame])
+ return;
+
+ _pcmDataSize = _pcmResource[kMusicIngame]->pcmDataSize;
+ _pcmData = new uint8[_pcmDataSize];
+ _pcmVol = _pcmResource[set]->pcmVolume;
+ memcpy(_pcmData, _pcmResource[kMusicIngame]->pcmData, _pcmDataSize);
+
+ if (set == kMusicIngame)
+ return;
+
+ memcpy(_pcmData, _pcmResource[set]->pcmData, _pcmResource[set]->pcmDataSize);
+}
+
+bool SoundTowns_Darkmoon::hasSoundFile(uint file) const {
+ return true;
+}
+
+void SoundTowns_Darkmoon::loadSoundFile(Common::String name) {
+ Common::SeekableReadStream *s = _vm->resource()->createReadStream(Common::String::format("%s.SDT", name.c_str()));
+ if (!s)
+ error("Failed to load sound file '%s.SDT'", name.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(Common::String::format("%s.PMB", name.c_str()).c_str(), &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.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(uint8 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