aboutsummaryrefslogtreecommitdiff
path: root/engines/sci/sound
diff options
context:
space:
mode:
authorFilippos Karapetis2010-01-05 01:22:16 +0000
committerFilippos Karapetis2010-01-05 01:22:16 +0000
commit84cd8d2dc7673bf883945cfdf600d98769817bc6 (patch)
tree9a57872c63fbf0a144b5fdd463d6bda43aa714df /engines/sci/sound
parentd8c59f5baa386a6baaf62685b794c2531b9cdd64 (diff)
downloadscummvm-rg350-84cd8d2dc7673bf883945cfdf600d98769817bc6.tar.gz
scummvm-rg350-84cd8d2dc7673bf883945cfdf600d98769817bc6.tar.bz2
scummvm-rg350-84cd8d2dc7673bf883945cfdf600d98769817bc6.zip
Renamed /gui to /graphics and /sfx to /sound, to better illustrate their purpose
svn-id: r47007
Diffstat (limited to 'engines/sci/sound')
-rw-r--r--engines/sci/sound/audio.cpp355
-rw-r--r--engines/sci/sound/audio.h98
-rw-r--r--engines/sci/sound/iterator/core.cpp1015
-rw-r--r--engines/sci/sound/iterator/core.h209
-rw-r--r--engines/sci/sound/iterator/iterator.cpp1707
-rw-r--r--engines/sci/sound/iterator/iterator.h326
-rw-r--r--engines/sci/sound/iterator/iterator_internal.h276
-rw-r--r--engines/sci/sound/iterator/songlib.cpp189
-rw-r--r--engines/sci/sound/iterator/songlib.h171
-rw-r--r--engines/sci/sound/iterator/test-iterator.cpp423
-rw-r--r--engines/sci/sound/midiparser_sci.cpp506
-rw-r--r--engines/sci/sound/midiparser_sci.h85
-rw-r--r--engines/sci/sound/music.cpp573
-rw-r--r--engines/sci/sound/music.h219
-rw-r--r--engines/sci/sound/seq/gm.cpp167
-rw-r--r--engines/sci/sound/seq/instrument-map.cpp505
-rw-r--r--engines/sci/sound/seq/instrument-map.h125
-rw-r--r--engines/sci/sound/seq/map-mt32-to-gm.cpp792
-rw-r--r--engines/sci/sound/seq/midiwriter.h88
-rw-r--r--engines/sci/sound/softseq/adlib.cpp827
-rw-r--r--engines/sci/sound/softseq/amiga.cpp676
-rw-r--r--engines/sci/sound/softseq/mididriver.h110
-rw-r--r--engines/sci/sound/softseq/pcjr.cpp209
-rw-r--r--engines/sci/sound/softseq/pcjr.h84
-rw-r--r--engines/sci/sound/soundcmd.cpp1090
-rw-r--r--engines/sci/sound/soundcmd.h122
26 files changed, 10947 insertions, 0 deletions
diff --git a/engines/sci/sound/audio.cpp b/engines/sci/sound/audio.cpp
new file mode 100644
index 0000000000..801864265f
--- /dev/null
+++ b/engines/sci/sound/audio.cpp
@@ -0,0 +1,355 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "sci/resource.h"
+#include "sci/engine/kernel.h"
+#include "sci/engine/seg_manager.h"
+#include "sci/sound/audio.h"
+
+#include "common/system.h"
+
+#include "sound/audiostream.h"
+#include "sound/audiocd.h"
+#include "sound/wave.h"
+
+namespace Sci {
+
+AudioPlayer::AudioPlayer(ResourceManager *resMan) : _resMan(resMan), _audioRate(11025),
+ _syncResource(NULL), _syncOffset(0), _audioCdStart(0) {
+
+ _mixer = g_system->getMixer();
+}
+
+AudioPlayer::~AudioPlayer() {
+ stopAllAudio();
+}
+
+void AudioPlayer::stopAllAudio() {
+ stopSoundSync();
+ stopAudio();
+ if (_audioCdStart > 0)
+ audioCdStop();
+}
+
+int AudioPlayer::startAudio(uint16 module, uint32 number) {
+ int sampleLen;
+ Audio::AudioStream *audioStream = getAudioStream(number, module, &sampleLen);
+
+ if (audioStream) {
+ _mixer->playInputStream(Audio::Mixer::kSpeechSoundType, &_audioHandle, audioStream);
+ return sampleLen;
+ }
+
+ return 0;
+}
+
+void AudioPlayer::stopAudio() {
+ _mixer->stopHandle(_audioHandle);
+}
+
+void AudioPlayer::pauseAudio() {
+ _mixer->pauseHandle(_audioHandle, true);
+}
+
+void AudioPlayer::resumeAudio() {
+ _mixer->pauseHandle(_audioHandle, false);
+}
+
+int AudioPlayer::getAudioPosition() {
+ if (_mixer->isSoundHandleActive(_audioHandle))
+ return _mixer->getSoundElapsedTime(_audioHandle) * 6 / 100; // return elapsed time in ticks
+ else
+ return -1; // Sound finished
+}
+
+enum SolFlags {
+ kSolFlagCompressed = 1 << 0,
+ kSolFlagUnknown = 1 << 1,
+ kSolFlag16Bit = 1 << 2,
+ kSolFlagIsSigned = 1 << 3
+};
+
+// FIXME: Move this to sound/adpcm.cpp?
+// Note that the 16-bit version is also used in coktelvideo.cpp
+static const uint16 tableDPCM16[128] = {
+ 0x0000, 0x0008, 0x0010, 0x0020, 0x0030, 0x0040, 0x0050, 0x0060, 0x0070, 0x0080,
+ 0x0090, 0x00A0, 0x00B0, 0x00C0, 0x00D0, 0x00E0, 0x00F0, 0x0100, 0x0110, 0x0120,
+ 0x0130, 0x0140, 0x0150, 0x0160, 0x0170, 0x0180, 0x0190, 0x01A0, 0x01B0, 0x01C0,
+ 0x01D0, 0x01E0, 0x01F0, 0x0200, 0x0208, 0x0210, 0x0218, 0x0220, 0x0228, 0x0230,
+ 0x0238, 0x0240, 0x0248, 0x0250, 0x0258, 0x0260, 0x0268, 0x0270, 0x0278, 0x0280,
+ 0x0288, 0x0290, 0x0298, 0x02A0, 0x02A8, 0x02B0, 0x02B8, 0x02C0, 0x02C8, 0x02D0,
+ 0x02D8, 0x02E0, 0x02E8, 0x02F0, 0x02F8, 0x0300, 0x0308, 0x0310, 0x0318, 0x0320,
+ 0x0328, 0x0330, 0x0338, 0x0340, 0x0348, 0x0350, 0x0358, 0x0360, 0x0368, 0x0370,
+ 0x0378, 0x0380, 0x0388, 0x0390, 0x0398, 0x03A0, 0x03A8, 0x03B0, 0x03B8, 0x03C0,
+ 0x03C8, 0x03D0, 0x03D8, 0x03E0, 0x03E8, 0x03F0, 0x03F8, 0x0400, 0x0440, 0x0480,
+ 0x04C0, 0x0500, 0x0540, 0x0580, 0x05C0, 0x0600, 0x0640, 0x0680, 0x06C0, 0x0700,
+ 0x0740, 0x0780, 0x07C0, 0x0800, 0x0900, 0x0A00, 0x0B00, 0x0C00, 0x0D00, 0x0E00,
+ 0x0F00, 0x1000, 0x1400, 0x1800, 0x1C00, 0x2000, 0x3000, 0x4000
+};
+
+static const byte tableDPCM8[8] = {0, 1, 2, 3, 6, 10, 15, 21};
+
+static void deDPCM16(byte *soundBuf, Common::SeekableReadStream &audioStream, uint32 n) {
+ int16 *out = (int16 *) soundBuf;
+
+ int32 s = 0;
+ for (uint32 i = 0; i < n; i++) {
+ byte b = audioStream.readByte();
+ if (b & 0x80)
+ s -= tableDPCM16[b & 0x7f];
+ else
+ s += tableDPCM16[b];
+
+ s = CLIP<int32>(s, -32768, 32767);
+ *out++ = s;
+ }
+}
+
+static void deDPCM8Nibble(byte *soundBuf, int32 &s, byte b) {
+ if (b & 8)
+ s -= tableDPCM8[7 - (b & 7)];
+ else
+ s += tableDPCM8[b & 7];
+ s = CLIP<int32>(s, 0, 255);
+ *soundBuf = TO_LE_16(s);
+}
+
+static void deDPCM8(byte *soundBuf, Common::SeekableReadStream &audioStream, uint32 n) {
+ int32 s = 0x80;
+
+ for (uint i = 0; i < n; i++) {
+ byte b = audioStream.readByte();
+
+ deDPCM8Nibble(soundBuf++, s, b >> 4);
+ deDPCM8Nibble(soundBuf++, s, b & 0xf);
+ }
+}
+
+// Sierra SOL audio file reader
+// Check here for more info: http://wiki.multimedia.cx/index.php?title=Sierra_Audio
+static bool readSOLHeader(Common::SeekableReadStream *audioStream, int headerSize, uint32 &size, uint16 &audioRate, byte &audioFlags) {
+ if (headerSize != 11 && headerSize != 12) {
+ warning("SOL audio header of size %i not supported", headerSize);
+ return false;
+ }
+
+ audioStream->readUint32LE(); // skip "SOL" + 0 (4 bytes)
+ audioRate = audioStream->readUint16LE();
+ audioFlags = audioStream->readByte();
+
+ size = audioStream->readUint32LE();
+ return true;
+}
+
+static byte* readSOLAudio(Common::SeekableReadStream *audioStream, uint32 &size, byte audioFlags, byte &flags) {
+ byte *buffer;
+
+ // Convert the SOL stream flags to our own format
+ flags = 0;
+ if (audioFlags & kSolFlag16Bit)
+ flags |= Audio::Mixer::FLAG_16BITS | Audio::Mixer::FLAG_LITTLE_ENDIAN;
+
+ if (!(audioFlags & kSolFlagIsSigned))
+ flags |= Audio::Mixer::FLAG_UNSIGNED;
+
+ if (audioFlags & kSolFlagCompressed) {
+ buffer = (byte *)malloc(size * 2);
+
+ if (audioFlags & kSolFlag16Bit)
+ deDPCM16(buffer, *audioStream, size);
+ else
+ deDPCM8(buffer, *audioStream, size);
+
+ size *= 2;
+ } else {
+ // We assume that the sound data is raw PCM
+ buffer = (byte *)malloc(size);
+ audioStream->read(buffer, size);
+ }
+
+ return buffer;
+}
+
+Audio::AudioStream* AudioPlayer::getAudioStream(uint32 number, uint32 volume, int *sampleLen) {
+ Audio::AudioStream *audioStream = 0;
+ uint32 size = 0;
+ byte *data = 0;
+ byte flags = 0;
+ Sci::Resource* audioRes;
+
+ if (volume == 65535) {
+ audioRes = _resMan->findResource(ResourceId(kResourceTypeAudio, number), false);
+ if (!audioRes) {
+ warning("Failed to find audio entry %i", number);
+ return NULL;
+ }
+ } else {
+ audioRes = _resMan->findResource(ResourceId(kResourceTypeAudio36, volume, number), false);
+ if (!audioRes) {
+ warning("Failed to find audio entry (%i, %i, %i, %i, %i)", volume, (number >> 24) & 0xff,
+ (number >> 16) & 0xff, (number >> 8) & 0xff, number & 0xff);
+ return NULL;
+ }
+ }
+
+ byte audioFlags;
+
+ if (audioRes->headerSize > 0) {
+ // SCI1.1
+ Common::MemoryReadStream headerStream(audioRes->header, audioRes->headerSize, Common::DisposeAfterUse::NO);
+
+ if (readSOLHeader(&headerStream, audioRes->headerSize, size, _audioRate, audioFlags)) {
+ Common::MemoryReadStream dataStream(audioRes->data, audioRes->size, Common::DisposeAfterUse::NO);
+ data = readSOLAudio(&dataStream, size, audioFlags, flags);
+ }
+ } else {
+ // SCI1 or WAVE file
+ if (audioRes->size > 4) {
+ if (memcmp(audioRes->data, "RIFF", 4) == 0) {
+ // WAVE detected
+ Common::MemoryReadStream *waveStream = new Common::MemoryReadStream(audioRes->data, audioRes->size, Common::DisposeAfterUse::NO);
+ audioStream = Audio::makeWAVStream(waveStream, true, false);
+ }
+ }
+ if (!audioStream) {
+ // SCI1 raw audio
+ size = audioRes->size;
+ data = (byte *)malloc(size);
+ assert(data);
+ memcpy(data, audioRes->data, size);
+ flags = Audio::Mixer::FLAG_UNSIGNED;
+ }
+ }
+
+ if (data) {
+ audioStream = Audio::makeLinearInputStream(data, size, _audioRate,
+ flags | Audio::Mixer::FLAG_AUTOFREE, 0, 0);
+ }
+ if (audioStream) {
+ *sampleLen = (flags & Audio::Mixer::FLAG_16BITS ? size >> 1 : size) * 60 / _audioRate;
+ return audioStream;
+ }
+
+ return NULL;
+}
+
+void AudioPlayer::setSoundSync(ResourceId id, reg_t syncObjAddr, SegManager *segMan) {
+ _syncResource = _resMan->findResource(id, 1);
+ _syncOffset = 0;
+
+ if (_syncResource) {
+ PUT_SEL32V(segMan, syncObjAddr, syncCue, 0);
+ } else {
+ warning("setSoundSync: failed to find resource %s", id.toString().c_str());
+ // Notify the scripts to stop sound sync
+ PUT_SEL32V(segMan, syncObjAddr, syncCue, SIGNAL_OFFSET);
+ }
+}
+
+void AudioPlayer::doSoundSync(reg_t syncObjAddr, SegManager *segMan) {
+ if (_syncResource && (_syncOffset < _syncResource->size - 1)) {
+ int16 syncCue = -1;
+ int16 syncTime = (int16)READ_LE_UINT16(_syncResource->data + _syncOffset);
+
+ _syncOffset += 2;
+
+ if ((syncTime != -1) && (_syncOffset < _syncResource->size - 1)) {
+ syncCue = (int16)READ_LE_UINT16(_syncResource->data + _syncOffset);
+ _syncOffset += 2;
+ }
+
+ PUT_SEL32V(segMan, syncObjAddr, syncTime, syncTime);
+ PUT_SEL32V(segMan, syncObjAddr, syncCue, syncCue);
+ }
+}
+
+void AudioPlayer::stopSoundSync() {
+ if (_syncResource) {
+ _resMan->unlockResource(_syncResource);
+ _syncResource = NULL;
+ }
+}
+
+int AudioPlayer::audioCdPlay(int track, int start, int duration) {
+ if (getSciVersion() == SCI_VERSION_1_1) {
+ // King's Quest VI CD Audio format
+ _audioCdStart = g_system->getMillis();
+
+ // Subtract one from track. KQ6 starts at track 1, while ScummVM
+ // ignores the data track and considers track 2 to be track 1.
+ AudioCD.play(track - 1, 1, start, duration);
+ return 1;
+ } else {
+ // Jones in the Fast Lane CD Audio format
+ uint32 length = 0;
+
+ audioCdStop();
+
+ Common::File audioMap;
+ if(!audioMap.open("cdaudio.map"))
+ error("Could not open cdaudio.map");
+
+ while (audioMap.pos() < audioMap.size()) {
+ uint16 res = audioMap.readUint16LE();
+ uint32 startFrame = audioMap.readUint16LE();
+ startFrame += audioMap.readByte() << 16;
+ audioMap.readByte(); // Unknown, always 0x20
+ length = audioMap.readUint16LE();
+ length += audioMap.readByte() << 16;
+ audioMap.readByte(); // Unknown, always 0x00
+
+ // Jones uses the track as the resource value in the map
+ if (res == track) {
+ AudioCD.play(1, 1, startFrame, length);
+ _audioCdStart = g_system->getMillis();
+ break;
+ }
+ }
+
+ audioMap.close();
+
+ return length * 60 / 75; // return sample length in ticks
+ }
+}
+
+void AudioPlayer::audioCdStop() {
+ _audioCdStart = 0;
+ AudioCD.stop();
+}
+
+void AudioPlayer::audioCdUpdate() {
+ AudioCD.updateCD();
+}
+
+int AudioPlayer::audioCdPosition() {
+ // Return -1 if the sample is done playing. Converting to frames to compare.
+ if (((g_system->getMillis() - _audioCdStart) * 75 / 1000) >= (uint32)AudioCD.getStatus().duration)
+ return -1;
+
+ // Return the position otherwise (in ticks).
+ return (g_system->getMillis() - _audioCdStart) * 60 / 1000;
+}
+
+} // End of namespace Sci
diff --git a/engines/sci/sound/audio.h b/engines/sci/sound/audio.h
new file mode 100644
index 0000000000..f71cabc735
--- /dev/null
+++ b/engines/sci/sound/audio.h
@@ -0,0 +1,98 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+/* Sound engine */
+#ifndef SCI_AUDIO_H
+#define SCI_AUDIO_H
+
+#include "sound/mixer.h"
+
+namespace Sci {
+
+enum AudioCommands {
+ // TODO: find the difference between kSci1AudioWPlay and kSci1AudioPlay
+ kSciAudioWPlay = 1, /* Plays an audio stream */
+ kSciAudioPlay = 2, /* Plays an audio stream */
+ kSciAudioStop = 3, /* Stops an audio stream */
+ kSciAudioPause = 4, /* Pauses an audio stream */
+ kSciAudioResume = 5, /* Resumes an audio stream */
+ kSciAudioPosition = 6, /* Return current position in audio stream */
+ kSciAudioRate = 7, /* Return audio rate */
+ kSciAudioVolume = 8, /* Return audio volume */
+ kSciAudioLanguage = 9, /* Return audio language */
+ kSciAudioCD = 10 /* Plays SCI1.1 CD audio */
+};
+
+enum AudioSyncCommands {
+ kSciAudioSyncStart = 0,
+ kSciAudioSyncNext = 1,
+ kSciAudioSyncStop = 2
+};
+
+#define AUDIO_VOLUME_MAX 127
+
+class Resource;
+class ResourceId;
+class ResourceManager;
+class SegManager;
+
+class AudioPlayer {
+public:
+ AudioPlayer(ResourceManager *resMan);
+ ~AudioPlayer();
+
+ void setAudioRate(uint16 rate) { _audioRate = rate; }
+ Audio::SoundHandle* getAudioHandle() { return &_audioHandle; }
+ Audio::AudioStream* getAudioStream(uint32 number, uint32 volume, int *sampleLen);
+ int getAudioPosition();
+ int startAudio(uint16 module, uint32 tuple);
+ void stopAudio();
+ void pauseAudio();
+ void resumeAudio();
+
+ void setSoundSync(ResourceId id, reg_t syncObjAddr, SegManager *segMan);
+ void doSoundSync(reg_t syncObjAddr, SegManager *segMan);
+ void stopSoundSync();
+
+ int audioCdPlay(int track, int start, int duration);
+ void audioCdStop();
+ void audioCdUpdate();
+ int audioCdPosition();
+
+ void stopAllAudio();
+
+private:
+ ResourceManager *_resMan;
+ uint16 _audioRate;
+ Audio::SoundHandle _audioHandle;
+ Audio::Mixer* _mixer;
+ Resource *_syncResource; /**< Used by kDoSync for speech syncing in CD talkie games */
+ uint _syncOffset;
+ uint32 _audioCdStart;
+};
+
+} // End of namespace Sci
+
+#endif // SCI_SFX_CORE_H
diff --git a/engines/sci/sound/iterator/core.cpp b/engines/sci/sound/iterator/core.cpp
new file mode 100644
index 0000000000..89e4c7b14f
--- /dev/null
+++ b/engines/sci/sound/iterator/core.cpp
@@ -0,0 +1,1015 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+/* Sound subsystem core: Event handler, sound player dispatching */
+
+#include "sci/sci.h"
+#ifdef USE_OLD_MUSIC_FUNCTIONS
+
+#include "sci/sound/iterator/core.h"
+#include "sci/sound/iterator/iterator.h"
+#include "sci/sound/softseq/mididriver.h"
+
+#include "sci/sound/softseq/pcjr.h"
+
+#include "common/system.h"
+#include "common/timer.h"
+
+#include "sound/mixer.h"
+
+namespace Sci {
+
+/* Plays a song iterator that found a PCM through a PCM device, if possible
+** Parameters: (SongIterator *) it: The iterator to play
+** (SongHandle) handle: Debug handle
+** Returns : (int) 0 if the effect will not be played, nonzero if it will
+** This assumes that the last call to 'it->next()' returned SI_PCM.
+*/
+static int sfx_play_iterator_pcm(SongIterator *it, SongHandle handle);
+
+
+#pragma mark -
+
+
+class SfxPlayer {
+public:
+ /** Number of voices that can play simultaneously */
+ int _polyphony;
+
+protected:
+ SciVersion _soundVersion;
+ MidiPlayer *_mididrv;
+
+ SongIterator *_iterator;
+ Audio::Timestamp _wakeupTime;
+ Audio::Timestamp _currentTime;
+ uint32 _pauseTimeDiff;
+
+ bool _paused;
+ bool _iteratorIsDone;
+ uint32 _tempo;
+
+ Common::Mutex _mutex;
+ int _volume;
+
+ void play_song(SongIterator *it);
+ static void player_timer_callback(void *refCon);
+
+public:
+ SfxPlayer(SciVersion soundVersion);
+ ~SfxPlayer();
+
+ /**
+ * Initializes the player.
+ * @param resMan a resource manager for driver initialization
+ * @param expected_latency expected delay in between calls to 'maintenance' (in microseconds)
+ * @return Common::kNoError on success, Common::kUnknownError on failure
+ */
+ Common::Error init(ResourceManager *resMan, int expected_latency);
+
+ /**
+ * Adds an iterator to the song player
+ * @param it The iterator to play
+ * @param start_time The time to assume as the time the first MIDI command executes at
+ * @return Common::kNoError on success, Common::kUnknownError on failure
+ *
+ * The iterator should not be cloned (to avoid memory leaks) and
+ * may be modified according to the needs of the player.
+ * Implementors may use the 'sfx_iterator_combine()' function
+ * to add iterators onto their already existing iterators.
+ */
+ Common::Error add_iterator(SongIterator *it, uint32 start_time);
+
+ /**
+ * Stops the currently playing song and deletes the associated iterator.
+ * @return Common::kNoError on success, Common::kUnknownError on failure
+ */
+ Common::Error stop();
+
+ /**
+ * Transmits a song iterator message to the active song.
+ * @param msg the message to transmit
+ * @return Common::kNoError on success, Common::kUnknownError on failure
+ */
+ Common::Error iterator_message(const SongIterator::Message &msg);
+
+ /**
+ * Pauses song playing.
+ * @return Common::kNoError on success, Common::kUnknownError on failure
+ */
+ Common::Error pause();
+
+ /**
+ * Resumes song playing after a pause.
+ * @return Common::kNoError on success, Common::kUnknownError on failure
+ */
+ Common::Error resume();
+
+ /**
+ * Pass a raw MIDI event to the synth.
+ * @param argc length of buffer holding the midi event
+ * @param argv the buffer itself
+ */
+ void tell_synth(int buf_nr, byte *buf);
+
+ void setVolume(int vol);
+
+ int getVolume();
+};
+
+SfxPlayer::SfxPlayer(SciVersion soundVersion)
+ : _soundVersion(soundVersion), _wakeupTime(0, SFX_TICKS_PER_SEC), _currentTime(0, 1) {
+ _polyphony = 0;
+
+ _mididrv = 0;
+
+ _iterator = NULL;
+ _pauseTimeDiff = 0;
+
+ _paused = false;
+ _iteratorIsDone = false;
+ _tempo = 0;
+
+ _volume = 15;
+}
+
+SfxPlayer::~SfxPlayer() {
+ if (_mididrv) {
+ _mididrv->close();
+ delete _mididrv;
+ }
+ delete _iterator;
+ _iterator = NULL;
+}
+
+void SfxPlayer::play_song(SongIterator *it) {
+ while (_iterator && _wakeupTime.msecsDiff(_currentTime) <= 0) {
+ int delay;
+ byte buf[8];
+ int result;
+
+ switch ((delay = songit_next(&(_iterator),
+ buf, &result,
+ IT_READER_MASK_ALL
+ | IT_READER_MAY_FREE
+ | IT_READER_MAY_CLEAN))) {
+
+ case SI_FINISHED:
+ delete _iterator;
+ _iterator = NULL;
+ _iteratorIsDone = true;
+ return;
+
+ case SI_IGNORE:
+ case SI_LOOP:
+ case SI_RELATIVE_CUE:
+ case SI_ABSOLUTE_CUE:
+ break;
+
+ case SI_PCM:
+ sfx_play_iterator_pcm(_iterator, 0);
+ break;
+
+ case 0:
+ static_cast<MidiDriver *>(_mididrv)->send(buf[0], buf[1], buf[2]);
+
+ break;
+
+ default:
+ _wakeupTime = _wakeupTime.addFrames(delay);
+ }
+ }
+}
+
+void SfxPlayer::tell_synth(int buf_nr, byte *buf) {
+ byte op1 = (buf_nr < 2 ? 0 : buf[1]);
+ byte op2 = (buf_nr < 3 ? 0 : buf[2]);
+
+ static_cast<MidiDriver *>(_mididrv)->send(buf[0], op1, op2);
+}
+
+void SfxPlayer::player_timer_callback(void *refCon) {
+ SfxPlayer *thePlayer = (SfxPlayer *)refCon;
+ assert(refCon);
+ Common::StackLock lock(thePlayer->_mutex);
+
+ if (thePlayer->_iterator && !thePlayer->_iteratorIsDone && !thePlayer->_paused) {
+ thePlayer->play_song(thePlayer->_iterator);
+ }
+
+ thePlayer->_currentTime = thePlayer->_currentTime.addFrames(1);
+}
+
+/* API implementation */
+
+Common::Error SfxPlayer::init(ResourceManager *resMan, int expected_latency) {
+ MidiDriverType musicDriver = MidiDriver::detectMusicDriver(MDT_PCSPK | MDT_ADLIB);
+
+ switch (musicDriver) {
+ case MD_ADLIB:
+ // FIXME: There's no Amiga sound option, so we hook it up to Adlib
+ if (((SciEngine *)g_engine)->getPlatform() == Common::kPlatformAmiga)
+ _mididrv = MidiPlayer_Amiga_create();
+ else
+ _mididrv = MidiPlayer_Adlib_create();
+ break;
+ case MD_PCJR:
+ _mididrv = new MidiPlayer_PCJr();
+ break;
+ case MD_PCSPK:
+ _mididrv = new MidiPlayer_PCSpeaker();
+ break;
+ default:
+ break;
+ }
+
+ assert(_mididrv);
+
+ _polyphony = _mididrv->getPolyphony();
+
+ _tempo = _mididrv->getBaseTempo();
+ uint32 time = g_system->getMillis();
+ _currentTime = Audio::Timestamp(time, 1000000 / _tempo);
+ _wakeupTime = Audio::Timestamp(time, SFX_TICKS_PER_SEC);
+
+ _mididrv->setTimerCallback(this, player_timer_callback);
+ _mididrv->open(resMan);
+ _mididrv->setVolume(_volume);
+
+ return Common::kNoError;
+}
+
+Common::Error SfxPlayer::add_iterator(SongIterator *it, uint32 start_time) {
+ Common::StackLock lock(_mutex);
+ SIMSG_SEND(it, SIMSG_SET_PLAYMASK(_mididrv->getPlayMask(_soundVersion)));
+ SIMSG_SEND(it, SIMSG_SET_RHYTHM(_mididrv->hasRhythmChannel()));
+
+ if (_iterator == NULL) {
+ // Resync with clock
+ _currentTime = Audio::Timestamp(g_system->getMillis(), 1000000 / _tempo);
+ _wakeupTime = Audio::Timestamp(start_time, SFX_TICKS_PER_SEC);
+ }
+
+ _iterator = sfx_iterator_combine(_iterator, it);
+ _iteratorIsDone = false;
+
+ return Common::kNoError;
+}
+
+Common::Error SfxPlayer::stop() {
+ debug(3, "Player: Stopping song iterator %p", (void *)_iterator);
+ Common::StackLock lock(_mutex);
+ delete _iterator;
+ _iterator = NULL;
+ for (int i = 0; i < MIDI_CHANNELS; i++)
+ static_cast<MidiDriver *>(_mididrv)->send(0xb0 + i, SCI_MIDI_CHANNEL_NOTES_OFF, 0);
+
+ return Common::kNoError;
+}
+
+Common::Error SfxPlayer::iterator_message(const SongIterator::Message &msg) {
+ Common::StackLock lock(_mutex);
+ if (!_iterator) {
+ return Common::kUnknownError;
+ }
+
+ songit_handle_message(&_iterator, msg);
+
+ return Common::kNoError;
+}
+
+Common::Error SfxPlayer::pause() {
+ Common::StackLock lock(_mutex);
+
+ _paused = true;
+ _pauseTimeDiff = _wakeupTime.msecsDiff(_currentTime);
+
+ _mididrv->playSwitch(false);
+
+ return Common::kNoError;
+}
+
+Common::Error SfxPlayer::resume() {
+ Common::StackLock lock(_mutex);
+
+ _wakeupTime = Audio::Timestamp(_currentTime.msecs() + _pauseTimeDiff, SFX_TICKS_PER_SEC);
+ _mididrv->playSwitch(true);
+ _paused = false;
+
+ return Common::kNoError;
+}
+
+void SfxPlayer::setVolume(int vol) {
+ _mididrv->setVolume(vol);
+}
+
+int SfxPlayer::getVolume() {
+ return _mididrv->getVolume();
+}
+
+#pragma mark -
+
+void SfxState::sfx_reset_player() {
+ if (_player)
+ _player->stop();
+}
+
+void SfxState::sfx_player_tell_synth(int buf_nr, byte *buf) {
+ if (_player)
+ _player->tell_synth(buf_nr, buf);
+}
+
+int SfxState::sfx_get_player_polyphony() {
+ if (_player)
+ return _player->_polyphony;
+ else
+ return 0;
+}
+
+SfxState::SfxState() {
+ _player = NULL;
+ _it = NULL;
+ _flags = 0;
+ _song = NULL;
+ _suspended = 0;
+}
+
+SfxState::~SfxState() {
+}
+
+
+void SfxState::freezeTime() {
+ /* Freezes the top song delay time */
+ const Audio::Timestamp ctime = Audio::Timestamp(g_system->getMillis(), SFX_TICKS_PER_SEC);
+ Song *song = _song;
+
+ while (song) {
+ song->_delay = song->_wakeupTime.frameDiff(ctime);
+ if (song->_delay < 0)
+ song->_delay = 0;
+
+ song = song->_nextPlaying;
+ }
+}
+
+void SfxState::thawTime() {
+ /* inverse of freezeTime() */
+ const Audio::Timestamp ctime = Audio::Timestamp(g_system->getMillis(), SFX_TICKS_PER_SEC);
+ Song *song = _song;
+
+ while (song) {
+ song->_wakeupTime = ctime.addFrames(song->_delay);
+
+ song = song->_nextPlaying;
+ }
+}
+
+#if 0
+// Unreferenced - removed
+static void _dump_playing_list(SfxState *self, char *msg) {
+ Song *song = self->_song;
+
+ fprintf(stderr, "[] Song list : [ ");
+ song = *(self->_songlib.lib);
+ while (song) {
+ fprintf(stderr, "%08lx:%d ", song->handle, song->_status);
+ song = song->_nextPlaying;
+ }
+ fprintf(stderr, "]\n");
+
+ fprintf(stderr, "[] Play list (%s) : [ " , msg);
+
+ while (song) {
+ fprintf(stderr, "%08lx ", song->handle);
+ song = song->_nextPlaying;
+ }
+
+ fprintf(stderr, "]\n");
+}
+#endif
+
+#if 0
+static void _dump_songs(SfxState *self) {
+ Song *song = self->_song;
+
+ fprintf(stderr, "Cue iterators:\n");
+ song = *(self->_songlib.lib);
+ while (song) {
+ fprintf(stderr, " **\tHandle %08x (p%d): status %d\n",
+ song->handle, song->_priority, song->_status);
+ SIMSG_SEND(song->_it, SIMSG_PRINT(1));
+ song = song->_next;
+ }
+
+ if (self->_player) {
+ fprintf(stderr, "Audio iterator:\n");
+ self->_player->iterator_message(SongIterator::Message(0, SIMSG_PRINT(1)));
+ }
+}
+#endif
+
+bool SfxState::isPlaying(Song *song) {
+ Song *playing_song = _song;
+
+ /* _dump_playing_list(this, "is-playing");*/
+
+ while (playing_song) {
+ if (playing_song == song)
+ return true;
+ playing_song = playing_song->_nextPlaying;
+ }
+ return false;
+}
+
+void SfxState::setSongStatus(Song *song, int status) {
+ const Audio::Timestamp ctime = Audio::Timestamp(g_system->getMillis(), SFX_TICKS_PER_SEC);
+
+ switch (status) {
+
+ case SOUND_STATUS_STOPPED:
+ // Reset
+ song->_it->init();
+ break;
+
+ case SOUND_STATUS_SUSPENDED:
+ case SOUND_STATUS_WAITING:
+ if (song->_status == SOUND_STATUS_PLAYING) {
+ // Update delay, set wakeup_time
+ song->_delay += song->_wakeupTime.frameDiff(ctime);
+ song->_wakeupTime = ctime;
+ }
+ if (status == SOUND_STATUS_SUSPENDED)
+ break;
+
+ /* otherwise... */
+
+ case SOUND_STATUS_PLAYING:
+ if (song->_status == SOUND_STATUS_STOPPED) {
+ // Starting anew
+ song->_wakeupTime = ctime;
+ }
+
+ if (isPlaying(song))
+ status = SOUND_STATUS_PLAYING;
+ else
+ status = SOUND_STATUS_WAITING;
+ break;
+
+ default:
+ fprintf(stderr, "%s L%d: Attempt to set invalid song"
+ " state %d!\n", __FILE__, __LINE__, status);
+ return;
+
+ }
+ song->_status = status;
+}
+
+/* Update internal state iff only one song may be played */
+void SfxState::updateSingleSong() {
+ Song *newsong = _songlib.findFirstActive();
+
+ if (newsong != _song) {
+ freezeTime(); /* Store song delay time */
+
+ if (_player)
+ _player->stop();
+
+ if (newsong) {
+ if (!newsong->_it)
+ return; /* Restore in progress and not ready for this yet */
+
+ /* Change song */
+ if (newsong->_status == SOUND_STATUS_WAITING)
+ setSongStatus(newsong, SOUND_STATUS_PLAYING);
+
+ /* Change instrument mappings */
+ } else {
+ /* Turn off sound */
+ }
+ if (_song) {
+ if (_song->_status == SOUND_STATUS_PLAYING)
+ setSongStatus(newsong, SOUND_STATUS_WAITING);
+ }
+
+ Common::String debugMessage = "[SFX] Changing active song:";
+ if (!_song) {
+ debugMessage += " New song:";
+ } else {
+ char tmp[50];
+ sprintf(tmp, " pausing %08lx, now playing ", _song->_handle);
+ debugMessage += tmp;
+ }
+
+ if (newsong) {
+ char tmp[20];
+ sprintf(tmp, "%08lx\n", newsong->_handle);
+ debugMessage += tmp;
+ } else {
+ debugMessage += " none\n";
+ }
+
+ debugC(2, kDebugLevelSound, "%s", debugMessage.c_str());
+
+ _song = newsong;
+ thawTime(); /* Recover song delay time */
+
+ if (newsong && _player) {
+ SongIterator *clonesong = newsong->_it->clone(newsong->_delay);
+
+ _player->add_iterator(clonesong, newsong->_wakeupTime.msecs());
+ }
+ }
+}
+
+
+void SfxState::updateMultiSong() {
+ Song *oldfirst = _song;
+ Song *oldseeker;
+ Song *newsong = _songlib.findFirstActive();
+ Song *newseeker;
+ Song not_playing_anymore; /* Dummy object, referenced by
+ ** songs which are no longer
+ ** active. */
+
+ /* _dump_playing_list(this, "before");*/
+ freezeTime(); /* Store song delay time */
+
+ // WORKAROUND: sometimes, newsong can be NULL (e.g. in SQ4).
+ // Handle this here, so that we avoid a crash
+ if (!newsong) {
+ // Iterators should get freed when there's only one song left playing
+ if(oldfirst && oldfirst->_status == SOUND_STATUS_STOPPED) {
+ debugC(2, kDebugLevelSound, "[SFX] Stopping song %lx\n", oldfirst->_handle);
+ if (_player && oldfirst->_it)
+ _player->iterator_message(SongIterator::Message(oldfirst->_it->ID, SIMSG_STOP));
+ }
+ return;
+ }
+
+ for (newseeker = newsong; newseeker;
+ newseeker = newseeker->_nextPlaying) {
+ if (!newseeker || !newseeker->_it)
+ return; /* Restore in progress and not ready for this yet */
+ }
+
+ /* First, put all old songs into the 'stopping' list and
+ ** mark their 'next-playing' as not_playing_anymore. */
+ for (oldseeker = oldfirst; oldseeker;
+ oldseeker = oldseeker->_nextStopping) {
+ oldseeker->_nextStopping = oldseeker->_nextPlaying;
+ oldseeker->_nextPlaying = &not_playing_anymore;
+
+ if (oldseeker == oldseeker->_nextPlaying) {
+ error("updateMultiSong() failed. Breakpoint in %s, line %d", __FILE__, __LINE__);
+ }
+ }
+
+ /* Second, re-generate the new song queue. */
+ for (newseeker = newsong; newseeker; newseeker = newseeker->_nextPlaying) {
+ newseeker->_nextPlaying = _songlib.findNextActive(newseeker);
+
+ if (newseeker == newseeker->_nextPlaying) {
+ error("updateMultiSong() failed. Breakpoint in %s, line %d", __FILE__, __LINE__);
+ }
+ }
+ /* We now need to update the currently playing song list, because we're
+ ** going to use some functions that require this list to be in a sane
+ ** state (particularly isPlaying(), indirectly */
+ _song = newsong;
+
+ /* Third, stop all old songs */
+ for (oldseeker = oldfirst; oldseeker;
+ oldseeker = oldseeker->_nextStopping)
+ if (oldseeker->_nextPlaying == &not_playing_anymore) {
+ setSongStatus(oldseeker, SOUND_STATUS_SUSPENDED);
+ debugC(2, kDebugLevelSound, "[SFX] Stopping song %lx\n", oldseeker->_handle);
+
+ if (_player && oldseeker->_it)
+ _player->iterator_message(SongIterator::Message(oldseeker->_it->ID, SIMSG_STOP));
+ oldseeker->_nextPlaying = NULL; /* Clear this pointer; we don't need the tag anymore */
+ }
+
+ for (newseeker = newsong; newseeker; newseeker = newseeker->_nextPlaying) {
+ if (newseeker->_status != SOUND_STATUS_PLAYING && _player) {
+ debugC(2, kDebugLevelSound, "[SFX] Adding song %lx\n", newseeker->_it->ID);
+
+ SongIterator *clonesong = newseeker->_it->clone(newseeker->_delay);
+ _player->add_iterator(clonesong, g_system->getMillis());
+ }
+ setSongStatus(newseeker, SOUND_STATUS_PLAYING);
+ }
+
+ _song = newsong;
+ thawTime();
+ /* _dump_playing_list(this, "after");*/
+}
+
+/* Update internal state */
+void SfxState::update() {
+ if (_flags & SFX_STATE_FLAG_MULTIPLAY)
+ updateMultiSong();
+ else
+ updateSingleSong();
+}
+
+static int sfx_play_iterator_pcm(SongIterator *it, SongHandle handle) {
+#ifdef DEBUG_SONG_API
+ fprintf(stderr, "[sfx-core] Playing PCM: %08lx\n", handle);
+#endif
+ if (g_system->getMixer()->isReady()) {
+ Audio::AudioStream *newfeed = it->getAudioStream();
+ if (newfeed) {
+ g_system->getMixer()->playInputStream(Audio::Mixer::kSFXSoundType, 0, newfeed);
+ return 1;
+ }
+ }
+ return 0;
+}
+
+#define DELAY (1000000 / SFX_TICKS_PER_SEC)
+
+void SfxState::sfx_init(ResourceManager *resMan, int flags, SciVersion soundVersion) {
+ _songlib._lib = 0;
+ _song = NULL;
+ _flags = flags;
+
+ _player = NULL;
+
+ if (flags & SFX_STATE_FLAG_NOSOUND) {
+ warning("[SFX] Sound disabled");
+ return;
+ }
+
+#ifdef DEBUG_SONG_API
+ fprintf(stderr, "[sfx-core] Initialising: flags=%x\n", flags);
+#endif
+
+ /*-------------------*/
+ /* Initialise player */
+ /*-------------------*/
+
+ if (!resMan) {
+ warning("[SFX] Warning: No resource manager present, cannot initialise player");
+ return;
+ }
+
+ _player = new SfxPlayer(soundVersion);
+
+ if (!_player) {
+ warning("[SFX] No song player found");
+ return;
+ }
+
+ if (_player->init(resMan, DELAY / 1000)) {
+ warning("[SFX] Song player reported error, disabled");
+ delete _player;
+ _player = NULL;
+ }
+
+ _resMan = resMan;
+}
+
+void SfxState::sfx_exit() {
+#ifdef DEBUG_SONG_API
+ fprintf(stderr, "[sfx-core] Uninitialising\n");
+#endif
+
+ delete _player;
+ _player = 0;
+
+ g_system->getMixer()->stopAll();
+
+ _songlib.freeSounds();
+}
+
+void SfxState::sfx_suspend(bool suspend) {
+#ifdef DEBUG_SONG_API
+ fprintf(stderr, "[sfx-core] Suspending? = %d\n", suspend);
+#endif
+ if (suspend && (!_suspended)) {
+ /* suspend */
+
+ freezeTime();
+ if (_player)
+ _player->pause();
+ /* Suspend song player */
+
+ } else if (!suspend && (_suspended)) {
+ /* unsuspend */
+
+ thawTime();
+ if (_player)
+ _player->resume();
+
+ /* Unsuspend song player */
+ }
+
+ _suspended = suspend;
+}
+
+int SfxState::sfx_poll(SongHandle *handle, int *cue) {
+ if (!_song)
+ return 0; /* No milk today */
+
+ *handle = _song->_handle;
+
+#ifdef DEBUG_SONG_API
+ fprintf(stderr, "[sfx-core] Polling any (%08lx)\n", *handle);
+#endif
+ return sfx_poll_specific(*handle, cue);
+}
+
+int SfxState::sfx_poll_specific(SongHandle handle, int *cue) {
+ const Audio::Timestamp ctime = Audio::Timestamp(g_system->getMillis(), SFX_TICKS_PER_SEC);
+ Song *song = _song;
+
+ while (song && song->_handle != handle)
+ song = song->_nextPlaying;
+
+ if (!song)
+ return 0; /* Song not playing */
+
+ debugC(2, kDebugLevelSound, "[SFX:CUE] Polled song %08lx ", handle);
+
+ while (1) {
+ if (song->_wakeupTime.frameDiff(ctime) > 0)
+ return 0; /* Patience, young hacker! */
+
+ byte buf[8];
+ int result = songit_next(&(song->_it), buf, cue, IT_READER_MASK_ALL);
+
+ switch (result) {
+
+ case SI_FINISHED:
+ setSongStatus(song, SOUND_STATUS_STOPPED);
+ update();
+ /* ...fall through... */
+ case SI_LOOP:
+ case SI_RELATIVE_CUE:
+ case SI_ABSOLUTE_CUE:
+ if (result == SI_FINISHED)
+ debugC(2, kDebugLevelSound, " => finished");
+ else {
+ if (result == SI_LOOP)
+ debugC(2, kDebugLevelSound, " => Loop: %d (0x%x)", *cue, *cue);
+ else
+ debugC(2, kDebugLevelSound, " => Cue: %d (0x%x)", *cue, *cue);
+
+ }
+ return result;
+
+ default:
+ if (result > 0)
+ song->_wakeupTime = song->_wakeupTime.addFrames(result);
+
+ /* Delay */
+ break;
+ }
+ }
+
+}
+
+
+/*****************/
+/* Song basics */
+/*****************/
+
+void SfxState::sfx_add_song(SongIterator *it, int priority, SongHandle handle, int number) {
+ Song *song = _songlib.findSong(handle);
+
+#ifdef DEBUG_SONG_API
+ fprintf(stderr, "[sfx-core] Adding song: %08lx at %d, it=%p\n", handle, priority, it);
+#endif
+ if (!it) {
+ error("[SFX] Attempt to add empty song with handle %08lx", handle);
+ return;
+ }
+
+ it->init();
+
+ /* If we're already playing this, stop it */
+ /* Tell player to shut up */
+// _dump_songs(this);
+
+ if (_player)
+ _player->iterator_message(SongIterator::Message(handle, SIMSG_STOP));
+
+ if (song) {
+ setSongStatus( song, SOUND_STATUS_STOPPED);
+
+ fprintf(stderr, "Overwriting old song (%08lx) ...\n", handle);
+ if (song->_status == SOUND_STATUS_PLAYING || song->_status == SOUND_STATUS_SUSPENDED) {
+ delete it;
+ error("Unexpected (error): Song %ld still playing/suspended (%d)",
+ handle, song->_status);
+ return;
+ } else {
+ _songlib.removeSong(handle); /* No duplicates */
+ }
+
+ }
+
+ song = new Song(handle, it, priority);
+ song->_resourceNum = number;
+ song->_hold = 0;
+ song->_loops = 0;
+ song->_wakeupTime = Audio::Timestamp(g_system->getMillis(), SFX_TICKS_PER_SEC);
+ _songlib.addSong(song);
+ _song = NULL; /* As above */
+ update();
+
+ return;
+}
+
+void SfxState::sfx_remove_song(SongHandle handle) {
+#ifdef DEBUG_SONG_API
+ fprintf(stderr, "[sfx-core] Removing song: %08lx\n", handle);
+#endif
+ if (_song && _song->_handle == handle)
+ _song = NULL;
+
+ _songlib.removeSong(handle);
+ update();
+}
+
+
+
+/**********************/
+/* Song modifications */
+/**********************/
+
+#define ASSERT_SONG(s) if (!(s)) { warning("Looking up song handle %08lx failed in %s, L%d", handle, __FILE__, __LINE__); return; }
+
+void SfxState::sfx_song_set_status(SongHandle handle, int status) {
+ Song *song = _songlib.findSong(handle);
+ ASSERT_SONG(song);
+#ifdef DEBUG_SONG_API
+ fprintf(stderr, "[sfx-core] Setting song status to %d"
+ " (0:stop, 1:play, 2:susp, 3:wait): %08lx\n", status, handle);
+#endif
+
+ setSongStatus(song, status);
+
+ update();
+}
+
+void SfxState::sfx_song_set_fade(SongHandle handle, fade_params_t *params) {
+#ifdef DEBUG_SONG_API
+ static const char *stopmsg[] = {"??? Should not happen", "Do not stop afterwards", "Stop afterwards"};
+#endif
+ Song *song = _songlib.findSong(handle);
+
+ ASSERT_SONG(song);
+
+#ifdef DEBUG_SONG_API
+ fprintf(stderr, "[sfx-core] Setting fade params of %08lx to "
+ "final volume %d in steps of %d per %d ticks. %s.",
+ handle, fade->final_volume, fade->step_size, fade->ticks_per_step,
+ stopmsg[fade->action]);
+#endif
+
+ SIMSG_SEND_FADE(song->_it, params);
+
+ update();
+}
+
+void SfxState::sfx_song_renice(SongHandle handle, int priority) {
+ Song *song = _songlib.findSong(handle);
+ ASSERT_SONG(song);
+#ifdef DEBUG_SONG_API
+ fprintf(stderr, "[sfx-core] Renicing song %08lx to %d\n",
+ handle, priority);
+#endif
+
+ song->_priority = priority;
+
+ update();
+}
+
+void SfxState::sfx_song_set_loops(SongHandle handle, int loops) {
+ Song *song = _songlib.findSong(handle);
+ SongIterator::Message msg = SongIterator::Message(handle, SIMSG_SET_LOOPS(loops));
+ ASSERT_SONG(song);
+
+ song->_loops = loops;
+#ifdef DEBUG_SONG_API
+ fprintf(stderr, "[sfx-core] Setting loops on %08lx to %d\n",
+ handle, loops);
+#endif
+ songit_handle_message(&(song->_it), msg);
+
+ if (_player/* && _player->send_iterator_message*/)
+ /* FIXME: The above should be optional! */
+ _player->iterator_message(msg);
+}
+
+void SfxState::sfx_song_set_hold(SongHandle handle, int hold) {
+ Song *song = _songlib.findSong(handle);
+ SongIterator::Message msg = SongIterator::Message(handle, SIMSG_SET_HOLD(hold));
+ ASSERT_SONG(song);
+
+ song->_hold = hold;
+#ifdef DEBUG_SONG_API
+ fprintf(stderr, "[sfx-core] Setting hold on %08lx to %d\n",
+ handle, hold);
+#endif
+ songit_handle_message(&(song->_it), msg);
+
+ if (_player/* && _player->send_iterator_message*/)
+ /* FIXME: The above should be optional! */
+ _player->iterator_message(msg);
+}
+
+/* Different from the one in iterator.c */
+static const int MIDI_cmdlen[16] = {0, 0, 0, 0, 0, 0, 0, 0,
+ 3, 3, 0, 3, 2, 0, 3, 0
+ };
+
+static const SongHandle midi_send_base = 0xffff0000;
+
+Common::Error SfxState::sfx_send_midi(SongHandle handle, int channel,
+ int command, int arg1, int arg2) {
+ byte buffer[5];
+
+ /* Yes, in that order. SCI channel mutes are actually done via
+ a counting semaphore. 0 means to decrement the counter, 1
+ to increment it. */
+ static const char *channel_state[] = {"ON", "OFF"};
+
+ if (command == 0xb0 &&
+ arg1 == SCI_MIDI_CHANNEL_MUTE) {
+ warning("TODO: channel mute (channel %d %s)", channel, channel_state[arg2]);
+ /* We need to have a GET_PLAYMASK interface to use
+ here. SET_PLAYMASK we've got.
+ */
+ return Common::kNoError;
+ }
+
+ buffer[0] = channel | command; /* No channel remapping yet */
+
+ switch (command) {
+ case 0x80 :
+ case 0x90 :
+ case 0xb0 :
+ buffer[1] = arg1 & 0xff;
+ buffer[2] = arg2 & 0xff;
+ break;
+ case 0xc0 :
+ buffer[1] = arg1 & 0xff;
+ break;
+ case 0xe0 :
+ buffer[1] = (arg1 & 0x7f) | 0x80;
+ buffer[2] = (arg1 & 0xff00) >> 7;
+ break;
+ default:
+ warning("Unexpected explicit MIDI command %02x", command);
+ return Common::kUnknownError;
+ }
+
+ if (_player)
+ _player->tell_synth(MIDI_cmdlen[command >> 4], buffer);
+ return Common::kNoError;
+}
+
+int SfxState::sfx_getVolume() {
+ return _player->getVolume();
+}
+
+void SfxState::sfx_setVolume(int volume) {
+ _player->setVolume(volume);
+}
+
+void SfxState::sfx_all_stop() {
+#ifdef DEBUG_SONG_API
+ fprintf(stderr, "[sfx-core] All stop\n");
+#endif
+
+ _songlib.freeSounds();
+ update();
+}
+
+} // End of namespace Sci
+
+#endif // USE_OLD_MUSIC_FUNCTIONS
diff --git a/engines/sci/sound/iterator/core.h b/engines/sci/sound/iterator/core.h
new file mode 100644
index 0000000000..a44fe2ecae
--- /dev/null
+++ b/engines/sci/sound/iterator/core.h
@@ -0,0 +1,209 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+/* Sound engine */
+#ifndef SCI_SFX_CORE_H
+#define SCI_SFX_CORE_H
+
+#include "common/error.h"
+
+#include "sci/sci.h" // for USE_OLD_MUSIC_FUNCTIONS
+
+#ifdef USE_OLD_MUSIC_FUNCTIONS
+#include "sci/sound/iterator/songlib.h"
+#include "sci/resource.h"
+
+namespace Sci {
+
+class SfxPlayer;
+class SongIterator;
+struct fade_params_t;
+
+#define SFX_TICKS_PER_SEC 60 /* MIDI ticks per second */
+
+
+#define SFX_STATE_FLAG_MULTIPLAY (1 << 0) /* More than one song playable
+** simultaneously ? */
+#define SFX_STATE_FLAG_NOSOUND (1 << 1) /* Completely disable sound playing */
+
+class SfxState {
+private:
+ SfxPlayer *_player;
+
+public: // FIXME, make private
+ SongIterator *_it; /**< The song iterator at the heart of things */
+ uint _flags; /**< SFX_STATE_FLAG_* */
+ SongLibrary _songlib; /**< Song library */
+ Song *_song; /**< Active song, or start of active song chain */
+ bool _suspended; /**< Whether we are suspended */
+ ResourceManager *_resMan;
+
+public:
+ SfxState();
+ ~SfxState();
+
+ /***********/
+ /* General */
+ /***********/
+
+ /* Initializes the sound engine
+ ** Parameters: (ResourceManager *) resMan: Resource manager for initialization
+ ** (int) flags: SFX_STATE_FLAG_*
+ */
+ void sfx_init(ResourceManager *resMan, int flags, SciVersion soundVersion);
+
+ /** Deinitializes the sound subsystem. */
+ void sfx_exit();
+
+ /* Suspends/unsuspends the sound sybsystem
+ ** Parameters: (int) suspend: Whether to suspend (non-null) or to unsuspend
+ */
+ void sfx_suspend(bool suspend);
+
+ /* Polls the sound server for cues etc.
+ ** Returns : (int) 0 if the cue queue is empty, SI_LOOP, SI_CUE, or SI_FINISHED otherwise
+ ** (SongHandle) *handle: The affected handle
+ ** (int) *cue: The sound cue number (if SI_CUE), or the loop number (if SI_LOOP)
+ */
+ int sfx_poll(SongHandle *handle, int *cue);
+
+ /* Polls the sound server for cues etc.
+ ** Parameters: (SongHandle) handle: The handle to poll
+ ** Returns : (int) 0 if the cue queue is empty, SI_LOOP, SI_CUE, or SI_FINISHED otherwise
+ ** (int) *cue: The sound cue number (if SI_CUE), or the loop number (if SI_LOOP)
+ */
+ int sfx_poll_specific(SongHandle handle, int *cue);
+
+ /* Determines the current global volume settings
+ ** Returns : (int) The global volume, between 0 (silent) and 127 (max. volume)
+ */
+ int sfx_getVolume();
+
+ /* Determines the current global volume settings
+ ** Parameters: (int) volume: The new global volume, between 0 and 127 (see above)
+ */
+ void sfx_setVolume(int volume);
+
+ /* Stops all songs currently playing, purges song library
+ */
+ void sfx_all_stop();
+
+
+ /*****************/
+ /* Song basics */
+ /*****************/
+
+ /* Adds a song to the internal sound library
+ ** Parameters: (SongIterator *) it: The iterator describing the song
+ ** (int) priority: Initial song priority (higher <-> more important)
+ ** (SongHandle) handle: The handle to associate with the song
+ */
+ void sfx_add_song(SongIterator *it, int priority, SongHandle handle, int resnum);
+
+
+ /* Deletes a song and its associated song iterator from the song queue
+ ** Parameters: (SongHandle) handle: The song to remove
+ */
+ void sfx_remove_song(SongHandle handle);
+
+
+ /**********************/
+ /* Song modifications */
+ /**********************/
+
+
+ /* Sets the song status, i.e. whether it is playing, suspended, or stopped.
+ ** Parameters: (SongHandle) handle: Handle of the song to modify
+ ** (int) status: The song status the song should assume
+ ** WAITING and PLAYING are set implicitly and essentially describe the same state
+ ** as far as this function is concerned.
+ */
+ void sfx_song_set_status(SongHandle handle, int status);
+
+ /* Sets the new song priority
+ ** Parameters: (SongHandle) handle: The handle to modify
+ ** (int) priority: The priority to set
+ */
+ void sfx_song_renice(SongHandle handle, int priority);
+
+ /* Sets the number of loops for the specified song
+ ** Parameters: (SongHandle) handle: The song handle to reference
+ ** (int) loops: Number of loops to set
+ */
+ void sfx_song_set_loops(SongHandle handle, int loops);
+
+ /* Sets the number of loops for the specified song
+ ** Parameters: (SongHandle) handle: The song handle to reference
+ ** (int) hold: Number of loops to setn
+ */
+ void sfx_song_set_hold(SongHandle handle, int hold);
+
+ /* Instructs a song to be faded out
+ ** Parameters: (SongHandle) handle: The song handle to reference
+ ** (fade_params_t *) fade_setup: The precise fade-out configuration to use
+ */
+ void sfx_song_set_fade(SongHandle handle, fade_params_t *fade_setup);
+
+
+ // Previously undocumented:
+ Common::Error sfx_send_midi(SongHandle handle, int channel,
+ int command, int arg1, int arg2);
+
+ // misc
+
+ /**
+ * Determines the polyphony of the player in use.
+ * @return Number of voices the active player can emit
+ */
+ int sfx_get_player_polyphony();
+
+ /**
+ * Tells the player to stop its internal iterator.
+ */
+ void sfx_reset_player();
+
+ /**
+ * Pass a raw MIDI event to the synth of the player.
+ * @param argc Length of buffer holding the midi event
+ * @param argv The buffer itself
+ */
+ void sfx_player_tell_synth(int buf_nr, byte *buf);
+
+protected:
+ void freezeTime();
+ void thawTime();
+
+ bool isPlaying(Song *song);
+ void setSongStatus(Song *song, int status);
+ void updateSingleSong();
+ void updateMultiSong();
+ void update();
+};
+
+} // End of namespace Sci
+
+#endif // USE_OLD_MUSIC_FUNCTIONS
+
+#endif // SCI_SFX_CORE_H
diff --git a/engines/sci/sound/iterator/iterator.cpp b/engines/sci/sound/iterator/iterator.cpp
new file mode 100644
index 0000000000..3359d0155b
--- /dev/null
+++ b/engines/sci/sound/iterator/iterator.cpp
@@ -0,0 +1,1707 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+/* Song iterators */
+
+#include "common/util.h"
+
+#include "sci/sci.h"
+#ifdef USE_OLD_MUSIC_FUNCTIONS
+
+#include "sci/sound/iterator/iterator_internal.h"
+#include "sci/engine/state.h" // for sfx_player_tell_synth :/
+#include "sci/sound/iterator/core.h" // for sfx_player_tell_synth
+
+#include "sound/audiostream.h"
+#include "sound/mixer.h"
+
+namespace Sci {
+
+
+static const int MIDI_cmdlen[16] = {0, 0, 0, 0, 0, 0, 0, 0,
+ 2, 2, 2, 2, 1, 1, 2, 0
+ };
+
+/*#define DEBUG_DECODING*/
+/*#define DEBUG_VERBOSE*/
+
+/** Find first set bit in bits and return its index. Returns 0 if bits is 0. */
+static int sci_ffs(int bits) {
+ if (!bits)
+ return 0;
+
+ int retval = 1;
+
+ while (!(bits & 1)) {
+ retval++;
+ bits >>= 1;
+ }
+
+ return retval;
+}
+
+static void print_tabs_id(int nr, songit_id_t id) {
+ while (nr-- > 0)
+ fprintf(stderr, "\t");
+
+ fprintf(stderr, "[%08lx] ", id);
+}
+
+BaseSongIterator::BaseSongIterator(byte *data, uint size, songit_id_t id)
+ : _data(data, size) {
+ ID = id;
+}
+
+/************************************/
+/*-- SCI0 iterator implementation --*/
+/************************************/
+
+#define SCI0_MIDI_OFFSET 33
+#define SCI0_END_OF_SONG 0xfc /* proprietary MIDI command */
+
+#define SCI0_PCM_SAMPLE_RATE_OFFSET 0x0e
+#define SCI0_PCM_SIZE_OFFSET 0x20
+#define SCI0_PCM_DATA_OFFSET 0x2c
+
+#define CHECK_FOR_END_ABSOLUTE(offset) \
+ if (offset > _data.size()) { \
+ warning("Reached end of song without terminator (%x/%x) at %d", offset, _data.size(), __LINE__); \
+ return SI_FINISHED; \
+ }
+
+#define CHECK_FOR_END(offset_augment) \
+ if ((channel->offset + (offset_augment)) > channel->end) { \
+ channel->state = SI_STATE_FINISHED; \
+ warning("Reached end of track %d without terminator (%x+%x/%x) at %d", channel->id, channel->offset, offset_augment, channel->end, __LINE__); \
+ return SI_FINISHED; \
+ }
+
+
+static int _parse_ticks(byte *data, int *offset_p, int size) {
+ int ticks = 0;
+ int tempticks;
+ int offset = 0;
+
+ do {
+ tempticks = data[offset++];
+ ticks += (tempticks == SCI_MIDI_TIME_EXPANSION_PREFIX) ?
+ SCI_MIDI_TIME_EXPANSION_LENGTH : tempticks;
+ } while (tempticks == SCI_MIDI_TIME_EXPANSION_PREFIX
+ && offset < size);
+
+ if (offset_p)
+ *offset_p = offset;
+
+ return ticks;
+}
+
+
+static int _sci0_get_pcm_data(Sci0SongIterator *self, int *rate, int *xoffset, uint *xsize);
+
+
+#define PARSE_FLAG_LOOPS_UNLIMITED (1 << 0) /* Unlimited # of loops? */
+#define PARSE_FLAG_PARAMETRIC_CUE (1 << 1) /* Assume that cues take an additional "cue value" argument */
+/* This implements a difference between SCI0 and SCI1 cues. */
+
+void SongIteratorChannel::init(int id_, int offset_, int end_) {
+ playmask = PLAYMASK_NONE; /* Disable all channels */
+ id = id_;
+ state = SI_STATE_DELTA_TIME;
+ loop_timepos = 0;
+ total_timepos = 0;
+ timepos_increment = 0;
+ delay = 0; /* Only used for more than one channel */
+ last_cmd = 0xfe;
+
+ offset = loop_offset = initial_offset = offset_;
+ end = end_;
+}
+
+void SongIteratorChannel::resetSynthChannels() {
+ byte buf[5];
+
+ // FIXME: Evil hack
+ SfxState &sound = ((SciEngine*)g_engine)->getEngineState()->_sound;
+
+ for (int i = 0; i < MIDI_CHANNELS; i++) {
+ if (playmask & (1 << i)) {
+ buf[0] = 0xe0 | i; /* Pitch bend */
+ buf[1] = 0x80; /* Wheel center */
+ buf[2] = 0x40;
+ sound.sfx_player_tell_synth(3, buf);
+
+ buf[0] = 0xb0 | i; // Set control
+ buf[1] = 0x40; // Hold pedal
+ buf[2] = 0x00; // Off
+ sound.sfx_player_tell_synth(3, buf);
+ /* TODO: Reset other controls? */
+ }
+ }
+}
+
+int BaseSongIterator::parseMidiCommand(byte *buf, int *result, SongIteratorChannel *channel, int flags) {
+ byte cmd;
+ int paramsleft;
+ int midi_op;
+ int midi_channel;
+
+ channel->state = SI_STATE_DELTA_TIME;
+
+ cmd = _data[channel->offset++];
+
+ if (!(cmd & 0x80)) {
+ /* 'Running status' mode */
+ channel->offset--;
+ cmd = channel->last_cmd;
+ }
+
+ if (cmd == 0xfe) {
+ warning("song iterator subsystem: Corrupted sound resource detected.");
+ return SI_FINISHED;
+ }
+
+ midi_op = cmd >> 4;
+ midi_channel = cmd & 0xf;
+ paramsleft = MIDI_cmdlen[midi_op];
+
+#if 0
+ if (1) {
+ fprintf(stderr, "[IT]: off=%x, cmd=%02x, takes %d args ",
+ channel->offset - 1, cmd, paramsleft);
+ fprintf(stderr, "[%02x %02x <%02x> %02x %02x %02x]\n",
+ _data[channel->offset-3],
+ _data[channel->offset-2],
+ _data[channel->offset-1],
+ _data[channel->offset],
+ _data[channel->offset+1],
+ _data[channel->offset+2]);
+ }
+#endif
+
+ buf[0] = cmd;
+
+
+ CHECK_FOR_END(paramsleft);
+ memcpy(buf + 1, _data.begin() + channel->offset, paramsleft);
+ *result = 1 + paramsleft;
+
+ channel->offset += paramsleft;
+
+ channel->last_cmd = cmd;
+
+ /* Are we supposed to play this channel? */
+ if (
+ /* First, exclude "global" properties-- such as cues-- from consideration */
+ (midi_op < 0xf
+ && !(cmd == SCI_MIDI_SET_SIGNAL)
+ && !(SCI_MIDI_CONTROLLER(cmd)
+ && buf[1] == SCI_MIDI_CUMULATIVE_CUE))
+
+ /* Next, check if the channel is allowed */
+ && (!((1 << midi_channel) & channel->playmask)))
+ return /* Execute next command */
+ nextCommand(buf, result);
+
+
+ if (cmd == SCI_MIDI_EOT) {
+ /* End of track? */
+ channel->resetSynthChannels();
+ if (_loops > 1) {
+ /* If allowed, decrement the number of loops */
+ if (!(flags & PARSE_FLAG_LOOPS_UNLIMITED))
+ *result = --_loops;
+
+#ifdef DEBUG_DECODING
+ fprintf(stderr, "%s L%d: (%p):%d Looping ", __FILE__, __LINE__, this, channel->id);
+ if (flags & PARSE_FLAG_LOOPS_UNLIMITED)
+ fprintf(stderr, "(indef.)");
+ else
+ fprintf(stderr, "(%d)", _loops);
+ fprintf(stderr, " %x -> %x\n",
+ channel->offset, channel->loop_offset);
+#endif
+ channel->offset = channel->loop_offset;
+ channel->state = SI_STATE_DELTA_TIME;
+ channel->total_timepos = channel->loop_timepos;
+ channel->last_cmd = 0xfe;
+ debugC(2, kDebugLevelSound, "Looping song iterator %08lx.\n", ID);
+ return SI_LOOP;
+ } else {
+ channel->state = SI_STATE_FINISHED;
+ return SI_FINISHED;
+ }
+
+ } else if (cmd == SCI_MIDI_SET_SIGNAL) {
+ if (buf[1] == SCI_MIDI_SET_SIGNAL_LOOP) {
+ channel->loop_offset = channel->offset;
+ channel->loop_timepos = channel->total_timepos;
+
+ return /* Execute next command */
+ nextCommand(buf, result);
+ } else {
+ /* Used to be conditional <= 127 */
+ *result = buf[1]; /* Absolute cue */
+ return SI_ABSOLUTE_CUE;
+ }
+ } else if (SCI_MIDI_CONTROLLER(cmd)) {
+ switch (buf[1]) {
+
+ case SCI_MIDI_CUMULATIVE_CUE:
+ if (flags & PARSE_FLAG_PARAMETRIC_CUE)
+ _ccc += buf[2];
+ else { /* No parameter to CC */
+ _ccc++;
+ /* channel->offset--; */
+ }
+ *result = _ccc;
+ return SI_RELATIVE_CUE;
+
+ case SCI_MIDI_RESET_ON_SUSPEND:
+ _resetflag = buf[2];
+ break;
+
+ case SCI_MIDI_SET_POLYPHONY:
+ _polyphony[midi_channel] = buf[2];
+
+#if 0
+ {
+ Sci1SongIterator *self1 = (Sci1SongIterator *)this;
+ int i;
+ int voices = 0;
+ for (i = 0; i < self1->_numChannels; i++) {
+ voices += _polyphony[i];
+ }
+
+ printf("SET_POLYPHONY(%d, %d) for a total of %d voices\n", midi_channel, buf[2], voices);
+ printf("[iterator] DEBUG: Polyphony = [ ");
+ for (i = 0; i < self1->_numChannels; i++)
+ printf("%d ", _polyphony[i]);
+ printf("]\n");
+ printf("[iterator] DEBUG: Importance = [ ");
+ printf("]\n");
+ }
+#endif
+ break;
+
+ case SCI_MIDI_SET_REVERB:
+ break;
+
+ case SCI_MIDI_CHANNEL_MUTE:
+ warning("CHANNEL_MUTE(%d, %d)", midi_channel, buf[2]);
+ break;
+
+ case SCI_MIDI_HOLD: {
+ // Safe cast: This controller is only used in SCI1
+ Sci1SongIterator *self1 = (Sci1SongIterator *)this;
+
+ if (buf[2] == self1->_hold) {
+ channel->offset = channel->initial_offset;
+ channel->state = SI_STATE_COMMAND;
+ channel->total_timepos = 0;
+
+ self1->_numLoopedChannels = self1->_numActiveChannels - 1;
+
+ // FIXME:
+ // This implementation of hold breaks getting out of the
+ // limo when visiting the airport near the start of LSL5.
+ // It seems like all channels should be reset here somehow,
+ // but not sure how.
+ // Forcing all channel offsets to 0 seems to fix the hang,
+ // but somehow slows the exit sequence down to take 20 seconds
+ // instead of about 3.
+
+ return SI_LOOP;
+ }
+
+ break;
+ }
+ case 0x04: /* UNKNOWN NYI (happens in LSL2 gameshow) */
+ case 0x46: /* UNKNOWN NYI (happens in LSL3 binoculars) */
+ case 0x61: /* UNKNOWN NYI (special for adlib? Iceman) */
+ case 0x73: /* UNKNOWN NYI (happens in Hoyle) */
+ case 0xd1: /* UNKNOWN NYI (happens in KQ4 when riding the unicorn) */
+ return /* Execute next command */
+ nextCommand(buf, result);
+
+ case 0x01: /* modulation */
+ case 0x07: /* volume */
+ case 0x0a: /* panpot */
+ case 0x0b: /* expression */
+ case 0x40: /* hold */
+ case 0x79: /* reset all */
+ /* No special treatment neccessary */
+ break;
+
+ }
+ return 0;
+
+ } else {
+#if 0
+ /* Perform remapping, if neccessary */
+ if (cmd != SCI_MIDI_SET_SIGNAL
+ && cmd < 0xf0) { /* Not a generic command */
+ int chan = cmd & 0xf;
+ int op = cmd & 0xf0;
+
+ chan = channel_remap[chan];
+ buf[0] = chan | op;
+ }
+#endif
+
+ /* Process as normal MIDI operation */
+ return 0;
+ }
+}
+
+int BaseSongIterator::processMidi(byte *buf, int *result,
+ SongIteratorChannel *channel, int flags) {
+ CHECK_FOR_END(0);
+
+ switch (channel->state) {
+
+ case SI_STATE_PCM: {
+ if (_data[channel->offset] == 0
+ && _data[channel->offset + 1] == SCI_MIDI_EOT)
+ /* Fake one extra tick to trick the interpreter into not killing the song iterator right away */
+ channel->state = SI_STATE_PCM_MAGIC_DELTA;
+ else
+ channel->state = SI_STATE_DELTA_TIME;
+ return SI_PCM;
+ }
+
+ case SI_STATE_PCM_MAGIC_DELTA: {
+ int rate;
+ int offset;
+ uint size;
+ int delay;
+ if (_sci0_get_pcm_data((Sci0SongIterator *)this, &rate, &offset, &size))
+ return SI_FINISHED; /* 'tis broken */
+ channel->state = SI_STATE_FINISHED;
+ delay = (size * 50 + rate - 1) / rate; /* number of ticks to completion*/
+
+ debugC(2, kDebugLevelSound, "delaying %d ticks\n", delay);
+ return delay;
+ }
+
+ case SI_STATE_UNINITIALISED:
+ warning("Attempt to read command from uninitialized iterator");
+ init();
+ return nextCommand(buf, result);
+
+ case SI_STATE_FINISHED:
+ return SI_FINISHED;
+
+ case SI_STATE_DELTA_TIME: {
+ int offset;
+ int ticks = _parse_ticks(_data.begin() + channel->offset,
+ &offset,
+ _data.size() - channel->offset);
+
+ channel->offset += offset;
+ channel->delay += ticks;
+ channel->timepos_increment = ticks;
+
+ CHECK_FOR_END(0);
+
+ channel->state = SI_STATE_COMMAND;
+
+ if (ticks)
+ return ticks;
+ }
+
+ /* continute otherwise... */
+
+ case SI_STATE_COMMAND: {
+ int retval;
+ channel->total_timepos += channel->timepos_increment;
+ channel->timepos_increment = 0;
+
+ retval = parseMidiCommand(buf, result, channel, flags);
+
+ if (retval == SI_FINISHED) {
+ if (_numActiveChannels)
+ --(_numActiveChannels);
+#ifdef DEBUG_DECODING
+ fprintf(stderr, "%s L%d: (%p):%d Finished channel, %d channels left\n",
+ __FILE__, __LINE__, this, channel->id,
+ _numActiveChannels);
+#endif
+ /* If we still have channels left... */
+ if (_numActiveChannels) {
+ return nextCommand(buf, result);
+ }
+
+ /* Otherwise, we have reached the end */
+ _loops = 0;
+ }
+
+ return retval;
+ }
+
+ default:
+ error("Invalid iterator state %d", channel->state);
+ return SI_FINISHED;
+ }
+}
+
+int Sci0SongIterator::nextCommand(byte *buf, int *result) {
+ return processMidi(buf, result, &_channel, PARSE_FLAG_PARAMETRIC_CUE);
+}
+
+static int _sci0_header_magic_p(byte *data, int offset, int size) {
+ if (offset + 0x10 > size)
+ return 0;
+ return (data[offset] == 0x1a)
+ && (data[offset + 1] == 0x00)
+ && (data[offset + 2] == 0x01)
+ && (data[offset + 3] == 0x00);
+}
+
+
+static int _sci0_get_pcm_data(Sci0SongIterator *self,
+ int *rate, int *xoffset, uint *xsize) {
+ int tries = 2;
+ bool found_it = false;
+ byte *pcm_data;
+ int size;
+ uint offset = SCI0_MIDI_OFFSET;
+
+ if (self->_data[0] != 2)
+ return 1;
+ /* No such luck */
+
+ while ((tries--) && (offset < self->_data.size()) && (!found_it)) {
+ // Search through the garbage manually
+ // FIXME: Replace offset by an iterator
+ Common::Array<byte>::iterator iter = Common::find(self->_data.begin() + offset, self->_data.end(), SCI0_END_OF_SONG);
+
+ if (iter == self->_data.end()) {
+ warning("Playing unterminated song");
+ return 1;
+ }
+
+ // add one to move it past the END_OF_SONG marker
+ iter++;
+ offset = iter - self->_data.begin(); // FIXME
+
+
+ if (_sci0_header_magic_p(self->_data.begin(), offset, self->_data.size()))
+ found_it = true;
+ }
+
+ if (!found_it) {
+ warning("Song indicates presence of PCM, but"
+ " none found (finally at offset %04x)", offset);
+
+ return 1;
+ }
+
+ pcm_data = self->_data.begin() + offset;
+
+ size = READ_LE_UINT16(pcm_data + SCI0_PCM_SIZE_OFFSET);
+
+ /* Two of the format parameters are fixed by design: */
+ *rate = READ_LE_UINT16(pcm_data + SCI0_PCM_SAMPLE_RATE_OFFSET);
+
+ if (offset + SCI0_PCM_DATA_OFFSET + size != self->_data.size()) {
+ int d = offset + SCI0_PCM_DATA_OFFSET + size - self->_data.size();
+
+ warning("PCM advertizes %d bytes of data, but %d"
+ " bytes are trailing in the resource",
+ size, self->_data.size() - (offset + SCI0_PCM_DATA_OFFSET));
+
+ if (d > 0)
+ size -= d; /* Fix this */
+ }
+
+ *xoffset = offset;
+ *xsize = size;
+
+ return 0;
+}
+
+static Audio::AudioStream *makeStream(byte *data, int size, int rate) {
+ debugC(2, kDebugLevelSound, "Playing PCM data of size %d, rate %d\n", size, rate);
+
+ // Duplicate the data
+ byte *sound = (byte *)malloc(size);
+ memcpy(sound, data, size);
+
+ // Convert stream format flags
+ int flags = Audio::Mixer::FLAG_AUTOFREE | Audio::Mixer::FLAG_UNSIGNED;
+ return Audio::makeLinearInputStream(sound, size, rate, flags, 0, 0);
+}
+
+Audio::AudioStream *Sci0SongIterator::getAudioStream() {
+ int rate;
+ int offset;
+ uint size;
+ if (_sci0_get_pcm_data(this, &rate, &offset, &size))
+ return NULL;
+
+ _channel.state = SI_STATE_FINISHED; /* Don't play both PCM and music */
+
+ return makeStream(_data.begin() + offset + SCI0_PCM_DATA_OFFSET, size, rate);
+}
+
+SongIterator *Sci0SongIterator::handleMessage(Message msg) {
+ if (msg._class == _SIMSG_BASE) {
+ switch (msg._type) {
+
+ case _SIMSG_BASEMSG_PRINT:
+ print_tabs_id(msg._arg.i, ID);
+ debugC(2, kDebugLevelSound, "SCI0: dev=%d, active-chan=%d, size=%d, loops=%d\n",
+ _deviceId, _numActiveChannels, _data.size(), _loops);
+ break;
+
+ case _SIMSG_BASEMSG_SET_LOOPS:
+ _loops = msg._arg.i;
+ break;
+
+ case _SIMSG_BASEMSG_STOP: {
+ songit_id_t sought_id = msg.ID;
+
+ if (sought_id == ID)
+ _channel.state = SI_STATE_FINISHED;
+ break;
+ }
+
+ case _SIMSG_BASEMSG_SET_PLAYMASK: {
+ int i;
+ _deviceId = msg._arg.i;
+
+ /* Set all but the rhytm channel mask bits */
+ _channel.playmask &= ~(1 << MIDI_RHYTHM_CHANNEL);
+
+ for (i = 0; i < MIDI_CHANNELS; i++)
+ if (_data[2 + (i << 1)] & _deviceId
+ && i != MIDI_RHYTHM_CHANNEL)
+ _channel.playmask |= (1 << i);
+ }
+ break;
+
+ case _SIMSG_BASEMSG_SET_RHYTHM:
+ _channel.playmask &= ~(1 << MIDI_RHYTHM_CHANNEL);
+ if (msg._arg.i)
+ _channel.playmask |= (1 << MIDI_RHYTHM_CHANNEL);
+ break;
+
+ case _SIMSG_BASEMSG_SET_FADE: {
+ fade_params_t *fp = (fade_params_t *) msg._arg.p;
+ fade.action = fp->action;
+ fade.final_volume = fp->final_volume;
+ fade.ticks_per_step = fp->ticks_per_step;
+ fade.step_size = fp->step_size;
+ break;
+ }
+
+ default:
+ return NULL;
+ }
+
+ return this;
+ }
+ return NULL;
+}
+
+int Sci0SongIterator::getTimepos() {
+ return _channel.total_timepos;
+}
+
+Sci0SongIterator::Sci0SongIterator(byte *data, uint size, songit_id_t id)
+ : BaseSongIterator(data, size, id) {
+ channel_mask = 0xffff; // Allocate all channels by default
+ _channel.state = SI_STATE_UNINITIALISED;
+
+ for (int i = 0; i < MIDI_CHANNELS; i++)
+ _polyphony[i] = data[1 + (i << 1)];
+
+ init();
+}
+
+void Sci0SongIterator::init() {
+ fade.action = FADE_ACTION_NONE;
+ _resetflag = 0;
+ _loops = 0;
+ priority = 0;
+
+ _ccc = 0; /* Reset cumulative cue counter */
+ _numActiveChannels = 1;
+ _channel.init(0, SCI0_MIDI_OFFSET, _data.size());
+ _channel.resetSynthChannels();
+
+ if (_data[0] == 2) /* Do we have an embedded PCM? */
+ _channel.state = SI_STATE_PCM;
+}
+
+SongIterator *Sci0SongIterator::clone(int delta) {
+ Sci0SongIterator *newit = new Sci0SongIterator(*this);
+ return newit;
+}
+
+
+/***************************/
+/*-- SCI1 song iterators --*/
+/***************************/
+
+#define SCI01_INVALID_DEVICE 0xff
+
+/* Second index determines whether PCM output is supported */
+static const int sci0_to_sci1_device_map[][2] = {
+ {0x06, 0x0c}, /* MT-32 */
+ {0xff, 0xff}, /* YM FB-01 */
+ {0x00, 0x00}, /* CMS/Game Blaster-- we assume OPL/2 here... */
+ {0xff, 0xff}, /* Casio MT540/CT460 */
+ {0x13, 0x13}, /* Tandy 3-voice */
+ {0x12, 0x12}, /* PC speaker */
+ {0xff, 0xff},
+ {0xff, 0xff},
+}; /* Maps bit number to device ID */
+
+int Sci1SongIterator::initSample(const int offset) {
+ Sci1Sample sample;
+ int rate;
+ int length;
+ int begin;
+ int end;
+
+ CHECK_FOR_END_ABSOLUTE((uint)offset + 10);
+ if (_data[offset + 1] != 0)
+ warning("[iterator-1] In sample at offset 0x04x: Byte #1 is %02x instead of zero",
+ _data[offset + 1]);
+
+ rate = (int16)READ_LE_UINT16(_data.begin() + offset + 2);
+ length = READ_LE_UINT16(_data.begin() + offset + 4);
+ begin = (int16)READ_LE_UINT16(_data.begin() + offset + 6);
+ end = (int16)READ_LE_UINT16(_data.begin() + offset + 8);
+
+ CHECK_FOR_END_ABSOLUTE((uint)(offset + 10 + length));
+
+ sample.delta = begin;
+ sample.size = length;
+ sample._data = _data.begin() + offset + 10;
+
+#ifdef DEBUG_VERBOSE
+ fprintf(stderr, "[SAMPLE] %x/%x/%x/%x l=%x\n",
+ offset + 10, begin, end, _data.size(), length);
+#endif
+
+ sample.rate = rate;
+
+ sample.announced = false;
+
+ /* Insert into the sample list at the right spot, keeping it sorted by delta */
+ Common::List<Sci1Sample>::iterator seeker = _samples.begin();
+ while (seeker != _samples.end() && seeker->delta < begin)
+ ++seeker;
+ _samples.insert(seeker, sample);
+
+ return 0; /* Everything's fine */
+}
+
+int Sci1SongIterator::initSong() {
+ int last_time;
+ uint offset = 0;
+ _numChannels = 0;
+ _samples.clear();
+// _deviceId = 0x0c;
+
+ if (_data[offset] == 0xf0) {
+ priority = _data[offset + 1];
+
+ offset += 8;
+ }
+
+ while (_data[offset] != 0xff
+ && _data[offset] != _deviceId) {
+ offset++;
+ CHECK_FOR_END_ABSOLUTE(offset + 1);
+ while (_data[offset] != 0xff) {
+ CHECK_FOR_END_ABSOLUTE(offset + 7);
+ offset += 6;
+ }
+ offset++;
+ }
+
+ if (_data[offset] == 0xff) {
+ warning("[iterator] Song does not support hardware 0x%02x", _deviceId);
+ return 1;
+ }
+
+ offset++;
+
+ while (_data[offset] != 0xff) { /* End of list? */
+ uint track_offset;
+ int end;
+ offset += 2;
+
+ CHECK_FOR_END_ABSOLUTE(offset + 4);
+
+ track_offset = READ_LE_UINT16(_data.begin() + offset);
+ end = READ_LE_UINT16(_data.begin() + offset + 2);
+
+ CHECK_FOR_END_ABSOLUTE(track_offset - 1);
+
+ if (_data[track_offset] == 0xfe) {
+ if (initSample(track_offset))
+ return 1; /* Error */
+ } else {
+ /* Regular MIDI channel */
+ if (_numChannels >= MIDI_CHANNELS) {
+ warning("[iterator] Song has more than %d channels, cutting them off",
+ MIDI_CHANNELS);
+ break; /* Scan for remaining samples */
+ } else {
+ int channel_nr = _data[track_offset] & 0xf;
+ SongIteratorChannel &channel = _channels[_numChannels++];
+
+ /*
+ if (_data[track_offset] & 0xf0)
+ printf("Channel %d has mapping bits %02x\n",
+ channel_nr, _data[track_offset] & 0xf0);
+ */
+
+ // Add 2 to skip over header bytes */
+ channel.init(channel_nr, track_offset + 2, track_offset + end);
+ channel.resetSynthChannels();
+
+ _polyphony[_numChannels - 1] = _data[channel.offset - 1] & 15;
+
+ channel.playmask = ~0; /* Enable all */
+ channel_mask |= (1 << channel_nr);
+
+ CHECK_FOR_END_ABSOLUTE(offset + end);
+ }
+ }
+ offset += 4;
+ CHECK_FOR_END_ABSOLUTE(offset);
+ }
+
+ /* Now ensure that sample deltas are relative to the previous sample */
+ last_time = 0;
+ _numActiveChannels = _numChannels;
+ _numLoopedChannels = 0;
+
+ for (Common::List<Sci1Sample>::iterator seeker = _samples.begin();
+ seeker != _samples.end(); ++seeker) {
+ int prev_last_time = last_time;
+ //printf("[iterator] Detected sample: %d Hz, %d bytes at time %d\n",
+ // seeker->format.rate, seeker->size, seeker->delta);
+ last_time = seeker->delta;
+ seeker->delta -= prev_last_time;
+ }
+
+ return 0; /* Success */
+}
+
+int Sci1SongIterator::getSmallestDelta() const {
+ int d = -1;
+ for (int i = 0; i < _numChannels; i++)
+ if (_channels[i].state == SI_STATE_COMMAND
+ && (d == -1 || _channels[i].delay < d))
+ d = _channels[i].delay;
+
+ if (!_samples.empty() && _samples.begin()->delta < d)
+ return _samples.begin()->delta;
+ else
+ return d;
+}
+
+void Sci1SongIterator::updateDelta(int delta) {
+ if (!_samples.empty())
+ _samples.begin()->delta -= delta;
+
+ for (int i = 0; i < _numChannels; i++)
+ if (_channels[i].state == SI_STATE_COMMAND)
+ _channels[i].delay -= delta;
+}
+
+bool Sci1SongIterator::noDeltaTime() const {
+ for (int i = 0; i < _numChannels; i++)
+ if (_channels[i].state == SI_STATE_DELTA_TIME)
+ return false;
+ return true;
+}
+
+#define COMMAND_INDEX_NONE -1
+#define COMMAND_INDEX_PCM -2
+
+int Sci1SongIterator::getCommandIndex() const {
+ /* Determine the channel # of the next active event, or -1 */
+ int i;
+ int base_delay = 0x7ffffff;
+ int best_chan = COMMAND_INDEX_NONE;
+
+ for (i = 0; i < _numChannels; i++)
+ if ((_channels[i].state != SI_STATE_PENDING)
+ && (_channels[i].state != SI_STATE_FINISHED)) {
+
+ if ((_channels[i].state == SI_STATE_DELTA_TIME)
+ && (_channels[i].delay == 0))
+ return i;
+ /* First, read all unknown delta times */
+
+ if (_channels[i].delay < base_delay) {
+ best_chan = i;
+ base_delay = _channels[i].delay;
+ }
+ }
+
+ if (!_samples.empty() && base_delay >= _samples.begin()->delta)
+ return COMMAND_INDEX_PCM;
+
+ return best_chan;
+}
+
+
+Audio::AudioStream *Sci1SongIterator::getAudioStream() {
+ Common::List<Sci1Sample>::iterator sample = _samples.begin();
+ if (sample != _samples.end() && sample->delta <= 0) {
+ Audio::AudioStream *feed = makeStream(sample->_data, sample->size, sample->rate);
+ _samples.erase(sample);
+
+ return feed;
+ } else
+ return NULL;
+}
+
+int Sci1SongIterator::nextCommand(byte *buf, int *result) {
+
+ if (!_initialised) {
+ //printf("[iterator] DEBUG: Initialising for %d\n", _deviceId);
+ _initialised = true;
+ if (initSong())
+ return SI_FINISHED;
+ }
+
+
+ if (_delayRemaining) {
+ int delay = _delayRemaining;
+ _delayRemaining = 0;
+ return delay;
+ }
+
+ int retval = 0;
+ do { /* All delays must be processed separately */
+ int chan = getCommandIndex();
+
+ if (chan == COMMAND_INDEX_NONE) {
+ return SI_FINISHED;
+ }
+
+ if (chan == COMMAND_INDEX_PCM) {
+
+ if (_samples.begin()->announced) {
+ /* Already announced; let's discard it */
+ Audio::AudioStream *feed = getAudioStream();
+ delete feed;
+ } else {
+ int delay = _samples.begin()->delta;
+
+ if (delay) {
+ updateDelta(delay);
+ return delay;
+ }
+ /* otherwise we're touching a PCM */
+ _samples.begin()->announced = true;
+ return SI_PCM;
+ }
+ } else { /* Not a PCM */
+
+ retval = processMidi(buf, result,
+ &(_channels[chan]),
+ PARSE_FLAG_LOOPS_UNLIMITED);
+
+ if (retval == SI_LOOP) {
+ _numLoopedChannels++;
+ _channels[chan].state = SI_STATE_PENDING;
+ _channels[chan].delay = 0;
+
+ if (_numLoopedChannels == _numActiveChannels) {
+ int i;
+
+ /* Everyone's ready: Let's loop */
+ for (i = 0; i < _numChannels; i++)
+ if (_channels[i].state == SI_STATE_PENDING)
+ _channels[i].state = SI_STATE_DELTA_TIME;
+
+ _numLoopedChannels = 0;
+ return SI_LOOP;
+ }
+ } else if (retval == SI_FINISHED) {
+#ifdef DEBUG
+ fprintf(stderr, "FINISHED some channel\n");
+#endif
+ } else if (retval > 0) {
+ int sd ;
+ sd = getSmallestDelta();
+
+ if (noDeltaTime() && sd) {
+ /* No other channel is ready */
+ updateDelta(sd);
+
+ /* Only from here do we return delta times */
+ return sd;
+ }
+ }
+
+ } /* Not a PCM */
+
+ } while (retval > 0);
+
+ return retval;
+}
+
+SongIterator *Sci1SongIterator::handleMessage(Message msg) {
+ if (msg._class == _SIMSG_BASE) { /* May extend this in the future */
+ switch (msg._type) {
+
+ case _SIMSG_BASEMSG_PRINT: {
+ int playmask = 0;
+ int i;
+
+ for (i = 0; i < _numChannels; i++)
+ playmask |= _channels[i].playmask;
+
+ print_tabs_id(msg._arg.i, ID);
+ debugC(2, kDebugLevelSound, "SCI1: chan-nr=%d, playmask=%04x\n",
+ _numChannels, playmask);
+ }
+ break;
+
+ case _SIMSG_BASEMSG_STOP: {
+ songit_id_t sought_id = msg.ID;
+ int i;
+
+ if (sought_id == ID) {
+ ID = 0;
+
+ for (i = 0; i < _numChannels; i++)
+ _channels[i].state = SI_STATE_FINISHED;
+ }
+ break;
+ }
+
+ case _SIMSG_BASEMSG_SET_PLAYMASK:
+ if (msg.ID == ID) {
+ channel_mask = 0;
+
+ _deviceId
+ = sci0_to_sci1_device_map
+ [sci_ffs(msg._arg.i & 0xff) - 1]
+ [g_system->getMixer()->isReady()]
+ ;
+
+ if (_deviceId == 0xff) {
+ warning("[iterator] Device %d(%d) not supported",
+ msg._arg.i & 0xff, g_system->getMixer()->isReady());
+ }
+ if (_initialised) {
+ int i;
+ int toffset = -1;
+
+ for (i = 0; i < _numChannels; i++)
+ if (_channels[i].state != SI_STATE_FINISHED
+ && _channels[i].total_timepos > toffset) {
+ toffset = _channels[i].total_timepos
+ + _channels[i].timepos_increment
+ - _channels[i].delay;
+ }
+
+ /* Find an active channel so that we can
+ ** get the correct time offset */
+
+ initSong();
+
+ toffset -= _delayRemaining;
+ _delayRemaining = 0;
+
+ if (toffset > 0)
+ return new_fast_forward_iterator(this, toffset);
+ } else {
+ initSong();
+ _initialised = true;
+ }
+
+ break;
+
+ }
+
+ case _SIMSG_BASEMSG_SET_LOOPS:
+ if (msg.ID == ID)
+ _loops = (msg._arg.i > 32767) ? 99 : 0;
+ /* 99 is arbitrary, but we can't use '1' because of
+ ** the way we're testing in the decoding section. */
+ break;
+
+ case _SIMSG_BASEMSG_SET_HOLD:
+ _hold = msg._arg.i;
+ break;
+ case _SIMSG_BASEMSG_SET_RHYTHM:
+ /* Ignore */
+ break;
+
+ case _SIMSG_BASEMSG_SET_FADE: {
+ fade_params_t *fp = (fade_params_t *) msg._arg.p;
+ fade.action = fp->action;
+ fade.final_volume = fp->final_volume;
+ fade.ticks_per_step = fp->ticks_per_step;
+ fade.step_size = fp->step_size;
+ break;
+ }
+
+ default:
+ warning("Unsupported command %d to SCI1 iterator", msg._type);
+ }
+ return this;
+ }
+ return NULL;
+}
+
+Sci1SongIterator::Sci1SongIterator(byte *data, uint size, songit_id_t id)
+ : BaseSongIterator(data, size, id) {
+ channel_mask = 0; // Defer channel allocation
+
+ for (int i = 0; i < MIDI_CHANNELS; i++)
+ _polyphony[i] = 0; // Unknown
+
+ init();
+}
+
+void Sci1SongIterator::init() {
+ fade.action = FADE_ACTION_NONE;
+ _resetflag = 0;
+ _loops = 0;
+ priority = 0;
+
+ _ccc = 0;
+ _deviceId = 0x00; // Default to Sound Blaster/Adlib for purposes of cue computation
+ _numChannels = 0;
+ _initialised = false;
+ _delayRemaining = 0;
+ _loops = 0;
+ _hold = 0;
+ memset(_polyphony, 0, sizeof(_polyphony));
+}
+
+Sci1SongIterator::~Sci1SongIterator() {
+}
+
+
+SongIterator *Sci1SongIterator::clone(int delta) {
+ Sci1SongIterator *newit = new Sci1SongIterator(*this);
+ newit->_delayRemaining = delta;
+ return newit;
+}
+
+int Sci1SongIterator::getTimepos() {
+ int max = 0;
+ int i;
+
+ for (i = 0; i < _numChannels; i++)
+ if (_channels[i].total_timepos > max)
+ max = _channels[i].total_timepos;
+
+ return max;
+}
+
+/**
+ * A song iterator with the purpose of sending notes-off channel commands.
+ */
+class CleanupSongIterator : public SongIterator {
+public:
+ CleanupSongIterator(uint channels) {
+ channel_mask = channels;
+ ID = 17;
+ }
+
+ int nextCommand(byte *buf, int *result);
+ Audio::AudioStream *getAudioStream() { return NULL; }
+ SongIterator *handleMessage(Message msg);
+ int getTimepos() { return 0; }
+ SongIterator *clone(int delta) { return new CleanupSongIterator(*this); }
+};
+
+SongIterator *CleanupSongIterator::handleMessage(Message msg) {
+ if (msg._class == _SIMSG_BASEMSG_PRINT && msg._type == _SIMSG_BASEMSG_PRINT) {
+ print_tabs_id(msg._arg.i, ID);
+ debugC(2, kDebugLevelSound, "CLEANUP\n");
+ }
+
+ return NULL;
+}
+
+int CleanupSongIterator::nextCommand(byte *buf, int *result) {
+ /* Task: Return channel-notes-off for each channel */
+ if (channel_mask) {
+ int bs = sci_ffs(channel_mask) - 1;
+
+ channel_mask &= ~(1 << bs);
+ buf[0] = 0xb0 | bs; /* Controller */
+ buf[1] = SCI_MIDI_CHANNEL_NOTES_OFF;
+ buf[2] = 0; /* Hmm... */
+ *result = 3;
+ return 0;
+ } else
+ return SI_FINISHED;
+}
+
+/**********************/
+/*-- Timer iterator --*/
+/**********************/
+int TimerSongIterator::nextCommand(byte *buf, int *result) {
+ if (_delta) {
+ int d = _delta;
+ _delta = 0;
+ return d;
+ }
+ return SI_FINISHED;
+}
+
+SongIterator *new_timer_iterator(int delta) {
+ return new TimerSongIterator(delta);
+}
+
+/**********************************/
+/*-- Fast-forward song iterator --*/
+/**********************************/
+
+int FastForwardSongIterator::nextCommand(byte *buf, int *result) {
+ if (_delta <= 0)
+ return SI_MORPH; /* Did our duty */
+
+ while (1) {
+ int rv = _delegate->nextCommand(buf, result);
+
+ if (rv > 0) {
+ /* Subtract from the delta we want to wait */
+ _delta -= rv;
+
+ /* Done */
+ if (_delta < 0)
+ return -_delta;
+ }
+
+ if (rv <= 0)
+ return rv;
+ }
+}
+
+Audio::AudioStream *FastForwardSongIterator::getAudioStream() {
+ return _delegate->getAudioStream();
+}
+
+SongIterator *FastForwardSongIterator::handleMessage(Message msg) {
+ if (msg._class == _SIMSG_PLASTICWRAP) {
+ assert(msg._type == _SIMSG_PLASTICWRAP_ACK_MORPH);
+
+ if (_delta <= 0) {
+ SongIterator *it = _delegate;
+ delete this;
+ return it;
+ }
+
+ warning("[ff-iterator] Morphing without need");
+ return this;
+ }
+
+ if (msg._class == _SIMSG_BASE && msg._type == _SIMSG_BASEMSG_PRINT) {
+ print_tabs_id(msg._arg.i, ID);
+ debugC(2, kDebugLevelSound, "FASTFORWARD:\n");
+ msg._arg.i++;
+ }
+
+ // And continue with the delegate
+ songit_handle_message(&_delegate, msg);
+
+ return NULL;
+}
+
+
+int FastForwardSongIterator::getTimepos() {
+ return _delegate->getTimepos();
+}
+
+FastForwardSongIterator::FastForwardSongIterator(SongIterator *capsit, int delta)
+ : _delegate(capsit), _delta(delta) {
+
+ channel_mask = capsit->channel_mask;
+}
+
+SongIterator *FastForwardSongIterator::clone(int delta) {
+ FastForwardSongIterator *newit = new FastForwardSongIterator(*this);
+ newit->_delegate = _delegate->clone(delta);
+ return newit;
+}
+
+SongIterator *new_fast_forward_iterator(SongIterator *capsit, int delta) {
+ if (capsit == NULL)
+ return NULL;
+
+ FastForwardSongIterator *it = new FastForwardSongIterator(capsit, delta);
+ return it;
+}
+
+
+/********************/
+/*-- Tee iterator --*/
+/********************/
+
+
+static void song_iterator_add_death_listener(SongIterator *it, TeeSongIterator *client) {
+ for (int i = 0; i < SONGIT_MAX_LISTENERS; ++i) {
+ if (it->_deathListeners[i] == 0) {
+ it->_deathListeners[i] = client;
+ return;
+ }
+ }
+ error("FATAL: Too many death listeners for song iterator");
+}
+
+static void song_iterator_remove_death_listener(SongIterator *it, TeeSongIterator *client) {
+ for (int i = 0; i < SONGIT_MAX_LISTENERS; ++i) {
+ if (it->_deathListeners[i] == client) {
+ it->_deathListeners[i] = 0;
+ return;
+ }
+ }
+}
+
+static void song_iterator_transfer_death_listeners(SongIterator *it, SongIterator *it_from) {
+ for (int i = 0; i < SONGIT_MAX_LISTENERS; ++i) {
+ if (it_from->_deathListeners[i])
+ song_iterator_add_death_listener(it, it_from->_deathListeners[i]);
+ it_from->_deathListeners[i] = 0;
+ }
+}
+
+static void songit_tee_death_notification(TeeSongIterator *self, SongIterator *corpse) {
+ if (corpse == self->_children[TEE_LEFT].it) {
+ self->_status &= ~TEE_LEFT_ACTIVE;
+ self->_children[TEE_LEFT].it = NULL;
+ } else if (corpse == self->_children[TEE_RIGHT].it) {
+ self->_status &= ~TEE_RIGHT_ACTIVE;
+ self->_children[TEE_RIGHT].it = NULL;
+ } else {
+ error("songit_tee_death_notification() failed: Breakpoint in %s, line %d", __FILE__, __LINE__);
+ }
+}
+
+TeeSongIterator::TeeSongIterator(SongIterator *left, SongIterator *right) {
+ int i;
+ int firstfree = 1; /* First free channel */
+ int incomplete_map = 0;
+
+ _readyToMorph = false;
+ _status = TEE_LEFT_ACTIVE | TEE_RIGHT_ACTIVE;
+
+ _children[TEE_LEFT].it = left;
+ _children[TEE_RIGHT].it = right;
+
+ /* Default to lhs channels */
+ channel_mask = left->channel_mask;
+ for (i = 0; i < 16; i++)
+ if (channel_mask & (1 << i) & right->channel_mask
+ && (i != MIDI_RHYTHM_CHANNEL) /* Share rhythm */) { /*conflict*/
+ while ((firstfree == MIDI_RHYTHM_CHANNEL)
+ /* Either if it's the rhythm channel or if it's taken */
+ || (firstfree < MIDI_CHANNELS
+ && ((1 << firstfree) & channel_mask)))
+ ++firstfree;
+
+ if (firstfree == MIDI_CHANNELS) {
+ incomplete_map = 1;
+ //warning("[songit-tee <%08lx,%08lx>] Could not remap right channel #%d: Out of channels",
+ // left->ID, right->ID, i);
+ } else {
+ _children[TEE_RIGHT].it->channel_remap[i] = firstfree;
+
+ channel_mask |= (1 << firstfree);
+ }
+ }
+#ifdef DEBUG_TEE_ITERATOR
+ if (incomplete_map) {
+ int c;
+ fprintf(stderr, "[songit-tee <%08lx,%08lx>] Channels:"
+ " %04x <- %04x | %04x\n",
+ left->ID, right->ID,
+ channel_mask,
+ left->channel_mask, right->channel_mask);
+ for (c = 0 ; c < 2; c++)
+ for (i = 0 ; i < 16; i++)
+ fprintf(stderr, " map [%d][%d] -> %d\n",
+ c, i, _children[c].it->channel_remap[i]);
+ }
+#endif
+
+
+ song_iterator_add_death_listener(left, this);
+ song_iterator_add_death_listener(right, this);
+}
+
+TeeSongIterator::~TeeSongIterator() {
+ // When we die, remove any listeners from our children
+ if (_children[TEE_LEFT].it) {
+ song_iterator_remove_death_listener(_children[TEE_LEFT].it, this);
+ }
+
+ if (_children[TEE_RIGHT].it) {
+ song_iterator_remove_death_listener(_children[TEE_RIGHT].it, this);
+ }
+}
+
+
+int TeeSongIterator::nextCommand(byte *buf, int *result) {
+ static const int ready_masks[2] = {TEE_LEFT_READY, TEE_RIGHT_READY};
+ static const int active_masks[2] = {TEE_LEFT_ACTIVE, TEE_RIGHT_ACTIVE};
+ static const int pcm_masks[2] = {TEE_LEFT_PCM, TEE_RIGHT_PCM};
+ int i;
+ int retid;
+
+#ifdef DEBUG_TEE_ITERATOR
+ fprintf(stderr, "[Tee] %02x\n", _status);
+#endif
+
+ if (!(_status & (TEE_LEFT_ACTIVE | TEE_RIGHT_ACTIVE)))
+ /* None is active? */
+ return SI_FINISHED;
+
+ if (_readyToMorph)
+ return SI_MORPH;
+
+ if ((_status & (TEE_LEFT_ACTIVE | TEE_RIGHT_ACTIVE))
+ != (TEE_LEFT_ACTIVE | TEE_RIGHT_ACTIVE)) {
+ /* Not all are is active? */
+ int which = 0;
+#ifdef DEBUG_TEE_ITERATOR
+ fprintf(stderr, "\tRequesting transformation...\n");
+#endif
+ if (_status & TEE_LEFT_ACTIVE)
+ which = TEE_LEFT;
+ else if (_status & TEE_RIGHT_ACTIVE)
+ which = TEE_RIGHT;
+ memcpy(buf, _children[which].buf, sizeof(buf));
+ *result = _children[which].result;
+ _readyToMorph = true;
+ return _children[which].retval;
+ }
+
+ /* First, check for unreported PCMs */
+ for (i = TEE_LEFT; i <= TEE_RIGHT; i++)
+ if ((_status & (ready_masks[i] | pcm_masks[i]))
+ == (ready_masks[i] | pcm_masks[i])) {
+ _status &= ~ready_masks[i];
+ return SI_PCM;
+ }
+
+ for (i = TEE_LEFT; i <= TEE_RIGHT; i++)
+ if (!(_status & ready_masks[i])) {
+
+ /* Buffers aren't ready yet */
+ _children[i].retval =
+ songit_next(&(_children[i].it),
+ _children[i].buf,
+ &(_children[i].result),
+ IT_READER_MASK_ALL
+ | IT_READER_MAY_FREE
+ | IT_READER_MAY_CLEAN);
+
+ _status |= ready_masks[i];
+#ifdef DEBUG_TEE_ITERATOR
+ fprintf(stderr, "\t Must check %d: %d\n", i, _children[i].retval);
+#endif
+
+ if (_children[i].retval == SI_ABSOLUTE_CUE ||
+ _children[i].retval == SI_RELATIVE_CUE)
+ return _children[i].retval;
+ if (_children[i].retval == SI_FINISHED) {
+ _status &= ~active_masks[i];
+ /* Recurse to complete */
+#ifdef DEBUG_TEE_ITERATOR
+ fprintf(stderr, "\t Child %d signalled completion, recursing w/ status %02x\n", i, _status);
+#endif
+ return nextCommand(buf, result);
+ } else if (_children[i].retval == SI_PCM) {
+ _status |= pcm_masks[i];
+ _status &= ~ready_masks[i];
+ return SI_PCM;
+ }
+ }
+
+
+ /* We've already handled PCM, MORPH and FINISHED, CUEs & LOOP remain */
+
+ retid = TEE_LEFT;
+ if ((_children[TEE_LEFT].retval > 0)
+ /* Asked to delay */
+ && (_children[TEE_RIGHT].retval <= _children[TEE_LEFT].retval))
+ /* Is not delaying or not delaying as much */
+ retid = TEE_RIGHT;
+
+#ifdef DEBUG_TEE_ITERATOR
+ fprintf(stderr, "\tl:%d / r:%d / chose %d\n",
+ _children[TEE_LEFT].retval, _children[TEE_RIGHT].retval, retid);
+#endif
+
+ /* Adjust delta times */
+ if (_children[retid].retval > 0
+ && _children[1-retid].retval > 0) {
+ if (_children[1-retid].retval
+ == _children[retid].retval)
+ /* If both _children wait the same amount of time,
+ ** we have to re-fetch commands from both */
+ _status &= ~ready_masks[1-retid];
+ else
+ /* If they don't, we can/must re-use the other
+ ** child's delay time */
+ _children[1-retid].retval
+ -= _children[retid].retval;
+ }
+
+ _status &= ~ready_masks[retid];
+ memcpy(buf, _children[retid].buf, sizeof(buf));
+ *result = _children[retid].result;
+
+ return _children[retid].retval;
+}
+
+Audio::AudioStream *TeeSongIterator::getAudioStream() {
+ static const int pcm_masks[2] = {TEE_LEFT_PCM, TEE_RIGHT_PCM};
+ int i;
+
+ for (i = TEE_LEFT; i <= TEE_RIGHT; i++)
+ if (_status & pcm_masks[i]) {
+ _status &= ~pcm_masks[i];
+ return _children[i].it->getAudioStream();
+ }
+
+ return NULL; // No iterator
+}
+
+SongIterator *TeeSongIterator::handleMessage(Message msg) {
+ if (msg._class == _SIMSG_PLASTICWRAP) {
+ assert(msg._type == _SIMSG_PLASTICWRAP_ACK_MORPH);
+
+ SongIterator *old_it;
+ if (!(_status & (TEE_LEFT_ACTIVE | TEE_RIGHT_ACTIVE))) {
+ delete this;
+ return NULL;
+ } else if (!(_status & TEE_LEFT_ACTIVE)) {
+ delete _children[TEE_LEFT].it;
+ _children[TEE_LEFT].it = 0;
+ old_it = _children[TEE_RIGHT].it;
+ song_iterator_remove_death_listener(old_it, this);
+ song_iterator_transfer_death_listeners(old_it, this);
+ delete this;
+ return old_it;
+ } else if (!(_status & TEE_RIGHT_ACTIVE)) {
+ delete _children[TEE_RIGHT].it;
+ _children[TEE_RIGHT].it = 0;
+ old_it = _children[TEE_LEFT].it;
+ song_iterator_remove_death_listener(old_it, this);
+ song_iterator_transfer_death_listeners(old_it, this);
+ delete this;
+ return old_it;
+ }
+
+ warning("[tee-iterator] Morphing without need");
+ return this;
+ }
+
+ if (msg._class == _SIMSG_BASE && msg._type == _SIMSG_BASEMSG_PRINT) {
+ print_tabs_id(msg._arg.i, ID);
+ debugC(2, kDebugLevelSound, "TEE:\n");
+ msg._arg.i++;
+ }
+
+ // And continue with the children
+ if (_children[TEE_LEFT].it)
+ songit_handle_message(&(_children[TEE_LEFT].it), msg);
+ if (_children[TEE_RIGHT].it)
+ songit_handle_message(&(_children[TEE_RIGHT].it), msg);
+
+ return NULL;
+}
+
+void TeeSongIterator::init() {
+ _status = TEE_LEFT_ACTIVE | TEE_RIGHT_ACTIVE;
+ _children[TEE_LEFT].it->init();
+ _children[TEE_RIGHT].it->init();
+}
+
+SongIterator *TeeSongIterator::clone(int delta) {
+ TeeSongIterator *newit = new TeeSongIterator(*this);
+
+ if (_children[TEE_LEFT].it)
+ newit->_children[TEE_LEFT].it = _children[TEE_LEFT].it->clone(delta);
+ if (_children[TEE_RIGHT].it)
+ newit->_children[TEE_RIGHT].it = _children[TEE_RIGHT].it->clone(delta);
+
+ return newit;
+}
+
+
+/*************************************/
+/*-- General purpose functionality --*/
+/*************************************/
+
+int songit_next(SongIterator **it, byte *buf, int *result, int mask) {
+ int retval;
+
+ if (!*it)
+ return SI_FINISHED;
+
+ do {
+ retval = (*it)->nextCommand(buf, result);
+ if (retval == SI_MORPH) {
+ debugC(2, kDebugLevelSound, " Morphing %p (stored at %p)\n", (void *)*it, (void *)it);
+ if (!SIMSG_SEND((*it), SIMSG_ACK_MORPH)) {
+ error("SI_MORPH failed. Breakpoint in %s, line %d", __FILE__, __LINE__);
+ } else
+ debugC(2, kDebugLevelSound, "SI_MORPH successful\n");
+ }
+
+ if (retval == SI_FINISHED)
+ debugC(2, kDebugLevelSound, "[song-iterator] Song finished. mask = %04x, cm=%04x\n",
+ mask, (*it)->channel_mask);
+ if (retval == SI_FINISHED
+ && (mask & IT_READER_MAY_CLEAN)
+ && (*it)->channel_mask) { /* This last test will fail
+ ** with a terminated
+ ** cleanup iterator */
+ int channel_mask = (*it)->channel_mask;
+
+ SongIterator *old_it = *it;
+ *it = new CleanupSongIterator(channel_mask);
+ for(uint i = 0; i < MIDI_CHANNELS; i++)
+ (*it)->channel_remap[i] = old_it->channel_remap[i];
+ song_iterator_transfer_death_listeners(*it, old_it);
+ if (mask & IT_READER_MAY_FREE)
+ delete old_it;
+ retval = -9999; /* Continue */
+ }
+ } while (!( /* Until one of the following holds */
+ (retval > 0 && (mask & IT_READER_MASK_DELAY))
+ || (retval == 0 && (mask & IT_READER_MASK_MIDI))
+ || (retval == SI_LOOP && (mask & IT_READER_MASK_LOOP))
+ || (retval == SI_ABSOLUTE_CUE &&
+ (mask & IT_READER_MASK_CUE))
+ || (retval == SI_RELATIVE_CUE &&
+ (mask & IT_READER_MASK_CUE))
+ || (retval == SI_PCM && (mask & IT_READER_MASK_PCM))
+ || (retval == SI_FINISHED)
+ ));
+
+ if (retval == SI_FINISHED && (mask & IT_READER_MAY_FREE)) {
+ delete *it;
+ *it = NULL;
+ }
+
+ return retval;
+}
+
+SongIterator::SongIterator() {
+ ID = 0;
+ channel_mask = 0;
+ fade.action = FADE_ACTION_NONE;
+ priority = 0;
+ memset(_deathListeners, 0, sizeof(_deathListeners));
+
+ // By default, don't remap
+ for (uint i = 0; i < 16; i++)
+ channel_remap[i] = i;
+}
+
+SongIterator::SongIterator(const SongIterator &si) {
+ ID = si.ID;
+ channel_mask = si.channel_mask;
+ fade = si.fade;
+ priority = si.priority;
+ memset(_deathListeners, 0, sizeof(_deathListeners));
+
+ for (uint i = 0; i < 16; i++)
+ channel_remap[i] = si.channel_remap[i];
+}
+
+
+SongIterator::~SongIterator() {
+ for (int i = 0; i < SONGIT_MAX_LISTENERS; ++i)
+ if (_deathListeners[i])
+ songit_tee_death_notification(_deathListeners[i], this);
+}
+
+SongIterator *songit_new(byte *data, uint size, SongIteratorType type, songit_id_t id) {
+ BaseSongIterator *it;
+
+ if (!data || size < 22) {
+ warning("Attempt to instantiate song iterator for null song data");
+ return NULL;
+ }
+
+
+ switch (type) {
+ case SCI_SONG_ITERATOR_TYPE_SCI0:
+ it = new Sci0SongIterator(data, size, id);
+ break;
+
+ case SCI_SONG_ITERATOR_TYPE_SCI1:
+ it = new Sci1SongIterator(data, size, id);
+ break;
+
+ default:
+ /**-- Invalid/unsupported sound resources --**/
+ warning("Attempt to instantiate invalid/unknown song iterator type %d", type);
+ return NULL;
+ }
+
+ return it;
+}
+
+int songit_handle_message(SongIterator **it_reg_p, SongIterator::Message msg) {
+ SongIterator *it = *it_reg_p;
+ SongIterator *newit;
+
+ newit = it->handleMessage(msg);
+
+ if (!newit)
+ return 0; /* Couldn't handle */
+
+ *it_reg_p = newit; /* Might have self-morphed */
+ return 1;
+}
+
+SongIterator *sfx_iterator_combine(SongIterator *it1, SongIterator *it2) {
+ if (it1 == NULL)
+ return it2;
+ if (it2 == NULL)
+ return it1;
+
+ /* Both are non-NULL: */
+ return new TeeSongIterator(it1, it2);
+}
+
+} // End of namespace Sci
+
+#endif // USE_OLD_MUSIC_FUNCTIONS
diff --git a/engines/sci/sound/iterator/iterator.h b/engines/sci/sound/iterator/iterator.h
new file mode 100644
index 0000000000..92a619d80e
--- /dev/null
+++ b/engines/sci/sound/iterator/iterator.h
@@ -0,0 +1,326 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+/* Song iterator declarations */
+
+#ifndef SCI_SFX_SFX_ITERATOR_H
+#define SCI_SFX_SFX_ITERATOR_H
+
+#include "sci/sci.h" // for USE_OLD_MUSIC_FUNCTIONS
+
+#ifdef USE_OLD_MUSIC_FUNCTIONS
+#include "sci/sound/softseq/mididriver.h"
+
+namespace Audio {
+ class AudioStream;
+}
+
+namespace Sci {
+
+enum SongIteratorStatus {
+ SI_FINISHED = -1, /**< Song finished playing */
+ SI_LOOP = -2, /**< Song just looped */
+ SI_ABSOLUTE_CUE = -3, /**< Found a song cue (absolute) */
+ SI_RELATIVE_CUE = -4, /**< Found a song cue (relative) */
+ SI_PCM = -5, /**< Found a PCM */
+ SI_IGNORE = -6, /**< This event got edited out by the remapper */
+ SI_MORPH = -255 /**< Song iterator requested self-morph. */
+};
+
+#define FADE_ACTION_NONE 0
+#define FADE_ACTION_FADE_AND_STOP 1
+#define FADE_ACTION_FADE_AND_CONT 2
+
+struct fade_params_t {
+ int ticks_per_step;
+ int final_volume;
+ int step_size;
+ int action;
+};
+
+/* Helper defs for messages */
+enum {
+ _SIMSG_BASE, /* Any base decoder */
+ _SIMSG_PLASTICWRAP /* Any "Plastic" (discardable) wrapper decoder */
+};
+
+/* Base messages */
+enum {
+ _SIMSG_BASEMSG_SET_LOOPS, /* Set loops */
+ _SIMSG_BASEMSG_SET_PLAYMASK, /* Set the current playmask for filtering */
+ _SIMSG_BASEMSG_SET_RHYTHM, /* Activate/deactivate rhythm channel */
+ _SIMSG_BASEMSG_ACK_MORPH, /* Acknowledge self-morph */
+ _SIMSG_BASEMSG_STOP, /* Stop iterator */
+ _SIMSG_BASEMSG_PRINT, /* Print self to stderr, after printing param1 tabs */
+ _SIMSG_BASEMSG_SET_HOLD, /* Set value of hold parameter to expect */
+ _SIMSG_BASEMSG_SET_FADE /* Set fade parameters */
+};
+
+/* "Plastic" (discardable) wrapper messages */
+enum {
+ _SIMSG_PLASTICWRAP_ACK_MORPH = _SIMSG_BASEMSG_ACK_MORPH /* Acknowledge self-morph */
+};
+
+/* Messages */
+#define SIMSG_SET_LOOPS(x) _SIMSG_BASE,_SIMSG_BASEMSG_SET_LOOPS,(x)
+#define SIMSG_SET_PLAYMASK(x) _SIMSG_BASE,_SIMSG_BASEMSG_SET_PLAYMASK,(x)
+#define SIMSG_SET_RHYTHM(x) _SIMSG_BASE,_SIMSG_BASEMSG_SET_RHYTHM,(x)
+#define SIMSG_ACK_MORPH _SIMSG_PLASTICWRAP,_SIMSG_PLASTICWRAP_ACK_MORPH,0
+#define SIMSG_STOP _SIMSG_BASE,_SIMSG_BASEMSG_STOP,0
+#define SIMSG_PRINT(indentation) _SIMSG_BASE,_SIMSG_BASEMSG_PRINT,(indentation)
+#define SIMSG_SET_HOLD(x) _SIMSG_BASE,_SIMSG_BASEMSG_SET_HOLD,(x)
+
+/* Message transmission macro: Takes song reference, message reference */
+#define SIMSG_SEND(o, m) songit_handle_message(&(o), SongIterator::Message((o)->ID, m))
+#define SIMSG_SEND_FADE(o, m) songit_handle_message(&(o), SongIterator::Message((o)->ID, _SIMSG_BASE, _SIMSG_BASEMSG_SET_FADE, m))
+
+typedef unsigned long songit_id_t;
+
+
+#define SONGIT_MAX_LISTENERS 2
+
+class TeeSongIterator;
+
+class SongIterator {
+public:
+ struct Message {
+ songit_id_t ID;
+ uint _class; /* Type of iterator supposed to receive this */
+ uint _type;
+ union {
+ uint i;
+ void *p;
+ } _arg;
+
+ Message() : ID(0), _class(0xFFFF), _type(0xFFFF) {}
+
+ /**
+ * Create a song iterator message.
+ *
+ * @param id: song ID the message is targeted to
+ * @param recipient_class: Message recipient class
+ * @param type message type
+ * @param a argument
+ *
+ * @note You should only use this with the SIMSG_* macros
+ */
+ Message(songit_id_t id, int recipient_class, int type, int a)
+ : ID(id), _class(recipient_class), _type(type) {
+ _arg.i = a;
+ }
+
+ /**
+ * Create a song iterator message, wherein the first parameter is a pointer.
+ *
+ * @param id: song ID the message is targeted to
+ * @param recipient_class: Message recipient class
+ * @param type message type
+ * @param a argument
+ *
+ * @note You should only use this with the SIMSG_* macros
+ */
+ Message(songit_id_t id, int recipient_class, int type, void *a)
+ : ID(id), _class(recipient_class), _type(type) {
+ _arg.p = a;
+ }
+ };
+
+public:
+ songit_id_t ID;
+ uint16 channel_mask; /* Bitmask of all channels this iterator will use */
+ fade_params_t fade;
+ int priority;
+
+ /* Death listeners */
+ /* These are not reset during initialisation */
+ TeeSongIterator *_deathListeners[SONGIT_MAX_LISTENERS];
+
+ /* See songit_* for the constructor and non-virtual member functions */
+
+ byte channel_remap[MIDI_CHANNELS]; ///< Remapping for channels
+
+public:
+ SongIterator();
+ SongIterator(const SongIterator &);
+ virtual ~SongIterator();
+
+ /**
+ * Resets/initializes the sound iterator.
+ */
+ virtual void init() {}
+
+ /**
+ * Reads the next MIDI operation _or_ delta time.
+ * @param buf The buffer to write to (needs to be able to store at least 4 bytes)
+ * @param result Number of bytes written to the buffer
+ * (equals the number of bytes that need to be passed
+ * to the lower layers) for 0, the cue value for SI_CUE,
+ * or the number of loops remaining for SI_LOOP.
+ * @return zero if a MIDI operation was written, SI_FINISHED
+ * if the song has finished playing, SI_LOOP if looping
+ * (after updating the loop variable), SI_CUE if we found
+ * a cue, SI_PCM if a PCM was found, or the number of ticks
+ * to wait before this function should be called next.
+ *
+ * @note If SI_PCM is returned, get_pcm() may be used to retrieve the associated
+ * PCM, but this must be done before any subsequent calls to next().
+ *
+ * @todo The actual buffer size should either be specified or passed in, so that
+ * we can detect buffer overruns.
+ */
+ virtual int nextCommand(byte *buf, int *result) = 0;
+
+ /**
+ Checks for the presence of a pcm sample.
+ * @return NULL if no PCM data was found, an AudioStream otherwise.
+ */
+ virtual Audio::AudioStream *getAudioStream() = 0;
+
+ /**
+ * Handles a message to the song iterator.
+ * @param msg the message to handle
+ * @return NULL if the message was not understood,
+ * this if the message could be handled, or a new song iterator
+ * if the current iterator had to be morphed (but the message could
+ * still be handled)
+ *
+ * @note This function is not supposed to be called directly; use
+ * songit_handle_message() instead. It should not recurse, since songit_handle_message()
+ * takes care of that and makes sure that its delegate received the message (and
+ * was morphed) before self.
+ */
+ virtual SongIterator *handleMessage(Message msg) = 0;
+
+ /**
+ * Gets the song position to store in a savegame.
+ */
+ virtual int getTimepos() = 0;
+
+ /**
+ * Clone this song iterator.
+ * @param delta number of ticks that still need to elapse until the
+ * next item should be read from the song iterator
+ */
+ virtual SongIterator *clone(int delta) = 0;
+
+
+private:
+ // Make the assignment operator unreachable, just in case...
+ SongIterator& operator=(const SongIterator&);
+};
+
+
+/********************************/
+/*-- Song iterator operations --*/
+/********************************/
+
+enum SongIteratorType {
+ SCI_SONG_ITERATOR_TYPE_SCI0 = 0,
+ SCI_SONG_ITERATOR_TYPE_SCI1 = 1
+};
+
+#define IT_READER_MASK_MIDI (1 << 0)
+#define IT_READER_MASK_DELAY (1 << 1)
+#define IT_READER_MASK_LOOP (1 << 2)
+#define IT_READER_MASK_CUE (1 << 3)
+#define IT_READER_MASK_PCM (1 << 4)
+#define IT_READER_MAY_FREE (1 << 10) /* Free SI_FINISHED iterators */
+#define IT_READER_MAY_CLEAN (1 << 11)
+/* MAY_CLEAN: May instantiate cleanup iterators
+** (use for players; this closes open channels at the end of a song) */
+
+#define IT_READER_MASK_ALL ( IT_READER_MASK_MIDI \
+ | IT_READER_MASK_DELAY \
+ | IT_READER_MASK_LOOP \
+ | IT_READER_MASK_CUE \
+ | IT_READER_MASK_PCM )
+
+/* Convenience wrapper around it->next
+** Parameters: (SongIterator **it) Reference to the iterator to access
+** (byte *) buf: The buffer to write to (needs to be able to
+** store at least 4 bytes)
+** (int) mask: IT_READER_MASK options specifying the events to
+** listen for
+** Returns : (int) zero if a MIDI operation was written, SI_FINISHED
+** if the song has finished playing, SI_LOOP if looping
+** (after updating the loop variable), SI_CUE if we found
+** a cue, SI_PCM if a PCM was found, or the number of ticks
+** to wait before this function should be called next.
+** (int) *result: Number of bytes written to the buffer
+** (equals the number of bytes that need to be passed
+** to the lower layers) for 0, the cue value for SI_CUE,
+** or the number of loops remaining for SI_LOOP.
+*/
+int songit_next(SongIterator **it, byte *buf, int *result, int mask);
+
+/* Constructs a new song iterator object
+** Parameters: (byte *) data: The song data to iterate over
+** (uint) size: Number of bytes in the song
+** (int) type: One of the SCI_SONG_ITERATOR_TYPEs
+** (songit_id_t) id: An ID for addressing the song iterator
+** Returns : (SongIterator *) A newly allocated but uninitialized song
+** iterator, or NULL if 'type' was invalid or unsupported
+*/
+SongIterator *songit_new(byte *data, uint size, SongIteratorType type, songit_id_t id);
+
+/* Constructs a new song timer iterator object
+** Parameters: (int) delta: The delta after which to fire SI_FINISHED
+** Returns : (SongIterator *) A newly allocated but uninitialized song
+** iterator
+*/
+SongIterator *new_timer_iterator(int delta);
+
+/* Handles a message to the song iterator
+** Parameters: (SongIterator **): A reference to the variable storing the song iterator
+** Returns : (int) Non-zero if the message was understood
+** The song iterator may polymorph as result of msg, so a writeable reference is required.
+*/
+int songit_handle_message(SongIterator **it_reg, SongIterator::Message msg);
+
+
+/* Creates a new song iterator which fast-forwards
+** Parameters: (SongIterator *) it: The iterator to wrap
+** (int) delta: The number of ticks to skip
+** Returns : (SongIterator) A newly created song iterator
+** which skips all delta times
+** until 'delta' has been used up
+*/
+SongIterator *new_fast_forward_iterator(SongIterator *it, int delta);
+
+/* Combines two song iterators into one
+** Parameters: (sfx_iterator_t *) it1: One of the two iterators, or NULL
+** (sfx_iterator_t *) it2: The other iterator, or NULL
+** Returns : (sfx_iterator_t *) A combined iterator
+** If a combined iterator is returned, it will be flagged to be allowed to
+** dispose of 'it1' and 'it2', where applicable. This means that this
+** call should be used by song players, but not by the core sound system
+*/
+SongIterator *sfx_iterator_combine(SongIterator *it1, SongIterator *it2);
+
+} // End of namespace Sci
+
+#endif // USE_OLD_MUSIC_FUNCTIONS
+
+#endif // SCI_SFX_SFX_ITERATOR_H
diff --git a/engines/sci/sound/iterator/iterator_internal.h b/engines/sci/sound/iterator/iterator_internal.h
new file mode 100644
index 0000000000..8eb35d7a40
--- /dev/null
+++ b/engines/sci/sound/iterator/iterator_internal.h
@@ -0,0 +1,276 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef SCI_SFX_SFX_ITERATOR_INTERNAL
+#define SCI_SFX_SFX_ITERATOR_INTERNAL
+
+#include "sci/sci.h" // for USE_OLD_MUSIC_FUNCTIONS
+
+#ifdef USE_OLD_MUSIC_FUNCTIONS
+#include "sci/sound/iterator/iterator.h"
+#include "sci/sound/softseq/mididriver.h"
+
+#include "common/array.h"
+#include "common/list.h"
+
+namespace Sci {
+
+/* Iterator types */
+
+enum {
+ SI_STATE_UNINITIALISED = -1,
+ SI_STATE_DELTA_TIME = 0, ///< Now at a delta time
+ SI_STATE_COMMAND = 1, ///< Now at a MIDI operation
+ SI_STATE_PENDING = 2, ///< Pending for loop
+ SI_STATE_FINISHED = 3, ///< End of song
+ SI_STATE_PCM = 4, ///< Should report a PCM next (-> DELTA_TIME)
+ SI_STATE_PCM_MAGIC_DELTA = 5 ///< Should report a ``magic'' one tick delta time next (goes on to FINISHED)
+};
+
+struct SongIteratorChannel {
+
+ int state; ///< State of this song iterator channel
+ int offset; ///< Offset into the data chunk */
+ int end; ///< Last allowed byte in track */
+ int id; ///< Some channel ID */
+
+ /**
+ * Number of ticks before the specified channel is next used, or
+ * CHANNEL_DELAY_MISSING to indicate that the delay has not yet
+ * been read.
+ */
+ int delay;
+
+ /* Two additional offsets for recovering: */
+ int loop_offset;
+ int initial_offset;
+
+ int playmask; ///< Active playmask (MIDI channels to play in here) */
+ int loop_timepos; ///< Total delay for this channel's loop marker */
+ int total_timepos; ///< Number of ticks since the beginning, ignoring loops */
+ int timepos_increment; ///< Number of ticks until the next command (to add) */
+
+ byte last_cmd; ///< Last operation executed, for running status */
+
+public:
+ void init(int id, int offset, int end);
+ void resetSynthChannels();
+};
+
+class BaseSongIterator : public SongIterator {
+public:
+ int _polyphony[MIDI_CHANNELS]; ///< # of simultaneous notes on each
+
+ int _ccc; ///< Cumulative cue counter, for those who need it
+ byte _resetflag; ///< for 0x4C -- on DoSound StopSound, do we return to start?
+ int _deviceId; ///< ID of the device we generating events for
+ int _numActiveChannels; ///< Number of active channels
+ Common::Array<byte> _data; ///< Song data
+
+ int _loops; ///< Number of loops remaining
+
+public:
+ BaseSongIterator(byte *data, uint size, songit_id_t id);
+
+protected:
+ int parseMidiCommand(byte *buf, int *result, SongIteratorChannel *channel, int flags);
+ int processMidi(byte *buf, int *result, SongIteratorChannel *channel, int flags);
+};
+
+/********************************/
+/*--------- SCI 0 --------------*/
+/********************************/
+
+class Sci0SongIterator : public BaseSongIterator {
+public:
+ SongIteratorChannel _channel;
+
+public:
+ Sci0SongIterator(byte *data, uint size, songit_id_t id);
+
+ int nextCommand(byte *buf, int *result);
+ Audio::AudioStream *getAudioStream();
+ SongIterator *handleMessage(Message msg);
+ void init();
+ int getTimepos();
+ SongIterator *clone(int delta);
+};
+
+
+/********************************/
+/*--------- SCI 1 --------------*/
+/********************************/
+
+
+struct Sci1Sample {
+ /**
+ * Time left-- initially, this is 'Sample point 1'.
+ * After initialisation, it is 'sample point 1 minus the sample
+ * point of the previous sample'
+ */
+ int delta;
+ int size;
+ bool announced; /* Announced for download (SI_PCM) */
+ int rate;
+ byte *_data;
+};
+
+class Sci1SongIterator : public BaseSongIterator {
+public:
+ SongIteratorChannel _channels[MIDI_CHANNELS];
+
+ /* Invariant: Whenever channels[i].delay == CHANNEL_DELAY_MISSING,
+ ** channel_offset[i] points to a delta time object. */
+
+ bool _initialised; /**!< Whether the MIDI channel setup has been initialised */
+ int _numChannels; /**!< Number of channels actually used */
+ Common::List<Sci1Sample> _samples;
+ int _numLoopedChannels; /**!< Number of channels that are ready to loop */
+
+ int _delayRemaining; /**!< Number of ticks that haven't been polled yet */
+ int _hold;
+
+public:
+ Sci1SongIterator(byte *data, uint size, songit_id_t id);
+ ~Sci1SongIterator();
+
+ int nextCommand(byte *buf, int *result);
+ Audio::AudioStream *getAudioStream();
+ SongIterator *handleMessage(Message msg);
+ void init();
+ int getTimepos();
+ SongIterator *clone(int delta);
+
+private:
+ int initSample(const int offset);
+ int initSong();
+
+ int getSmallestDelta() const;
+
+ void updateDelta(int delta);
+
+ /** Checks that none of the channels is waiting for its delta to be read */
+ bool noDeltaTime() const;
+
+ /** Determine the channel # of the next active event, or -1 */
+ int getCommandIndex() const;
+};
+
+#define PLAYMASK_NONE 0x0
+
+/***************************/
+/*--------- Timer ---------*/
+/***************************/
+
+/**
+ * A song iterator which waits a specified time and then fires
+ * SI_FINISHED. Used by DoSound, where audio resources are played (SCI1)
+ */
+class TimerSongIterator : public SongIterator {
+protected:
+ int _delta; /**!< Remaining time */
+
+public:
+ TimerSongIterator(int delta) : _delta(delta) {}
+
+ int nextCommand(byte *buf, int *result);
+ Audio::AudioStream *getAudioStream() { return NULL; }
+ SongIterator *handleMessage(Message msg) { return NULL; }
+ int getTimepos() { return 0; }
+ SongIterator *clone(int delta) { return new TimerSongIterator(*this); }
+};
+
+/**********************************/
+/*--------- Fast Forward ---------*/
+/**********************************/
+
+/**
+ * A song iterator which fast-forwards another iterator.
+ * Skips all delta times until a specified 'delta' has been used up.
+ */
+class FastForwardSongIterator : public SongIterator {
+protected:
+ SongIterator *_delegate;
+ int _delta; /**!< Remaining time */
+
+public:
+ FastForwardSongIterator(SongIterator *capsit, int delta);
+
+ int nextCommand(byte *buf, int *result);
+ Audio::AudioStream *getAudioStream();
+ SongIterator *handleMessage(Message msg);
+ int getTimepos();
+ SongIterator *clone(int delta);
+};
+
+
+/**********************************/
+/*--------- Tee iterator ---------*/
+/**********************************/
+
+enum {
+ TEE_LEFT = 0,
+ TEE_RIGHT = 1,
+ TEE_LEFT_ACTIVE = (1<<0),
+ TEE_RIGHT_ACTIVE = (1<<1),
+ TEE_LEFT_READY = (1<<2), /**!< left result is ready */
+ TEE_RIGHT_READY = (1<<3), /**!< right result is ready */
+ TEE_LEFT_PCM = (1<<4),
+ TEE_RIGHT_PCM = (1<<5)
+};
+
+/**
+ * This iterator combines two iterators, returns the next event available from either.
+ */
+class TeeSongIterator : public SongIterator {
+public:
+ int _status;
+
+ bool _readyToMorph; /**!< One of TEE_MORPH_* above */
+
+ struct {
+ SongIterator *it;
+ byte buf[4];
+ int result;
+ int retval;
+ } _children[2];
+
+public:
+ TeeSongIterator(SongIterator *left, SongIterator *right);
+ ~TeeSongIterator();
+
+ int nextCommand(byte *buf, int *result);
+ Audio::AudioStream *getAudioStream();
+ SongIterator *handleMessage(Message msg);
+ void init();
+ int getTimepos() { return 0; }
+ SongIterator *clone(int delta);
+};
+
+} // End of namespace Sci
+
+#endif // USE_OLD_MUSIC_FUNCTIONS
+
+#endif // SCI_SFX_SFX_ITERATOR_INTERNAL
diff --git a/engines/sci/sound/iterator/songlib.cpp b/engines/sci/sound/iterator/songlib.cpp
new file mode 100644
index 0000000000..8bc2e8f476
--- /dev/null
+++ b/engines/sci/sound/iterator/songlib.cpp
@@ -0,0 +1,189 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "sci/sci.h" // for USE_OLD_MUSIC_FUNCTIONS
+
+#ifdef USE_OLD_MUSIC_FUNCTIONS
+#include "sci/sound/iterator/core.h"
+#include "sci/sound/iterator/iterator.h"
+
+namespace Sci {
+
+#define debug_stream stderr
+
+Song::Song() : _wakeupTime(0, SFX_TICKS_PER_SEC) {
+ _handle = 0;
+ _resourceNum = 0;
+ _priority = 0;
+ _status = SOUND_STATUS_STOPPED;
+
+ _restoreBehavior = RESTORE_BEHAVIOR_CONTINUE;
+ _restoreTime = 0;
+
+ _loops = 0;
+ _hold = 0;
+
+ _it = 0;
+ _delay = 0;
+
+ _next = NULL;
+ _nextPlaying = NULL;
+ _nextStopping = NULL;
+}
+
+Song::Song(SongHandle handle, SongIterator *it, int priority) : _wakeupTime(0, SFX_TICKS_PER_SEC) {
+ _handle = handle;
+ _resourceNum = 0;
+ _priority = priority;
+ _status = SOUND_STATUS_STOPPED;
+
+ _restoreBehavior = RESTORE_BEHAVIOR_CONTINUE;
+ _restoreTime = 0;
+
+ _loops = 0;
+ _hold = 0;
+
+ _it = it;
+ _delay = 0;
+
+ _next = NULL;
+ _nextPlaying = NULL;
+ _nextStopping = NULL;
+}
+
+void SongLibrary::addSong(Song *song) {
+ Song **seeker = NULL;
+ int pri = song->_priority;
+
+ if (NULL == song) {
+ warning("addSong(): NULL passed for song");
+ return;
+ }
+
+ seeker = &_lib;
+ while (*seeker && ((*seeker)->_priority > pri))
+ seeker = &((*seeker)->_next);
+
+ song->_next = *seeker;
+ *seeker = song;
+}
+
+void SongLibrary::freeSounds() {
+ Song *next = _lib;
+ while (next) {
+ Song *song = next;
+ delete song->_it;
+ song->_it = NULL;
+ next = song->_next;
+ delete song;
+ }
+ _lib = NULL;
+}
+
+
+Song *SongLibrary::findSong(SongHandle handle) {
+ Song *seeker = _lib;
+
+ while (seeker) {
+ if (seeker->_handle == handle)
+ break;
+ seeker = seeker->_next;
+ }
+
+ return seeker;
+}
+
+Song *SongLibrary::findNextActive(Song *other) {
+ Song *seeker = other ? other->_next : _lib;
+
+ while (seeker) {
+ if ((seeker->_status == SOUND_STATUS_WAITING) ||
+ (seeker->_status == SOUND_STATUS_PLAYING))
+ break;
+ seeker = seeker->_next;
+ }
+
+ /* Only return songs that have equal priority */
+ if (other && seeker && other->_priority > seeker->_priority)
+ return NULL;
+
+ return seeker;
+}
+
+Song *SongLibrary::findFirstActive() {
+ return findNextActive(NULL);
+}
+
+int SongLibrary::removeSong(SongHandle handle) {
+ int retval;
+ Song *goner = _lib;
+
+ if (!goner)
+ return -1;
+
+ if (goner->_handle == handle)
+ _lib = goner->_next;
+
+ else {
+ while ((goner->_next) && (goner->_next->_handle != handle))
+ goner = goner->_next;
+
+ if (goner->_next) { /* Found him? */
+ Song *oldnext = goner->_next;
+
+ goner->_next = goner->_next->_next;
+ goner = oldnext;
+ } else return -1; /* No. */
+ }
+
+ retval = goner->_status;
+
+ delete goner->_it;
+ delete goner;
+
+ return retval;
+}
+
+int SongLibrary::countSongs() {
+ Song *seeker = _lib;
+ int retval = 0;
+
+ while (seeker) {
+ retval++;
+ seeker = seeker->_next;
+ }
+
+ return retval;
+}
+
+void SongLibrary::setSongRestoreBehavior(SongHandle handle, RESTORE_BEHAVIOR action) {
+ Song *seeker = findSong(handle);
+
+ seeker->_restoreBehavior = action;
+}
+
+} // End of namespace Sci
+
+#endif // USE_OLD_MUSIC_FUNCTIONS
diff --git a/engines/sci/sound/iterator/songlib.h b/engines/sci/sound/iterator/songlib.h
new file mode 100644
index 0000000000..acb704edaa
--- /dev/null
+++ b/engines/sci/sound/iterator/songlib.h
@@ -0,0 +1,171 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+/* Song library */
+
+#ifndef SCI_SFX_SFX_SONGLIB_H
+#define SCI_SFX_SFX_SONGLIB_H
+
+#include "common/scummsys.h"
+#include "sound/timestamp.h"
+
+#include "sci/sci.h" // for USE_OLD_MUSIC_FUNCTIONS
+#ifdef USE_OLD_MUSIC_FUNCTIONS
+
+namespace Sci {
+
+class SongIterator;
+
+#define SOUND_STATUS_STOPPED 0
+#define SOUND_STATUS_PLAYING 1
+#define SOUND_STATUS_SUSPENDED 2
+/* suspended: only if ordered from kernel space */
+#define SOUND_STATUS_WAITING 3
+/* "waiting" means "tagged for playing, but not active right now" */
+
+typedef unsigned long SongHandle;
+
+enum RESTORE_BEHAVIOR {
+ RESTORE_BEHAVIOR_CONTINUE, /* restart a song when restored from
+ a saved game */
+ RESTORE_BEHAVIOR_RESTART /* continue it from where it was */
+};
+
+class Song {
+public:
+ SongHandle _handle;
+ int _resourceNum; /**<! Resource number */
+ int _priority; /**!< Song priority (more important if priority is higher) */
+ int _status; /* See above */
+
+ int _restoreBehavior;
+ int _restoreTime;
+
+ /* Grabbed from the sound iterator, for save/restore purposes */
+ int _loops;
+ int _hold;
+
+ SongIterator *_it;
+ int _delay; /**!< Delay before accessing the iterator, in ticks */
+
+ Audio::Timestamp _wakeupTime; /**!< Timestamp indicating the next MIDI event */
+
+ Song *_next; /**!< Next song or NULL if this is the last one */
+
+ /**
+ * Next playing song. Used by the core song system.
+ */
+ Song *_nextPlaying;
+
+ /**
+ * Next song pending stopping. Used exclusively by the core song system's
+ * _update_multi_song()
+ */
+ Song *_nextStopping;
+
+public:
+
+ Song();
+
+ /**
+ * Initializes a new song.
+ * @param handle the sound handle
+ * @param it the song
+ * @param priority the song's priority
+ * @return a freshly allocated song
+ */
+ Song(SongHandle handle, SongIterator *it, int priority);
+};
+
+
+class SongLibrary {
+public:
+ Song *_lib;
+
+public:
+ SongLibrary() : _lib(0) {}
+
+ /** Frees a song library. */
+ void freeSounds();
+
+ /**
+ * Adds a song to a song library.
+ * @param song song to add
+ */
+ void addSong(Song *song);
+
+ /**
+ * Looks up the song with the specified handle.
+ * @param handle sound handle to look for
+ * @return the song or NULL if it wasn't found
+ */
+ Song *findSong(SongHandle handle);
+
+ /**
+ * Finds the first song playing with the highest priority.
+ * @return the song that should be played next, or NULL if there is none
+ */
+ Song *findFirstActive();
+
+ /**
+ * Finds the next song playing with the highest priority.
+ *
+ * The functions 'findFirstActive' and 'findNextActive'
+ * allow to iterate over all songs that satisfy the requirement of
+ * being 'playable'.
+ *
+ * @param song a song previously returned from the song library
+ * @return the next song to play relative to 'song', or NULL if none are left
+ */
+ Song *findNextActive(Song *song);
+
+ /**
+ * Removes a song from the library.
+ * @param handle handle of the song to remove
+ * @return the status of the song that was removed
+ */
+ int removeSong(SongHandle handle);
+
+ /**
+ * Counts the number of songs in a song library.
+ * @return the number of songs
+ */
+ int countSongs();
+
+ /**
+ * Determines what should be done with the song "handle" when restoring
+ * it from a saved game.
+ * @param handle sound handle being restored
+ * @param action desired action
+ */
+ void setSongRestoreBehavior(SongHandle handle,
+ RESTORE_BEHAVIOR action);
+};
+
+} // End of namespace Sci
+
+#endif // USE_OLD_MUSIC_FUNCTIONS
+
+#endif // SCI_SSFX_SFX_SONGLIB_H
diff --git a/engines/sci/sound/iterator/test-iterator.cpp b/engines/sci/sound/iterator/test-iterator.cpp
new file mode 100644
index 0000000000..0d603a89fd
--- /dev/null
+++ b/engines/sci/sound/iterator/test-iterator.cpp
@@ -0,0 +1,423 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "iterator.h"
+#include "iterator_internal.h"
+#include <stdarg.h>
+#include <stdio.h>
+
+using namespace Sci;
+
+#define ASSERT_S(x) if (!(x)) { error("Failed assertion in L%d: " #x, __LINE__); return; }
+#define ASSERT(x) ASSERT_S(x)
+
+/* Tests the song iterators */
+
+int errors = 0;
+
+void error(char *fmt, ...) {
+ va_list ap;
+
+ fprintf(stderr, "[ERROR] ");
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+
+ ++errors;
+}
+
+
+/* The simple iterator will finish after a fixed amount of time. Before that,
+** it emits (absolute) cues in ascending order. */
+struct simple_iterator : public SongIterator {
+ int lifetime_remaining;
+ char *cues;
+ int cue_counter;
+ int cue_progress;
+ int cues_nr;
+};
+
+int simple_it_next(SongIterator *_self, unsigned char *buf, int *result) {
+ simple_iterator *self = (simple_iterator *)_self;
+
+ if (self->lifetime_remaining == -1) {
+ error("Song iterator called post mortem");
+ return SI_FINISHED;
+ }
+
+ if (self->lifetime_remaining) {
+
+ if (self->cue_counter < self->cues_nr) {
+ int time_to_cue = self->cues[self->cue_counter];
+
+ if (self->cue_progress == time_to_cue) {
+ ++self->cue_counter;
+ self->cue_progress = 0;
+ *result = self->cue_counter;
+ return SI_ABSOLUTE_CUE;
+ } else {
+ int retval = time_to_cue - self->cue_progress;
+ self->cue_progress = time_to_cue;
+
+ if (retval > self->lifetime_remaining) {
+ retval = self->lifetime_remaining;
+ self->lifetime_remaining = 0;
+ self->cue_progress = retval;
+ return retval;
+ }
+
+ self->lifetime_remaining -= retval;
+ return retval;
+ }
+ } else {
+ int retval = self->lifetime_remaining;
+ self->lifetime_remaining = 0;
+ return retval;
+ }
+
+ } else {
+ self->lifetime_remaining = -1;
+ return SI_FINISHED;
+ }
+}
+
+Audio::AudioStream *simple_it_pcm_feed(SongIterator *_self) {
+ error("No PCM feed");
+ return NULL;
+}
+
+void simple_it_init(SongIterator *_self) {
+}
+
+SongIterator *simple_it_handle_message(SongIterator *_self, SongIterator::Message msg) {
+ return NULL;
+}
+
+void simple_it_cleanup(SongIterator *_self) {
+}
+
+/* Initialises the simple iterator.
+** Parameters: (int) delay: Number of ticks until the iterator finishes
+** (int *) cues: An array of cue delays (cue values are [1,2...])
+** (int) cues_nr: Number of cues in ``cues''
+** The first cue is emitted after cues[0] ticks, and it is 1. After cues[1] additional ticks
+** the next cue is emitted, and so on. */
+SongIterator *setup_simple_iterator(int delay, char *cues, int cues_nr) {
+ simple_iterator.lifetime_remaining = delay;
+ simple_iterator.cues = cues;
+ simple_iterator.cue_counter = 0;
+ simple_iterator.cues_nr = cues_nr;
+ simple_iterator.cue_progress = 0;
+
+ simple_iterator.ID = 42;
+ simple_iterator.channel_mask = 0x004f;
+ simple_iterator.flags = 0;
+ simple_iterator.priority = 1;
+
+ simple_iterator.death_listeners_nr = 0;
+
+ simple_iterator.cleanup = simple_it_cleanup;
+ simple_iterator.init = simple_it_init;
+ simple_iterator.handle_message = simple_it_handle_message;
+ simple_iterator.get_pcm_feed = simple_it_pcm_feed;
+ simple_iterator.next = simple_it_next;
+
+ return (SongIterator *) &simple_iterator;
+}
+
+#define ASSERT_SIT ASSERT(it == simple_it)
+#define ASSERT_FFIT ASSERT(it == ff_it)
+#define ASSERT_NEXT(n) ASSERT(songit_next(&it, data, &result, IT_READER_MASK_ALL) == n)
+#define ASSERT_RESULT(n) ASSERT(result == n)
+#define ASSERT_CUE(n) ASSERT_NEXT(SI_ABSOLUTE_CUE); ASSERT_RESULT(n)
+
+void test_simple_it() {
+ SongIterator *it;
+ SongIterator *simple_it = (SongIterator *) & simple_iterator;
+ unsigned char data[4];
+ int result;
+ puts("[TEST] simple iterator (test artifact)");
+
+ it = setup_simple_iterator(42, NULL, 0);
+
+ ASSERT_SIT;
+ ASSERT_NEXT(42);
+ ASSERT_SIT;
+ ASSERT_NEXT(SI_FINISHED);
+ ASSERT_SIT;
+
+ it = setup_simple_iterator(42, "\003\004", 2);
+ ASSERT_SIT;
+ ASSERT_NEXT(3);
+ ASSERT_CUE(1);
+ ASSERT_SIT;
+ ASSERT_NEXT(4);
+ ASSERT_CUE(2);
+ ASSERT_SIT;
+// warning("XXX => %d", songit_next(&it, data, &result, IT_READER_MASK_ALL));
+ ASSERT_NEXT(35);
+ ASSERT_NEXT(SI_FINISHED);
+ ASSERT_SIT;
+
+ puts("[TEST] Test OK.");
+}
+
+void test_fastforward() {
+ SongIterator *it;
+ SongIterator *simple_it = (SongIterator *) & simple_iterator;
+ SongIterator *ff_it;
+ unsigned char data[4];
+ int result;
+ puts("[TEST] fast-forward iterator");
+
+ it = setup_simple_iterator(42, NULL, 0);
+ ff_it = it = new_fast_forward_iterator(it, 0);
+ ASSERT_FFIT;
+ ASSERT_NEXT(42);
+ ASSERT_SIT; /* Must have morphed back */
+ ASSERT_NEXT(SI_FINISHED);
+ ASSERT_SIT;
+
+ it = setup_simple_iterator(42, NULL, 0);
+ ff_it = it = new_fast_forward_iterator(it, 1);
+ ASSERT_FFIT;
+ ASSERT_NEXT(41);
+ /* May or may not have morphed back here */
+ ASSERT_NEXT(SI_FINISHED);
+ ASSERT_SIT;
+
+ it = setup_simple_iterator(42, NULL, 0);
+ ff_it = it = new_fast_forward_iterator(it, 41);
+ ASSERT_FFIT;
+ ASSERT_NEXT(1);
+ /* May or may not have morphed back here */
+ ASSERT_NEXT(SI_FINISHED);
+ ASSERT_SIT;
+
+ it = setup_simple_iterator(42, NULL, 0);
+ ff_it = it = new_fast_forward_iterator(it, 42);
+ ASSERT_NEXT(SI_FINISHED);
+ /* May or may not have morphed back here */
+
+ it = setup_simple_iterator(42, NULL, 0);
+ ff_it = it = new_fast_forward_iterator(it, 10000);
+ ASSERT_NEXT(SI_FINISHED);
+ /* May or may not have morphed back here */
+
+ it = setup_simple_iterator(42, "\003\004", 2);
+ ff_it = it = new_fast_forward_iterator(it, 2);
+ ASSERT_FFIT;
+ ASSERT_NEXT(1);
+ ASSERT_CUE(1);
+ ASSERT_SIT;
+ ASSERT_NEXT(4);
+ ASSERT_CUE(2);
+ ASSERT_SIT;
+ ASSERT_NEXT(35);
+ ASSERT_NEXT(SI_FINISHED);
+ ASSERT_SIT;
+
+ it = setup_simple_iterator(42, "\003\004", 2);
+ ff_it = it = new_fast_forward_iterator(it, 5);
+ ASSERT_FFIT;
+ ASSERT_CUE(1);
+ ASSERT_FFIT;
+ ASSERT_NEXT(2);
+ ASSERT_CUE(2);
+ ASSERT_SIT;
+ ASSERT_NEXT(35);
+ ASSERT_NEXT(SI_FINISHED);
+ ASSERT_SIT;
+
+ it = setup_simple_iterator(42, "\003\004", 2);
+ ff_it = it = new_fast_forward_iterator(it, 41);
+ ASSERT_FFIT;
+ ASSERT_CUE(1);
+ ASSERT_FFIT;
+ ASSERT_CUE(2);
+ ASSERT_FFIT;
+ ASSERT_NEXT(1);
+ ASSERT_NEXT(SI_FINISHED);
+ ASSERT_SIT;
+
+ puts("[TEST] Test OK.");
+}
+
+#define SIMPLE_SONG_SIZE 50
+
+static unsigned char simple_song[SIMPLE_SONG_SIZE] = {
+ 0x00, /* Regular song */
+ /* Only use channel 0 for all devices */
+ 0x02, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ /* Song begins here */
+ 42, 0x90, 60, 0x7f, /* Play C after 42 ticks */
+ 02, 64, 0x42, /* Play E after 2 more ticks, using running status mode */
+ 0xf8, 10, 0x80, 60, 0x02, /* Stop C after 250 ticks */
+ 0, 64, 0x00, /* Stop E immediately */
+ 00, 0xfc /* Stop song */
+};
+
+#define ASSERT_MIDI3(cmd, arg0, arg1) \
+ ASSERT(data[0] == cmd); \
+ ASSERT(data[1] == arg0); \
+ ASSERT(data[2] == arg1);
+
+void test_iterator_sci0() {
+ SongIterator *it = songit_new(simple_song, SIMPLE_SONG_SIZE, SCI_SONG_ITERATOR_TYPE_SCI0, 0l);
+ unsigned char data[4];
+ int result;
+ SIMSG_SEND(it, SIMSG_SET_PLAYMASK(0x0001)); /* Initialise song, enabling channel 0 */
+
+ puts("[TEST] SCI0-style song");
+ ASSERT_NEXT(42);
+ ASSERT_NEXT(0);
+ ASSERT_MIDI3(0x90, 60, 0x7f);
+ ASSERT_NEXT(2);
+ ASSERT_NEXT(0);
+ ASSERT_MIDI3(0x90, 64, 0x42);
+ ASSERT_NEXT(250);
+ ASSERT_NEXT(0);
+ ASSERT_MIDI3(0x80, 60, 0x02);
+ ASSERT_NEXT(0);
+ ASSERT_MIDI3(0x80, 64, 0x00);
+ ASSERT_NEXT(SI_FINISHED);
+ puts("[TEST] Test OK.");
+}
+
+
+
+void test_iterator_sci0_loop() {
+ SongIterator *it = songit_new(simple_song, SIMPLE_SONG_SIZE, SCI_SONG_ITERATOR_TYPE_SCI0, 0l);
+ unsigned char data[4];
+ int result;
+ SIMSG_SEND(it, SIMSG_SET_PLAYMASK(0x0001)); /* Initialise song, enabling channel 0 */
+ SIMSG_SEND(it, SIMSG_SET_LOOPS(2)); /* Loop one additional time */
+
+ puts("[TEST] SCI0-style song with looping");
+ ASSERT_NEXT(42);
+ ASSERT_NEXT(0);
+ ASSERT_MIDI3(0x90, 60, 0x7f);
+ ASSERT_NEXT(2);
+ ASSERT_NEXT(0);
+ ASSERT_MIDI3(0x90, 64, 0x42);
+ ASSERT_NEXT(250);
+ ASSERT_NEXT(0);
+ ASSERT_MIDI3(0x80, 60, 0x02);
+ ASSERT_NEXT(0);
+ ASSERT_MIDI3(0x80, 64, 0x00);
+ ASSERT_NEXT(SI_LOOP);
+ ASSERT_NEXT(42);
+ ASSERT_NEXT(0);
+ ASSERT_MIDI3(0x90, 60, 0x7f);
+ ASSERT_NEXT(2);
+ ASSERT_NEXT(0);
+ ASSERT_MIDI3(0x90, 64, 0x42);
+ ASSERT_NEXT(250);
+ ASSERT_NEXT(0);
+ ASSERT_MIDI3(0x80, 60, 0x02);
+ ASSERT_NEXT(0);
+ ASSERT_MIDI3(0x80, 64, 0x00);
+ ASSERT_NEXT(SI_FINISHED);
+ puts("[TEST] Test OK.");
+}
+
+
+
+#define LOOP_SONG_SIZE 54
+
+unsigned char loop_song[LOOP_SONG_SIZE] = {
+ 0x00, /* Regular song song */
+ /* Only use channel 0 for all devices */
+ 0x02, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ /* Song begins here */
+ 42, 0x90, 60, 0x7f, /* Play C after 42 ticks */
+ 13, 0x80, 60, 0x00, /* Stop C after 13 ticks */
+ 00, 0xCF, 0x7f, /* Set loop point */
+ 02, 0x90, 64, 0x42, /* Play E after 2 more ticks, using running status mode */
+ 03, 0x80, 64, 0x00, /* Stop E after 3 ticks */
+ 00, 0xfc /* Stop song/loop */
+};
+
+
+void test_iterator_sci0_mark_loop() {
+ SongIterator *it = songit_new(loop_song, LOOP_SONG_SIZE, SCI_SONG_ITERATOR_TYPE_SCI0, 0l);
+ unsigned char data[4];
+ int result;
+ SIMSG_SEND(it, SIMSG_SET_PLAYMASK(0x0001)); /* Initialise song, enabling channel 0 */
+ SIMSG_SEND(it, SIMSG_SET_LOOPS(3)); /* Loop once more */
+
+ puts("[TEST] SCI0-style song with loop mark, looping");
+ ASSERT_NEXT(42);
+ ASSERT_NEXT(0);
+ ASSERT_MIDI3(0x90, 60, 0x7f);
+ ASSERT_NEXT(13);
+ ASSERT_NEXT(0);
+ ASSERT_MIDI3(0x80, 60, 0x00);
+ /* Loop point here: we don't observe that in the iterator interface yet, though */
+ ASSERT_NEXT(2);
+ ASSERT_NEXT(0);
+ ASSERT_MIDI3(0x90, 64, 0x42);
+ ASSERT_NEXT(3);
+ ASSERT_NEXT(0);
+ ASSERT_MIDI3(0x80, 64, 0x00);
+ /* Now we loop back to the loop pont */
+ ASSERT_NEXT(SI_LOOP);
+ ASSERT_NEXT(2);
+ ASSERT_NEXT(0);
+ ASSERT_MIDI3(0x90, 64, 0x42);
+ ASSERT_NEXT(3);
+ ASSERT_NEXT(0);
+ ASSERT_MIDI3(0x80, 64, 0x00);
+ /* ...and one final time */
+ ASSERT_NEXT(SI_LOOP);
+ ASSERT_NEXT(2);
+ ASSERT_NEXT(0);
+ ASSERT_MIDI3(0x90, 64, 0x42);
+ ASSERT_NEXT(3);
+ ASSERT_NEXT(0);
+ ASSERT_MIDI3(0x80, 64, 0x00);
+
+ ASSERT_NEXT(SI_FINISHED);
+ puts("[TEST] Test OK.");
+}
+
+
+
+int main(int argc, char **argv) {
+ test_simple_it();
+ test_fastforward();
+ test_iterator_sci0();
+ test_iterator_sci0_loop();
+ test_iterator_sci0_mark_loop();
+ if (errors != 0)
+ warning("[ERROR] %d errors total", errors);
+ return (errors != 0);
+}
diff --git a/engines/sci/sound/midiparser_sci.cpp b/engines/sci/sound/midiparser_sci.cpp
new file mode 100644
index 0000000000..70421d7e15
--- /dev/null
+++ b/engines/sci/sound/midiparser_sci.cpp
@@ -0,0 +1,506 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "sci/engine/kernel.h"
+#include "sci/engine/state.h"
+#include "sci/sound/midiparser_sci.h"
+#include "sci/sound/softseq/mididriver.h"
+
+namespace Sci {
+
+static const int nMidiParams[] = { 2, 2, 2, 2, 1, 1, 2, 0 };
+
+enum SciMidiCommands {
+ kSetSignalLoop = 0x7F,
+ kEndOfTrack = 0xFC,
+ kSetReverb = 0x50,
+ kMidiHold = 0x52,
+ kUpdateCue = 0x60
+};
+
+// MidiParser_SCI
+//
+MidiParser_SCI::MidiParser_SCI(SciVersion soundVersion) :
+ MidiParser() {
+ _soundVersion = soundVersion;
+ _mixedData = NULL;
+ // mididata contains delta in 1/60th second
+ // values of ppqn and tempo are found experimentally and may be wrong
+ _ppqn = 1;
+ setTempo(16667);
+
+ _volume = 0;
+
+ _signalSet = false;
+ _signalToSet = 0;
+}
+
+MidiParser_SCI::~MidiParser_SCI() {
+ unloadMusic();
+}
+
+bool MidiParser_SCI::loadMusic(SoundResource::Track *track, MusicEntry *psnd, int channelFilterMask, SciVersion soundVersion) {
+ unloadMusic();
+ _track = track;
+ _pSnd = psnd;
+ _soundVersion = soundVersion;
+
+ setVolume(psnd->volume);
+
+ if (channelFilterMask) {
+ // SCI0 only has 1 data stream, but we need to filter out channels depending on music hardware selection
+ midiFilterChannels(channelFilterMask);
+ } else {
+ midiMixChannels();
+ }
+
+ _num_tracks = 1;
+ _tracks[0] = _mixedData;
+ setTrack(0);
+ _loopTick = 0;
+ return true;
+}
+
+void MidiParser_SCI::unloadMusic() {
+ allNotesOff();
+ resetTracking();
+ _num_tracks = 0;
+ if (_mixedData) {
+ delete[] _mixedData;
+ _mixedData = NULL;
+ }
+}
+
+void MidiParser_SCI::parseNextEvent(EventInfo &info) {
+ // Set signal AFTER waiting for delta, otherwise we would set signal too soon resulting in all sorts of bugs
+ if (_signalSet) {
+ _signalSet = false;
+ _pSnd->signal = _signalToSet;
+ debugC(2, kDebugLevelSound, "signal %04x", _signalToSet);
+ }
+
+ info.start = _position._play_pos;
+ info.delta = 0;
+ while (*_position._play_pos == 0xF8) {
+ info.delta += 240;
+ _position._play_pos++;
+ }
+ info.delta += *(_position._play_pos++);
+
+ // Process the next info.
+ if ((_position._play_pos[0] & 0xF0) >= 0x80)
+ info.event = *(_position._play_pos++);
+ else
+ info.event = _position._running_status;
+ if (info.event < 0x80)
+ return;
+
+ _position._running_status = info.event;
+ switch (info.command()) {
+ case 0xC:
+ info.basic.param1 = *(_position._play_pos++);
+ info.basic.param2 = 0;
+ if (info.channel() == 0xF) {// SCI special case
+ if (info.basic.param1 != kSetSignalLoop) {
+ _signalSet = true;
+ _signalToSet = info.basic.param1;
+ } else {
+ _loopTick = _position._play_tick;
+ }
+ }
+ break;
+ case 0xD:
+ info.basic.param1 = *(_position._play_pos++);
+ info.basic.param2 = 0;
+ break;
+
+ case 0xB:
+ info.basic.param1 = *(_position._play_pos++);
+ info.basic.param2 = *(_position._play_pos++);
+ if (info.channel() == 0xF) {// SCI special
+ // Reference for some events:
+ // http://wiki.scummvm.org/index.php/SCI/Specifications/Sound/SCI0_Resource_Format#Status_Reference
+ // Also, sci/sound/iterator/iterator.cpp, function BaseSongIterator::parseMidiCommand()
+ switch (info.basic.param1) {
+ case kSetReverb:
+ // TODO: Not implemented yet
+ break;
+ case kMidiHold:
+ // Check if the hold ID marker is the same as the hold ID
+ // marker set for that song by cmdSetSoundHold.
+ // If it is, loop back, but don't stop notes when jumping.
+ if (info.basic.param2 == _pSnd->hold)
+ jumpToTick(_loopTick, false, false);
+ break;
+ case kUpdateCue:
+ switch (_soundVersion) {
+ case SCI_VERSION_0_EARLY:
+ case SCI_VERSION_0_LATE:
+ _pSnd->dataInc += info.basic.param2;
+ _signalSet = true;
+ _signalToSet = 0x7f + _pSnd->dataInc;
+ break;
+ case SCI_VERSION_1_EARLY:
+ case SCI_VERSION_1_LATE:
+ _pSnd->dataInc++;
+ break;
+ default:
+ break;
+ }
+ break;
+ // Unhandled SCI commands
+ case 0x46: // LSL3 - binoculars
+ case 0x61: // Iceman (Adlib?)
+ case 0x73: // Hoyle
+ case 0xd1: // KQ4, when riding the unicorn
+ // Obscure SCI commands - ignored
+ break;
+ // Standard MIDI commands
+ case 0x01: // mod wheel
+ case 0x04: // foot controller
+ case 0x07: // channel volume
+ case 0x0A: // pan
+ case 0x0B: // expression
+ case 0x40: // sustain
+ case 0x4E: // velocity control
+ case 0x79: // reset all
+ case 0x7B: // notes off
+ // These are all handled by the music driver, so ignore them
+ break;
+ case 0x4B: // voice mapping
+ // TODO: is any support for this needed at the MIDI parser level?
+ warning("Unhanded SCI MIDI command 0x%x - voice mapping (parameter %d)", info.basic.param1, info.basic.param2);
+ break;
+ default:
+ warning("Unhandled SCI MIDI command 0x%x (parameter %d)", info.basic.param1, info.basic.param2);
+ break;
+ }
+ }
+ if (info.basic.param1 == 7) // channel volume change -scale it
+ info.basic.param2 = info.basic.param2 * _volume / 0x7F;
+ info.length = 0;
+ break;
+
+ case 0x8:
+ case 0x9:
+ case 0xA:
+ case 0xE:
+ info.basic.param1 = *(_position._play_pos++);
+ info.basic.param2 = *(_position._play_pos++);
+ if (info.command() == 0x9 && info.basic.param2 == 0)
+ info.event = info.channel() | 0x80;
+ info.length = 0;
+ break;
+
+ case 0xF: // System Common, Meta or SysEx event
+ switch (info.event & 0x0F) {
+ case 0x2: // Song Position Pointer
+ info.basic.param1 = *(_position._play_pos++);
+ info.basic.param2 = *(_position._play_pos++);
+ break;
+
+ case 0x3: // Song Select
+ info.basic.param1 = *(_position._play_pos++);
+ info.basic.param2 = 0;
+ break;
+
+ case 0x6:
+ case 0x8:
+ case 0xA:
+ case 0xB:
+ case 0xC:
+ case 0xE:
+ info.basic.param1 = info.basic.param2 = 0;
+ break;
+
+ case 0x0: // SysEx
+ info.length = readVLQ(_position._play_pos);
+ info.ext.data = _position._play_pos;
+ _position._play_pos += info.length;
+ break;
+
+ case 0xF: // META event
+ info.ext.type = *(_position._play_pos++);
+ info.length = readVLQ(_position._play_pos);
+ info.ext.data = _position._play_pos;
+ _position._play_pos += info.length;
+ if (info.ext.type == 0x2F) {// end of track reached
+ if (_pSnd->loop)
+ _pSnd->loop--;
+ if (_pSnd->loop) {
+ // We need to play it again...
+ jumpToTick(_loopTick);
+ } else {
+ _pSnd->status = kSoundStopped;
+ _pSnd->signal = SIGNAL_OFFSET;
+
+ debugC(2, kDebugLevelSound, "signal EOT");
+ }
+ }
+ break;
+ default:
+ warning(
+ "MidiParser_SCI::parseNextEvent: Unsupported event code %x",
+ info.event);
+ } // // System Common, Meta or SysEx event
+ }// switch (info.command())
+}
+
+
+byte MidiParser_SCI::midiGetNextChannel(long ticker) {
+ byte curr = 0xFF;
+ long closest = ticker + 1000000, next = 0;
+
+ for (int i = 0; i < _track->channelCount; i++) {
+ if (_track->channels[i].time == -1) // channel ended
+ continue;
+ next = *_track->channels[i].data; // when the next event shoudl occur
+ if (next == 0xF8) // 0xF8 means 240 ticks delay
+ next = 240;
+ next += _track->channels[i].time;
+ if (next < closest) {
+ curr = i;
+ closest = next;
+ }
+ }
+
+ return curr;
+}
+
+byte *MidiParser_SCI::midiMixChannels() {
+ int totalSize = 0;
+ byte **dataPtr = new byte *[_track->channelCount];
+
+ for (int i = 0; i < _track->channelCount; i++) {
+ dataPtr[i] = _track->channels[i].data;
+ _track->channels[i].time = 0;
+ _track->channels[i].prev = 0;
+ totalSize += _track->channels[i].size;
+ }
+
+ byte *mixedData = new byte[totalSize * 2]; // FIXME: creates overhead and still may be not enough to hold all data
+ _mixedData = mixedData;
+ long ticker = 0;
+ byte curr, delta;
+ byte cmd, par1, global_prev = 0;
+ long new_delta;
+ SoundResource::Channel *channel;
+ while ((curr = midiGetNextChannel(ticker)) != 0xFF) { // there is still active channel
+ channel = &_track->channels[curr];
+ delta = *channel->data++;
+ channel->time += (delta == 0xF8 ? 240 : delta); // when the comamnd is supposed to occur
+ if (delta == 0xF8)
+ continue;
+ new_delta = channel->time - ticker;
+ ticker += new_delta;
+
+ cmd = *channel->data++;
+ if (cmd != kEndOfTrack) {
+ // output new delta
+ while (new_delta > 240) {
+ *mixedData++ = 0xF8;
+ new_delta -= 240;
+ }
+ *mixedData++ = (byte)new_delta;
+ }
+ switch (cmd) {
+ case 0xF0: // sysEx
+ *mixedData++ = cmd;
+ do {
+ par1 = *channel->data++;
+ *mixedData++ = par1; // out
+ } while (par1 != 0xF7);
+ break;
+ case kEndOfTrack: // end channel
+ channel->time = -1; // FIXME
+ break;
+ default: // MIDI command
+ if (cmd & 0x80)
+ par1 = *channel->data++;
+ else {// running status
+ par1 = cmd;
+ cmd = channel->prev;
+ }
+ if (cmd != global_prev)
+ *mixedData++ = cmd; // out cmd
+ *mixedData++ = par1;// pout par1
+ if (nMidiParams[(cmd >> 4) - 8] == 2)
+ *mixedData++ = *channel->data++; // out par2
+ channel->prev = cmd;
+ global_prev = cmd;
+ }// switch(cmd)
+ }// while (curr)
+ // mixing finished. inserting stop event
+ *mixedData++ = 0;
+ *mixedData++ = 0xFF;
+ *mixedData++ = 0x2F;
+ *mixedData++ = 0x00;
+ *mixedData++ = 0x00;
+
+ for (int channelNr = 0; channelNr < _track->channelCount; channelNr++)
+ _track->channels[channelNr].data = dataPtr[channelNr];
+
+ delete[] dataPtr;
+ return _mixedData;
+}
+
+// This is used for SCI0 sound-data. SCI0 only has one stream that may
+// contain several channels and according to output device we remove
+// certain channels from that data.
+byte *MidiParser_SCI::midiFilterChannels(int channelMask) {
+ SoundResource::Channel *channel = &_track->channels[0];
+ byte *channelData = channel->data;
+ byte *channelDataEnd = channel->data + channel->size;
+ byte *filterData = new byte[channel->size + 5];
+ byte curChannel, curByte, curDelta;
+ byte command, lastCommand;
+ int delta = 0;
+ //int dataLeft = channel->size;
+ int midiParamCount;
+
+ _mixedData = filterData;
+ command = 0;
+ midiParamCount = 0;
+ lastCommand = 0;
+ curChannel = 15;
+
+ while (channelData < channelDataEnd) {
+ curDelta = *channelData++;
+ if (curDelta == 0xF8) {
+ delta += 240;
+ continue;
+ }
+ delta += curDelta;
+ curByte = *channelData++;
+
+ switch (curByte) {
+ case 0xF0: // sysEx
+ case kEndOfTrack: // end of channel
+ command = curByte;
+ curChannel = 15;
+ break;
+ default:
+ if (curByte & 0x80) {
+ command = curByte;
+ curChannel = command & 0x0F;
+ midiParamCount = nMidiParams[(command >> 4) - 8];
+ }
+ }
+ if ((1 << curChannel) & channelMask) {
+ if (command != kEndOfTrack) {
+ debugC(2, kDebugLevelSound, "\nDELTA ");
+ // Write delta
+ while (delta > 240) {
+ *filterData++ = 0xF8;
+ debugC(2, kDebugLevelSound, "F8 ");
+ delta -= 240;
+ }
+ *filterData++ = (byte)delta;
+ debugC(2, kDebugLevelSound, "%02X ", delta);
+ delta = 0;
+ }
+ // Write command
+ switch (command) {
+ case 0xF0: // sysEx
+ *filterData++ = command;
+ debugC(2, kDebugLevelSound, "%02X ", command);
+ do {
+ curByte = *channelData++;
+ *filterData++ = curByte; // out
+ } while (curByte != 0xF7);
+ lastCommand = command;
+ break;
+
+ case kEndOfTrack: // end of channel
+ break;
+
+ default: // MIDI command
+ if (lastCommand != command) {
+ *filterData++ = command;
+ debugC(2, kDebugLevelSound, "%02X ", command);
+ lastCommand = command;
+ }
+ if (midiParamCount > 0) {
+ if (curByte & 0x80) {
+ debugC(2, kDebugLevelSound, "%02X ", *channelData);
+ *filterData++ = *channelData++;
+ } else {
+ debugC(2, kDebugLevelSound, "%02X ", curByte);
+ *filterData++ = curByte;
+ }
+ }
+ if (midiParamCount > 1) {
+ debugC(2, kDebugLevelSound, "%02X ", *channelData);
+ *filterData++ = *channelData++;
+ }
+ }
+ } else {
+ if (curByte & 0x80) {
+ channelData += midiParamCount;
+ } else {
+ channelData += midiParamCount - 1;
+ }
+ }
+ }
+ // Stop event
+ *filterData++ = 0; // delta
+ *filterData++ = 0xFF; // Meta-Event
+ *filterData++ = 0x2F; // End-Of-Track
+ *filterData++ = 0x00;
+ *filterData++ = 0x00;
+
+ return _mixedData;
+}
+
+void MidiParser_SCI::setVolume(byte bVolume) {
+ if (bVolume > 0x7F)
+ bVolume = 0x7F;
+ if (_volume != bVolume) {
+ _volume = bVolume;
+
+ switch (_soundVersion) {
+ case SCI_VERSION_0_EARLY:
+ case SCI_VERSION_0_LATE: {
+ MidiPlayer *SCIDriver = (MidiPlayer *)_driver;
+ int16 globalVolume = _volume * 15 / 127;
+ SCIDriver->setVolume(globalVolume);
+ break;
+ }
+
+ case SCI_VERSION_1_EARLY:
+ case SCI_VERSION_1_LATE:
+ // sending volume change to all active channels
+ for (int i = 0; i < _track->channelCount; i++)
+ if (_track->channels[i].number <= 0xF)
+ _driver->send(0xB0 + _track->channels[i].number, 7, _volume);
+ break;
+
+ default:
+ error("MidiParser_SCI::setVolume: Unsupported soundVersion");
+ }
+ }
+}
+
+} // End of namespace Sci
diff --git a/engines/sci/sound/midiparser_sci.h b/engines/sci/sound/midiparser_sci.h
new file mode 100644
index 0000000000..a5b2074fbc
--- /dev/null
+++ b/engines/sci/sound/midiparser_sci.h
@@ -0,0 +1,85 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef SCI_MIDIPARSER_H
+#define SCI_MIDIPARSER_H
+
+#include "sci/resource.h"
+#include "sci/sound/music.h"
+#include "sound/midiparser.h"
+
+/*
+ Sound drivers info: (from driver cmd0)
+ Adlib/SB : track 0 , voices 9 , patch 3 ah=1
+ ProAudioSp: track 0 , voices 9 , patch 3 ah=17
+ GenerlMIDI: track 7 , voices 32, patch 4 ah=1 SCI1.1
+ Game Blast: track 9 , voices 12, patch 101 ah=1
+ MT-32 : track 12, voices 32, patch 1 ah=1
+ PC Speaker: track 18, voices 1 , patch 0xFF ah=1
+ Tandy : track 19, voices 3 , patch 101 ah=1
+ IBM PS/1 : track 19, voices 3 , patch 101 ah=1
+
+ */
+
+namespace Sci {
+
+class MidiParser_SCI : public MidiParser {
+public:
+ MidiParser_SCI(SciVersion soundVersion);
+ ~MidiParser_SCI();
+ bool loadMusic(SoundResource::Track *track, MusicEntry *psnd, int channelFilterMask, SciVersion soundVersion);
+ bool loadMusic(byte *, uint32) {
+ return false;
+ }
+ void unloadMusic();
+ void setVolume(byte bVolume);
+ void stop() {
+ _abort_parse = true;
+ allNotesOff();
+ }
+ void pause() {
+ allNotesOff();
+ }
+
+protected:
+ void parseNextEvent(EventInfo &info);
+ byte *midiMixChannels();
+ byte *midiFilterChannels(int channelMask);
+ byte midiGetNextChannel(long ticker);
+
+ SciVersion _soundVersion;
+ byte *_mixedData;
+ SoundResource::Track *_track;
+ MusicEntry *_pSnd;
+ uint32 _loopTick;
+ byte _volume;
+
+ bool _signalSet;
+ int16 _signalToSet;
+};
+
+} // End of namespace Sci
+
+#endif
diff --git a/engines/sci/sound/music.cpp b/engines/sci/sound/music.cpp
new file mode 100644
index 0000000000..fe8c0fed09
--- /dev/null
+++ b/engines/sci/sound/music.cpp
@@ -0,0 +1,573 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "sound/audiostream.h"
+#include "common/config-manager.h"
+
+#include "sci/sci.h"
+#include "sci/console.h"
+#include "sci/resource.h"
+#include "sci/engine/kernel.h"
+#include "sci/engine/state.h"
+#include "sci/sound/midiparser_sci.h"
+#include "sci/sound/music.h"
+#include "sci/sound/softseq/pcjr.h"
+
+namespace Sci {
+
+SciMusic::SciMusic(SciVersion soundVersion)
+ : _soundVersion(soundVersion), _soundOn(true) {
+
+ // Reserve some space in the playlist, to avoid expensive insertion
+ // operations
+ _playList.reserve(10);
+}
+
+SciMusic::~SciMusic() {
+ if (_pMidiDrv) {
+ _pMidiDrv->close();
+ delete _pMidiDrv;
+ }
+}
+
+void SciMusic::init() {
+ // system init
+ _pMixer = g_system->getMixer();
+ _pMixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, ConfMan.getInt(
+ "sfx_volume"));
+ _pMixer->setVolumeForSoundType(Audio::Mixer::kSpeechSoundType,
+ ConfMan.getInt("speech_volume"));
+ _pMixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType,
+ ConfMan.getInt("music_volume"));
+ // SCI sound init
+ _dwTempo = 0;
+
+ const MidiDriverDescription *md = MidiDriver::findMusicDriver(ConfMan.get("music_driver"));
+ _midiType = md ? md->id : MD_AUTO;
+
+ switch (_midiType) {
+ case MD_ADLIB:
+ // FIXME: There's no Amiga sound option, so we hook it up to Adlib
+ if (((SciEngine *)g_engine)->getPlatform() == Common::kPlatformAmiga)
+ _pMidiDrv = MidiPlayer_Amiga_create();
+ else
+ _pMidiDrv = MidiPlayer_Adlib_create();
+ break;
+ case MD_PCJR:
+ _pMidiDrv = new MidiPlayer_PCJr();
+ break;
+ case MD_PCSPK:
+ _pMidiDrv = new MidiPlayer_PCSpeaker();
+ break;
+ case MD_MT32:
+ // TODO
+ default:
+ warning("Unhandled MIDI type, switching to Adlib");
+ _midiType = MD_ADLIB;
+ _pMidiDrv = MidiPlayer_Adlib_create();
+ break;
+ }
+
+ if (_pMidiDrv) {
+ _pMidiDrv->open();
+ _pMidiDrv->setTimerCallback(this, &miditimerCallback);
+ _dwTempo = _pMidiDrv->getBaseTempo();
+ } else
+ warning("Can't initialise music driver");
+ _bMultiMidi = ConfMan.getBool("multi_midi");
+}
+
+void SciMusic::clearPlayList() {
+ _pMixer->stopAll();
+
+ _mutex.lock();
+ while (!_playList.empty()) {
+ soundStop(_playList[0]);
+ soundKill(_playList[0]);
+ }
+ _mutex.unlock();
+}
+
+void SciMusic::miditimerCallback(void *p) {
+ SciMusic *aud = (SciMusic *)p;
+
+ Common::StackLock lock(aud->_mutex);
+ aud->onTimer();
+}
+
+void SciMusic::soundSetSoundOn(bool soundOnFlag) {
+ Common::StackLock lock(_mutex);
+
+ _soundOn = soundOnFlag;
+ _pMidiDrv->playSwitch(soundOnFlag);
+}
+
+uint16 SciMusic::soundGetVoices() {
+ Common::StackLock lock(_mutex);
+
+ return _pMidiDrv->getPolyphony();
+}
+
+MusicEntry *SciMusic::getSlot(reg_t obj) {
+ Common::StackLock lock(_mutex);
+
+ const MusicList::iterator end = _playList.end();
+ for (MusicList::iterator i = _playList.begin(); i != end; ++i) {
+ if ((*i)->soundObj == obj)
+ return *i;
+ }
+
+ return NULL;
+}
+
+void SciMusic::setReverb(byte reverb) {
+ _reverb = reverb;
+
+ // TODO: actually set reverb for MT-32
+
+ // A good test case for this are the first two rooms in Longbow:
+ // reverb is set for the first room (the cave) and is subsequently
+ // cleared when Robin exits the cave
+}
+
+void SciMusic::resetDriver() {
+ Common::StackLock lock(_mutex);
+
+ _pMidiDrv->close();
+ _pMidiDrv->open();
+ _pMidiDrv->setTimerCallback(this, &miditimerCallback);
+}
+
+static int f_compare(const void *arg1, const void *arg2) {
+ return ((const MusicEntry *)arg2)->prio - ((const MusicEntry *)arg1)->prio;
+}
+
+void SciMusic::sortPlayList() {
+ MusicEntry ** pData = _playList.begin();
+ qsort(pData, _playList.size(), sizeof(MusicEntry *), &f_compare);
+}
+
+#if 0
+void SciMusic::patchSysEx(byte * addr, byte *pdata, int len) {
+ byte *buff = new byte[7 + len + 1];
+ uint16 chk = 0;
+ int i;
+
+ buff[0] = 0x41;
+ buff[1] = 0x10;
+ buff[2] = 0x16;
+ buff[3] = 0x12;
+ buff[4] = addr[0];
+ buff[5] = addr[1];
+ buff[6] = addr[2];
+ for (i = 0; i < len; i++) {
+ buff[7 + i] = pdata[i];
+ chk += pdata[i];
+ }
+ chk += addr[0] + addr[1] + addr[2];
+ buff[7 + i] = 128 - chk % 128;
+ _pMidiDrv->sysEx(buff, len + 8);
+ delete[] buff;
+}
+
+void SciMusic::patchUpdateAddr(byte *addr, int len) {
+ addr[2] += len;
+ if (addr[2] >= 0x7F) {
+ addr[1]++;
+ addr[2] -= 0x80;
+ }
+}
+#endif
+
+// FIXME: This should be done at the driver level
+#if 0
+void SciMusic::loadPatch() {
+ if (_midiType == MD_MT32)
+ loadPatchMT32();
+}
+#endif
+
+#if 0
+// currently loads patch 1.pat for Roland/MT-32 device
+void SciMusic::loadPatchMT32() {
+ //byte sysText[] = { 0x20, 0, 0 };
+ byte sysMem[] = { 0x5, 0, 0 }; // patch memory
+ byte sysRhytm[] = { 0x3, 0x1, 0x10 }; // rhytm
+ byte sysMsg3[15] = { 0x41, 0x10, 0x16, 0x12, 0x52, 0, 0xA, 0x16, 0x16,
+ 0x16, 0x16, 0x16, 0x16, 0x20, 0x80 };
+ byte sysTimbre[] = { 0x8, 0, 0 }; // timbre memory
+ byte sysSystem[] = { 0x10, 0, 4 }; // partial reserve & midi channel
+ byte arr[3][11];
+
+ Resource *res = ((SciEngine *)g_engine)->getResourceManager()->findResource(ResourceId(kResourceTypePatch, 1), 0);
+
+ if (res) {
+ byte *pData = res->data, *p;
+ // welcome message
+ //patchSysEx(sysText, pData + 20, 20);
+ // reading reverb mode, time and level
+ p = pData + 74;
+ for (int i = 0; i < 11; i++) {
+ arr[0][i] = *p++;
+ arr[1][i] = *p++;
+ arr[2][i] = *p++;
+ }
+ // sub_657 - patch memory
+ for (int i = 0; i < 48; i++) {
+ patchSysEx(sysMem, p, 8);
+ patchUpdateAddr(sysMem, 8);
+ p += 8;
+ }
+ // sub_696 - timbre
+ byte dl = *p++, cl = 0;
+ while (dl--) {
+ patchSysEx(sysTimbre, p, 14); // common area
+ patchUpdateAddr(sysTimbre, 14);
+ patchSysEx(sysTimbre, p + 14, 58);// partial 1
+ patchUpdateAddr(sysTimbre, 58);
+ patchSysEx(sysTimbre, p + 72, 58);// partial 2
+ patchUpdateAddr(sysTimbre, 58);
+ patchSysEx(sysTimbre, p + 130, 58);// partial 3
+ patchUpdateAddr(sysTimbre, 58);
+ patchSysEx(sysTimbre, p + 188, 58);// partial 4
+ patchUpdateAddr(sysTimbre, 58);
+ p += 246;
+ cl += 2;
+ sysTimbre[1] = cl;
+ sysTimbre[2] = 0;
+ }
+ // patch memory or rhytm
+ uint16 flag = READ_BE_UINT16(p);
+ p += 2;
+ if (flag == 0xABCD) {
+ // sub_657
+ for (int i = 0; i < 48; i++) {
+ patchSysEx(sysMem, p, 8);
+ patchUpdateAddr(sysMem, 8);
+ p += 8;
+ }
+ } else if (flag == 0xDCBA) {
+ // sub_756
+ for (int i = 0; i < 64; i++) {
+ patchSysEx(sysRhytm, p, 4);
+ patchUpdateAddr(sysRhytm, 4);
+ p += 4;
+ }
+ patchSysEx(sysSystem, p, 18);
+ }
+ // after-init text message
+ //patchSysEx(sysText, pData, 20);
+ // some final sysex
+ _pMidiDrv->sysEx(sysMsg3, 15);
+ // releasing patch resource
+ //g_sci->ResMgr.ResUnload(SCI_RES_PATCH, 1);
+ debug("MT-32 patch loaded");
+ }
+}
+#endif
+
+
+void SciMusic::soundInitSnd(MusicEntry *pSnd) {
+ SoundResource::Track *track = NULL;
+ int channelFilterMask = 0;
+
+ switch (_midiType) {
+ case MD_PCSPK:
+ track = pSnd->soundRes->getTrackByType(SoundResource::TRACKTYPE_SPEAKER);
+ break;
+ case MD_PCJR:
+ track = pSnd->soundRes->getTrackByType(SoundResource::TRACKTYPE_TANDY);
+ break;
+ case MD_ADLIB:
+ track = pSnd->soundRes->getTrackByType(SoundResource::TRACKTYPE_ADLIB);
+ break;
+ case MD_MT32:
+ track = pSnd->soundRes->getTrackByType(SoundResource::TRACKTYPE_MT32);
+ break;
+ default:
+ // Should never occur
+ error("soundInitSnd: Unknown MIDI type");
+ break;
+ }
+
+ if (track) {
+ // If MIDI device is selected but there is no digital track in sound resource
+ // try to use adlib's digital sample if possible
+ if (_bMultiMidi && pSnd->soundRes->getTrackByType(SoundResource::TRACKTYPE_ADLIB)->digitalChannelNr != -1)
+ track = pSnd->soundRes->getTrackByType(SoundResource::TRACKTYPE_ADLIB);
+ // Play digital sample
+ if (track->digitalChannelNr != -1) {
+ byte *channelData = track->channels[track->digitalChannelNr].data;
+ delete pSnd->pStreamAud;
+ pSnd->pStreamAud = Audio::makeLinearInputStream(channelData, track->digitalSampleSize, track->digitalSampleRate,
+ Audio::Mixer::FLAG_UNSIGNED, 0, 0);
+ pSnd->soundType = Audio::Mixer::kSFXSoundType;
+ pSnd->hCurrentAud = Audio::SoundHandle();
+ } else {
+ // play MIDI track
+ _mutex.lock();
+ pSnd->soundType = Audio::Mixer::kMusicSoundType;
+ if (pSnd->pMidiParser == NULL) {
+ pSnd->pMidiParser = new MidiParser_SCI(_soundVersion);
+ pSnd->pMidiParser->setMidiDriver(_pMidiDrv);
+ pSnd->pMidiParser->setTimerRate(_dwTempo);
+ pSnd->pMidiParser->property(MidiParser::mpCenterPitchWheelOnUnload, 1);
+ }
+
+ pSnd->pauseCounter = 0;
+
+ // Find out what channels to filter for SCI0
+ channelFilterMask = pSnd->soundRes->getChannelFilterMask(_pMidiDrv->getPlayMask(_soundVersion));
+ pSnd->pMidiParser->loadMusic(track, pSnd, channelFilterMask, _soundVersion);
+
+ // Fast forward to the last position and perform associated events when loading
+ pSnd->pMidiParser->jumpToTick(pSnd->ticker, true);
+ _mutex.unlock();
+ }
+ }
+}
+
+void SciMusic::onTimer() {
+ const MusicList::iterator end = _playList.end();
+ for (MusicList::iterator i = _playList.begin(); i != end; ++i)
+ (*i)->onTimer();
+}
+
+void SciMusic::soundPlay(MusicEntry *pSnd) {
+ _mutex.lock();
+
+ uint sz = _playList.size(), i;
+ // searching if sound is already in _playList
+ for (i = 0; i < sz && _playList[i] != pSnd; i++)
+ ;
+ if (i == sz) {// not found
+ _playList.push_back(pSnd);
+ sortPlayList();
+ }
+
+ _mutex.unlock(); // unlock to perform mixer-related calls
+
+ if (pSnd->pStreamAud && !_pMixer->isSoundHandleActive(pSnd->hCurrentAud)) {
+ // Are we supposed to loop the stream?
+ if (pSnd->loop > 1)
+ pSnd->pStreamAud->setNumLoops(pSnd->loop);
+ else
+ pSnd->pStreamAud->setNumLoops(1);
+ _pMixer->playInputStream(pSnd->soundType, &pSnd->hCurrentAud,
+ pSnd->pStreamAud, -1, pSnd->volume, 0, false);
+ } else {
+ _mutex.lock();
+ if (pSnd->pMidiParser) {
+ pSnd->pMidiParser->setVolume(pSnd->volume);
+ if (pSnd->status == kSoundStopped)
+ pSnd->pMidiParser->jumpToTick(0);
+ }
+ _mutex.unlock();
+ }
+
+ pSnd->status = kSoundPlaying;
+}
+
+void SciMusic::soundStop(MusicEntry *pSnd) {
+ pSnd->status = kSoundStopped;
+ if (pSnd->pStreamAud)
+ _pMixer->stopHandle(pSnd->hCurrentAud);
+
+ _mutex.lock();
+ if (pSnd->pMidiParser)
+ pSnd->pMidiParser->stop();
+ _mutex.unlock();
+}
+
+void SciMusic::soundSetVolume(MusicEntry *pSnd, byte volume) {
+ assert(volume <= MUSIC_VOLUME_MAX);
+ if (pSnd->pStreamAud) {
+ _pMixer->setChannelVolume(pSnd->hCurrentAud, volume * 2); // Mixer is 0-255, SCI is 0-127
+ } else if (pSnd->pMidiParser) {
+ _mutex.lock();
+ pSnd->pMidiParser->setVolume(volume);
+ _mutex.unlock();
+ }
+}
+
+void SciMusic::soundSetPriority(MusicEntry *pSnd, byte prio) {
+ Common::StackLock lock(_mutex);
+
+ pSnd->prio = prio;
+ sortPlayList();
+}
+
+void SciMusic::soundKill(MusicEntry *pSnd) {
+ pSnd->status = kSoundStopped;
+
+ _mutex.lock();
+ if (pSnd->pMidiParser) {
+ pSnd->pMidiParser->unloadMusic();
+ delete pSnd->pMidiParser;
+ pSnd->pMidiParser = NULL;
+ }
+ _mutex.unlock();
+
+ if (pSnd->pStreamAud) {
+ _pMixer->stopHandle(pSnd->hCurrentAud);
+ pSnd->pStreamAud = NULL;
+ }
+
+ _mutex.lock();
+ uint sz = _playList.size(), i;
+ // Remove sound from playlist
+ for (i = 0; i < sz; i++) {
+ if (_playList[i] == pSnd) {
+ delete _playList[i]->soundRes;
+ delete _playList[i];
+ _playList.remove_at(i);
+ break;
+ }
+ }
+ _mutex.unlock();
+}
+
+void SciMusic::soundPause(MusicEntry *pSnd) {
+ pSnd->pauseCounter++;
+ if (pSnd->status != kSoundPlaying)
+ return;
+ pSnd->status = kSoundPaused;
+ if (pSnd->pStreamAud) {
+ _pMixer->pauseHandle(pSnd->hCurrentAud, true);
+ } else {
+ _mutex.lock();
+ if (pSnd->pMidiParser)
+ pSnd->pMidiParser->pause();
+ _mutex.unlock();
+ }
+}
+
+void SciMusic::soundResume(MusicEntry *pSnd) {
+ if (pSnd->pauseCounter > 0)
+ pSnd->pauseCounter--;
+ if (pSnd->pauseCounter != 0)
+ return;
+ if (pSnd->status != kSoundPaused)
+ return;
+ soundPlay(pSnd);
+}
+
+uint16 SciMusic::soundGetMasterVolume() {
+ return (_pMixer->getVolumeForSoundType(Audio::Mixer::kMusicSoundType) + 8) * 0xF / Audio::Mixer::kMaxMixerVolume;
+}
+
+void SciMusic::soundSetMasterVolume(uint16 vol) {
+ vol = vol & 0xF; // 0..15
+ vol = vol * Audio::Mixer::kMaxMixerVolume / 0xF;
+ // "master volume" is music and SFX only, speech (audio resources) are supposed to be unaffected
+ _pMixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, vol);
+ _pMixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, vol);
+}
+
+void SciMusic::printPlayList(Console *con) {
+ Common::StackLock lock(_mutex);
+
+ const char *musicStatus[] = { "Stopped", "Initialized", "Paused", "Playing" };
+
+ const MusicList::iterator end = _playList.end();
+ for (MusicList::iterator i = _playList.begin(); i != end; ++i) {
+ con->DebugPrintf("%d: %04x:%04x, priority: %d, status: %s\n", i,
+ PRINT_REG((*i)->soundObj), (*i)->prio,
+ musicStatus[(*i)->status]);
+ }
+}
+
+MusicEntry::MusicEntry() {
+ soundObj = NULL_REG;
+
+ soundRes = 0;
+ resnum = 0;
+
+ dataInc = 0;
+ ticker = 0;
+ signal = 0;
+ prio = 0;
+ loop = 0;
+ volume = MUSIC_VOLUME_DEFAULT;
+ hold = 0;
+
+ pauseCounter = 0;
+ sampleLoopCounter = 0;
+
+ fadeTo = 0;
+ fadeStep = 0;
+ fadeTicker = 0;
+ fadeTickerStep = 0;
+ fadeSetVolume = false;
+ fadeCompleted = false;
+
+ status = kSoundStopped;
+
+ soundType = Audio::Mixer::kMusicSoundType;
+
+ pStreamAud = 0;
+ pMidiParser = 0;
+}
+
+MusicEntry::~MusicEntry() {
+}
+
+void MusicEntry::onTimer() {
+ if (status != kSoundPlaying)
+ return;
+
+ // Fade MIDI and digital sound effects
+ if (fadeStep)
+ doFade();
+
+ // Only process MIDI streams in this thread, not digital sound effects
+ if (pMidiParser) {
+ pMidiParser->onTimer();
+ ticker = (uint16)pMidiParser->getTick();
+ }
+}
+
+void MusicEntry::doFade() {
+ if (fadeTicker)
+ fadeTicker--;
+ else {
+ int16 fadeVolume = volume;
+ fadeTicker = fadeTickerStep;
+ fadeVolume += fadeStep;
+ if (((fadeStep > 0) && (fadeVolume >= fadeTo)) || ((fadeStep < 0) && (fadeVolume <= fadeTo))) {
+ fadeVolume = fadeTo;
+ fadeStep = 0;
+ fadeCompleted = true;
+ }
+ volume = fadeVolume;
+
+ // Only process MIDI streams in this thread, not digital sound effects
+ if (pMidiParser)
+ pMidiParser->setVolume(volume);
+ fadeSetVolume = true; // set flag so that SoundCommandParser::cmdUpdateCues will set the volume of the stream
+ }
+}
+
+} // End of namespace Sci
diff --git a/engines/sci/sound/music.h b/engines/sci/sound/music.h
new file mode 100644
index 0000000000..eaed0af64d
--- /dev/null
+++ b/engines/sci/sound/music.h
@@ -0,0 +1,219 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef SCI_MUSIC_H
+#define SCI_MUSIC_H
+
+#include "common/savefile.h"
+#include "common/serializer.h"
+#include "common/mutex.h"
+
+#include "sound/mixer.h"
+#include "sound/audiostream.h"
+#include "sound/mididrv.h"
+#include "sound/midiparser.h"
+
+#include "sci/sci.h"
+#include "sci/resource.h"
+#include "sci/sound/softseq/mididriver.h"
+
+namespace Sci {
+
+enum TrackType {
+ kTrackAdlib = 0,
+ kTrackGameBlaster = 9,
+ kTrackMT32 = 12,
+ kTrackSpeaker = 18,
+ kTrackTandy = 19
+};
+
+enum SoundStatus {
+ kSoundStopped = 0,
+ kSoundInitialized = 1,
+ kSoundPaused = 2,
+ kSoundPlaying = 3
+};
+
+#define MUSIC_VOLUME_DEFAULT 127
+#define MUSIC_VOLUME_MAX 127
+
+class MidiParser_SCI;
+class SegManager;
+
+class MusicEntry
+#ifndef USE_OLD_MUSIC_FUNCTIONS
+ : public Common::Serializable
+#endif
+{
+public:
+ // Do not get these directly for the sound objects!
+ // It's a bad idea, as the sound code (i.e. the SciMusic
+ // class) should be as separate as possible from the rest
+ // of the engine
+
+ reg_t soundObj;
+
+ SoundResource *soundRes;
+ uint16 resnum;
+
+ uint16 dataInc;
+ uint16 ticker;
+ uint16 signal;
+ byte prio;
+ uint16 loop;
+ byte volume;
+ byte hold;
+
+ int16 pauseCounter;
+ uint sampleLoopCounter;
+
+ byte fadeTo;
+ short fadeStep;
+ uint32 fadeTicker;
+ uint32 fadeTickerStep;
+ bool fadeSetVolume;
+ bool fadeCompleted;
+
+ SoundStatus status;
+
+ Audio::Mixer::SoundType soundType;
+
+#ifndef USE_OLD_MUSIC_FUNCTIONS
+//protected:
+#endif
+ MidiParser_SCI *pMidiParser;
+ Audio::AudioStream* pStreamAud;
+ Audio::SoundHandle hCurrentAud;
+
+public:
+ MusicEntry();
+ ~MusicEntry();
+
+ void doFade();
+ void onTimer();
+
+#ifndef USE_OLD_MUSIC_FUNCTIONS
+ virtual void saveLoadWithSerializer(Common::Serializer &ser);
+#endif
+};
+
+typedef Common::Array<MusicEntry *> MusicList;
+
+class SciMusic
+#ifndef USE_OLD_MUSIC_FUNCTIONS
+ : public Common::Serializable
+#endif
+{
+
+public:
+ SciMusic(SciVersion soundVersion);
+ ~SciMusic();
+
+ void init();
+#if 0
+ void loadPatch();
+#endif
+ void onTimer();
+ void clearPlayList();
+
+ // sound and midi functions
+ void soundInitSnd(MusicEntry *pSnd);
+ void soundPlay(MusicEntry *pSnd);
+ void soundStop(MusicEntry *pSnd);
+ void soundKill(MusicEntry *pSnd);
+ void soundPause(MusicEntry *pSnd);
+ void soundResume(MusicEntry *pSnd);
+ void soundSetVolume(MusicEntry *pSnd, byte volume);
+ void soundSetPriority(MusicEntry *pSnd, byte prio);
+ uint16 soundGetMasterVolume();
+ void soundSetMasterVolume(uint16 vol);
+ uint16 soundGetSoundOn() const { return _soundOn; }
+ void soundSetSoundOn(bool soundOnFlag);
+ uint16 soundGetVoices();
+ uint32 soundGetTempo() const { return _dwTempo; }
+
+ MusicEntry *getSlot(reg_t obj);
+
+ void pushBackSlot(MusicEntry *slotEntry) {
+ Common::StackLock lock(_mutex);
+ _playList.push_back(slotEntry);
+ }
+
+ void printPlayList(Console *con);
+
+ // The following two methods are NOT thread safe - make sure that
+ // the mutex is always locked before calling them
+ MusicList::iterator getPlayListStart() { return _playList.begin(); }
+ MusicList::iterator getPlayListEnd() { return _playList.end(); }
+
+ void sendMidiCommand (uint32 cmd) {
+ Common::StackLock lock(_mutex);
+ _pMidiDrv->send(cmd);
+ }
+
+ void setReverb(byte reverb);
+
+ void resetDriver();
+
+#ifndef USE_OLD_MUSIC_FUNCTIONS
+ virtual void saveLoadWithSerializer(Common::Serializer &ser);
+#endif
+
+ // Mutex for music code. Used to guard access to the song playlist, to the
+ // MIDI parser and to the MIDI driver/player. Note that guarded code must NOT
+ // include references to the mixer, otherwise there will probably be situations
+ // where a deadlock can occur
+ Common::Mutex _mutex;
+
+protected:
+ byte findAudEntry(uint16 nAud, byte&oVolume, uint32& oOffset, uint32&oSize);
+ void sortPlayList();
+#if 0
+ void loadPatchMT32();
+ void patchSysEx(byte * addr, byte *pdata, int len);
+ void patchUpdateAddr(byte *addr, int len);
+#endif
+
+ SciVersion _soundVersion;
+
+ Audio::Mixer *_pMixer;
+ MidiPlayer *_pMidiDrv;
+ MidiDriverType _midiType;
+
+ uint32 _dwTempo;
+ // Mixed Adlib/MIDI mode: when enabled from the ScummVM sound options screen,
+ // and a sound has a digital track, the sound from the Adlib track is played
+ bool _bMultiMidi;
+private:
+ static void miditimerCallback(void *p);
+
+ MusicList _playList;
+ bool _soundOn;
+ byte _reverb;
+};
+
+} // End of namespace Sci
+
+#endif
diff --git a/engines/sci/sound/seq/gm.cpp b/engines/sci/sound/seq/gm.cpp
new file mode 100644
index 0000000000..25f833915c
--- /dev/null
+++ b/engines/sci/sound/seq/gm.cpp
@@ -0,0 +1,167 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "common/util.h"
+
+#include "sci/sound/seq/midiwriter.h"
+#include "sci/sound/seq/instrument-map.h"
+
+namespace Sci {
+
+#if 0
+static midi_writer_t *writer = NULL;
+
+static Common::Error midi_gm_open(int patch_len, byte *data, int patch2_len, byte *data2, void *device) {
+ sfx_instrument_map_t *instrument_map = sfx_instrument_map_load_sci(data, patch_len);
+
+ if (!instrument_map) {
+ warning("[GM] No GM instrument map found, trying MT-32 instrument map..");
+ instrument_map = sfx_instrument_map_mt32_to_gm(data2, patch2_len);
+ }
+
+ writer = sfx_mapped_writer((midi_writer_t *) device, instrument_map);
+
+ if (!writer)
+ return Common::kUnknownError;
+
+ if (writer->reset_timer)
+ writer->reset_timer(writer);
+
+ return Common::kNoError;
+}
+
+static Common::Error midi_gm_close() {
+ return Common::kNoError;
+}
+
+static Common::Error midi_gm_event(byte command, int argc, byte *argv) {
+ byte data[4];
+
+ assert(argc < 4);
+ data[0] = command;
+ memcpy(data + 1, argv, argc);
+
+ writer->write(writer, data, argc + 1);
+
+ return Common::kNoError;
+}
+
+static Common::Error midi_gm_delay(int ticks) {
+ writer->delay(writer, ticks);
+
+ return Common::kNoError;
+}
+
+static Common::Error midi_gm_reset_timer(uint32 ts) {
+ writer->reset_timer(writer);
+
+ return Common::kNoError;
+}
+
+#define MIDI_MASTER_VOLUME_LEN 8
+
+static Common::Error midi_gm_volume(uint8 volume) {
+ byte data[MIDI_MASTER_VOLUME_LEN] = {
+ 0xf0,
+ 0x7f,
+ 0x7f,
+ 0x04,
+ 0x01,
+ volume,
+ volume,
+ 0xf7
+ };
+
+ writer->write(writer, data, MIDI_MASTER_VOLUME_LEN);
+ if (writer->flush)
+ writer->flush(writer);
+
+ return Common::kNoError;
+}
+
+static Common::Error midi_gm_allstop() {
+ byte data[3] = { 0xb0,
+ 0x78, /* all sound off */
+ 0
+ };
+ int i;
+
+ /* All sound off on all channels */
+ for (i = 0; i < 16; i++) {
+ data[0] = 0xb0 | i;
+ writer->write(writer, data, 3);
+ }
+ if (writer->flush)
+ writer->flush(writer);
+
+ return Common::kNoError;
+}
+
+static Common::Error midi_gm_reverb(int reverb) {
+ byte data[3] = { 0xb0,
+ 91, /* set reverb */
+ reverb
+ };
+ int i;
+
+ /* Set reverb on all channels */
+ for (i = 0; i < 16; i++)
+ if (i != 9) {
+ data[0] = 0xb0 | i;
+ writer->write(writer, data, 3);
+ }
+ if (writer->flush)
+ writer->flush(writer);
+
+ return Common::kNoError;
+}
+
+static Common::Error midi_gm_set_option(char *x, char *y) {
+ return Common::kUnknownError;
+}
+
+sfx_sequencer_t sfx_sequencer_gm = {
+ "General MIDI",
+ "0.1",
+ SFX_DEVICE_MIDI,
+ &midi_gm_set_option,
+ &midi_gm_open,
+ &midi_gm_close,
+ &midi_gm_event,
+ &midi_gm_delay,
+ &midi_gm_reset_timer,
+ &midi_gm_allstop,
+ &midi_gm_volume,
+ &midi_gm_reverb,
+ 004, /* patch.004 */
+ 001, /* patch.001 */
+ 0x01, /* playflag */
+ 1, /* do play rhythm */
+ 64, /* max polyphony */
+ 0 /* no write-ahead needed inherently */
+};
+#endif
+
+} // End of namespace Sci
diff --git a/engines/sci/sound/seq/instrument-map.cpp b/engines/sci/sound/seq/instrument-map.cpp
new file mode 100644
index 0000000000..2f1fdb2532
--- /dev/null
+++ b/engines/sci/sound/seq/instrument-map.cpp
@@ -0,0 +1,505 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "common/scummsys.h"
+#include "sci/sound/softseq/mididriver.h"
+#include "sci/sound/seq/instrument-map.h"
+
+namespace Sci {
+
+sfx_instrument_map_t *sfx_instrument_map_new(int velocity_maps_nr) {
+ sfx_instrument_map_t *map = (sfx_instrument_map_t *)malloc(sizeof(sfx_instrument_map_t));
+ int i;
+
+ map->initialisation_block_size = 0;
+ map->initialisation_block = NULL;
+
+ map->velocity_maps_nr = velocity_maps_nr;
+ map->percussion_velocity_map_index = SFX_NO_VELOCITY_MAP;
+
+ if (velocity_maps_nr == 0)
+ map->velocity_map = NULL; /* Yes, this complicates control flow needlessly, but it avoids some of the pointless
+ ** warnings that certain memory tools seem to find appropriate. */
+ else {
+ map->velocity_map = (byte **)malloc(sizeof(byte *) * velocity_maps_nr);
+ for (i = 0; i < velocity_maps_nr; ++i)
+ map->velocity_map[i] = (byte *)malloc(SFX_VELOCITIES_NR);
+ }
+ for (i = 0; i < SFX_INSTRUMENTS_NR; ++i)
+ map->velocity_map_index[i] = SFX_NO_VELOCITY_MAP;
+
+ map->percussion_volume_adjust = 0;
+ for (i = 0; i < SFX_RHYTHM_NR; ++i)
+ map->percussion_map[i] = i;
+
+
+ for (i = 0; i < SFX_INSTRUMENTS_NR; ++i) {
+ map->patch_map[i].patch = i;
+ map->patch_key_shift[i] = 0;
+ map->patch_volume_adjust[i] = 0;
+ }
+
+ return map;
+}
+
+void sfx_instrument_map_free(sfx_instrument_map_t *map) {
+ if (!map)
+ return;
+
+ if (map->velocity_map) {
+ int i;
+ for (i = 0; i < map->velocity_maps_nr; i++)
+ free(map->velocity_map[i]);
+ free(map->velocity_map);
+ map->velocity_map = NULL;
+ }
+
+ if (map->initialisation_block) {
+ free(map->initialisation_block);
+ map->initialisation_block = NULL;
+ }
+
+ free(map);
+}
+
+#define PATCH_MAP_OFFSET 0x0000
+#define PATCH_KEY_SHIFT_OFFSET 0x0080
+#define PATCH_VOLUME_ADJUST_OFFSET 0x0100
+#define PATCH_PERCUSSION_MAP_OFFSET 0x0180
+#define PATCH_PERCUSSION_VOLUME_ADJUST 0x0200
+#define PATCH_VELOCITY_MAP_INDEX 0x0201
+#define PATCH_VELOCITY_MAP(i) (0x0281 + (0x80 * i))
+#define PATCH_INIT_DATA_SIZE_LE 0x0481
+#define PATCH_INIT_DATA 0x0483
+
+#define PATCH_INSTRUMENT_MAPS_NR 4
+
+#define PATCH_MIN_SIZE PATCH_INIT_DATA
+
+
+static int patch001_type0_length(byte *data, size_t length) {
+ unsigned int pos = 492 + 246 * data[491];
+
+ /* printf("timbres %d (post = %04x)\n",data[491], pos);*/
+
+ if ((length >= (pos + 386)) && (data[pos] == 0xAB) && (data[pos + 1] == 0xCD))
+ pos += 386;
+
+ /* printf("pos = %04x (%02x %02x)\n", pos, data[pos], data[pos + 1]); */
+
+ if ((length >= (pos + 267)) && (data[pos] == 0xDC) && (data[pos + 1] == 0xBA))
+ pos += 267;
+
+ /* printf("pos = %04x %04x (%d)\n", pos, length, pos-length); */
+
+
+ if (pos == length)
+ return 1;
+ return 0;
+}
+
+static int patch001_type1_length(byte *data, size_t length) {
+ if ((length >= 1155) && (((data[1154] << 8) + data[1153] + 1155) == (int)length))
+ return 1;
+ return 0;
+}
+
+int sfx_instrument_map_detect(byte *data, size_t length) {
+ /* length test */
+ if (length < 1155)
+ return SFX_MAP_MT32;
+ if (length > 16889)
+ return SFX_MAP_MT32_GM;
+ if (patch001_type0_length(data, length) &&
+ !patch001_type1_length(data, length))
+ return SFX_MAP_MT32;
+ if (patch001_type1_length(data, length) &&
+ !patch001_type0_length(data, length))
+ return SFX_MAP_MT32_GM;
+ return SFX_MAP_UNKNOWN;
+}
+
+
+sfx_instrument_map_t *sfx_instrument_map_load_sci(byte *data, size_t size) {
+ sfx_instrument_map_t * map;
+ int i, m;
+
+ if (data == NULL)
+ return NULL;
+
+ if (size < PATCH_MIN_SIZE) {
+ warning("[instrument-map] Instrument map too small: %d of %d", (int) size, PATCH_MIN_SIZE);
+ return NULL;
+ }
+
+ map = sfx_instrument_map_new(PATCH_INSTRUMENT_MAPS_NR);
+
+ /* Set up MIDI intialisation data */
+ map->initialisation_block_size = (int16)READ_LE_UINT16(data + PATCH_INIT_DATA_SIZE_LE);
+ if (map->initialisation_block_size) {
+ if (size < PATCH_MIN_SIZE + map->initialisation_block_size) {
+ warning("[instrument-map] Instrument map too small for initialisation block: %d of %d", (int) size, PATCH_MIN_SIZE);
+ return NULL;
+ }
+
+ if (size > PATCH_MIN_SIZE + map->initialisation_block_size)
+ warning("[instrument-map] Instrument larger than required by initialisation block: %d of %d", (int) size, PATCH_MIN_SIZE);
+
+ if (map->initialisation_block_size != 0) {
+ map->initialisation_block = (byte *)malloc(map->initialisation_block_size);
+ memcpy(map->initialisation_block, data + PATCH_INIT_DATA, map->initialisation_block_size);
+ }
+ }
+
+ /* Set up basic instrument info */
+ for (i = 0; i < SFX_INSTRUMENTS_NR; i++) {
+ map->patch_map[i].patch = (char)data[PATCH_MAP_OFFSET + i];
+ map->patch_key_shift[i] = (char)data[PATCH_KEY_SHIFT_OFFSET + i];
+ map->patch_volume_adjust[i] = (char)data[PATCH_VOLUME_ADJUST_OFFSET + i];
+ map->patch_bend_range[i] = SFX_UNMAPPED;
+ map->velocity_map_index[i] = data[PATCH_VELOCITY_MAP_INDEX + i];
+ }
+
+ /* Set up percussion maps */
+ map->percussion_volume_adjust = data[PATCH_PERCUSSION_VOLUME_ADJUST];
+ for (i = 0; i < SFX_RHYTHM_NR; i++) {
+ map->percussion_map[i] = data[PATCH_PERCUSSION_MAP_OFFSET + i];
+ map->percussion_velocity_scale[i] = SFX_MAX_VELOCITY;
+ }
+
+ /* Set up velocity maps */
+ for (m = 0; m < PATCH_INSTRUMENT_MAPS_NR; m++) {
+ byte *velocity_map = map->velocity_map[m];
+ for (i = 0; i < SFX_VELOCITIES_NR; i++)
+ velocity_map[i] = data[PATCH_VELOCITY_MAP(m) + i];
+ }
+
+ map->percussion_velocity_map_index = 0;
+
+ return map;
+}
+
+
+/* Output with the instrument map */
+#define MIDI_CHANNELS_NR 0x10
+
+struct decorated_midi_writer_t : public midi_writer_t {
+ midi_writer_t *writer;
+ sfx_patch_map_t patches[MIDI_CHANNELS_NR];
+ sfx_instrument_map_t *map;
+};
+
+
+static void init_decorated(struct _midi_writer *self_) {
+ decorated_midi_writer_t *self = (decorated_midi_writer_t *) self_;
+ self->writer->init(self->writer);
+}
+
+static void set_option_decorated(struct _midi_writer *self_, char *name, char *value) {
+ decorated_midi_writer_t *self = (decorated_midi_writer_t *) self_;
+ self->writer->set_option(self->writer, name, value);
+}
+
+static void delay_decorated(struct _midi_writer *self_, int ticks) {
+ decorated_midi_writer_t *self = (decorated_midi_writer_t *) self_;
+ self->writer->delay(self->writer, ticks);
+}
+
+static void flush_decorated(struct _midi_writer *self_) {
+ decorated_midi_writer_t *self = (decorated_midi_writer_t *) self_;
+ if (self->writer->flush)
+ self->writer->flush(self->writer);
+}
+
+static void reset_timer_decorated(struct _midi_writer *self_) {
+ decorated_midi_writer_t *self = (decorated_midi_writer_t *) self_;
+ self->writer->reset_timer(self->writer);
+}
+
+
+static void close_decorated(decorated_midi_writer_t *self) {
+ sfx_instrument_map_free(self->map);
+ self->map = NULL;
+ self->writer->close(self->writer);
+ free((void *)self->name);
+ self->name = NULL;
+ free(self);
+}
+
+#define BOUND_127(x) (((x) < 0)? 0 : (((x) > 0x7f)? 0x7f : (x)))
+
+static int bound_hard_127(int i, const char *descr) {
+ int r = BOUND_127(i);
+ if (r != i)
+ warning("[instrument-map] Hard-clipping %02x to %02x in %s", i, r, descr);
+ return r;
+}
+
+static Common::Error set_bend_range(midi_writer_t *writer, int channel, int range) {
+ byte buf[3] = {0xb0, 0x65, 0x00};
+
+ buf[0] |= channel & 0xf;
+ if (writer->write(writer, buf, 3) != Common::kNoError)
+ return Common::kUnknownError;
+
+ buf[1] = 0x64;
+ if (writer->write(writer, buf, 3) != Common::kNoError)
+ return Common::kUnknownError;
+
+ buf[1] = 0x06;
+ buf[2] = BOUND_127(range);
+ if (writer->write(writer, buf, 3) != Common::kNoError)
+ return Common::kUnknownError;
+
+ buf[1] = 0x26;
+ buf[2] = 0;
+ if (writer->write(writer, buf, 3) != Common::kNoError)
+ return Common::kUnknownError;
+
+ return Common::kNoError;
+}
+
+static Common::Error write_decorated(decorated_midi_writer_t *self, byte *buf, int len) {
+ sfx_instrument_map_t *map = self->map;
+ int op = *buf & 0xf0;
+ int chan = *buf & 0x0f;
+ int patch = self->patches[chan].patch;
+ int rhythm = self->patches[chan].rhythm;
+
+ assert(len >= 1);
+
+ if (op == 0xC0 && chan != MIDI_RHYTHM_CHANNEL) { /* Program change */
+ /*int*/
+ patch = bound_hard_127(buf[1], "program change");
+ int instrument = map->patch_map[patch].patch;
+ int bend_range = map->patch_bend_range[patch];
+
+ self->patches[chan] = map->patch_map[patch];
+
+ if (instrument == SFX_UNMAPPED || instrument == SFX_MAPPED_TO_RHYTHM)
+ return Common::kNoError;
+
+ assert(len >= 2);
+ buf[1] = bound_hard_127(instrument, "patch lookup");
+
+ if (self->writer->write(self->writer, buf, len) != Common::kNoError)
+ return Common::kUnknownError;
+
+ if (bend_range != SFX_UNMAPPED)
+ return set_bend_range(self->writer, chan, bend_range);
+
+ return Common::kNoError;
+ }
+
+ if (chan == MIDI_RHYTHM_CHANNEL || patch == SFX_MAPPED_TO_RHYTHM) {
+ /* Rhythm channel handling */
+ switch (op) {
+ case 0x80:
+ case 0x90: { /* Note off / note on */
+ int velocity, instrument, velocity_map_index, velocity_scale;
+
+ if (patch == SFX_MAPPED_TO_RHYTHM) {
+ buf[0] = (buf[0] & ~0x0f) | MIDI_RHYTHM_CHANNEL;
+ instrument = rhythm;
+ velocity_scale = SFX_MAX_VELOCITY;
+ } else {
+ int instrument_index = bound_hard_127(buf[1], "rhythm instrument index");
+ instrument = map->percussion_map[instrument_index];
+ velocity_scale = map->percussion_velocity_scale[instrument_index];
+ }
+
+ if (instrument == SFX_UNMAPPED)
+ return Common::kNoError;
+
+ assert(len >= 3);
+
+ velocity = bound_hard_127(buf[2], "rhythm velocity");
+ velocity_map_index = map->percussion_velocity_map_index;
+
+ if (velocity_map_index != SFX_NO_VELOCITY_MAP)
+ velocity = BOUND_127(velocity + map->velocity_map[velocity_map_index][velocity]);
+
+ velocity = BOUND_127(velocity * velocity_scale / SFX_MAX_VELOCITY);
+
+ buf[1] = bound_hard_127(instrument, "rhythm instrument");
+ buf[2] = velocity;
+
+ break;
+ }
+
+ case 0xB0: { /* Controller change */
+ assert(len >= 3);
+ if (buf[1] == 0x7) /* Volume change */
+ buf[2] = BOUND_127(buf[2] + map->percussion_volume_adjust);
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ } else {
+ /* Instrument channel handling */
+
+ if (patch == SFX_UNMAPPED)
+ return Common::kNoError;
+
+ switch (op) {
+ case 0x80:
+ case 0x90: { /* Note off / note on */
+ int note = bound_hard_127(buf[1], "note");
+ int velocity = bound_hard_127(buf[2], "velocity");
+ int velocity_map_index = map->velocity_map_index[patch];
+ assert(len >= 3);
+
+ note += map->patch_key_shift[patch];
+ /* Not the most efficient solutions, but the least error-prone */
+ while (note < 0)
+ note += 12;
+ while (note > 0x7f)
+ note -= 12;
+
+ if (velocity_map_index != SFX_NO_VELOCITY_MAP)
+ velocity = BOUND_127(velocity + map->velocity_map[velocity_map_index][velocity]);
+
+ buf[1] = note;
+ buf[2] = velocity;
+ break;
+ }
+
+ case 0xB0: /* Controller change */
+ assert(len >= 3);
+ if (buf[1] == 0x7) /* Volume change */
+ buf[2] = BOUND_127(buf[2] + map->patch_volume_adjust[patch]);
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ return self->writer->write(self->writer, buf, len);
+}
+
+#define MIDI_BYTES_PER_SECOND 3250 /* This seems to be the minimum guarantee by the standard */
+#define MAX_PER_TICK (MIDI_BYTES_PER_SECOND / 60) /* After this, we ought to issue one tick of pause */
+
+static void init(midi_writer_t *writer, byte *data, size_t len) {
+ size_t offset = 0;
+ byte status = 0;
+
+ /* Send init data as separate MIDI commands */
+ while (offset < len) {
+ int args;
+ byte op = data[offset];
+ byte msg[3];
+ int i;
+
+ if (op == 0xf0) {
+ int msg_len;
+ byte *find = (byte *) memchr(data + offset, 0xf7, len - offset);
+
+ if (!find) {
+ warning("[instrument-map] Failed to find end of sysex message");
+ return;
+ }
+
+ msg_len = find - data - offset + 1;
+ writer->write(writer, data + offset, msg_len);
+
+ /* Wait at least 40ms after sysex */
+ writer->delay(writer, 3);
+ offset += msg_len;
+ continue;
+ }
+
+ if (op < 0x80)
+ op = status;
+ else {
+ status = op;
+ offset++;
+ }
+
+ msg[0] = op;
+
+ switch (op & 0xf0) {
+ case 0xc0:
+ case 0xd0:
+ args = 1;
+ break;
+ default:
+ args = 2;
+ }
+
+ if (args + offset > len) {
+ warning("[instrument-map] Insufficient bytes remaining for MIDI command %02x", op);
+ return;
+ }
+
+ for (i = 0; i < args; i++)
+ msg[i + 1] = data[offset + i];
+
+ writer->write(writer, msg, args + 1);
+ offset += args;
+
+ if (writer->flush)
+ writer->flush(writer);
+ }
+}
+
+#define NAME_SUFFIX "+instruments"
+
+midi_writer_t *sfx_mapped_writer(midi_writer_t *writer, sfx_instrument_map_t *map) {
+ int i;
+ decorated_midi_writer_t *retval;
+
+ if (map == NULL)
+ return writer;
+
+ retval = (decorated_midi_writer_t *)malloc(sizeof(decorated_midi_writer_t));
+ retval->writer = writer;
+ retval->name = (char *)malloc(strlen(writer->name) + strlen(NAME_SUFFIX) + 1);
+ strcpy(retval->name, writer->name);
+ strcat(retval->name, NAME_SUFFIX);
+
+ retval->init = (Common::Error (*)(midi_writer_t *)) init_decorated;
+ retval->set_option = (Common::Error (*)(midi_writer_t *, char *, char *)) set_option_decorated;
+ retval->write = (Common::Error (*)(midi_writer_t *, byte *, int)) write_decorated;
+ retval->delay = (void (*)(midi_writer_t *, int)) delay_decorated;
+ retval->flush = (void (*)(midi_writer_t *)) flush_decorated;
+ retval->reset_timer = (void (*)(midi_writer_t *)) reset_timer_decorated;
+ retval->close = (void (*)(midi_writer_t *)) close_decorated;
+
+ retval->map = map;
+
+ init(writer, map->initialisation_block, map->initialisation_block_size);
+
+ for (i = 0; i < MIDI_CHANNELS_NR; i++)
+ retval->patches[i].patch = SFX_UNMAPPED;
+
+ return (midi_writer_t *) retval;
+}
+
+} // End of namespace Sci
diff --git a/engines/sci/sound/seq/instrument-map.h b/engines/sci/sound/seq/instrument-map.h
new file mode 100644
index 0000000000..8099dd39fd
--- /dev/null
+++ b/engines/sci/sound/seq/instrument-map.h
@@ -0,0 +1,125 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+/* Implementation of SCI instrument maps for GM and MT-32. */
+
+#ifndef SCI_SFX_SEQ_INSTRUMENT_MAP_H
+#define SCI_SFX_SEQ_INSTRUMENT_MAP_H
+
+#include "sci/sound/seq/midiwriter.h"
+
+namespace Sci {
+
+#define SFX_INSTRUMENTS_NR 0x80
+#define SFX_RHYTHM_NR 0x80
+#define SFX_VELOCITIES_NR 0x80
+#define SFX_NO_VELOCITY_MAP -1 /* use in velocity_map_index to indicate that no map should be used */
+
+/* Instrument map types */
+#define SFX_MAP_UNKNOWN 0
+#define SFX_MAP_MT32 1 /* Original MT-32 map format */
+#define SFX_MAP_MT32_GM 2 /* More recent map format used for both MT-32 and GM */
+
+/* Patch not mapped */
+#define SFX_UNMAPPED -1
+/* Patch mapped to rhythm key */
+#define SFX_MAPPED_TO_RHYTHM -2
+
+/* Maximum velocity (used for scaling) */
+#define SFX_MAX_VELOCITY 128
+
+struct sfx_patch_map_t {
+ int patch; /* Native instrument, SFX_UNMAPPED or SFX_MAPPED_TO_RHYTHM */
+ int rhythm; /* Rhythm key when patch == SFX_MAPPED_TO_RHYTHM */
+};
+
+struct sfx_instrument_map_t {
+ sfx_patch_map_t patch_map[SFX_INSTRUMENTS_NR]; /* Map patch nr to which native instrument or rhythm key */
+ int patch_key_shift[SFX_INSTRUMENTS_NR]; /* Shift patch key by how much? */
+ int patch_volume_adjust[SFX_INSTRUMENTS_NR]; /* Adjust controller 7 by how much? */
+ int patch_bend_range[SFX_INSTRUMENTS_NR]; /* Bend range in semitones or SFX_UNMAPPED for default */
+
+ int percussion_map[SFX_RHYTHM_NR]; /* Map percussion instrument (RHYTH_CHANNEL) to what native 'key'? */
+ int percussion_volume_adjust; /* unused in SCI patches */
+
+ int velocity_map_index[SFX_INSTRUMENTS_NR]; /* Velocity translation map to use for that instrument */
+ int velocity_maps_nr; /* How many velocity translation maps do we have? */
+ byte **velocity_map; /* velocity_maps_nr entries, each of size SFX_VELOCITIES_NR */
+ int percussion_velocity_map_index; /* Special index for the percussion map */
+ int percussion_velocity_scale[SFX_INSTRUMENTS_NR]; /* Velocity scale (0 - SFX_PERC_MAX_VOL) */
+
+ size_t initialisation_block_size;
+ byte *initialisation_block; /* Initial MIDI commands to set up the device */
+};
+
+sfx_instrument_map_t *sfx_instrument_map_new(int velocity_maps_nr);
+/* Constructs a new default-initialised velocity map
+** Parameters: (int) velocity_maps_nr: Number of velocity maps to allocate
+** Returns : (sfx_instrument_map *) an initialised instrument map
+*/
+
+void sfx_instrument_map_free(sfx_instrument_map_t *map);
+/* Deallocates an instrument map
+** Parameters: (sfx_instrument_map *) map: The map to deallocate, or NULL for a no-op
+*/
+
+sfx_instrument_map_t *sfx_instrument_map_load_sci(byte *data, size_t length);
+/* Allocate and initialise an instrument map from SCI data
+** Parameters: (byte *) Pointer to the data to initialise from
+** (size_t) Number of bytes to expect within
+** Returns : (sfx_instrument_map_t *) An initialised instrument map for these settings, or NULL
+** if `data' is NULL or `data' and `length' do not permit a valid instrument map
+** If `data' is null, the function will return NULL quietly.
+*/
+
+sfx_instrument_map_t *sfx_instrument_map_mt32_to_gm(byte *data, size_t size);
+/* Allocate and initialise an instrument map from MT-32 patch data
+** Parameters: (byte *) Pointer to the MT-32 patch data to initialise from
+** (size_t) Number of bytes to expect within
+** Returns : (sfx_instrument_map_t *) An initialised instrument map for these settings
+** If `data' is null or invalid, the function will return a default MT-32 to GM map.
+*/
+
+int sfx_instrument_map_detect(byte *data, size_t size);
+/* Detects the type of patch data
+** Parameters: (byte *) Pointer to the patch data
+** (size_t) Number of bytes to expect within
+** Returns : (int) SFX_MAP_SCI1 for an SCI1 instrument map, SFX_MAP_SCI0_MT32 for SCI0 MT-32 patch data,
+** or SFX_MAP_UNKNOWN for unknown.
+*/
+
+midi_writer_t *sfx_mapped_writer(midi_writer_t *writer, sfx_instrument_map_t *map);
+/* Wrap a midi_writer_t into an instrument map
+** Parameters: (midi_writer_t *) writer: The writer to wrap
+** (sfx_instrument_map_t *) map: The map to apply to all commands going into the writer, or NULL
+** Returns : (midi_writer_t *) A MIDI writer that preprocesses all data by `map' and otherwise relies on `writer'
+** Effects : If successful and neccessary, this operation will send initialisation messages to the writer, as needed.
+** If `map' is NULL, this returns `writer'. Otherwise it sets up a Decorator that handles translation and automatically
+** deallocates the instrument map when the writer is closed.
+*/
+
+} // End of namespace Sci
+
+#endif // SCI_SFX_SEQ_INSTRUMENT_MAP_H
diff --git a/engines/sci/sound/seq/map-mt32-to-gm.cpp b/engines/sci/sound/seq/map-mt32-to-gm.cpp
new file mode 100644
index 0000000000..6bd2c4f6c0
--- /dev/null
+++ b/engines/sci/sound/seq/map-mt32-to-gm.cpp
@@ -0,0 +1,792 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "common/scummsys.h"
+#include "sci/resource.h"
+#include "sci/engine/state.h"
+#include "sci/sound/seq/instrument-map.h"
+
+namespace Sci {
+
+#define DEBUG_MT32_TO_GM
+
+static const char *GM_Instrument_Names[] = {
+ /*000*/ "Acoustic Grand Piano",
+ /*001*/ "Bright Acoustic Piano",
+ /*002*/ "Electric Grand Piano",
+ /*003*/ "Honky-tonk Piano",
+ /*004*/ "Electric Piano 1",
+ /*005*/ "Electric Piano 2",
+ /*006*/ "Harpsichord",
+ /*007*/ "Clavinet",
+ /*008*/ "Celesta",
+ /*009*/ "Glockenspiel",
+ /*010*/ "Music Box",
+ /*011*/ "Vibraphone",
+ /*012*/ "Marimba",
+ /*013*/ "Xylophone",
+ /*014*/ "Tubular Bells",
+ /*015*/ "Dulcimer",
+ /*016*/ "Drawbar Organ",
+ /*017*/ "Percussive Organ",
+ /*018*/ "Rock Organ",
+ /*019*/ "Church Organ",
+ /*020*/ "Reed Organ",
+ /*021*/ "Accordion",
+ /*022*/ "Harmonica",
+ /*023*/ "Tango Accordion",
+ /*024*/ "Acoustic Guitar (nylon)",
+ /*025*/ "Acoustic Guitar (steel)",
+ /*026*/ "Electric Guitar (jazz)",
+ /*027*/ "Electric Guitar (clean)",
+ /*028*/ "Electric Guitar (muted)",
+ /*029*/ "Overdriven Guitar",
+ /*030*/ "Distortion Guitar",
+ /*031*/ "Guitar Harmonics",
+ /*032*/ "Acoustic Bass",
+ /*033*/ "Electric Bass (finger)",
+ /*034*/ "Electric Bass (pick)",
+ /*035*/ "Fretless Bass",
+ /*036*/ "Slap Bass 1",
+ /*037*/ "Slap Bass 2",
+ /*038*/ "Synth Bass 1",
+ /*039*/ "Synth Bass 2",
+ /*040*/ "Violin",
+ /*041*/ "Viola",
+ /*042*/ "Cello",
+ /*043*/ "Contrabass",
+ /*044*/ "Tremolo Strings",
+ /*045*/ "Pizzicato Strings",
+ /*046*/ "Orchestral Harp",
+ /*047*/ "Timpani",
+ /*048*/ "String Ensemble 1",
+ /*049*/ "String Ensemble 2",
+ /*050*/ "SynthStrings 1",
+ /*051*/ "SynthStrings 2",
+ /*052*/ "Choir Aahs",
+ /*053*/ "Voice Oohs",
+ /*054*/ "Synth Voice",
+ /*055*/ "Orchestra Hit",
+ /*056*/ "Trumpet",
+ /*057*/ "Trombone",
+ /*058*/ "Tuba",
+ /*059*/ "Muted Trumpet",
+ /*060*/ "French Horn",
+ /*061*/ "Brass Section",
+ /*062*/ "SynthBrass 1",
+ /*063*/ "SynthBrass 2",
+ /*064*/ "Soprano Sax",
+ /*065*/ "Alto Sax",
+ /*066*/ "Tenor Sax",
+ /*067*/ "Baritone Sax",
+ /*068*/ "Oboe",
+ /*069*/ "English Horn",
+ /*070*/ "Bassoon",
+ /*071*/ "Clarinet",
+ /*072*/ "Piccolo",
+ /*073*/ "Flute",
+ /*074*/ "Recorder",
+ /*075*/ "Pan Flute",
+ /*076*/ "Blown Bottle",
+ /*077*/ "Shakuhachi",
+ /*078*/ "Whistle",
+ /*079*/ "Ocarina",
+ /*080*/ "Lead 1 (square)",
+ /*081*/ "Lead 2 (sawtooth)",
+ /*082*/ "Lead 3 (calliope)",
+ /*083*/ "Lead 4 (chiff)",
+ /*084*/ "Lead 5 (charang)",
+ /*085*/ "Lead 6 (voice)",
+ /*086*/ "Lead 7 (fifths)",
+ /*087*/ "Lead 8 (bass+lead)",
+ /*088*/ "Pad 1 (new age)",
+ /*089*/ "Pad 2 (warm)",
+ /*090*/ "Pad 3 (polysynth)",
+ /*091*/ "Pad 4 (choir)",
+ /*092*/ "Pad 5 (bowed)",
+ /*093*/ "Pad 6 (metallic)",
+ /*094*/ "Pad 7 (halo)",
+ /*095*/ "Pad 8 (sweep)",
+ /*096*/ "FX 1 (rain)",
+ /*097*/ "FX 2 (soundtrack)",
+ /*098*/ "FX 3 (crystal)",
+ /*099*/ "FX 4 (atmosphere)",
+ /*100*/ "FX 5 (brightness)",
+ /*101*/ "FX 6 (goblins)",
+ /*102*/ "FX 7 (echoes)",
+ /*103*/ "FX 8 (sci-fi)",
+ /*104*/ "Sitar",
+ /*105*/ "Banjo",
+ /*106*/ "Shamisen",
+ /*107*/ "Koto",
+ /*108*/ "Kalimba",
+ /*109*/ "Bag pipe",
+ /*110*/ "Fiddle",
+ /*111*/ "Shannai",
+ /*112*/ "Tinkle Bell",
+ /*113*/ "Agogo",
+ /*114*/ "Steel Drums",
+ /*115*/ "Woodblock",
+ /*116*/ "Taiko Drum",
+ /*117*/ "Melodic Tom",
+ /*118*/ "Synth Drum",
+ /*119*/ "Reverse Cymbal",
+ /*120*/ "Guitar Fret Noise",
+ /*121*/ "Breath Noise",
+ /*122*/ "Seashore",
+ /*123*/ "Bird Tweet",
+ /*124*/ "Telephone Ring",
+ /*125*/ "Helicopter",
+ /*126*/ "Applause",
+ /*127*/ "Gunshot"
+};
+
+/* The GM Percussion map is downwards compatible to the MT32 map, which is used in SCI */
+static const char *GM_Percussion_Names[] = {
+ /*00*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ /*10*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ /*20*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ /*30*/ 0, 0, 0, 0,
+ /* The preceeding percussions are not covered by the GM standard */
+ /*34*/ "Acoustic Bass Drum",
+ /*35*/ "Bass Drum 1",
+ /*36*/ "Side Stick",
+ /*37*/ "Acoustic Snare",
+ /*38*/ "Hand Clap",
+ /*39*/ "Electric Snare",
+ /*40*/ "Low Floor Tom",
+ /*41*/ "Closed Hi-Hat",
+ /*42*/ "High Floor Tom",
+ /*43*/ "Pedal Hi-Hat",
+ /*44*/ "Low Tom",
+ /*45*/ "Open Hi-Hat",
+ /*46*/ "Low-Mid Tom",
+ /*47*/ "Hi-Mid Tom",
+ /*48*/ "Crash Cymbal 1",
+ /*49*/ "High Tom",
+ /*50*/ "Ride Cymbal 1",
+ /*51*/ "Chinese Cymbal",
+ /*52*/ "Ride Bell",
+ /*53*/ "Tambourine",
+ /*54*/ "Splash Cymbal",
+ /*55*/ "Cowbell",
+ /*56*/ "Crash Cymbal 2",
+ /*57*/ "Vibraslap",
+ /*58*/ "Ride Cymbal 2",
+ /*59*/ "Hi Bongo",
+ /*60*/ "Low Bongo",
+ /*61*/ "Mute Hi Conga",
+ /*62*/ "Open Hi Conga",
+ /*63*/ "Low Conga",
+ /*64*/ "High Timbale",
+ /*65*/ "Low Timbale",
+ /*66*/ "High Agogo",
+ /*67*/ "Low Agogo",
+ /*68*/ "Cabasa",
+ /*69*/ "Maracas",
+ /*70*/ "Short Whistle",
+ /*71*/ "Long Whistle",
+ /*72*/ "Short Guiro",
+ /*73*/ "Long Guiro",
+ /*74*/ "Claves",
+ /*75*/ "Hi Wood Block",
+ /*76*/ "Low Wood Block",
+ /*77*/ "Mute Cuica",
+ /*78*/ "Open Cuica",
+ /*79*/ "Mute Triangle",
+ /*80*/ "Open Triangle"
+};
+
+/*******************************************
+ * Fancy instrument mappings begin here... *
+ *******************************************/
+
+
+static struct {
+ const char *name;
+ int8 gm_instr;
+ int8 gm_rhythm_key;
+} MT32_PresetTimbreMaps[] = {
+ /*000*/ {"AcouPiano1", 0, SFX_UNMAPPED},
+ /*001*/ {"AcouPiano2", 1, SFX_UNMAPPED},
+ /*002*/ {"AcouPiano3", 0, SFX_UNMAPPED},
+ /*003*/ {"ElecPiano1", 4, SFX_UNMAPPED},
+ /*004*/ {"ElecPiano2", 5, SFX_UNMAPPED},
+ /*005*/ {"ElecPiano3", 4, SFX_UNMAPPED},
+ /*006*/ {"ElecPiano4", 5, SFX_UNMAPPED},
+ /*007*/ {"Honkytonk ", 3, SFX_UNMAPPED},
+ /*008*/ {"Elec Org 1", 16, SFX_UNMAPPED},
+ /*009*/ {"Elec Org 2", 17, SFX_UNMAPPED},
+ /*010*/ {"Elec Org 3", 18, SFX_UNMAPPED},
+ /*011*/ {"Elec Org 4", 18, SFX_UNMAPPED},
+ /*012*/ {"Pipe Org 1", 19, SFX_UNMAPPED},
+ /*013*/ {"Pipe Org 2", 19, SFX_UNMAPPED},
+ /*014*/ {"Pipe Org 3", 20, SFX_UNMAPPED},
+ /*015*/ {"Accordion ", 21, SFX_UNMAPPED},
+ /*016*/ {"Harpsi 1 ", 6, SFX_UNMAPPED},
+ /*017*/ {"Harpsi 2 ", 6, SFX_UNMAPPED},
+ /*018*/ {"Harpsi 3 ", 6, SFX_UNMAPPED},
+ /*019*/ {"Clavi 1 ", 7, SFX_UNMAPPED},
+ /*020*/ {"Clavi 2 ", 7, SFX_UNMAPPED},
+ /*021*/ {"Clavi 3 ", 7, SFX_UNMAPPED},
+ /*022*/ {"Celesta 1 ", 8, SFX_UNMAPPED},
+ /*023*/ {"Celesta 2 ", 8, SFX_UNMAPPED},
+ /*024*/ {"Syn Brass1", 62, SFX_UNMAPPED},
+ /*025*/ {"Syn Brass2", 63, SFX_UNMAPPED},
+ /*026*/ {"Syn Brass3", 62, SFX_UNMAPPED},
+ /*027*/ {"Syn Brass4", 63, SFX_UNMAPPED},
+ /*028*/ {"Syn Bass 1", 38, SFX_UNMAPPED},
+ /*029*/ {"Syn Bass 2", 39, SFX_UNMAPPED},
+ /*030*/ {"Syn Bass 3", 38, SFX_UNMAPPED},
+ /*031*/ {"Syn Bass 4", 39, SFX_UNMAPPED},
+ /*032*/ {"Fantasy ", 88, SFX_UNMAPPED},
+ /*033*/ {"Harmo Pan ", 89, SFX_UNMAPPED},
+ /*034*/ {"Chorale ", 52, SFX_UNMAPPED},
+ /*035*/ {"Glasses ", 98, SFX_UNMAPPED},
+ /*036*/ {"Soundtrack", 97, SFX_UNMAPPED},
+ /*037*/ {"Atmosphere", 99, SFX_UNMAPPED},
+ /*038*/ {"Warm Bell ", 89, SFX_UNMAPPED},
+ /*039*/ {"Funny Vox ", 85, SFX_UNMAPPED},
+ /*040*/ {"Echo Bell ", 39, SFX_UNMAPPED},
+ /*041*/ {"Ice Rain ", 101, SFX_UNMAPPED},
+ /*042*/ {"Oboe 2001 ", 68, SFX_UNMAPPED},
+ /*043*/ {"Echo Pan ", 87, SFX_UNMAPPED},
+ /*044*/ {"DoctorSolo", 86, SFX_UNMAPPED},
+ /*045*/ {"Schooldaze", 103, SFX_UNMAPPED},
+ /*046*/ {"BellSinger", 88, SFX_UNMAPPED},
+ /*047*/ {"SquareWave", 80, SFX_UNMAPPED},
+ /*048*/ {"Str Sect 1", 48, SFX_UNMAPPED},
+ /*049*/ {"Str Sect 2", 48, SFX_UNMAPPED},
+ /*050*/ {"Str Sect 3", 49, SFX_UNMAPPED},
+ /*051*/ {"Pizzicato ", 45, SFX_UNMAPPED},
+ /*052*/ {"Violin 1 ", 40, SFX_UNMAPPED},
+ /*053*/ {"Violin 2 ", 40, SFX_UNMAPPED},
+ /*054*/ {"Cello 1 ", 42, SFX_UNMAPPED},
+ /*055*/ {"Cello 2 ", 42, SFX_UNMAPPED},
+ /*056*/ {"Contrabass", 43, SFX_UNMAPPED},
+ /*057*/ {"Harp 1 ", 46, SFX_UNMAPPED},
+ /*058*/ {"Harp 2 ", 46, SFX_UNMAPPED},
+ /*059*/ {"Guitar 1 ", 24, SFX_UNMAPPED},
+ /*060*/ {"Guitar 2 ", 25, SFX_UNMAPPED},
+ /*061*/ {"Elec Gtr 1", 26, SFX_UNMAPPED},
+ /*062*/ {"Elec Gtr 2", 27, SFX_UNMAPPED},
+ /*063*/ {"Sitar ", 104, SFX_UNMAPPED},
+ /*064*/ {"Acou Bass1", 32, SFX_UNMAPPED},
+ /*065*/ {"Acou Bass2", 33, SFX_UNMAPPED},
+ /*066*/ {"Elec Bass1", 34, SFX_UNMAPPED},
+ /*067*/ {"Elec Bass2", 39, SFX_UNMAPPED},
+ /*068*/ {"Slap Bass1", 36, SFX_UNMAPPED},
+ /*069*/ {"Slap Bass2", 37, SFX_UNMAPPED},
+ /*070*/ {"Fretless 1", 35, SFX_UNMAPPED},
+ /*071*/ {"Fretless 2", 35, SFX_UNMAPPED},
+ /*072*/ {"Flute 1 ", 73, SFX_UNMAPPED},
+ /*073*/ {"Flute 2 ", 73, SFX_UNMAPPED},
+ /*074*/ {"Piccolo 1 ", 72, SFX_UNMAPPED},
+ /*075*/ {"Piccolo 2 ", 72, SFX_UNMAPPED},
+ /*076*/ {"Recorder ", 74, SFX_UNMAPPED},
+ /*077*/ {"Panpipes ", 75, SFX_UNMAPPED},
+ /*078*/ {"Sax 1 ", 64, SFX_UNMAPPED},
+ /*079*/ {"Sax 2 ", 65, SFX_UNMAPPED},
+ /*080*/ {"Sax 3 ", 66, SFX_UNMAPPED},
+ /*081*/ {"Sax 4 ", 67, SFX_UNMAPPED},
+ /*082*/ {"Clarinet 1", 71, SFX_UNMAPPED},
+ /*083*/ {"Clarinet 2", 71, SFX_UNMAPPED},
+ /*084*/ {"Oboe ", 68, SFX_UNMAPPED},
+ /*085*/ {"Engl Horn ", 69, SFX_UNMAPPED},
+ /*086*/ {"Bassoon ", 70, SFX_UNMAPPED},
+ /*087*/ {"Harmonica ", 22, SFX_UNMAPPED},
+ /*088*/ {"Trumpet 1 ", 56, SFX_UNMAPPED},
+ /*089*/ {"Trumpet 2 ", 56, SFX_UNMAPPED},
+ /*090*/ {"Trombone 1", 57, SFX_UNMAPPED},
+ /*091*/ {"Trombone 2", 57, SFX_UNMAPPED},
+ /*092*/ {"Fr Horn 1 ", 60, SFX_UNMAPPED},
+ /*093*/ {"Fr Horn 2 ", 60, SFX_UNMAPPED},
+ /*094*/ {"Tuba ", 58, SFX_UNMAPPED},
+ /*095*/ {"Brs Sect 1", 61, SFX_UNMAPPED},
+ /*096*/ {"Brs Sect 2", 61, SFX_UNMAPPED},
+ /*097*/ {"Vibe 1 ", 11, SFX_UNMAPPED},
+ /*098*/ {"Vibe 2 ", 11, SFX_UNMAPPED},
+ /*099*/ {"Syn Mallet", 15, SFX_UNMAPPED},
+ /*100*/ {"Wind Bell ", 88, SFX_UNMAPPED},
+ /*101*/ {"Glock ", 9, SFX_UNMAPPED},
+ /*102*/ {"Tube Bell ", 14, SFX_UNMAPPED},
+ /*103*/ {"Xylophone ", 13, SFX_UNMAPPED},
+ /*104*/ {"Marimba ", 12, SFX_UNMAPPED},
+ /*105*/ {"Koto ", 107, SFX_UNMAPPED},
+ /*106*/ {"Sho ", 111, SFX_UNMAPPED},
+ /*107*/ {"Shakuhachi", 77, SFX_UNMAPPED},
+ /*108*/ {"Whistle 1 ", 78, SFX_UNMAPPED},
+ /*109*/ {"Whistle 2 ", 78, SFX_UNMAPPED},
+ /*110*/ {"BottleBlow", 76, SFX_UNMAPPED},
+ /*111*/ {"BreathPipe", 121, SFX_UNMAPPED},
+ /*112*/ {"Timpani ", 47, SFX_UNMAPPED},
+ /*113*/ {"MelodicTom", 117, SFX_UNMAPPED},
+ /*114*/ {"Deep Snare", SFX_MAPPED_TO_RHYTHM, 37},
+ /*115*/ {"Elec Perc1", 115, SFX_UNMAPPED}, /* ? */
+ /*116*/ {"Elec Perc2", 118, SFX_UNMAPPED}, /* ? */
+ /*117*/ {"Taiko ", 116, SFX_UNMAPPED},
+ /*118*/ {"Taiko Rim ", 118, SFX_UNMAPPED},
+ /*119*/ {"Cymbal ", SFX_MAPPED_TO_RHYTHM, 50},
+ /*120*/ {"Castanets ", SFX_MAPPED_TO_RHYTHM, SFX_UNMAPPED},
+ /*121*/ {"Triangle ", 112, SFX_UNMAPPED},
+ /*122*/ {"Orche Hit ", 55, SFX_UNMAPPED},
+ /*123*/ {"Telephone ", 124, SFX_UNMAPPED},
+ /*124*/ {"Bird Tweet", 123, SFX_UNMAPPED},
+ /*125*/ {"OneNoteJam", SFX_UNMAPPED, SFX_UNMAPPED}, /* ? */
+ /*126*/ {"WaterBells", 98, SFX_UNMAPPED},
+ /*127*/ {"JungleTune", SFX_UNMAPPED, SFX_UNMAPPED} /* ? */
+};
+
+static struct {
+ const char *name;
+ int8 gm_instr;
+ int8 gm_rhythmkey;
+} MT32_RhythmTimbreMaps[] = {
+ /*00*/ {"Acou BD ", SFX_MAPPED_TO_RHYTHM, 34},
+ /*01*/ {"Acou SD ", SFX_MAPPED_TO_RHYTHM, 37},
+ /*02*/ {"Acou HiTom", 117, 49},
+ /*03*/ {"AcouMidTom", 117, 46},
+ /*04*/ {"AcouLowTom", 117, 40},
+ /*05*/ {"Elec SD ", SFX_MAPPED_TO_RHYTHM, 39},
+ /*06*/ {"Clsd HiHat", SFX_MAPPED_TO_RHYTHM, 41},
+ /*07*/ {"OpenHiHat1", SFX_MAPPED_TO_RHYTHM, 45},
+ /*08*/ {"Crash Cym ", SFX_MAPPED_TO_RHYTHM, 48},
+ /*09*/ {"Ride Cym ", SFX_MAPPED_TO_RHYTHM, 50},
+ /*10*/ {"Rim Shot ", SFX_MAPPED_TO_RHYTHM, 36},
+ /*11*/ {"Hand Clap ", SFX_MAPPED_TO_RHYTHM, 38},
+ /*12*/ {"Cowbell ", SFX_MAPPED_TO_RHYTHM, 55},
+ /*13*/ {"Mt HiConga", SFX_MAPPED_TO_RHYTHM, 61},
+ /*14*/ {"High Conga", SFX_MAPPED_TO_RHYTHM, 62},
+ /*15*/ {"Low Conga ", SFX_MAPPED_TO_RHYTHM, 63},
+ /*16*/ {"Hi Timbale", SFX_MAPPED_TO_RHYTHM, 64},
+ /*17*/ {"LowTimbale", SFX_MAPPED_TO_RHYTHM, 65},
+ /*18*/ {"High Bongo", SFX_MAPPED_TO_RHYTHM, 59},
+ /*19*/ {"Low Bongo ", SFX_MAPPED_TO_RHYTHM, 60},
+ /*20*/ {"High Agogo", 113, 66},
+ /*21*/ {"Low Agogo ", 113, 67},
+ /*22*/ {"Tambourine", SFX_MAPPED_TO_RHYTHM, 53},
+ /*23*/ {"Claves ", SFX_MAPPED_TO_RHYTHM, 74},
+ /*24*/ {"Maracas ", SFX_MAPPED_TO_RHYTHM, 69},
+ /*25*/ {"SmbaWhis L", 78, 71},
+ /*26*/ {"SmbaWhis S", 78, 70},
+ /*27*/ {"Cabasa ", SFX_MAPPED_TO_RHYTHM, 68},
+ /*28*/ {"Quijada ", SFX_MAPPED_TO_RHYTHM, 72},
+ /*29*/ {"OpenHiHat2", SFX_MAPPED_TO_RHYTHM, 43}
+};
+
+static int8 MT32_PresetRhythmKeymap[] = {
+ SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED,
+ SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED,
+ SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED,
+ SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, 34, 34, 36, 37, 38, 39,
+ 40, 41, 42, 43, 44, 45, 46, 47, 48, 49,
+ 50, SFX_UNMAPPED, SFX_UNMAPPED, 53, SFX_UNMAPPED, 55, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, 59,
+ 60, 61, 62, 63, 64, 65, 66, 67, 68, 69,
+ 70, 71, 72, SFX_UNMAPPED, 74, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED,
+ SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED,
+ SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED,
+ SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED,
+ SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED,
+ SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED
+};
+
+/* +++ - Don't change unless you've got a good reason
+ ++ - Looks good, sounds ok
+ + - Not too bad, but is it right?
+ ? - Where do I map this one?
+ ?? - Any good ideas?
+ ??? - I'm clueless?
+ R - Rhythm... */
+static struct {
+ const char *name;
+ int8 gm_instr;
+ int8 gm_rhythm_key;
+} MT32_MemoryTimbreMaps[] = {
+ {"AccPnoKA2 ", 1, SFX_UNMAPPED}, /* ++ (KQ1) */
+ {"Acou BD ", SFX_MAPPED_TO_RHYTHM, 34}, /* R (PQ2) */
+ {"Acou SD ", SFX_MAPPED_TO_RHYTHM, 37}, /* R (PQ2) */
+ {"AcouPnoKA ", 0, SFX_UNMAPPED}, /* ++ (KQ1) */
+ {"BASS ", 32, SFX_UNMAPPED}, /* + (LSL3) */
+ {"BASSOONPCM", 70, SFX_UNMAPPED}, /* + (CB) */
+ {"BEACH WAVE", 122, SFX_UNMAPPED}, /* + (LSL3) */
+ {"BagPipes ", 109, SFX_UNMAPPED},
+ {"BassPizzMS", 45, SFX_UNMAPPED}, /* ++ (HQ) */
+ {"BassoonKA ", 70, SFX_UNMAPPED}, /* ++ (KQ1) */
+ {"Bell MS", 112, SFX_UNMAPPED}, /* ++ (iceMan) */
+ {"Bells MS", 112, SFX_UNMAPPED}, /* + (HQ) */
+ {"Big Bell ", 14, SFX_UNMAPPED}, /* + (CB) */
+ {"Bird Tweet", 123, SFX_UNMAPPED},
+ {"BrsSect MS", 61, SFX_UNMAPPED}, /* +++ (iceMan) */
+ {"CLAPPING ", 126, SFX_UNMAPPED}, /* ++ (LSL3) */
+ {"Cabasa ", SFX_MAPPED_TO_RHYTHM, 68}, /* R (HBoG) */
+ {"Calliope ", 82, SFX_UNMAPPED}, /* +++ (HQ) */
+ {"CelticHarp", 46, SFX_UNMAPPED}, /* ++ (CoC) */
+ {"Chicago MS", 1, SFX_UNMAPPED}, /* ++ (iceMan) */
+ {"Chop ", 117, SFX_UNMAPPED},
+ {"Chorale MS", 52, SFX_UNMAPPED}, /* + (CoC) */
+ {"ClarinetMS", 71, SFX_UNMAPPED},
+ {"Claves ", SFX_MAPPED_TO_RHYTHM, 74}, /* R (PQ2) */
+ {"Claw MS", 118, SFX_UNMAPPED}, /* + (HQ) */
+ {"ClockBell ", 14, SFX_UNMAPPED}, /* + (CB) */
+ {"ConcertCym", SFX_MAPPED_TO_RHYTHM, 54}, /* R ? (KQ1) */
+ {"Conga MS", SFX_MAPPED_TO_RHYTHM, 63}, /* R (HQ) */
+ {"CoolPhone ", 124, SFX_UNMAPPED}, /* ++ (LSL3) */
+ {"CracklesMS", 115, SFX_UNMAPPED}, /* ? (CoC, HQ) */
+ {"CreakyD MS", SFX_UNMAPPED, SFX_UNMAPPED}, /* ??? (KQ1) */
+ {"Cricket ", 120, SFX_UNMAPPED}, /* ? (CB) */
+ {"CrshCymbMS", SFX_MAPPED_TO_RHYTHM, 56}, /* R +++ (iceMan) */
+ {"CstlGateMS", SFX_UNMAPPED, SFX_UNMAPPED}, /* ? (HQ) */
+ {"CymSwellMS", SFX_MAPPED_TO_RHYTHM, 54}, /* R ? (CoC, HQ) */
+ {"CymbRollKA", SFX_MAPPED_TO_RHYTHM, 56}, /* R ? (KQ1) */
+ {"Cymbal Lo ", SFX_UNMAPPED, SFX_UNMAPPED}, /* R ? (LSL3) */
+ {"card ", SFX_UNMAPPED, SFX_UNMAPPED}, /* ? (HBoG) */
+ {"DirtGtr MS", 30, SFX_UNMAPPED}, /* + (iceMan) */
+ {"DirtGtr2MS", 29, SFX_UNMAPPED}, /* + (iceMan) */
+ {"E Bass MS", 33, SFX_UNMAPPED}, /* + (SQ3) */
+ {"ElecBassMS", 33, SFX_UNMAPPED},
+ {"ElecGtr MS", 27, SFX_UNMAPPED}, /* ++ (iceMan) */
+ {"EnglHornMS", 69, SFX_UNMAPPED},
+ {"FantasiaKA", 88, SFX_UNMAPPED},
+ {"Fantasy ", 99, SFX_UNMAPPED}, /* + (PQ2) */
+ {"Fantasy2MS", 99, SFX_UNMAPPED}, /* ++ (CoC, HQ) */
+ {"Filter MS", 95, SFX_UNMAPPED}, /* +++ (iceMan) */
+ {"Filter2 MS", 95, SFX_UNMAPPED}, /* ++ (iceMan) */
+ {"Flame2 MS", 121, SFX_UNMAPPED}, /* ? (HQ) */
+ {"Flames MS", 121, SFX_UNMAPPED}, /* ? (HQ) */
+ {"Flute MS", 73, SFX_UNMAPPED}, /* +++ (HQ) */
+ {"FogHorn MS", 58, SFX_UNMAPPED},
+ {"FrHorn1 MS", 60, SFX_UNMAPPED}, /* +++ (HQ) */
+ {"FunnyTrmp ", 56, SFX_UNMAPPED}, /* ++ (CB) */
+ {"GameSnd MS", 80, SFX_UNMAPPED},
+ {"Glock MS", 9, SFX_UNMAPPED}, /* +++ (HQ) */
+ {"Gunshot ", 127, SFX_UNMAPPED}, /* +++ (CB) */
+ {"Hammer MS", SFX_UNMAPPED, SFX_UNMAPPED}, /* ? (HQ) */
+ {"Harmonica2", 22, SFX_UNMAPPED}, /* +++ (CB) */
+ {"Harpsi 1 ", 6, SFX_UNMAPPED}, /* + (HBoG) */
+ {"Harpsi 2 ", 6, SFX_UNMAPPED}, /* +++ (CB) */
+ {"Heart MS", 116, SFX_UNMAPPED}, /* ? (iceMan) */
+ {"Horse1 MS", 115, SFX_UNMAPPED}, /* ? (CoC, HQ) */
+ {"Horse2 MS", 115, SFX_UNMAPPED}, /* ? (CoC, HQ) */
+ {"InHale MS", 121, SFX_UNMAPPED}, /* ++ (iceMan) */
+ {"KNIFE ", 120, SFX_UNMAPPED}, /* ? (LSL3) */
+ {"KenBanjo ", 105, SFX_UNMAPPED}, /* +++ (CB) */
+ {"Kiss MS", 25, SFX_UNMAPPED}, /* ++ (HQ) */
+ {"KongHit ", SFX_UNMAPPED, SFX_UNMAPPED}, /* ??? (KQ1) */
+ {"Koto ", 107, SFX_UNMAPPED}, /* +++ (PQ2) */
+ {"Laser MS", 81, SFX_UNMAPPED}, /* ?? (HQ) */
+ {"Meeps MS", 62, SFX_UNMAPPED}, /* ? (HQ) */
+ {"MTrak MS", 62, SFX_UNMAPPED}, /* ?? (iceMan) */
+ {"MachGun MS", 127, SFX_UNMAPPED}, /* ? (iceMan) */
+ {"OCEANSOUND", 122, SFX_UNMAPPED}, /* + (LSL3) */
+ {"Oboe 2001 ", 68, SFX_UNMAPPED}, /* + (PQ2) */
+ {"Ocean MS", 122, SFX_UNMAPPED}, /* + (iceMan) */
+ {"PPG 2.3 MS", 75, SFX_UNMAPPED}, /* ? (iceMan) */
+ {"PianoCrank", SFX_UNMAPPED, SFX_UNMAPPED}, /* ? (CB) */
+ {"PicSnareMS", SFX_MAPPED_TO_RHYTHM, 39}, /* R ? (iceMan) */
+ {"PiccoloKA ", 72, SFX_UNMAPPED}, /* +++ (KQ1) */
+ {"PinkBassMS", 39, SFX_UNMAPPED},
+ {"Pizz2 ", 45, SFX_UNMAPPED}, /* ++ (CB) */
+ {"Portcullis", SFX_UNMAPPED, SFX_UNMAPPED}, /* ? (KQ1) */
+ {"Raspbry MS", 81, SFX_UNMAPPED}, /* ? (HQ) */
+ {"RatSqueek ", 72, SFX_UNMAPPED}, /* ? (CB, CoC) */
+ {"Record78 ", SFX_UNMAPPED, SFX_UNMAPPED}, /* +++ (CB) */
+ {"RecorderMS", 74, SFX_UNMAPPED}, /* +++ (CoC) */
+ {"Red Baron ", 125, SFX_UNMAPPED}, /* ? (CB) */
+ {"ReedPipMS ", 20, SFX_UNMAPPED}, /* +++ (Coc) */
+ {"RevCymb MS", 119, SFX_UNMAPPED},
+ {"RifleShot ", 127, SFX_UNMAPPED}, /* + (CB) */
+ {"RimShot MS", SFX_MAPPED_TO_RHYTHM, 36}, /* R */
+ {"SHOWER ", 52, SFX_UNMAPPED}, /* ? (LSL3) */
+ {"SQ Bass MS", 32, SFX_UNMAPPED}, /* + (SQ3) */
+ {"ShakuVibMS", 79, SFX_UNMAPPED}, /* + (iceMan) */
+ {"SlapBassMS", 36, SFX_UNMAPPED}, /* +++ (iceMan) */
+ {"Snare MS", SFX_MAPPED_TO_RHYTHM, 37}, /* R (HQ) */
+ {"Some Birds", 123, SFX_UNMAPPED}, /* + (CB) */
+ {"Sonar MS", 78, SFX_UNMAPPED}, /* ? (iceMan) */
+ {"Soundtrk2 ", 97, SFX_UNMAPPED}, /* +++ (CB) */
+ {"Soundtrack", 97, SFX_UNMAPPED}, /* ++ (CoC) */
+ {"SqurWaveMS", 80, SFX_UNMAPPED},
+ {"StabBassMS", 34, SFX_UNMAPPED}, /* + (iceMan) */
+ {"SteelDrmMS", 114, SFX_UNMAPPED}, /* +++ (iceMan) */
+ {"StrSect1MS", 48, SFX_UNMAPPED}, /* ++ (HQ) */
+ {"String MS", 45, SFX_UNMAPPED}, /* + (CoC) */
+ {"Syn-Choir ", 91, SFX_UNMAPPED},
+ {"Syn Brass4", 63, SFX_UNMAPPED}, /* ++ (PQ2) */
+ {"SynBass MS", 38, SFX_UNMAPPED},
+ {"SwmpBackgr", 120, SFX_UNMAPPED}, /* ?? (CB,HQ) */
+ {"T-Bone2 MS", 57, SFX_UNMAPPED}, /* +++ (HQ) */
+ {"Taiko ", 116, 34}, /* +++ (Coc) */
+ {"Taiko Rim ", 118, 36}, /* +++ (LSL3) */
+ {"Timpani1 ", 47, SFX_UNMAPPED}, /* +++ (CB) */
+ {"Tom MS", 117, 47}, /* +++ (iceMan) */
+ {"Toms MS", 117, 47}, /* +++ (CoC, HQ) */
+ {"Tpt1prtl ", 56, SFX_UNMAPPED}, /* +++ (KQ1) */
+ {"TriangleMS", 112, 80}, /* R (CoC) */
+ {"Trumpet 1 ", 56, SFX_UNMAPPED}, /* +++ (CoC) */
+ {"Type MS", 114, SFX_UNMAPPED}, /* ? (iceMan) */
+ {"WaterBells", 98, SFX_UNMAPPED}, /* + (PQ2) */
+ {"WaterFallK", SFX_UNMAPPED, SFX_UNMAPPED}, /* ? (KQ1) */
+ {"Whiporill ", 123, SFX_UNMAPPED}, /* + (CB) */
+ {"Wind ", SFX_UNMAPPED, SFX_UNMAPPED}, /* ? (CB) */
+ {"Wind MS", SFX_UNMAPPED, SFX_UNMAPPED}, /* ? (HQ, iceMan) */
+ {"Wind2 MS", SFX_UNMAPPED, SFX_UNMAPPED}, /* ? (CoC) */
+ {"Woodpecker", 115, SFX_UNMAPPED}, /* ? (CB) */
+ {"WtrFall MS", SFX_UNMAPPED, SFX_UNMAPPED}, /* ? (CoC, HQ, iceMan) */
+ {0, 0, 0}
+};
+
+static int8 lookup_instrument(const char *iname) {
+ int i = 0;
+
+ while (MT32_MemoryTimbreMaps[i].name) {
+ if (scumm_strnicmp(iname, MT32_MemoryTimbreMaps[i].name, 10) == 0)
+ return MT32_MemoryTimbreMaps[i].gm_instr;
+ i++;
+ }
+ return SFX_UNMAPPED;
+}
+
+static int8 lookup_rhythm_key(const char *iname) {
+ int i = 0;
+
+ while (MT32_MemoryTimbreMaps[i].name) {
+ if (scumm_strnicmp(iname, MT32_MemoryTimbreMaps[i].name, 10) == 0)
+ return MT32_MemoryTimbreMaps[i].gm_rhythm_key;
+ i++;
+ }
+ return SFX_UNMAPPED;
+}
+
+static void print_map(int sci, int ins, int rhythm, int mt32) {
+#ifdef DEBUG_MT32_TO_GM
+ if (ins == SFX_UNMAPPED || (ins == SFX_MAPPED_TO_RHYTHM && rhythm == SFX_UNMAPPED)) {
+ printf("[MT32-to-GM] No mapping available for [%i] `%s' (%i)\n",
+ sci, MT32_PresetTimbreMaps[mt32].name, mt32);
+ return;
+ }
+
+ if (ins == SFX_MAPPED_TO_RHYTHM) {
+ printf("[MT32-to-GM] Mapping [%i] `%s' (%i) to `%s' [R] (%i)\n",
+ sci, MT32_PresetTimbreMaps[mt32].name, mt32,
+ GM_Percussion_Names[rhythm], rhythm);
+ return;
+ }
+
+ printf("[MT32-to-GM] Mapping [%i] `%s' (%i) to `%s' (%i)\n",
+ sci, MT32_PresetTimbreMaps[mt32].name, mt32,
+ GM_Instrument_Names[ins], ins);
+#endif
+}
+
+static void print_map_mem(int sci, int ins, int rhythm, char *mt32) {
+#ifdef DEBUG_MT32_TO_GM
+ char name[11];
+
+ strncpy(name, mt32, 10);
+ name[10] = 0;
+
+ if (ins == SFX_UNMAPPED || (ins == SFX_MAPPED_TO_RHYTHM && rhythm == SFX_UNMAPPED)) {
+ printf("[MT32-to-GM] No mapping available for [%i] `%s'\n",
+ sci, name);
+ return;
+ }
+
+ if (ins == SFX_MAPPED_TO_RHYTHM) {
+ printf("[MT32-to-GM] Mapping [%i] `%s' to `%s' [R] (%i)\n",
+ sci, name, GM_Percussion_Names[rhythm], rhythm);
+ return;
+ }
+
+ printf("[MT32-to-GM] Mapping [%i] `%s' to `%s' (%i)\n",
+ sci, name, GM_Instrument_Names[ins], ins);
+#endif
+}
+
+static void print_map_rhythm(int sci, int ins, int rhythm, int mt32) {
+#ifdef DEBUG_MT32_TO_GM
+ if (ins == SFX_UNMAPPED || (ins == SFX_MAPPED_TO_RHYTHM && rhythm == SFX_UNMAPPED)) {
+ printf("[MT32-to-GM] No mapping available for [%i] `%s' [R] (%i)\n",
+ sci, MT32_RhythmTimbreMaps[mt32].name, mt32);
+ return;
+ }
+
+ if (ins == SFX_MAPPED_TO_RHYTHM) {
+ printf("[MT32-to-GM] Mapping [%i] `%s' [R] (%i) to `%s' [R] (%i)\n",
+ sci, MT32_RhythmTimbreMaps[mt32].name, mt32,
+ GM_Percussion_Names[rhythm], rhythm);
+ return;
+ }
+
+ printf("[MT32-to-GM] Mapping [%i] `%s' [R] (%i) to `%s' (%i)\n",
+ sci, MT32_RhythmTimbreMaps[mt32].name, mt32,
+ GM_Instrument_Names[ins], ins);
+#endif
+}
+
+static void print_map_rhythm_mem(int sci, int rhythm, char *mt32) {
+#ifdef DEBUG_MT32_TO_GM
+ char name[11];
+
+ strncpy(name, mt32, 10);
+ name[10] = 0;
+
+ if (rhythm == SFX_UNMAPPED) {
+ printf("[MT32-to-GM] No mapping available for [%i] `%s'\n",
+ sci, name);
+ return;
+ }
+
+ printf("[MT32-to-GM] Mapping [%i] `%s' to `%s' (%i)\n",
+ sci, name, GM_Percussion_Names[rhythm], rhythm);
+#endif
+}
+
+sfx_instrument_map_t *sfx_instrument_map_mt32_to_gm(byte *data, size_t size) {
+ int memtimbres, patches;
+ uint8 group, number, keyshift, finetune, bender_range;
+ uint8 *patchpointer;
+ uint32 pos;
+ sfx_instrument_map_t * map;
+ int i;
+ int type;
+
+ map = sfx_instrument_map_new(0);
+
+ for (i = 0; i < SFX_INSTRUMENTS_NR; i++) {
+ map->patch_map[i].patch = MT32_PresetTimbreMaps[i].gm_instr;
+ map->patch_key_shift[i] = 0;
+ map->patch_volume_adjust[i] = 0;
+ map->patch_bend_range[i] = 12;
+ map->velocity_map_index[i] = SFX_NO_VELOCITY_MAP;
+ }
+
+ map->percussion_volume_adjust = 0;
+ map->percussion_velocity_map_index = SFX_NO_VELOCITY_MAP;
+
+ for (i = 0; i < SFX_RHYTHM_NR; i++) {
+ map->percussion_map[i] = MT32_PresetRhythmKeymap[i];
+ map->percussion_velocity_scale[i] = SFX_MAX_VELOCITY;
+ }
+
+ if (!data) {
+ printf("[MT32-to-GM] No MT-32 patch data supplied, using default mapping\n");
+ return map;
+ }
+
+ type = sfx_instrument_map_detect(data, size);
+
+ if (type == SFX_MAP_UNKNOWN) {
+ printf("[MT32-to-GM] Patch data format unknown, using default mapping\n");
+ return map;
+ }
+ if (type == SFX_MAP_MT32_GM) {
+ printf("[MT32-to-GM] Patch data format not supported, using default mapping\n");
+ return map;
+ }
+
+ memtimbres = *(data + 0x1EB);
+ pos = 0x1EC + memtimbres * 0xF6;
+
+ if (size > pos && ((0x100 * *(data + pos) + *(data + pos + 1)) == 0xABCD)) {
+ patches = 96;
+ pos += 2 + 8 * 48;
+ } else
+ patches = 48;
+
+ printf("[MT32-to-GM] %d MT-32 Patches detected\n", patches);
+ printf("[MT32-to-GM] %d MT-32 Memory Timbres\n", memtimbres);
+
+ printf("[MT32-to-GM] Mapping patches..\n");
+
+ for (i = 0; i < patches; i++) {
+ char *name;
+
+ if (i < 48)
+ patchpointer = data + 0x6B + 8 * i;
+ else
+ patchpointer = data + 0x1EC + 8 * (i - 48) + memtimbres * 0xF6 + 2;
+
+ group = *patchpointer;
+ number = *(patchpointer + 1);
+ keyshift = *(patchpointer + 2);
+ finetune = *(patchpointer + 3);
+ bender_range = *(patchpointer + 4);
+
+ switch (group) {
+ case 0:
+ map->patch_map[i].patch = MT32_PresetTimbreMaps[number].gm_instr;
+ map->patch_map[i].rhythm = MT32_PresetTimbreMaps[number].gm_rhythm_key;
+ print_map(i, map->patch_map[i].patch, map->patch_map[i].rhythm, number);
+ break;
+ case 1:
+ map->patch_map[i].patch = MT32_PresetTimbreMaps[number + 64].gm_instr;
+ map->patch_map[i].rhythm = MT32_PresetTimbreMaps[number + 64].gm_rhythm_key;
+ print_map(i, map->patch_map[i].patch, map->patch_map[i].rhythm, number + 64);
+ break;
+ case 2:
+ name = (char *) data + 0x1EC + number * 0xF6;
+ map->patch_map[i].patch = lookup_instrument(name);
+ map->patch_map[i].rhythm = SFX_UNMAPPED;
+ print_map_mem(i, map->patch_map[i].patch, map->patch_map[i].rhythm, name);
+ break;
+ case 3:
+ map->patch_map[i].patch = MT32_RhythmTimbreMaps[number].gm_instr;
+ map->patch_map[i].rhythm = SFX_UNMAPPED;
+ print_map_rhythm(i, map->patch_map[i].patch, map->patch_map[i].rhythm, number);
+ break;
+ default:
+ break;
+ }
+
+ /* map->patch_key_shift[i] = (int) (keyshift & 0x3F) - 24; */
+ map->patch_bend_range[i] = bender_range & 0x1F;
+ }
+
+ if (size > pos && ((0x100 * *(data + pos) + *(data + pos + 1)) == 0xDCBA)) {
+ printf("[MT32-to-GM] Mapping percussion..\n");
+
+ for (i = 0; i < 64 ; i++) {
+ number = *(data + pos + 4 * i + 2);
+
+ if (number < 64) {
+ char *name = (char *) data + 0x1EC + number * 0xF6;
+ map->percussion_map[i + 23] = lookup_rhythm_key(name);
+ print_map_rhythm_mem(i, map->percussion_map[i + 23], name);
+ } else {
+ if (number < 94) {
+ map->percussion_map[i + 23] = MT32_RhythmTimbreMaps[number - 64].gm_rhythmkey;
+ print_map_rhythm(i, SFX_MAPPED_TO_RHYTHM, map->percussion_map[i + 23], number - 64);
+ } else
+ map->percussion_map[i + 23] = SFX_UNMAPPED;
+ }
+
+ map->percussion_velocity_scale[i + 23] = *(data + pos + 4 * i + 3) * SFX_MAX_VELOCITY / 100;
+ }
+ }
+
+ return map;
+}
+
+} // End of namespace Sci
diff --git a/engines/sci/sound/seq/midiwriter.h b/engines/sci/sound/seq/midiwriter.h
new file mode 100644
index 0000000000..24e432ffc4
--- /dev/null
+++ b/engines/sci/sound/seq/midiwriter.h
@@ -0,0 +1,88 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+/* song player structure */
+
+#ifndef SCI_SFX_SEQ_MIDIWRITER_H
+#define SCI_SFX_SEQ_MIDIWRITER_H
+
+#include "common/scummsys.h"
+#include "common/error.h"
+
+namespace Sci {
+
+struct midi_writer_t {
+ char *name; /* Name description of the device */
+
+ Common::Error (*init)(midi_writer_t *self);
+ /* Initializes the writer
+ ** Parameters: (midi_writer_t *) self: Self reference
+ ** Returns : (int) Common::kNoError on success, Common::kUnknownError if the device could not be
+ ** opened
+ */
+
+ Common::Error (*set_option)(midi_writer_t *self, char *name, char *value);
+ /* Sets an option for the writer
+ ** Parameters: (char *) name: Name of the option to set
+ ** (char *) value: Value of the option to set
+ ** Returns : (int) Common::kNoError on success, Common::kUnknownError otherwise (unsupported option)
+ */
+
+ Common::Error (*write)(midi_writer_t *self, unsigned char *buf, int len);
+ /* Writes some bytes to the MIDI stream
+ ** Parameters: (char *) buf: The buffer to write
+ ** (int) len: Number of bytes to write
+ ** Returns : (int) Common::kNoError on success, Common::kUnknownError on failure
+ ** No delta time is expected here.
+ */
+
+ void (*delay)(midi_writer_t *self, int ticks);
+ /* Introduces an explicit delay
+ ** Parameters: (int) ticks: Number of 60 Hz ticks to sleep
+ */
+
+ void (*flush)(midi_writer_t *self); /* May be NULL */
+ /* Flushes the MIDI file descriptor
+ ** Parameters: (midi_writer_t *) self: Self reference
+ */
+
+ void (*reset_timer)(midi_writer_t *self);
+ /* Resets the timer associated with this device
+ ** Parameters: (midi_writer_t *) self: Self reference
+ ** This function makes sure that a subsequent write would have effect
+ ** immediately, and any delay() would be relative to the point in time
+ ** this function was invoked at.
+ */
+
+ void (*close)(midi_writer_t *self);
+ /* Closes the associated MIDI device
+ ** Parameters: (midi_writer_t *) self: Self reference
+ */
+};
+
+
+} // End of namespace Sci
+
+#endif // SCI_SFX_SEQ_MIDIWRITER_H
diff --git a/engines/sci/sound/softseq/adlib.cpp b/engines/sci/sound/softseq/adlib.cpp
new file mode 100644
index 0000000000..6f73b45650
--- /dev/null
+++ b/engines/sci/sound/softseq/adlib.cpp
@@ -0,0 +1,827 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "sci/sci.h"
+
+#include "sound/fmopl.h"
+#include "sound/softsynth/emumidi.h"
+
+#include "sci/resource.h"
+#include "sci/sound/softseq/mididriver.h"
+
+namespace Sci {
+
+#ifdef __DC__
+#define STEREO false
+#else
+#define STEREO true
+#endif
+
+// FIXME: We don't seem to be sending the polyphony init data, so disable this for now
+#define ADLIB_DISABLE_VOICE_MAPPING
+
+class MidiDriver_Adlib : public MidiDriver_Emulated {
+public:
+ enum {
+ kVoices = 9,
+ kRhythmKeys = 62
+ };
+
+ MidiDriver_Adlib(Audio::Mixer *mixer) : MidiDriver_Emulated(mixer), _playSwitch(true), _masterVolume(15), _rhythmKeyMap(0), _opl(0) { }
+ virtual ~MidiDriver_Adlib() { }
+
+ // MidiDriver
+ int open(bool isSCI0);
+ void close();
+ void send(uint32 b);
+ MidiChannel *allocateChannel() { return NULL; }
+ MidiChannel *getPercussionChannel() { return NULL; }
+
+ // AudioStream
+ bool isStereo() const { return _stereo; }
+ int getRate() const { return _mixer->getOutputRate(); }
+
+ // MidiDriver_Emulated
+ void generateSamples(int16 *buf, int len);
+
+ void setVolume(byte volume);
+ void playSwitch(bool play);
+ bool loadResource(const byte *data, uint size);
+ virtual uint32 property(int prop, uint32 param);
+
+private:
+ enum ChannelID {
+ kLeftChannel = 1,
+ kRightChannel = 2
+ };
+
+ struct AdlibOperator {
+ bool amplitudeMod;
+ bool vibrato;
+ bool envelopeType;
+ bool kbScaleRate;
+ byte frequencyMult; // (0-15)
+ byte kbScaleLevel; // (0-3)
+ byte totalLevel; // (0-63, 0=max, 63=min)
+ byte attackRate; // (0-15)
+ byte decayRate; // (0-15)
+ byte sustainLevel; // (0-15)
+ byte releaseRate; // (0-15)
+ byte waveForm; // (0-3)
+ };
+
+ struct AdlibModulator {
+ byte feedback; // (0-7)
+ bool algorithm;
+ };
+
+ struct AdlibPatch {
+ AdlibOperator op[2];
+ AdlibModulator mod;
+ };
+
+ struct Channel {
+ uint8 patch; // Patch setting
+ uint8 volume; // Channel volume (0-63)
+ uint8 pan; // Pan setting (0-127, 64 is center)
+ uint8 holdPedal; // Hold pedal setting (0 to 63 is off, 127 to 64 is on)
+ uint8 extraVoices; // The number of additional voices this channel optimally needs
+ uint16 pitchWheel; // Pitch wheel setting (0-16383, 8192 is center)
+ uint8 lastVoice; // Last voice used for this MIDI channel
+ bool enableVelocity; // Enable velocity control (SCI0)
+
+ Channel() : patch(0), volume(63), pan(64), holdPedal(0), extraVoices(0),
+ pitchWheel(8192), lastVoice(0), enableVelocity(false) { }
+ };
+
+ struct AdlibVoice {
+ int8 channel; // MIDI channel that this voice is assigned to or -1
+ int8 note; // Currently playing MIDI note or -1
+ int patch; // Currently playing patch or -1
+ uint8 velocity; // Note velocity
+ bool isSustained; // Flag indicating a note that is being sustained by the hold pedal
+ uint16 age; // Age of the current note
+
+ AdlibVoice() : channel(-1), note(-1), patch(-1), velocity(0), isSustained(false), age(0) { }
+ };
+
+ bool _stereo;
+ bool _isSCI0;
+ OPL::OPL *_opl;
+ bool _playSwitch;
+ int _masterVolume;
+ Channel _channels[MIDI_CHANNELS];
+ AdlibVoice _voices[kVoices];
+ byte *_rhythmKeyMap;
+ Common::Array<AdlibPatch> _patches;
+
+ void loadInstrument(const byte *ins);
+ void voiceOn(int voice, int note, int velocity);
+ void voiceOff(int voice);
+ void setPatch(int voice, int patch);
+ void setNote(int voice, int note, bool key);
+ void setVelocity(int voice);
+ void setOperator(int oper, AdlibOperator &op);
+ void setRegister(int reg, int value, int channels = kLeftChannel | kRightChannel);
+ void renewNotes(int channel, bool key);
+ void noteOn(int channel, int note, int velocity);
+ void noteOff(int channel, int note);
+ int findVoice(int channel);
+ void voiceMapping(int channel, int voices);
+ void assignVoices(int channel, int voices);
+ void releaseVoices(int channel, int voices);
+ void donateVoices();
+ int findVoiceBasic(int channel);
+ void setVelocityReg(int regOffset, int velocity, int kbScaleLevel, int pan);
+ int calcVelocity(int voice, int op);
+};
+
+class MidiPlayer_Adlib : public MidiPlayer {
+public:
+ MidiPlayer_Adlib() { _driver = new MidiDriver_Adlib(g_system->getMixer()); }
+ int open(ResourceManager *resMan);
+ int getPlayMask(SciVersion soundVersion);
+ int getPolyphony() const { return MidiDriver_Adlib::kVoices; }
+ bool hasRhythmChannel() const { return false; }
+ void setVolume(byte volume) { static_cast<MidiDriver_Adlib *>(_driver)->setVolume(volume); }
+ void playSwitch(bool play) { static_cast<MidiDriver_Adlib *>(_driver)->playSwitch(play); }
+ void loadInstrument(int idx, byte *data);
+};
+
+static const byte registerOffset[MidiDriver_Adlib::kVoices] = {
+ 0x00, 0x01, 0x02, 0x08, 0x09, 0x0A, 0x10, 0x11, 0x12
+};
+
+static const byte velocityMap1[64] = {
+ 0x00, 0x0c, 0x0d, 0x0e, 0x0f, 0x11, 0x12, 0x13,
+ 0x14, 0x16, 0x17, 0x18, 0x1a, 0x1b, 0x1c, 0x1d,
+ 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26,
+ 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2d, 0x2d, 0x2e,
+ 0x2f, 0x30, 0x31, 0x32, 0x32, 0x33, 0x34, 0x34,
+ 0x35, 0x36, 0x36, 0x37, 0x38, 0x38, 0x39, 0x3a,
+ 0x3b, 0x3b, 0x3b, 0x3c, 0x3c, 0x3c, 0x3d, 0x3d,
+ 0x3d, 0x3e, 0x3e, 0x3e, 0x3e, 0x3f, 0x3f, 0x3f
+};
+
+static const byte velocityMap2[64] = {
+ 0x00, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a,
+ 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x21,
+ 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29,
+ 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x2f, 0x30,
+ 0x31, 0x32, 0x32, 0x33, 0x34, 0x34, 0x35, 0x36,
+ 0x36, 0x37, 0x38, 0x38, 0x39, 0x39, 0x3a, 0x3a,
+ 0x3b, 0x3b, 0x3b, 0x3c, 0x3c, 0x3c, 0x3d, 0x3d,
+ 0x3d, 0x3e, 0x3e, 0x3e, 0x3e, 0x3f, 0x3f, 0x3f
+};
+
+static const int ym3812_note[13] = {
+ 0x157, 0x16b, 0x181, 0x198, 0x1b0, 0x1ca,
+ 0x1e5, 0x202, 0x220, 0x241, 0x263, 0x287,
+ 0x2ae
+};
+
+int MidiDriver_Adlib::open(bool isSCI0) {
+ int rate = _mixer->getOutputRate();
+
+ _stereo = STEREO;
+
+ debug(3, "ADLIB: Starting driver in %s mode", (isSCI0 ? "SCI0" : "SCI1"));
+ _isSCI0 = isSCI0;
+
+ _opl = OPL::Config::create(isStereo() ? OPL::Config::kDualOpl2 : OPL::Config::kOpl2);
+
+ // Try falling back to mono, thus plain OPL2 emualtor, when no Dual OPL2 is available.
+ if (!_opl && _stereo) {
+ _stereo = false;
+ _opl = OPL::Config::create(OPL::Config::kOpl2);
+ }
+
+ if (!_opl)
+ return -1;
+
+ _opl->init(rate);
+
+ setRegister(0xBD, 0);
+ setRegister(0x08, 0);
+ setRegister(0x01, 0x20);
+
+ MidiDriver_Emulated::open();
+
+ _mixer->playInputStream(Audio::Mixer::kMusicSoundType, &_mixerSoundHandle, this, -1, _mixer->kMaxChannelVolume, 0, false);
+
+ return 0;
+}
+
+void MidiDriver_Adlib::close() {
+ _mixer->stopHandle(_mixerSoundHandle);
+
+ delete _opl;
+ delete[] _rhythmKeyMap;
+}
+
+void MidiDriver_Adlib::setVolume(byte volume) {
+ _masterVolume = volume;
+ renewNotes(-1, true);
+}
+
+// MIDI messages can be found at http://www.midi.org/techspecs/midimessages.php
+void MidiDriver_Adlib::send(uint32 b) {
+ byte command = b & 0xf0;
+ byte channel = b & 0xf;
+ byte op1 = (b >> 8) & 0xff;
+ byte op2 = (b >> 16) & 0xff;
+
+ switch (command) {
+ case 0x80:
+ noteOff(channel, op1);
+ break;
+ case 0x90:
+ noteOn(channel, op1, op2);
+ break;
+ case 0xe0:
+ _channels[channel].pitchWheel = (op1 & 0x7f) | ((op2 & 0x7f) << 7);
+ renewNotes(channel, true);
+ break;
+ case 0xb0:
+ switch (op1) {
+ case 0x07:
+ _channels[channel].volume = op2 >> 1;
+ renewNotes(channel, true);
+ break;
+ case 0x0a:
+ _channels[channel].pan = op2;
+ renewNotes(channel, true);
+ break;
+ case 0x40:
+ _channels[channel].holdPedal = op2;
+ if (op2 == 0) {
+ for (int i = 0; i < kVoices; i++) {
+ if ((_voices[i].channel == channel) && _voices[i].isSustained)
+ voiceOff(i);
+ }
+ }
+ break;
+ case 0x4b:
+#ifndef ADLIB_DISABLE_VOICE_MAPPING
+ voiceMapping(channel, op2);
+#endif
+ break;
+ case 0x4e:
+ // FIXME: this flag should be set to 0 when a new song is started
+ debug(3, "ADLIB: Setting velocity control flag for channel %i to %i", channel, op2);
+ _channels[channel].enableVelocity = op2;
+ break;
+ case SCI_MIDI_CHANNEL_NOTES_OFF:
+ for (int i = 0; i < kVoices; i++)
+ if ((_voices[i].channel == channel) && (_voices[i].note != -1))
+ voiceOff(i);
+ break;
+ default:
+ //warning("ADLIB: ignoring MIDI command %02x %02x %02x", command | channel, op1, op2);
+ break;
+ }
+ break;
+ case 0xc0:
+ _channels[channel].patch = op1;
+ break;
+ // The original adlib driver from sierra ignores aftertouch completely, so should we
+ case 0xa0: // Polyphonic key pressure (aftertouch)
+ case 0xd0: // Channel pressure (aftertouch)
+ break;
+ case 0xf0: // SysEx, ignore it
+ break;
+ default:
+ warning("ADLIB: Unknown event %02x", command);
+ }
+}
+
+void MidiDriver_Adlib::generateSamples(int16 *data, int len) {
+ if (isStereo())
+ len <<= 1;
+ _opl->readBuffer(data, len);
+
+ // Increase the age of the notes
+ for (int i = 0; i < kVoices; i++) {
+ if (_voices[i].note != -1)
+ _voices[i].age++;
+ }
+}
+
+void MidiDriver_Adlib::loadInstrument(const byte *ins) {
+ AdlibPatch patch;
+
+ // Set data for the operators
+ for (int i = 0; i < 2; i++) {
+ const byte *op = ins + i * 13;
+ patch.op[i].kbScaleLevel = op[0] & 0x3;
+ patch.op[i].frequencyMult = op[1] & 0xf;
+ patch.op[i].attackRate = op[3] & 0xf;
+ patch.op[i].sustainLevel = op[4] & 0xf;
+ patch.op[i].envelopeType = op[5];
+ patch.op[i].decayRate = op[6] & 0xf;
+ patch.op[i].releaseRate = op[7] & 0xf;
+ patch.op[i].totalLevel = op[8] & 0x3f;
+ patch.op[i].amplitudeMod = op[9];
+ patch.op[i].vibrato = op[10];
+ patch.op[i].kbScaleRate = op[11];
+ }
+ patch.op[0].waveForm = ins[26] & 0x3;
+ patch.op[1].waveForm = ins[27] & 0x3;
+
+ // Set data for the modulator
+ patch.mod.feedback = ins[2] & 0x7;
+ patch.mod.algorithm = !ins[12]; // Flag is inverted
+
+ _patches.push_back(patch);
+}
+
+void MidiDriver_Adlib::voiceMapping(int channel, int voices) {
+ int curVoices = 0;
+
+ for (int i = 0; i < kVoices; i++)
+ if (_voices[i].channel == channel)
+ curVoices++;
+
+ curVoices += _channels[channel].extraVoices;
+
+ if (curVoices < voices) {
+ debug(3, "ADLIB: assigning %i additional voices to channel %i", voices - curVoices, channel);
+ assignVoices(channel, voices - curVoices);
+ } else if (curVoices > voices) {
+ debug(3, "ADLIB: releasing %i voices from channel %i", curVoices - voices, channel);
+ releaseVoices(channel, curVoices - voices);
+ donateVoices();
+ }
+}
+
+void MidiDriver_Adlib::assignVoices(int channel, int voices) {
+ assert(voices > 0);
+
+ for (int i = 0; i < kVoices; i++)
+ if (_voices[i].channel == -1) {
+ _voices[i].channel = channel;
+ if (--voices == 0)
+ return;
+ }
+
+ _channels[channel].extraVoices += voices;
+}
+
+void MidiDriver_Adlib::releaseVoices(int channel, int voices) {
+ if (_channels[channel].extraVoices >= voices) {
+ _channels[channel].extraVoices -= voices;
+ return;
+ }
+
+ voices -= _channels[channel].extraVoices;
+ _channels[channel].extraVoices = 0;
+
+ for (int i = 0; i < kVoices; i++) {
+ if ((_voices[i].channel == channel) && (_voices[i].note == -1)) {
+ _voices[i].channel = -1;
+ if (--voices == 0)
+ return;
+ }
+ }
+
+ for (int i = 0; i < kVoices; i++) {
+ if (_voices[i].channel == channel) {
+ voiceOff(i);
+ _voices[i].channel = -1;
+ if (--voices == 0)
+ return;
+ }
+ }
+}
+
+void MidiDriver_Adlib::donateVoices() {
+ int freeVoices = 0;
+
+ for (int i = 0; i < kVoices; i++)
+ if (_voices[i].channel == -1)
+ freeVoices++;
+
+ if (freeVoices == 0)
+ return;
+
+ for (int i = 0; i < MIDI_CHANNELS; i++) {
+ if (_channels[i].extraVoices >= freeVoices) {
+ assignVoices(i, freeVoices);
+ _channels[i].extraVoices -= freeVoices;
+ return;
+ } else if (_channels[i].extraVoices > 0) {
+ assignVoices(i, _channels[i].extraVoices);
+ freeVoices -= _channels[i].extraVoices;
+ _channels[i].extraVoices = 0;
+ }
+ }
+}
+
+void MidiDriver_Adlib::renewNotes(int channel, bool key) {
+ for (int i = 0; i < kVoices; i++) {
+ // Update all notes playing this channel
+ if ((channel == -1) || (_voices[i].channel == channel)) {
+ if (_voices[i].note != -1)
+ setNote(i, _voices[i].note, key);
+ }
+ }
+}
+
+void MidiDriver_Adlib::noteOn(int channel, int note, int velocity) {
+ if (velocity == 0)
+ return noteOff(channel, note);
+
+ velocity >>= 1;
+
+ // Check for playable notes
+ if ((note < 12) || (note > 107))
+ return;
+
+ for (int i = 0; i < kVoices; i++) {
+ if ((_voices[i].channel == channel) && (_voices[i].note == note)) {
+ voiceOff(i);
+ voiceOn(i, note, velocity);
+ return;
+ }
+ }
+
+#ifdef ADLIB_DISABLE_VOICE_MAPPING
+ int voice = findVoiceBasic(channel);
+#else
+ int voice = findVoice(channel);
+#endif
+
+ if (voice == -1) {
+ debug(3, "ADLIB: failed to find free voice assigned to channel %i", channel);
+ return;
+ }
+
+ voiceOn(voice, note, velocity);
+}
+
+// FIXME: Temporary, see comment at top of file regarding ADLIB_DISABLE_VOICE_MAPPING
+int MidiDriver_Adlib::findVoiceBasic(int channel) {
+ int voice = -1;
+ int oldestVoice = -1;
+ int oldestAge = -1;
+
+ // Try to find a voice assigned to this channel that is free (round-robin)
+ for (int i = 0; i < kVoices; i++) {
+ int v = (_channels[channel].lastVoice + i + 1) % kVoices;
+
+ if (_voices[v].note == -1) {
+ voice = v;
+ break;
+ }
+
+ // We also keep track of the oldest note in case the search fails
+ if (_voices[v].age > oldestAge) {
+ oldestAge = _voices[v].age;
+ oldestVoice = v;
+ }
+ }
+
+ if (voice == -1) {
+ if (oldestVoice != -1) {
+ voiceOff(oldestVoice);
+ voice = oldestVoice;
+ } else {
+ return -1;
+ }
+ }
+
+ _voices[voice].channel = channel;
+ _channels[channel].lastVoice = voice;
+ return voice;
+}
+
+int MidiDriver_Adlib::findVoice(int channel) {
+ int voice = -1;
+ int oldestVoice = -1;
+ uint32 oldestAge = 0;
+
+ // Try to find a voice assigned to this channel that is free (round-robin)
+ for (int i = 0; i < kVoices; i++) {
+ int v = (_channels[channel].lastVoice + i + 1) % kVoices;
+
+ if (_voices[v].channel == channel) {
+ if (_voices[v].note == -1) {
+ voice = v;
+ break;
+ }
+
+ // We also keep track of the oldest note in case the search fails
+ // Notes started in the current time slice will not be selected
+ if (_voices[v].age > oldestAge) {
+ oldestAge = _voices[v].age;
+ oldestVoice = v;
+ }
+ }
+ }
+
+ if (voice == -1) {
+ if (oldestVoice != -1) {
+ voiceOff(oldestVoice);
+ voice = oldestVoice;
+ } else {
+ return -1;
+ }
+ }
+
+ _channels[channel].lastVoice = voice;
+ return voice;
+}
+
+void MidiDriver_Adlib::noteOff(int channel, int note) {
+ for (int i = 0; i < kVoices; i++) {
+ if ((_voices[i].channel == channel) && (_voices[i].note == note)) {
+ if (_channels[channel].holdPedal)
+ _voices[i].isSustained = true;
+ else
+ voiceOff(i);
+ return;
+ }
+ }
+}
+
+void MidiDriver_Adlib::voiceOn(int voice, int note, int velocity) {
+ int channel = _voices[voice].channel;
+ int patch;
+
+ _voices[voice].age = 0;
+
+ if ((channel == 9) && _rhythmKeyMap) {
+ patch = CLIP(note, 27, 88) + 101;
+ } else {
+ patch = _channels[channel].patch;
+ }
+
+ // Set patch if different from current patch
+ if ((patch != _voices[voice].patch) && _playSwitch)
+ setPatch(voice, patch);
+
+ _voices[voice].velocity = velocity;
+ setNote(voice, note, true);
+}
+
+void MidiDriver_Adlib::voiceOff(int voice) {
+ _voices[voice].isSustained = false;
+ setNote(voice, _voices[voice].note, 0);
+ _voices[voice].note = -1;
+ _voices[voice].age = 0;
+}
+
+void MidiDriver_Adlib::setNote(int voice, int note, bool key) {
+ int channel = _voices[voice].channel;
+ int n, fre, oct;
+ float delta;
+ int bend = _channels[channel].pitchWheel;
+
+ if ((channel == 9) && _rhythmKeyMap) {
+ note = _rhythmKeyMap[CLIP(note, 27, 88) - 27];
+ }
+
+ _voices[voice].note = note;
+
+ delta = 0;
+
+ n = note % 12;
+
+ if (bend < 8192)
+ bend = 8192 - bend;
+ delta = (float)pow(2.0, (bend % 8192) / 8192.0);
+
+ if (bend > 8192)
+ fre = (int)(ym3812_note[n] * delta);
+ else
+ fre = (int)(ym3812_note[n] / delta);
+
+ oct = note / 12 - 1;
+
+ if (oct < 0)
+ oct = 0;
+
+ if (oct > 7)
+ oct = 7;
+
+ setRegister(0xA0 + voice, fre & 0xff);
+ setRegister(0xB0 + voice, (key << 5) | (oct << 2) | (fre >> 8));
+
+ setVelocity(voice);
+}
+
+void MidiDriver_Adlib::setVelocity(int voice) {
+ AdlibPatch &patch = _patches[_voices[voice].patch];
+ int pan = _channels[_voices[voice].channel].pan;
+ setVelocityReg(registerOffset[voice] + 3, calcVelocity(voice, 1), patch.op[1].kbScaleLevel, pan);
+
+ // In AM mode we need to set the level for both operators
+ if (_patches[_voices[voice].patch].mod.algorithm == 1)
+ setVelocityReg(registerOffset[voice], calcVelocity(voice, 0), patch.op[0].kbScaleLevel, pan);
+}
+
+int MidiDriver_Adlib::calcVelocity(int voice, int op) {
+ if (_isSCI0) {
+ int velocity = _masterVolume;
+
+ if (velocity > 0)
+ velocity += 3;
+
+ if (velocity > 15)
+ velocity = 15;
+
+ int insVelocity;
+ if (_channels[_voices[voice].channel].enableVelocity)
+ insVelocity = _voices[voice].velocity;
+ else
+ insVelocity = 63 - _patches[_voices[voice].patch].op[op].totalLevel;
+
+ // Note: Later SCI0 has a static table that is close to this formula, but not exactly the same.
+ // Early SCI0 does (velocity * (insVelocity / 15))
+ return velocity * insVelocity / 15;
+ } else {
+ AdlibOperator &oper = _patches[_voices[voice].patch].op[op];
+ int velocity = _channels[_voices[voice].channel].volume + 1;
+ velocity = velocity * (velocityMap1[_voices[voice].velocity] + 1) / 64;
+ velocity = velocity * (_masterVolume + 1) / 16;
+
+ if (--velocity < 0)
+ velocity = 0;
+
+ return velocityMap2[velocity] * (63 - oper.totalLevel) / 63;
+ }
+}
+
+void MidiDriver_Adlib::setVelocityReg(int regOffset, int velocity, int kbScaleLevel, int pan) {
+ if (!_playSwitch)
+ velocity = 0;
+
+ if (isStereo()) {
+ int velLeft = velocity;
+ int velRight = velocity;
+
+ if (pan > 0x40)
+ velLeft = velLeft * (0x7f - pan) / 0x3f;
+ else if (pan < 0x40)
+ velRight = velRight * pan / 0x40;
+
+ setRegister(0x40 + regOffset, (kbScaleLevel << 6) | (63 - velLeft), kLeftChannel);
+ setRegister(0x40 + regOffset, (kbScaleLevel << 6) | (63 - velRight), kRightChannel);
+ } else {
+ setRegister(0x40 + regOffset, (kbScaleLevel << 6) | (63 - velocity));
+ }
+}
+
+void MidiDriver_Adlib::setPatch(int voice, int patch) {
+ if ((patch < 0) || ((uint)patch >= _patches.size())) {
+ warning("ADLIB: Invalid patch %i requested", patch);
+ patch = 0;
+ }
+
+ _voices[voice].patch = patch;
+ AdlibModulator &mod = _patches[patch].mod;
+
+ // Set the common settings for both operators
+ setOperator(registerOffset[voice], _patches[patch].op[0]);
+ setOperator(registerOffset[voice] + 3, _patches[patch].op[1]);
+
+ // Set the additional settings for the modulator
+ byte algorithm = mod.algorithm ? 1 : 0;
+ setRegister(0xC0 + voice, (mod.feedback << 1) | algorithm);
+}
+
+void MidiDriver_Adlib::setOperator(int reg, AdlibOperator &op) {
+ setRegister(0x40 + reg, (op.kbScaleLevel << 6) | op.totalLevel);
+ setRegister(0x60 + reg, (op.attackRate << 4) | op.decayRate);
+ setRegister(0x80 + reg, (op.sustainLevel << 4) | op.releaseRate);
+ setRegister(0x20 + reg, (op.amplitudeMod << 7) | (op.vibrato << 6)
+ | (op.envelopeType << 5) | (op.kbScaleRate << 4) | op.frequencyMult);
+ setRegister(0xE0 + reg, op.waveForm);
+}
+
+void MidiDriver_Adlib::setRegister(int reg, int value, int channels) {
+ if (channels & kLeftChannel) {
+ _opl->write(0x220, reg);
+ _opl->write(0x221, value);
+ }
+
+ if (isStereo()) {
+ if (channels & kRightChannel) {
+ _opl->write(0x222, reg);
+ _opl->write(0x223, value);
+ }
+ }
+}
+
+void MidiDriver_Adlib::playSwitch(bool play) {
+ _playSwitch = play;
+ renewNotes(-1, play);
+}
+
+bool MidiDriver_Adlib::loadResource(const byte *data, uint size) {
+ if ((size != 1344) && (size != 2690) && (size != 5382)) {
+ warning("ADLIB: Unsupported patch format (%i bytes)", size);
+ return false;
+ }
+
+ for (int i = 0; i < 48; i++)
+ loadInstrument(data + (28 * i));
+
+ if (size == 2690) {
+ for (int i = 48; i < 96; i++)
+ loadInstrument(data + 2 + (28 * i));
+ } else if (size == 5382) {
+ for (int i = 48; i < 190; i++)
+ loadInstrument(data + (28 * i));
+ _rhythmKeyMap = new byte[kRhythmKeys];
+ memcpy(_rhythmKeyMap, data + 5320, kRhythmKeys);
+ }
+
+ return true;
+}
+
+uint32 MidiDriver_Adlib::property(int prop, uint32 param) {
+ switch(prop) {
+ case MIDI_PROP_MASTER_VOLUME:
+ if (param != 0xffff)
+ _masterVolume = param;
+ return _masterVolume;
+ default:
+ break;
+ }
+ return 0;
+}
+
+
+int MidiPlayer_Adlib::open(ResourceManager *resMan) {
+ assert(resMan != NULL);
+
+ // Load up the patch.003 file, parse out the instruments
+ Resource *res = resMan->findResource(ResourceId(kResourceTypePatch, 3), 0);
+ bool ok = false;
+
+ if (res) {
+ ok = static_cast<MidiDriver_Adlib *>(_driver)->loadResource(res->data, res->size);
+ } else {
+ // Early SCI0 games have the sound bank embedded in the adlib driver
+
+ Common::File f;
+
+ if (f.open("ADL.DRV")) {
+ int size = f.size();
+ const uint patchSize = 1344;
+
+ if ((size == 5684) || (size == 5720) || (size == 5727)) {
+ byte *buf = new byte[patchSize];
+
+ if (f.seek(0x45a) && (f.read(buf, patchSize) == patchSize))
+ ok = static_cast<MidiDriver_Adlib *>(_driver)->loadResource(buf, patchSize);
+
+ delete[] buf;
+ }
+ }
+ }
+
+ if (!ok) {
+ warning("ADLIB: Failed to load patch.003");
+ return -1;
+ }
+
+ return static_cast<MidiDriver_Adlib *>(_driver)->open(getSciVersion() <= SCI_VERSION_0_LATE);
+}
+
+int MidiPlayer_Adlib::getPlayMask(SciVersion soundVersion) {
+ return (soundVersion == SCI_VERSION_0_EARLY) ? 0x01 : 0x04;
+}
+
+MidiPlayer *MidiPlayer_Adlib_create() {
+ return new MidiPlayer_Adlib();
+}
+
+MidiDriver *MidiDriver_Adlib_create() {
+ return new MidiDriver_Adlib(g_system->getMixer());
+}
+
+} // End of namespace Sci
diff --git a/engines/sci/sound/softseq/amiga.cpp b/engines/sci/sound/softseq/amiga.cpp
new file mode 100644
index 0000000000..452768901a
--- /dev/null
+++ b/engines/sci/sound/softseq/amiga.cpp
@@ -0,0 +1,676 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "sound/softsynth/emumidi.h"
+#include "sci/sound/softseq/mididriver.h"
+
+#include "common/file.h"
+#include "common/frac.h"
+#include "common/util.h"
+
+namespace Sci {
+
+/* #define DEBUG */
+
+// Frequencies for every note
+// FIXME Store only one octave
+static const int freq_table[] = {
+ 58, 62, 65, 69, 73, 78, 82, 87,
+ 92, 98, 104, 110, 117, 124, 131, 139,
+ 147, 156, 165, 175, 185, 196, 208, 220,
+ 234, 248, 262, 278, 294, 312, 331, 350,
+ 371, 393, 417, 441, 468, 496, 525, 556,
+ 589, 625, 662, 701, 743, 787, 834, 883,
+ 936, 992, 1051, 1113, 1179, 1250, 1324, 1403,
+ 1486, 1574, 1668, 1767, 1872, 1984, 2102, 2227,
+ 2359, 2500, 2648, 2806, 2973, 3149, 3337, 3535,
+ 3745, 3968, 4204, 4454, 4719, 5000, 5297, 5612,
+ 5946, 6299, 6674, 7071, 7491, 7937, 8408, 8908,
+ 9438, 10000, 10594, 11224, 11892, 12599, 13348, 14142,
+ 14983, 15874, 16817, 17817, 18877, 20000, 21189, 22449,
+ 23784, 25198, 26696, 28284, 29966, 31748, 33635, 35635,
+ 37754, 40000, 42378, 44898, 47568, 50396, 53393, 56568,
+ 59932, 63496, 67271, 71271, 75509, 80000, 84757, 89796
+};
+
+class MidiDriver_Amiga : public MidiDriver_Emulated {
+public:
+ enum {
+ kVoices = 4
+ };
+
+ MidiDriver_Amiga(Audio::Mixer *mixer) : MidiDriver_Emulated(mixer), _playSwitch(true), _masterVolume(15) { }
+ virtual ~MidiDriver_Amiga() { }
+
+ // MidiDriver
+ int open();
+ void close();
+ void send(uint32 b);
+ MidiChannel *allocateChannel() { return NULL; }
+ MidiChannel *getPercussionChannel() { return NULL; }
+
+ // AudioStream
+ bool isStereo() const { return true; }
+ int getRate() const { return _mixer->getOutputRate(); }
+
+ // MidiDriver_Emulated
+ void generateSamples(int16 *buf, int len);
+
+ void setVolume(byte volume);
+ void playSwitch(bool play);
+ virtual uint32 property(int prop, uint32 param);
+
+private:
+ enum {
+ kModeLoop = 1 << 0, // Instrument looping flag
+ kModePitch = 1 << 1 // Instrument pitch changes flag
+ };
+
+ enum {
+ kChannels = 10,
+ kBaseFreq = 20000, // Samplerate of the instrument bank
+ kPanLeft = 91,
+ kPanRight = 164
+ };
+
+ struct Channel {
+ int instrument;
+ int volume;
+ int pan;
+ };
+
+ struct Envelope {
+ int length; // Phase period length in samples
+ int delta; // Velocity delta per period
+ int target; // Target velocity
+ };
+
+ struct Voice {
+ int instrument;
+ int note;
+ int note_velocity;
+ int velocity;
+ int envelope;
+ int envelope_samples; // Number of samples till next envelope event
+ int decay;
+ int looping;
+ int hw_channel;
+ frac_t offset;
+ frac_t rate;
+ };
+
+ struct Instrument {
+ char name[30];
+ int mode;
+ int size; // Size of non-looping part in bytes
+ int loop_size; // Starting offset and size of loop in bytes
+ int transpose; // Transpose value in semitones
+ Envelope envelope[4]; // Envelope
+ int8 *samples;
+ int8 *loop;
+ };
+
+ struct Bank {
+ char name[30];
+ uint size;
+ Instrument *instruments[256];
+ };
+
+ bool _playSwitch;
+ int _masterVolume;
+ int _frequency;
+ Envelope _envDecay;
+ Bank _bank; // Instrument bank
+
+ Channel _channels[MIDI_CHANNELS];
+ /* Internal channels */
+ Voice _voices[kChannels];
+
+ void setEnvelope(Voice *channel, Envelope *envelope, int phase);
+ int interpolate(int8 *samples, frac_t offset);
+ void playInstrument(int16 *dest, Voice *channel, int count);
+ void changeInstrument(int channel, int instrument);
+ void stopChannel(int ch);
+ void stopNote(int ch, int note);
+ void startNote(int ch, int note, int velocity);
+ Instrument *readInstrument(Common::File &file, int *id);
+};
+
+void MidiDriver_Amiga::setEnvelope(Voice *channel, Envelope *envelope, int phase) {
+ channel->envelope = phase;
+ channel->envelope_samples = envelope[phase].length;
+
+ if (phase == 0)
+ channel->velocity = channel->note_velocity / 2;
+ else
+ channel->velocity = envelope[phase - 1].target;
+}
+
+int MidiDriver_Amiga::interpolate(int8 *samples, frac_t offset) {
+ int x = fracToInt(offset);
+ int diff = (samples[x + 1] - samples[x]) << 8;
+
+ return (samples[x] << 8) + fracToInt(diff * (offset & FRAC_LO_MASK));
+}
+
+void MidiDriver_Amiga::playInstrument(int16 *dest, Voice *channel, int count) {
+ int index = 0;
+ int vol = _channels[channel->hw_channel].volume;
+ Instrument *instrument = _bank.instruments[channel->instrument];
+
+ while (1) {
+ /* Available source samples until end of segment */
+ frac_t lin_avail;
+ int seg_end, rem, i, amount;
+ int8 *samples;
+
+ if (channel->looping) {
+ samples = instrument->loop;
+ seg_end = instrument->loop_size;
+ } else {
+ samples = instrument->samples;
+ seg_end = instrument->size;
+ }
+
+ lin_avail = intToFrac(seg_end) - channel->offset;
+
+ rem = count - index;
+
+ /* Amount of destination samples that we will compute this iteration */
+ amount = lin_avail / channel->rate;
+
+ if (lin_avail % channel->rate)
+ amount++;
+
+ if (amount > rem)
+ amount = rem;
+
+ /* Stop at next envelope event */
+ if ((channel->envelope_samples != -1) && (amount > channel->envelope_samples))
+ amount = channel->envelope_samples;
+
+ for (i = 0; i < amount; i++) {
+ dest[index++] = interpolate(samples, channel->offset) * channel->velocity / 64 * channel->note_velocity * vol / (127 * 127);
+ channel->offset += channel->rate;
+ }
+
+ if (channel->envelope_samples != -1)
+ channel->envelope_samples -= amount;
+
+ if (channel->envelope_samples == 0) {
+ Envelope *envelope;
+ int delta, target, velocity;
+
+ if (channel->decay)
+ envelope = &_envDecay;
+ else
+ envelope = &instrument->envelope[channel->envelope];
+
+ delta = envelope->delta;
+ target = envelope->target;
+ velocity = channel->velocity - envelope->delta;
+
+ /* Check whether we have reached the velocity target for the current phase */
+ if ((delta >= 0 && velocity <= target) || (delta < 0 && velocity >= target)) {
+ channel->velocity = target;
+
+ /* Stop note after velocity has dropped to 0 */
+ if (target == 0) {
+ channel->note = -1;
+ break;
+ } else
+ switch (channel->envelope) {
+ case 0:
+ case 2:
+ /* Go to next phase */
+ setEnvelope(channel, instrument->envelope, channel->envelope + 1);
+ break;
+ case 1:
+ case 3:
+ /* Stop envelope */
+ channel->envelope_samples = -1;
+ break;
+ }
+ } else {
+ /* We haven't reached the target yet */
+ channel->envelope_samples = envelope->length;
+ channel->velocity = velocity;
+ }
+ }
+
+ if (index == count)
+ break;
+
+ if (fracToInt(channel->offset) >= seg_end) {
+ if (instrument->mode & kModeLoop) {
+ /* Loop the samples */
+ channel->offset -= intToFrac(seg_end);
+ channel->looping = 1;
+ } else {
+ /* All samples have been played */
+ channel->note = -1;
+ break;
+ }
+ }
+ }
+}
+
+void MidiDriver_Amiga::changeInstrument(int channel, int instrument) {
+#ifdef DEBUG
+ if (_bank.instruments[instrument])
+ printf("[sfx:seq:amiga] Setting channel %i to \"%s\" (%i)\n", channel, _bank.instruments[instrument]->name, instrument);
+ else
+ warning("[sfx:seq:amiga] instrument %i does not exist (channel %i)", instrument, channel);
+#endif
+ _channels[channel].instrument = instrument;
+}
+
+void MidiDriver_Amiga::stopChannel(int ch) {
+ int i;
+
+ /* Start decay phase for note on this hw channel, if any */
+ for (i = 0; i < kChannels; i++)
+ if (_voices[i].note != -1 && _voices[i].hw_channel == ch && !_voices[i].decay) {
+ /* Trigger fast decay envelope */
+ _voices[i].decay = 1;
+ _voices[i].envelope_samples = _envDecay.length;
+ break;
+ }
+}
+
+void MidiDriver_Amiga::stopNote(int ch, int note) {
+ int channel;
+ Instrument *instrument;
+
+ for (channel = 0; channel < kChannels; channel++)
+ if (_voices[channel].note == note && _voices[channel].hw_channel == ch && !_voices[channel].decay)
+ break;
+
+ if (channel == kChannels) {
+#ifdef DEBUG
+ warning("[sfx:seq:amiga] cannot stop note %i on channel %i", note, ch);
+#endif
+ return;
+ }
+
+ instrument = _bank.instruments[_voices[channel].instrument];
+
+ /* Start the envelope phases for note-off if looping is on and envelope is enabled */
+ if ((instrument->mode & kModeLoop) && (instrument->envelope[0].length != 0))
+ setEnvelope(&_voices[channel], instrument->envelope, 2);
+}
+
+void MidiDriver_Amiga::startNote(int ch, int note, int velocity) {
+ Instrument *instrument;
+ int channel;
+
+ if (_channels[ch].instrument < 0 || _channels[ch].instrument > 255) {
+ warning("[sfx:seq:amiga] invalid instrument %i on channel %i", _channels[ch].instrument, ch);
+ return;
+ }
+
+ instrument = _bank.instruments[_channels[ch].instrument];
+
+ if (!instrument) {
+ warning("[sfx:seq:amiga] instrument %i does not exist", _channels[ch].instrument);
+ return;
+ }
+
+ for (channel = 0; channel < kChannels; channel++)
+ if (_voices[channel].note == -1)
+ break;
+
+ if (channel == kChannels) {
+ warning("[sfx:seq:amiga] could not find a free channel");
+ return;
+ }
+
+ stopChannel(ch);
+
+ if (instrument->mode & kModePitch) {
+ int fnote = note + instrument->transpose;
+
+ if (fnote < 0 || fnote > 127) {
+ warning("[sfx:seq:amiga] illegal note %i\n", fnote);
+ return;
+ }
+
+ /* Compute rate for note */
+ _voices[channel].rate = doubleToFrac(freq_table[fnote] / (double) _frequency);
+ } else
+ _voices[channel].rate = doubleToFrac(kBaseFreq / (double) _frequency);
+
+ _voices[channel].instrument = _channels[ch].instrument;
+ _voices[channel].note = note;
+ _voices[channel].note_velocity = velocity;
+
+ if ((instrument->mode & kModeLoop) && (instrument->envelope[0].length != 0))
+ setEnvelope(&_voices[channel], instrument->envelope, 0);
+ else {
+ _voices[channel].velocity = 64;
+ _voices[channel].envelope_samples = -1;
+ }
+
+ _voices[channel].offset = 0;
+ _voices[channel].hw_channel = ch;
+ _voices[channel].decay = 0;
+ _voices[channel].looping = 0;
+}
+
+MidiDriver_Amiga::Instrument *MidiDriver_Amiga::readInstrument(Common::File &file, int *id) {
+ Instrument *instrument;
+ byte header[61];
+ int size;
+ int seg_size[3];
+ int loop_offset;
+ int i;
+
+ if (file.read(header, 61) < 61) {
+ warning("[sfx:seq:amiga] failed to read instrument header");
+ return NULL;
+ }
+
+ instrument = new Instrument;
+
+ seg_size[0] = READ_BE_UINT16(header + 35) * 2;
+ seg_size[1] = READ_BE_UINT16(header + 41) * 2;
+ seg_size[2] = READ_BE_UINT16(header + 47) * 2;
+
+ instrument->mode = header[33];
+ instrument->transpose = (int8) header[34];
+ for (i = 0; i < 4; i++) {
+ int length = (int8) header[49 + i];
+
+ if (length == 0 && i > 0)
+ length = 256;
+
+ instrument->envelope[i].length = length * _frequency / 60;
+ instrument->envelope[i].delta = (int8)header[53 + i];
+ instrument->envelope[i].target = header[57 + i];
+ }
+ /* Final target must be 0 */
+ instrument->envelope[3].target = 0;
+
+ loop_offset = READ_BE_UINT32(header + 37) & ~1;
+ size = seg_size[0] + seg_size[1] + seg_size[2];
+
+ *id = READ_BE_UINT16(header);
+
+ strncpy(instrument->name, (char *) header + 2, 29);
+ instrument->name[29] = 0;
+#ifdef DEBUG
+ printf("[sfx:seq:amiga] Reading instrument %i: \"%s\" (%i bytes)\n",
+ *id, instrument->name, size);
+ printf(" Mode: %02x\n", instrument->mode);
+ printf(" Looping: %s\n", instrument->mode & kModeLoop ? "on" : "off");
+ printf(" Pitch changes: %s\n", instrument->mode & kModePitch ? "on" : "off");
+ printf(" Segment sizes: %i %i %i\n", seg_size[0], seg_size[1], seg_size[2]);
+ printf(" Segment offsets: 0 %i %i\n", loop_offset, read_int32(header + 43));
+#endif
+ instrument->samples = (int8 *) malloc(size + 1);
+ if (file.read(instrument->samples, size) < (unsigned int)size) {
+ warning("[sfx:seq:amiga] failed to read instrument samples");
+ free(instrument->samples);
+ delete instrument;
+ return NULL;
+ }
+
+ if (instrument->mode & kModeLoop) {
+ if (loop_offset + seg_size[1] > size) {
+#ifdef DEBUG
+ warning("[sfx:seq:amiga] looping samples extend %i bytes past end of sample block",
+ loop_offset + seg_size[1] - size);
+#endif
+ seg_size[1] = size - loop_offset;
+ }
+
+ if (seg_size[1] < 0) {
+ warning("[sfx:seq:amiga] invalid looping point");
+ free(instrument->samples);
+ delete instrument;
+ return NULL;
+ }
+
+ instrument->size = seg_size[0];
+ instrument->loop_size = seg_size[1];
+
+ instrument->loop = (int8*)malloc(instrument->loop_size + 1);
+ memcpy(instrument->loop, instrument->samples + loop_offset, instrument->loop_size);
+
+ instrument->samples[instrument->size] = instrument->loop[0];
+ instrument->loop[instrument->loop_size] = instrument->loop[0];
+ } else {
+ instrument->loop = NULL;
+ instrument->size = size;
+ instrument->samples[instrument->size] = 0;
+ }
+
+ return instrument;
+}
+
+uint32 MidiDriver_Amiga::property(int prop, uint32 param) {
+ switch(prop) {
+ case MIDI_PROP_MASTER_VOLUME:
+ if (param != 0xffff)
+ _masterVolume = param;
+ return _masterVolume;
+ default:
+ break;
+ }
+ return 0;
+}
+
+int MidiDriver_Amiga::open() {
+ _frequency = _mixer->getOutputRate();
+ _envDecay.length = _frequency / (32 * 64);
+ _envDecay.delta = 1;
+ _envDecay.target = 0;
+
+ Common::File file;
+ byte header[40];
+
+ if (!file.open("bank.001")) {
+ warning("[sfx:seq:amiga] file bank.001 not found");
+ return Common::kUnknownError;
+ }
+
+ if (file.read(header, 40) < 40) {
+ warning("[sfx:seq:amiga] failed to read header of file bank.001");
+ return Common::kUnknownError;
+ }
+
+ for (uint i = 0; i < 256; i++)
+ _bank.instruments[i] = NULL;
+
+ for (uint i = 0; i < kChannels; i++) {
+ _voices[i].note = -1;
+ _voices[i].hw_channel = 0;
+ }
+
+ for (uint i = 0; i < MIDI_CHANNELS; i++) {
+ _channels[i].instrument = -1;
+ _channels[i].volume = 127;
+ _channels[i].pan = (i % 4 == 0 || i % 4 == 3 ? kPanLeft : kPanRight);
+ }
+
+ _bank.size = READ_BE_UINT16(header + 38);
+ strncpy(_bank.name, (char *) header + 8, 29);
+ _bank.name[29] = 0;
+#ifdef DEBUG
+ printf("[sfx:seq:amiga] Reading %i instruments from bank \"%s\"\n", _bank.size, _bank.name);
+#endif
+
+ for (uint i = 0; i < _bank.size; i++) {
+ int id;
+ Instrument *instrument = readInstrument(file, &id);
+
+ if (!instrument) {
+ warning("[sfx:seq:amiga] failed to read bank.001");
+ return Common::kUnknownError;
+ }
+
+ if (id < 0 || id > 255) {
+ warning("[sfx:seq:amiga] Error: instrument ID out of bounds");
+ return Common::kUnknownError;
+ }
+
+ _bank.instruments[id] = instrument;
+ }
+
+ MidiDriver_Emulated::open();
+
+ _mixer->playInputStream(Audio::Mixer::kMusicSoundType, &_mixerSoundHandle, this, -1, _mixer->kMaxChannelVolume, 0, false);
+
+ return Common::kNoError;
+}
+
+void MidiDriver_Amiga::close() {
+ _mixer->stopHandle(_mixerSoundHandle);
+
+ for (uint i = 0; i < _bank.size; i++) {
+ if (_bank.instruments[i]) {
+ if (_bank.instruments[i]->loop)
+ free(_bank.instruments[i]->loop);
+ free(_bank.instruments[i]->samples);
+ delete _bank.instruments[i];
+ }
+ }
+}
+
+void MidiDriver_Amiga::playSwitch(bool play) {
+ _playSwitch = play;
+}
+
+void MidiDriver_Amiga::setVolume(byte volume_) {
+ _masterVolume = volume_;
+}
+
+void MidiDriver_Amiga::send(uint32 b) {
+ byte command = b & 0xf0;
+ byte channel = b & 0xf;
+ byte op1 = (b >> 8) & 0xff;
+ byte op2 = (b >> 16) & 0xff;
+
+ switch (command) {
+ case 0x80:
+ stopNote(channel, op1);
+ break;
+ case 0x90:
+ if (op2 > 0)
+ startNote(channel, op1, op2);
+ else
+ stopNote(channel, op1);
+ break;
+ case 0xb0:
+ switch (op1) {
+ case 0x07:
+ _channels[channel].volume = op2;
+ break;
+ case 0x0a:
+#ifdef DEBUG
+ warning("[sfx:seq:amiga] ignoring pan 0x%02x event for channel %i", op2, channel);
+#endif
+ break;
+ case 0x7b:
+ stopChannel(channel);
+ break;
+ default:
+ warning("[sfx:seq:amiga] unknown control event 0x%02x", op1);
+ }
+ break;
+ case 0xc0:
+ changeInstrument(channel, op1);
+ break;
+ default:
+ warning("[sfx:seq:amiga] unknown event %02x", command);
+ }
+}
+
+void MidiDriver_Amiga::generateSamples(int16 *data, int len) {
+ if (len == 0)
+ return;
+
+ int16 *buffers = (int16*)malloc(len * 2 * kChannels);
+
+ memset(buffers, 0, len * 2 * kChannels);
+
+ /* Generate samples for all notes */
+ for (int i = 0; i < kChannels; i++)
+ if (_voices[i].note >= 0)
+ playInstrument(buffers + i * len, &_voices[i], len);
+
+ if (isStereo()) {
+ for (int j = 0; j < len; j++) {
+ int mixedl = 0, mixedr = 0;
+
+ /* Mix and pan */
+ for (int i = 0; i < kChannels; i++) {
+ mixedl += buffers[i * len + j] * (256 - _channels[_voices[i].hw_channel].pan);
+ mixedr += buffers[i * len + j] * _channels[_voices[i].hw_channel].pan;
+ }
+
+ /* Adjust volume */
+ data[2 * j] = mixedl * _masterVolume >> 13;
+ data[2 * j + 1] = mixedr * _masterVolume >> 13;
+ }
+ } else {
+ for (int j = 0; j < len; j++) {
+ int mixed = 0;
+
+ /* Mix */
+ for (int i = 0; i < kChannels; i++)
+ mixed += buffers[i * len + j];
+
+ /* Adjust volume */
+ data[j] = mixed * _masterVolume >> 6;
+ }
+ }
+
+ free(buffers);
+}
+
+class MidiPlayer_Amiga : public MidiPlayer {
+public:
+ MidiPlayer_Amiga() { _driver = new MidiDriver_Amiga(g_system->getMixer()); }
+ int getPlayMask(SciVersion soundVersion);
+ int getPolyphony() const { return MidiDriver_Amiga::kVoices; }
+ bool hasRhythmChannel() const { return false; }
+ void setVolume(byte volume) { static_cast<MidiDriver_Amiga *>(_driver)->setVolume(volume); }
+ void playSwitch(bool play) { static_cast<MidiDriver_Amiga *>(_driver)->playSwitch(play); }
+ void loadInstrument(int idx, byte *data);
+};
+
+MidiPlayer *MidiPlayer_Amiga_create() {
+ return new MidiPlayer_Amiga();
+}
+
+int MidiPlayer_Amiga::getPlayMask(SciVersion soundVersion) {
+ if (soundVersion == SCI_VERSION_0_EARLY)
+ error("No amiga support for sci0early");
+
+ return 0x40;
+}
+
+} // End of namespace Sci
diff --git a/engines/sci/sound/softseq/mididriver.h b/engines/sci/sound/softseq/mididriver.h
new file mode 100644
index 0000000000..df0532d732
--- /dev/null
+++ b/engines/sci/sound/softseq/mididriver.h
@@ -0,0 +1,110 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef SCI_SFX_SOFTSEQ_MIDIDRIVER_H
+#define SCI_SFX_SOFTSEQ_MIDIDRIVER_H
+
+#include "sci/sci.h"
+#include "sound/mididrv.h"
+#include "sound/softsynth/emumidi.h"
+#include "common/error.h"
+
+namespace Sci {
+
+class ResourceManager;
+
+enum {
+ MIDI_CHANNELS = 16,
+ MIDI_PROP_MASTER_VOLUME = 0
+};
+
+
+#define MIDI_RHYTHM_CHANNEL 9
+
+/* Special SCI sound stuff */
+
+#define SCI_MIDI_TIME_EXPANSION_PREFIX 0xF8
+#define SCI_MIDI_TIME_EXPANSION_LENGTH 240
+
+#define SCI_MIDI_EOT 0xFC
+#define SCI_MIDI_SET_SIGNAL 0xCF
+#define SCI_MIDI_SET_POLYPHONY 0x4B
+#define SCI_MIDI_RESET_ON_SUSPEND 0x4C
+#define SCI_MIDI_CHANNEL_MUTE 0x4E
+#define SCI_MIDI_SET_REVERB 0x50
+#define SCI_MIDI_HOLD 0x52
+#define SCI_MIDI_CUMULATIVE_CUE 0x60
+#define SCI_MIDI_CHANNEL_SOUND_OFF 0x78 /* all-sound-off for Bn */
+#define SCI_MIDI_CHANNEL_NOTES_OFF 0x7B /* all-notes-off for Bn */
+
+#define SCI_MIDI_SET_SIGNAL_LOOP 0x7F
+/* If this is the parameter of 0xCF, the loop point is set here */
+
+#define SCI_MIDI_CONTROLLER(status) ((status & 0xF0) == 0xB0)
+
+class MidiPlayer : public MidiDriver {
+protected:
+ MidiDriver *_driver;
+public:
+ int open() {
+ ResourceManager *resMan = ((SciEngine *)g_engine)->getResourceManager(); // HACK
+ return open(resMan);
+ }
+ virtual int open(ResourceManager *resMan) { return _driver->open(); }
+ virtual void close() { _driver->close(); }
+ virtual void send(uint32 b) { _driver->send(b); }
+ uint32 getBaseTempo() { return _driver->getBaseTempo(); }
+ virtual bool hasRhythmChannel() const = 0;
+ MidiChannel *allocateChannel() { return _driver->allocateChannel(); }
+ MidiChannel *getPercussionChannel() { return _driver->getPercussionChannel(); }
+ void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) { _driver->setTimerCallback(timer_param, timer_proc); }
+
+ virtual int getPlayMask(SciVersion soundVersion) = 0;
+ virtual int getPolyphony() const = 0;
+
+ virtual void setVolume(byte volume) {
+ if(_driver)
+ _driver->property(MIDI_PROP_MASTER_VOLUME, volume);
+ }
+
+ virtual int getVolume() {
+ return _driver ? _driver->property(MIDI_PROP_MASTER_VOLUME, 0xffff) : 0;
+ }
+
+ virtual void playSwitch(bool play) {
+ if (!play) {
+ // Send "All Sound Off" on all channels
+ for (int i = 0; i < MIDI_CHANNELS; ++i)
+ _driver->send(0xb0 + i, SCI_MIDI_CHANNEL_NOTES_OFF, 0);
+ }
+ }
+};
+
+extern MidiPlayer *MidiPlayer_Adlib_create();
+extern MidiPlayer *MidiPlayer_Amiga_create();
+
+} // End of namespace Sci
+
+#endif // SCI_SFX_SOFTSEQ_MIDIDRIVER_H
diff --git a/engines/sci/sound/softseq/pcjr.cpp b/engines/sci/sound/softseq/pcjr.cpp
new file mode 100644
index 0000000000..c7dd2f6528
--- /dev/null
+++ b/engines/sci/sound/softseq/pcjr.cpp
@@ -0,0 +1,209 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "sci/sound/softseq/mididriver.h"
+#include "sci/sound/softseq/pcjr.h"
+
+namespace Sci {
+
+#define FREQUENCY 44100
+#define VOLUME_SHIFT 3
+
+#define BASE_NOTE 129 // A10
+#define BASE_OCTAVE 10 // A10, as I said
+
+const static int freq_table[12] = { // A4 is 440Hz, halftone map is x |-> ** 2^(x/12)
+ 28160, // A10
+ 29834,
+ 31608,
+ 33488,
+ 35479,
+ 37589,
+ 39824,
+ 42192,
+ 44701,
+ 47359,
+ 50175,
+ 53159
+};
+
+static inline int get_freq(int note) {
+ int halftone_delta = note - BASE_NOTE;
+ int oct_diff = ((halftone_delta + BASE_OCTAVE * 12) / 12) - BASE_OCTAVE;
+ int halftone_index = (halftone_delta + (12 * 100)) % 12 ;
+ int freq = (!note) ? 0 : freq_table[halftone_index] / (1 << (-oct_diff));
+
+ return freq;
+}
+
+void MidiDriver_PCJr::send(uint32 b) {
+ byte command = b & 0xff;
+ byte op1 = (b >> 8) & 0xff;
+ byte op2 = (b >> 16) & 0xff;
+ int i;
+ int mapped_chan = -1;
+ int chan_nr = command & 0xf;
+
+ // First, test for channel having been assigned already
+ if (_channels_assigned & (1 << chan_nr)) {
+ // Already assigned this channel number:
+ for (i = 0; i < _channels_nr; i++)
+ if (_chan_nrs[i] == chan_nr) {
+ mapped_chan = i;
+ break;
+ }
+ } else if ((command & 0xe0) == 0x80) {
+ // Assign new channel round-robin
+
+ // Mark channel as unused:
+ if (_chan_nrs[_channel_assigner] >= 0)
+ _channels_assigned &= ~(1 << _chan_nrs[_channel_assigner]);
+
+ // Remember channel:
+ _chan_nrs[_channel_assigner] = chan_nr;
+ // Mark channel as used
+ _channels_assigned |= (1 << _chan_nrs[_channel_assigner]);
+
+ // Save channel for use later in this call:
+ mapped_chan = _channel_assigner;
+ // Round-ropin iterate channel assigner:
+ _channel_assigner = (_channel_assigner + 1) % _channels_nr;
+ }
+
+ if (mapped_chan == -1)
+ return;
+
+ switch (command & 0xf0) {
+
+ case 0x80:
+ if (op1 == _notes[mapped_chan])
+ _notes[mapped_chan] = 0;
+ break;
+
+ case 0x90:
+ if (!op2) {
+ if (op1 == _notes[mapped_chan])
+ _notes[mapped_chan] = 0;
+ } else {
+ _notes[mapped_chan] = op1;
+ _volumes[mapped_chan] = op2;
+ }
+ break;
+
+ case 0xb0:
+ if ((op1 == SCI_MIDI_CHANNEL_NOTES_OFF) || (op1 == SCI_MIDI_CHANNEL_SOUND_OFF))
+ _notes[mapped_chan] = 0;
+ break;
+
+ default:
+ debug(2, "Unused MIDI command %02x %02x %02x", command, op1, op2);
+ break; /* ignore */
+ }
+}
+
+void MidiDriver_PCJr::generateSamples(int16 *data, int len) {
+ int i;
+ int chan;
+ int freq[kMaxChannels];
+
+ for (chan = 0; chan < _channels_nr; chan++)
+ freq[chan] = get_freq(_notes[chan]);
+
+ for (i = 0; i < len; i++) {
+ int16 result = 0;
+
+ for (chan = 0; chan < _channels_nr; chan++)
+ if (_notes[chan]) {
+ int volume = (_global_volume * _volumes[chan])
+ >> VOLUME_SHIFT;
+
+ _freq_count[chan] += freq[chan];
+ while (_freq_count[chan] >= (FREQUENCY << 1))
+ _freq_count[chan] -= (FREQUENCY << 1);
+
+ if (_freq_count[chan] - freq[chan] < 0) {
+ /* Unclean rising edge */
+ int l = volume << 1;
+ result += -volume + (l * _freq_count[chan]) / freq[chan];
+ } else if (_freq_count[chan] >= FREQUENCY
+ && _freq_count[chan] - freq[chan] < FREQUENCY) {
+ /* Unclean falling edge */
+ int l = volume << 1;
+ result += volume - (l * (_freq_count[chan] - FREQUENCY)) / freq[chan];
+ } else {
+ if (_freq_count[chan] < FREQUENCY)
+ result += volume;
+ else
+ result += -volume;
+ }
+ }
+ data[i] = result;
+ }
+}
+
+int MidiDriver_PCJr::open(int channels) {
+ if (_isOpen)
+ return MERR_ALREADY_OPEN;
+
+ if (channels > kMaxChannels)
+ return -1;
+
+ _channels_nr = channels;
+ _global_volume = 100;
+ for (int i = 0; i < _channels_nr; i++) {
+ _volumes[i] = 100;
+ _notes[i] = 0;
+ _freq_count[i] = 0;
+ _chan_nrs[i] = -1;
+ }
+ _channel_assigner = 0;
+ _channels_assigned = 0;
+
+ MidiDriver_Emulated::open();
+
+ _mixer->playInputStream(Audio::Mixer::kPlainSoundType, &_mixerSoundHandle, this, -1);
+
+ return 0;
+}
+
+void MidiDriver_PCJr::close() {
+ _mixer->stopHandle(_mixerSoundHandle);
+}
+
+int MidiPlayer_PCJr::getPlayMask(SciVersion soundVersion) {
+ if (soundVersion == SCI_VERSION_0_EARLY)
+ return 0x10; // FIXME: Not correct
+
+ return 0x10;
+}
+
+int MidiPlayer_PCSpeaker::getPlayMask(SciVersion soundVersion) {
+ if (soundVersion == SCI_VERSION_0_EARLY)
+ return 0x02;
+
+ return 0x20;
+}
+
+} // End of namespace Sci
diff --git a/engines/sci/sound/softseq/pcjr.h b/engines/sci/sound/softseq/pcjr.h
new file mode 100644
index 0000000000..e8b0b9f553
--- /dev/null
+++ b/engines/sci/sound/softseq/pcjr.h
@@ -0,0 +1,84 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "sci/sound/softseq/mididriver.h"
+
+namespace Sci {
+
+class MidiDriver_PCJr : public MidiDriver_Emulated {
+public:
+ friend class MidiPlayer_PCJr;
+
+ enum {
+ kMaxChannels = 3
+ };
+
+ MidiDriver_PCJr(Audio::Mixer *mixer) : MidiDriver_Emulated(mixer) { }
+ ~MidiDriver_PCJr() { }
+
+ // MidiDriver
+ int open() { return open(kMaxChannels); }
+ void close();
+ void send(uint32 b);
+ MidiChannel *allocateChannel() { return NULL; }
+ MidiChannel *getPercussionChannel() { return NULL; }
+
+ // AudioStream
+ bool isStereo() const { return false; }
+ int getRate() const { return _mixer->getOutputRate(); }
+
+ // MidiDriver_Emulated
+ void generateSamples(int16 *buf, int len);
+
+ int open(int channels);
+private:
+ int _channels_nr;
+ int _global_volume; // Base volume
+ int _volumes[kMaxChannels];
+ int _notes[kMaxChannels]; // Current halftone, or 0 if off
+ int _freq_count[kMaxChannels];
+ int _channel_assigner;
+ int _channels_assigned;
+ int _chan_nrs[kMaxChannels];
+};
+
+class MidiPlayer_PCJr : public MidiPlayer {
+public:
+ MidiPlayer_PCJr() { _driver = new MidiDriver_PCJr(g_system->getMixer()); }
+ int open(ResourceManager *resMan) { return static_cast<MidiDriver_PCJr *>(_driver)->open(getPolyphony()); }
+ int getPlayMask(SciVersion soundVersion);
+ int getPolyphony() const { return 3; }
+ bool hasRhythmChannel() const { return false; }
+ void setVolume(byte volume) { static_cast<MidiDriver_PCJr *>(_driver)->_global_volume = volume; }
+};
+
+class MidiPlayer_PCSpeaker : public MidiPlayer_PCJr {
+public:
+ int getPlayMask(SciVersion soundVersion);
+ int getPolyphony() const { return 1; }
+};
+
+} // End of namespace Sci
+
diff --git a/engines/sci/sound/soundcmd.cpp b/engines/sci/sound/soundcmd.cpp
new file mode 100644
index 0000000000..06b87afdfd
--- /dev/null
+++ b/engines/sci/sound/soundcmd.cpp
@@ -0,0 +1,1090 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "sci/sci.h" // for USE_OLD_MUSIC_FUNCTIONS
+
+#ifdef USE_OLD_MUSIC_FUNCTIONS
+#include "sci/sound/iterator/iterator.h" // for SongIteratorStatus
+#endif
+
+#include "sci/sound/music.h"
+#include "sci/sound/soundcmd.h"
+
+namespace Sci {
+
+#define SCI1_SOUND_FLAG_MAY_PAUSE 1 /* Only here for completeness; The interpreter doesn't touch this bit */
+#define SCI1_SOUND_FLAG_SCRIPTED_PRI 2 /* but does touch this */
+
+#ifdef USE_OLD_MUSIC_FUNCTIONS
+#define FROBNICATE_HANDLE(reg) ((reg).segment << 16 | (reg).offset)
+#define DEFROBNICATE_HANDLE(handle) (make_reg((handle >> 16) & 0xffff, handle & 0xffff))
+#endif
+
+#define SOUNDCOMMAND(x) _soundCommands.push_back(new MusicEntryCommand(#x, &SoundCommandParser::x))
+
+#ifdef USE_OLD_MUSIC_FUNCTIONS
+static void script_set_priority(ResourceManager *resMan, SegManager *segMan, SfxState *state, reg_t obj, int priority) {
+ int song_nr = GET_SEL32V(segMan, obj, number);
+ Resource *song = resMan->findResource(ResourceId(kResourceTypeSound, song_nr), 0);
+ int flags = GET_SEL32V(segMan, obj, flags);
+
+ if (priority == -1) {
+ if (song->data[0] == 0xf0)
+ priority = song->data[1];
+ else
+ warning("Attempt to unset song priority when there is no built-in value");
+
+ flags &= ~SCI1_SOUND_FLAG_SCRIPTED_PRI;
+ } else flags |= SCI1_SOUND_FLAG_SCRIPTED_PRI;
+
+ state->sfx_song_renice(FROBNICATE_HANDLE(obj), priority);
+ PUT_SEL32V(segMan, obj, flags, flags);
+}
+
+SongIterator *build_iterator(ResourceManager *resMan, int song_nr, SongIteratorType type, songit_id_t id) {
+ Resource *song = resMan->findResource(ResourceId(kResourceTypeSound, song_nr), 0);
+
+ if (!song)
+ return NULL;
+
+ return songit_new(song->data, song->size, type, id);
+}
+
+void process_sound_events(EngineState *s) { /* Get all sound events, apply their changes to the heap */
+ int result;
+ SongHandle handle;
+ int cue;
+ SegManager *segMan = s->_segMan;
+
+ if (getSciVersion() > SCI_VERSION_01)
+ return;
+ // SCI1 and later explicitly poll for everything
+
+ while ((result = s->_sound.sfx_poll(&handle, &cue))) {
+ reg_t obj = DEFROBNICATE_HANDLE(handle);
+ if (!s->_segMan->isObject(obj)) {
+ warning("Non-object %04x:%04x received sound signal (%d/%d)", PRINT_REG(obj), result, cue);
+ return;
+ }
+
+ switch (result) {
+
+ case SI_LOOP:
+ debugC(2, kDebugLevelSound, "[process-sound] Song %04x:%04x looped (to %d)\n",
+ PRINT_REG(obj), cue);
+ /* PUT_SEL32V(segMan, obj, loops, GET_SEL32V(segMan, obj, loop) - 1);*/
+ PUT_SEL32V(segMan, obj, signal, SIGNAL_OFFSET);
+ break;
+
+ case SI_RELATIVE_CUE:
+ debugC(2, kDebugLevelSound, "[process-sound] Song %04x:%04x received relative cue %d\n",
+ PRINT_REG(obj), cue);
+ PUT_SEL32V(segMan, obj, signal, cue + 0x7f);
+ break;
+
+ case SI_ABSOLUTE_CUE:
+ debugC(2, kDebugLevelSound, "[process-sound] Song %04x:%04x received absolute cue %d\n",
+ PRINT_REG(obj), cue);
+ PUT_SEL32V(segMan, obj, signal, cue);
+ break;
+
+ case SI_FINISHED:
+ debugC(2, kDebugLevelSound, "[process-sound] Song %04x:%04x finished\n",
+ PRINT_REG(obj));
+ PUT_SEL32V(segMan, obj, signal, SIGNAL_OFFSET);
+ PUT_SEL32V(segMan, obj, state, kSoundStopped);
+ break;
+
+ default:
+ warning("Unexpected result from sfx_poll: %d", result);
+ break;
+ }
+ }
+}
+
+#endif
+SoundCommandParser::SoundCommandParser(ResourceManager *resMan, SegManager *segMan, AudioPlayer *audio, SciVersion soundVersion) :
+ _resMan(resMan), _segMan(segMan), _audio(audio), _soundVersion(soundVersion) {
+
+#ifdef USE_OLD_MUSIC_FUNCTIONS
+ // The following hack is needed to ease the change from old to new sound code (because the new sound code does not use SfxState)
+ _state = &((SciEngine *)g_engine)->getEngineState()->_sound; // HACK
+#endif
+
+ #ifndef USE_OLD_MUSIC_FUNCTIONS
+ _music = new SciMusic(_soundVersion);
+ _music->init();
+ #endif
+
+ switch (_soundVersion) {
+ case SCI_VERSION_0_EARLY:
+ case SCI_VERSION_0_LATE:
+ SOUNDCOMMAND(cmdInitSound);
+ SOUNDCOMMAND(cmdPlaySound);
+ SOUNDCOMMAND(cmdDummy);
+ SOUNDCOMMAND(cmdDisposeSound);
+ SOUNDCOMMAND(cmdMuteSound);
+ SOUNDCOMMAND(cmdStopSound);
+ SOUNDCOMMAND(cmdPauseSound);
+ SOUNDCOMMAND(cmdResumeSound);
+ SOUNDCOMMAND(cmdMasterVolume);
+ SOUNDCOMMAND(cmdUpdateSound);
+ SOUNDCOMMAND(cmdFadeSound);
+ SOUNDCOMMAND(cmdGetPolyphony);
+ SOUNDCOMMAND(cmdStopAllSounds);
+ _cmdUpdateCuesIndex = -1;
+ break;
+ case SCI_VERSION_1_EARLY:
+ SOUNDCOMMAND(cmdMasterVolume);
+ SOUNDCOMMAND(cmdMuteSound);
+ SOUNDCOMMAND(cmdDummy);
+ SOUNDCOMMAND(cmdGetPolyphony);
+ SOUNDCOMMAND(cmdUpdateSound);
+ SOUNDCOMMAND(cmdInitSound);
+ SOUNDCOMMAND(cmdDisposeSound);
+ SOUNDCOMMAND(cmdPlaySound);
+ SOUNDCOMMAND(cmdStopSound);
+ SOUNDCOMMAND(cmdPauseSound);
+ SOUNDCOMMAND(cmdFadeSound);
+ SOUNDCOMMAND(cmdUpdateCues);
+ SOUNDCOMMAND(cmdSendMidi);
+ SOUNDCOMMAND(cmdReverb);
+ SOUNDCOMMAND(cmdSetSoundHold);
+ _cmdUpdateCuesIndex = 11;
+ break;
+ case SCI_VERSION_1_LATE:
+ SOUNDCOMMAND(cmdMasterVolume);
+ SOUNDCOMMAND(cmdMuteSound);
+ SOUNDCOMMAND(cmdDummy);
+ SOUNDCOMMAND(cmdGetPolyphony);
+ SOUNDCOMMAND(cmdGetAudioCapability);
+ SOUNDCOMMAND(cmdSuspendSound);
+ SOUNDCOMMAND(cmdInitSound);
+ SOUNDCOMMAND(cmdDisposeSound);
+ SOUNDCOMMAND(cmdPlaySound);
+ SOUNDCOMMAND(cmdStopSound);
+ SOUNDCOMMAND(cmdPauseSound);
+ SOUNDCOMMAND(cmdFadeSound);
+ SOUNDCOMMAND(cmdSetSoundHold);
+ SOUNDCOMMAND(cmdDummy);
+ SOUNDCOMMAND(cmdSetSoundVolume);
+ SOUNDCOMMAND(cmdSetSoundPriority);
+ SOUNDCOMMAND(cmdSetSoundLoop);
+ SOUNDCOMMAND(cmdUpdateCues);
+ SOUNDCOMMAND(cmdSendMidi);
+ SOUNDCOMMAND(cmdReverb);
+ SOUNDCOMMAND(cmdUpdateSound);
+ _cmdUpdateCuesIndex = 17;
+ break;
+ default:
+ warning("Sound command parser: unknown sound version %d", _soundVersion);
+ break;
+ }
+}
+
+SoundCommandParser::~SoundCommandParser() {
+}
+
+reg_t SoundCommandParser::parseCommand(int argc, reg_t *argv, reg_t acc) {
+ uint16 command = argv[0].toUint16();
+ reg_t obj = (argc > 1) ? argv[1] : NULL_REG;
+ int16 value = (argc > 2) ? argv[2].toSint16() : 0;
+ _acc = acc;
+ _argc = argc;
+ _argv = argv;
+
+ if (argc == 6) { // cmdSendMidi
+ byte channel = argv[2].toUint16() & 0xf;
+ byte midiCmd = argv[3].toUint16() & 0xff;
+
+ uint16 controller = argv[4].toUint16();
+ uint16 param = argv[5].toUint16();
+
+ _midiCommand = (channel | midiCmd) | ((uint32)controller << 8) | ((uint32)param << 16);
+ }
+
+ if (command < _soundCommands.size()) {
+ if (command != _cmdUpdateCuesIndex) {
+ //printf("%s, object %04x:%04x\n", _soundCommands[command]->desc, PRINT_REG(obj)); // debug
+ debugC(2, kDebugLevelSound, "%s, object %04x:%04x", _soundCommands[command]->desc, PRINT_REG(obj));
+ }
+
+ (this->*(_soundCommands[command]->sndCmd))(obj, value);
+ } else {
+ warning("Invalid sound command requested (%d), valid range is 0-%d", command, _soundCommands.size() - 1);
+ }
+
+ return _acc;
+}
+
+void SoundCommandParser::cmdInitSound(reg_t obj, int16 value) {
+ if (!obj.segment)
+ return;
+
+ int number = obj.segment ? GET_SEL32V(_segMan, obj, number) : 0;
+
+#ifdef USE_OLD_MUSIC_FUNCTIONS
+
+ SongHandle handle = FROBNICATE_HANDLE(obj);
+
+ if (_soundVersion != SCI_VERSION_1_LATE) {
+ if (!obj.segment)
+ return;
+ }
+
+ SongIteratorType type = (_soundVersion <= SCI_VERSION_0_LATE) ? SCI_SONG_ITERATOR_TYPE_SCI0 : SCI_SONG_ITERATOR_TYPE_SCI1;
+
+ if (_soundVersion <= SCI_VERSION_0_LATE) {
+ if (GET_SEL32V(_segMan, obj, nodePtr)) {
+ _state->sfx_song_set_status(handle, SOUND_STATUS_STOPPED);
+ _state->sfx_remove_song(handle);
+ }
+ }
+
+ if (!obj.segment || !_resMan->testResource(ResourceId(kResourceTypeSound, number)))
+ return;
+
+ _state->sfx_add_song(build_iterator(_resMan, number, type, handle), 0, handle, number);
+
+
+ // Notify the engine
+ if (_soundVersion <= SCI_VERSION_0_LATE)
+ PUT_SEL32V(_segMan, obj, state, kSoundInitialized);
+ else
+ PUT_SEL32(_segMan, obj, nodePtr, obj);
+
+ PUT_SEL32(_segMan, obj, handle, obj);
+
+#else
+
+ MusicEntry *newSound = new MusicEntry();
+ newSound->resnum = number;
+ if (number && _resMan->testResource(ResourceId(kResourceTypeSound, number)))
+ newSound->soundRes = new SoundResource(number, _resMan, _soundVersion);
+ else
+ newSound->soundRes = 0;
+
+ newSound->soundObj = obj;
+ newSound->loop = GET_SEL32V(_segMan, obj, loop);
+ newSound->prio = GET_SEL32V(_segMan, obj, pri) & 0xFF;
+ if (_soundVersion >= SCI_VERSION_1_LATE)
+ newSound->volume = CLIP<int>(GET_SEL32V(_segMan, obj, vol), 0, MUSIC_VOLUME_MAX);
+
+ // Check if a track with the same sound object is already playing
+ MusicEntry *oldSound = _music->getSlot(obj);
+ if (oldSound)
+ cmdDisposeSound(obj, value);
+
+ // In SCI1.1 games, sound effects are started from here. If we can find
+ // a relevant audio resource, play it, otherwise switch to synthesized
+ // effects. If the resource exists, play it using map 65535 (sound
+ // effects map)
+
+ if (getSciVersion() >= SCI_VERSION_1_1 && _resMan->testResource(ResourceId(kResourceTypeAudio, number))) {
+ // Found a relevant audio resource, play it
+ int sampleLen;
+ newSound->pStreamAud = _audio->getAudioStream(number, 65535, &sampleLen);
+ newSound->soundType = Audio::Mixer::kSpeechSoundType;
+ } else {
+ if (newSound->soundRes)
+ _music->soundInitSnd(newSound);
+ }
+
+ _music->pushBackSlot(newSound);
+
+ if (newSound->soundRes || newSound->pStreamAud) {
+ // Notify the engine
+ if (_soundVersion <= SCI_VERSION_0_LATE)
+ PUT_SEL32V(_segMan, obj, state, kSoundInitialized);
+ else
+ PUT_SEL32(_segMan, obj, nodePtr, obj);
+
+ PUT_SEL32(_segMan, obj, handle, obj);
+ }
+#endif
+
+}
+
+void SoundCommandParser::cmdPlaySound(reg_t obj, int16 value) {
+ if (!obj.segment)
+ return;
+
+#ifdef USE_OLD_MUSIC_FUNCTIONS
+ SongHandle handle = FROBNICATE_HANDLE(obj);
+
+ if (_soundVersion <= SCI_VERSION_0_LATE) {
+ _state->sfx_song_set_status(handle, SOUND_STATUS_PLAYING);
+ _state->sfx_song_set_loops(handle, GET_SEL32V(_segMan, obj, loop));
+ PUT_SEL32V(_segMan, obj, state, kSoundPlaying);
+ } else if (_soundVersion == SCI_VERSION_1_EARLY) {
+ _state->sfx_song_set_status(handle, SOUND_STATUS_PLAYING);
+ _state->sfx_song_set_loops(handle, GET_SEL32V(_segMan, obj, loop));
+ _state->sfx_song_renice(handle, GET_SEL32V(_segMan, obj, pri));
+ RESTORE_BEHAVIOR rb = (RESTORE_BEHAVIOR) value; /* Too lazy to look up a default value for this */
+ _state->_songlib.setSongRestoreBehavior(handle, rb);
+ PUT_SEL32V(_segMan, obj, signal, 0);
+ } else if (_soundVersion == SCI_VERSION_1_LATE) {
+ int looping = GET_SEL32V(_segMan, obj, loop);
+ //int vol = GET_SEL32V(_segMan, obj, vol);
+ int pri = GET_SEL32V(_segMan, obj, pri);
+ int sampleLen = 0;
+ Song *song = _state->_songlib.findSong(handle);
+ int songNumber = GET_SEL32V(_segMan, obj, number);
+
+ if (GET_SEL32V(_segMan, obj, nodePtr) && (song && songNumber != song->_resourceNum)) {
+ _state->sfx_song_set_status(handle, SOUND_STATUS_STOPPED);
+ _state->sfx_remove_song(handle);
+ PUT_SEL32(_segMan, obj, nodePtr, NULL_REG);
+ }
+
+ if (!GET_SEL32V(_segMan, obj, nodePtr) && obj.segment) {
+ // In SCI1.1 games, sound effects are started from here. If we can find
+ // a relevant audio resource, play it, otherwise switch to synthesized
+ // effects. If the resource exists, play it using map 65535 (sound
+ // effects map)
+ if (_resMan->testResource(ResourceId(kResourceTypeAudio, songNumber)) &&
+ getSciVersion() >= SCI_VERSION_1_1) {
+ // Found a relevant audio resource, play it
+ _audio->stopAudio();
+ warning("Initializing audio resource instead of requested sound resource %d", songNumber);
+ sampleLen = _audio->startAudio(65535, songNumber);
+ // Also create iterator, that will fire SI_FINISHED event, when the sound is done playing
+ _state->sfx_add_song(new_timer_iterator(sampleLen), 0, handle, songNumber);
+ } else {
+ if (!_resMan->testResource(ResourceId(kResourceTypeSound, songNumber))) {
+ warning("Could not open song number %d", songNumber);
+ // Send a "stop handle" event so that the engine won't wait forever here
+ _state->sfx_song_set_status(handle, SOUND_STATUS_STOPPED);
+ PUT_SEL32V(_segMan, obj, signal, SIGNAL_OFFSET);
+ return;
+ }
+ debugC(2, kDebugLevelSound, "Initializing song number %d\n", songNumber);
+ _state->sfx_add_song(build_iterator(_resMan, songNumber, SCI_SONG_ITERATOR_TYPE_SCI1,
+ handle), 0, handle, songNumber);
+ }
+
+ PUT_SEL32(_segMan, obj, nodePtr, obj);
+ PUT_SEL32(_segMan, obj, handle, obj);
+ }
+
+ if (obj.segment) {
+ _state->sfx_song_set_status(handle, SOUND_STATUS_PLAYING);
+ _state->sfx_song_set_loops(handle, looping);
+ _state->sfx_song_renice(handle, pri);
+ PUT_SEL32V(_segMan, obj, signal, 0);
+ }
+ }
+
+#else
+
+ MusicEntry *musicSlot = _music->getSlot(obj);
+ if (!musicSlot) {
+ warning("cmdPlaySound: Slot not found (%04x:%04x)", PRINT_REG(obj));
+ return;
+ }
+
+ int number = obj.segment ? GET_SEL32V(_segMan, obj, number) : -1;
+
+ if (musicSlot->resnum != number) { // another sound loaded into struct
+ cmdDisposeSound(obj, value);
+ cmdInitSound(obj, value);
+ // Find slot again :)
+ musicSlot = _music->getSlot(obj);
+ }
+ int16 loop = GET_SEL32V(_segMan, obj, loop);
+ debugC(2, kDebugLevelSound, "cmdPlaySound: resource number %d, loop %d", number, loop);
+
+ PUT_SEL32(_segMan, obj, handle, obj);
+
+ if (_soundVersion >= SCI_VERSION_1_EARLY) {
+ PUT_SEL32(_segMan, obj, nodePtr, obj);
+ PUT_SEL32V(_segMan, obj, min, 0);
+ PUT_SEL32V(_segMan, obj, sec, 0);
+ PUT_SEL32V(_segMan, obj, frame, 0);
+ PUT_SEL32V(_segMan, obj, signal, 0);
+ } else {
+ PUT_SEL32V(_segMan, obj, state, kSoundPlaying);
+ }
+
+ musicSlot->loop = GET_SEL32V(_segMan, obj, loop);
+ musicSlot->prio = GET_SEL32V(_segMan, obj, priority);
+ if (_soundVersion >= SCI_VERSION_1_LATE)
+ musicSlot->volume = GET_SEL32V(_segMan, obj, vol);
+ _music->soundPlay(musicSlot);
+
+#endif
+
+}
+
+void SoundCommandParser::cmdDummy(reg_t obj, int16 value) {
+ warning("cmdDummy invoked"); // not supposed to occur
+}
+
+#ifdef USE_OLD_MUSIC_FUNCTIONS
+void SoundCommandParser::changeSoundStatus(reg_t obj, int newStatus) {
+ SongHandle handle = FROBNICATE_HANDLE(obj);
+ if (obj.segment) {
+ _state->sfx_song_set_status(handle, newStatus);
+ if (_soundVersion <= SCI_VERSION_0_LATE)
+ PUT_SEL32V(_segMan, obj, state, newStatus);
+ }
+}
+#endif
+
+void SoundCommandParser::cmdDisposeSound(reg_t obj, int16 value) {
+ if (!obj.segment)
+ return;
+
+#ifdef USE_OLD_MUSIC_FUNCTIONS
+ SongHandle handle = FROBNICATE_HANDLE(obj);
+ changeSoundStatus(obj, SOUND_STATUS_STOPPED);
+
+ if (obj.segment) {
+ _state->sfx_remove_song(handle);
+
+ if (_soundVersion <= SCI_VERSION_0_LATE)
+ PUT_SEL32V(_segMan, obj, handle, 0x0000);
+ }
+
+#else
+
+ MusicEntry *musicSlot = _music->getSlot(obj);
+ if (!musicSlot) {
+ warning("cmdDisposeSound: Slot not found (%04x:%04x)", PRINT_REG(obj));
+ return;
+ }
+
+ cmdStopSound(obj, value);
+
+ _music->soundKill(musicSlot);
+ if (_soundVersion >= SCI_VERSION_1_EARLY)
+ PUT_SEL32(_segMan, obj, nodePtr, NULL_REG);
+ else
+ PUT_SEL32V(_segMan, obj, state, kSoundStopped);
+#endif
+}
+
+void SoundCommandParser::cmdStopSound(reg_t obj, int16 value) {
+ if (!obj.segment)
+ return;
+
+#ifdef USE_OLD_MUSIC_FUNCTIONS
+ changeSoundStatus(obj, SOUND_STATUS_STOPPED);
+
+ if (_soundVersion >= SCI_VERSION_1_EARLY)
+ PUT_SEL32V(_segMan, obj, signal, SIGNAL_OFFSET);
+#else
+ MusicEntry *musicSlot = _music->getSlot(obj);
+ if (!musicSlot) {
+ warning("cmdStopSound: Slot not found (%04x:%04x)", PRINT_REG(obj));
+ return;
+ }
+
+ PUT_SEL32V(_segMan, obj, handle, 0);
+ if (_soundVersion <= SCI_VERSION_0_LATE)
+ PUT_SEL32V(_segMan, obj, state, kSoundStopped);
+ else
+ PUT_SEL32V(_segMan, obj, signal, SIGNAL_OFFSET);
+
+ musicSlot->dataInc = 0;
+ musicSlot->signal = 0;
+ _music->soundStop(musicSlot);
+#endif
+}
+
+void SoundCommandParser::cmdPauseSound(reg_t obj, int16 value) {
+#ifdef USE_OLD_MUSIC_FUNCTIONS
+ if (!obj.segment)
+ return;
+
+ if (_soundVersion <= SCI_VERSION_0_LATE)
+ changeSoundStatus(obj, SOUND_STATUS_SUSPENDED);
+ else
+ changeSoundStatus(obj, value ? SOUND_STATUS_SUSPENDED : SOUND_STATUS_PLAYING);
+#else
+
+ MusicEntry *musicSlot = NULL;
+ MusicList::iterator slotLoop = NULL;
+ MusicList::iterator listEnd = NULL;
+
+ if (!obj.segment) {
+ // Pausing/Resuming the whole playlist was introduced
+ // in the SCI1 late sound scheme
+ if (_soundVersion <= SCI_VERSION_1_EARLY)
+ return;
+ _music->_mutex.lock();
+ slotLoop = _music->getPlayListStart();
+ listEnd = _music->getPlayListEnd();
+ musicSlot = *slotLoop;
+ _music->_mutex.unlock();
+ } else {
+ _music->_mutex.lock();
+ musicSlot = _music->getSlot(obj);
+ _music->_mutex.unlock();
+ if (!musicSlot) {
+ warning("cmdPauseSound: Slot not found (%04x:%04x)", PRINT_REG(obj));
+ return;
+ }
+ }
+
+ do {
+ if (_soundVersion <= SCI_VERSION_0_LATE) {
+ PUT_SEL32V(_segMan, musicSlot->soundObj, state, kSoundPaused);
+ _music->soundPause(musicSlot);
+ } else {
+ if (value)
+ _music->soundPause(musicSlot);
+ else
+ _music->soundResume(musicSlot);
+ }
+
+ if (slotLoop) {
+ if (slotLoop == listEnd) {
+ break;
+ } else {
+ _music->_mutex.lock();
+ musicSlot = *(slotLoop++);
+ _music->_mutex.unlock();
+ }
+ }
+ } while (slotLoop);
+
+#endif
+}
+
+void SoundCommandParser::cmdResumeSound(reg_t obj, int16 value) {
+ // SCI0 only command
+
+ if (!obj.segment)
+ return;
+
+#ifdef USE_OLD_MUSIC_FUNCTIONS
+ changeSoundStatus(obj, SOUND_STATUS_PLAYING);
+#else
+ MusicEntry *musicSlot = _music->getSlot(obj);
+ if (!musicSlot) {
+ warning("cmdResumeSound: Slot not found (%04x:%04x)", PRINT_REG(obj));
+ return;
+ }
+
+ PUT_SEL32V(_segMan, musicSlot->soundObj, state, kSoundPlaying);
+ _music->soundResume(musicSlot);
+#endif
+}
+
+void SoundCommandParser::cmdMuteSound(reg_t obj, int16 value) {
+#ifndef USE_OLD_MUSIC_FUNCTIONS
+ if (_argc > 1) // the first parameter is the sound command
+ _music->soundSetSoundOn(obj.toUint16());
+ _acc = make_reg(0, _music->soundGetSoundOn());
+#endif
+}
+
+void SoundCommandParser::cmdMasterVolume(reg_t obj, int16 value) {
+#ifdef USE_OLD_MUSIC_FUNCTIONS
+ if (obj != SIGNAL_REG)
+ _state->sfx_setVolume(obj.toSint16());
+
+ _acc = make_reg(0, _state->sfx_getVolume());
+#else
+ debugC(2, kDebugLevelSound, "cmdMasterVolume: %d", value);
+ if (_argc > 1) // the first parameter is the sound command
+ _music->soundSetMasterVolume(obj.toSint16());
+ _acc = make_reg(0, _music->soundGetMasterVolume());
+#endif
+}
+
+void SoundCommandParser::cmdFadeSound(reg_t obj, int16 value) {
+ if (!obj.segment)
+ return;
+
+#ifdef USE_OLD_MUSIC_FUNCTIONS
+ SongHandle handle = FROBNICATE_HANDLE(obj);
+ if (_soundVersion != SCI_VERSION_1_LATE) {
+ /*s->sound_server->command(s, SOUND_COMMAND_FADE_HANDLE, obj, 120);*/ /* Fade out in 2 secs */
+ /* FIXME: The next couple of lines actually STOP the handle, rather
+ ** than fading it! */
+ _state->sfx_song_set_status(handle, SOUND_STATUS_STOPPED);
+ if (_soundVersion <= SCI_VERSION_0_LATE)
+ PUT_SEL32V(_segMan, obj, state, SOUND_STATUS_STOPPED);
+ PUT_SEL32V(_segMan, obj, signal, SIGNAL_OFFSET);
+ } else {
+ fade_params_t fade;
+ fade.final_volume = _argv[2].toUint16();
+ fade.ticks_per_step = _argv[3].toUint16();
+ fade.step_size = _argv[4].toUint16();
+ fade.action = _argv[5].toUint16() ?
+ FADE_ACTION_FADE_AND_STOP :
+ FADE_ACTION_FADE_AND_CONT;
+
+ _state->sfx_song_set_fade(handle, &fade);
+
+ /* FIXME: The next couple of lines actually STOP the handle, rather
+ ** than fading it! */
+ if (_argv[5].toUint16()) {
+ PUT_SEL32V(_segMan, obj, signal, SIGNAL_OFFSET);
+ _state->sfx_song_set_status(handle, SOUND_STATUS_STOPPED);
+ } else {
+ // FIXME: Support fade-and-continue. For now, send signal right away.
+ PUT_SEL32V(_segMan, obj, signal, SIGNAL_OFFSET);
+ }
+ }
+#else
+ MusicEntry *musicSlot = _music->getSlot(obj);
+ if (!musicSlot) {
+ warning("cmdFadeSound: Slot not found (%04x:%04x)", PRINT_REG(obj));
+ return;
+ }
+
+ int volume = musicSlot->volume;
+
+ switch (_argc) {
+ case 2: // SCI0
+ // SCI0 fades out all the time and when fadeout is done it will also stop the music from playing
+ musicSlot->fadeTo = 0;
+ musicSlot->fadeStep = -5;
+ musicSlot->fadeTickerStep = 10 * 16667 / _music->soundGetTempo();
+ musicSlot->fadeTicker = 0;
+ break;
+
+ case 5: // Possibly SCI1?!
+ case 6: // SCI 1.1 TODO: find out what additional parameter is
+ musicSlot->fadeTo = CLIP<uint16>(_argv[2].toUint16(), 0, MUSIC_VOLUME_MAX);
+ musicSlot->fadeStep = volume > _argv[2].toUint16() ? -_argv[4].toUint16() : _argv[4].toUint16();
+ musicSlot->fadeTickerStep = _argv[3].toUint16() * 16667 / _music->soundGetTempo();
+ musicSlot->fadeTicker = 0;
+ break;
+
+ default:
+ error("cmdFadeSound: unsupported argc %d", _argc);
+ }
+
+ debugC(2, kDebugLevelSound, "cmdFadeSound: to %d, step %d, ticker %d", musicSlot->fadeTo, musicSlot->fadeStep, musicSlot->fadeTickerStep);
+#endif
+}
+
+void SoundCommandParser::cmdGetPolyphony(reg_t obj, int16 value) {
+#ifdef USE_OLD_MUSIC_FUNCTIONS
+ _acc = make_reg(0, _state->sfx_get_player_polyphony());
+#else
+ _acc = make_reg(0, _music->soundGetVoices()); // Get the number of voices
+#endif
+}
+
+void SoundCommandParser::cmdUpdateSound(reg_t obj, int16 value) {
+ if (!obj.segment)
+ return;
+
+#ifdef USE_OLD_MUSIC_FUNCTIONS
+ SongHandle handle = FROBNICATE_HANDLE(obj);
+ if (_soundVersion <= SCI_VERSION_0_LATE && obj.segment) {
+ _state->sfx_song_set_loops(handle, GET_SEL32V(_segMan, obj, loop));
+ script_set_priority(_resMan, _segMan, _state, obj, GET_SEL32V(_segMan, obj, pri));
+ }
+#else
+ MusicEntry *musicSlot = _music->getSlot(obj);
+ if (!musicSlot) {
+ warning("cmdUpdateSound: Slot not found (%04x:%04x)", PRINT_REG(obj));
+ return;
+ }
+
+ musicSlot->loop = GET_SEL32V(_segMan, obj, loop);
+ int16 objVol = CLIP<int>(GET_SEL32V(_segMan, obj, vol), 0, 255);
+ if (objVol != musicSlot->volume)
+ _music->soundSetVolume(musicSlot, objVol);
+ uint32 objPrio = GET_SEL32V(_segMan, obj, pri);
+ if (objPrio != musicSlot->prio)
+ _music->soundSetPriority(musicSlot, objPrio);
+
+#endif
+}
+
+void SoundCommandParser::cmdUpdateCues(reg_t obj, int16 value) {
+ if (!obj.segment)
+ return;
+
+#ifdef USE_OLD_MUSIC_FUNCTIONS
+ int signal = 0;
+ int min = 0;
+ int sec = 0;
+ int frame = 0;
+ int result = SI_LOOP; // small hack
+ SongHandle handle = FROBNICATE_HANDLE(obj);
+
+ while (result == SI_LOOP)
+ result = _state->sfx_poll_specific(handle, &signal);
+
+ switch (result) {
+ case SI_ABSOLUTE_CUE:
+ debugC(2, kDebugLevelSound, "--- [CUE] %04x:%04x Absolute Cue: %d\n",
+ PRINT_REG(obj), signal);
+ debugC(2, kDebugLevelSound, "abs-signal %04X\n", signal);
+ PUT_SEL32V(_segMan, obj, signal, signal);
+ break;
+
+ case SI_RELATIVE_CUE:
+ debugC(2, kDebugLevelSound, "--- [CUE] %04x:%04x Relative Cue: %d\n",
+ PRINT_REG(obj), signal);
+
+ /* FIXME to match commented-out semantics
+ * below, with proper storage of dataInc and
+ * signal in the iterator code. */
+ PUT_SEL32V(_segMan, obj, dataInc, signal);
+ debugC(2, kDebugLevelSound, "rel-signal %04X\n", signal);
+ if (_soundVersion == SCI_VERSION_1_EARLY)
+ PUT_SEL32V(_segMan, obj, signal, signal);
+ else
+ PUT_SEL32V(_segMan, obj, signal, signal + 127);
+ break;
+
+ case SI_FINISHED:
+ debugC(2, kDebugLevelSound, "--- [FINISHED] %04x:%04x\n", PRINT_REG(obj));
+ PUT_SEL32V(_segMan, obj, signal, SIGNAL_OFFSET);
+ break;
+
+ case SI_LOOP:
+ break; // Doesn't happen
+ }
+
+ //switch (signal) {
+ //case 0x00:
+ // if (dataInc!=GET_SEL32V(segMan, obj, dataInc)) {
+ // PUT_SEL32V(segMan, obj, dataInc, dataInc);
+ // PUT_SEL32V(segMan, obj, signal, dataInc+0x7f);
+ // } else {
+ // PUT_SEL32V(segMan, obj, signal, signal);
+ // }
+ // break;
+ //case 0xFF: // May be unnecessary
+ // s->_sound.sfx_song_set_status(handle, SOUND_STATUS_STOPPED);
+ // break;
+ //default :
+ // if (dataInc!=GET_SEL32V(segMan, obj, dataInc)) {
+ // PUT_SEL32V(segMan, obj, dataInc, dataInc);
+ // PUT_SEL32V(segMan, obj, signal, dataInc + 0x7f);
+ // } else {
+ // PUT_SEL32V(segMan, obj, signal, signal);
+ // }
+ // break;
+ //}
+
+ if (_soundVersion == SCI_VERSION_1_EARLY) {
+ PUT_SEL32V(_segMan, obj, min, min);
+ PUT_SEL32V(_segMan, obj, sec, sec);
+ PUT_SEL32V(_segMan, obj, frame, frame);
+ }
+#else
+ MusicEntry *musicSlot = _music->getSlot(obj);
+ if (!musicSlot) {
+ warning("cmdUpdateCues: Slot not found (%04x:%04x)", PRINT_REG(obj));
+ return;
+ }
+
+ if (musicSlot->pStreamAud) {
+ // Update digital sound effect slots here
+ Audio::Mixer *mixer = g_system->getMixer();
+
+ uint currentLoopCounter = musicSlot->pStreamAud->getNumPlayedLoops();
+ if (currentLoopCounter != musicSlot->sampleLoopCounter) {
+ // during last time we looped at least one time, update loop accordingly
+ musicSlot->loop -= currentLoopCounter - musicSlot->sampleLoopCounter;
+ musicSlot->sampleLoopCounter = currentLoopCounter;
+ }
+ if (!mixer->isSoundHandleActive(musicSlot->hCurrentAud)) {
+ cmdStopSound(obj, 0);
+ } else {
+ musicSlot->ticker = (uint16)(mixer->getSoundElapsedTime(musicSlot->hCurrentAud) * 0.06);
+ }
+ // We get a flag from MusicEntry::doFade() here to set volume for the stream
+ if (musicSlot->fadeSetVolume) {
+ mixer->setChannelVolume(musicSlot->hCurrentAud, musicSlot->volume);
+ musicSlot->fadeSetVolume = false;
+ }
+ } else {
+ switch (musicSlot->signal) {
+ case 0:
+ if (musicSlot->dataInc != GET_SEL32V(_segMan, obj, dataInc)) {
+ PUT_SEL32V(_segMan, obj, dataInc, musicSlot->dataInc);
+ PUT_SEL32V(_segMan, obj, signal, musicSlot->dataInc + 127);
+ }
+ break;
+ case SIGNAL_OFFSET:
+ PUT_SEL32V(_segMan, obj, signal, SIGNAL_OFFSET);
+ break;
+ default:
+ // Sync the signal of the sound object
+ PUT_SEL32V(_segMan, obj, signal, musicSlot->signal);
+ break;
+ }
+ }
+
+ if (musicSlot->fadeCompleted) {
+ musicSlot->fadeCompleted = false;
+ if (_soundVersion <= SCI_VERSION_0_LATE) {
+ cmdStopSound(obj, 0);
+ } else {
+ PUT_SEL32V(_segMan, obj, signal, SIGNAL_OFFSET);
+ }
+ }
+
+ // Sync loop selector for SCI0
+ if (_soundVersion <= SCI_VERSION_0_LATE)
+ PUT_SEL32V(_segMan, obj, loop, musicSlot->loop);
+
+ musicSlot->signal = 0;
+
+ if (_soundVersion >= SCI_VERSION_1_EARLY) {
+ PUT_SEL32V(_segMan, obj, min, musicSlot->ticker / 3600);
+ PUT_SEL32V(_segMan, obj, sec, musicSlot->ticker % 3600 / 60);
+ PUT_SEL32V(_segMan, obj, frame, musicSlot->ticker);
+ }
+
+#endif
+}
+
+void SoundCommandParser::cmdSendMidi(reg_t obj, int16 value) {
+#ifdef USE_OLD_MUSIC_FUNCTIONS
+ //SongHandle handle = FROBNICATE_HANDLE(obj);
+ //_state->sfx_send_midi(handle, value, _midiCmd, _controller, _param);
+#else
+ _music->sendMidiCommand(_midiCommand);
+#endif
+}
+
+void SoundCommandParser::cmdReverb(reg_t obj, int16 value) {
+#ifndef USE_OLD_MUSIC_FUNCTIONS
+ _music->setReverb(obj.toUint16() & 0xF);
+#endif
+}
+
+void SoundCommandParser::cmdSetSoundHold(reg_t obj, int16 value) {
+#ifdef USE_OLD_MUSIC_FUNCTIONS
+ SongHandle handle = FROBNICATE_HANDLE(obj);
+ _state->sfx_song_set_hold(handle, value);
+#else
+ MusicEntry *musicSlot = _music->getSlot(obj);
+ if (!musicSlot) {
+ warning("cmdSetSoundHold: Slot not found (%04x:%04x)", PRINT_REG(obj));
+ return;
+ }
+
+ // Set the special hold marker ID where the song should be looped at.
+ musicSlot->hold = value;
+#endif
+}
+
+void SoundCommandParser::cmdGetAudioCapability(reg_t obj, int16 value) {
+ // Tests for digital audio support
+ _acc = make_reg(0, 1);
+}
+
+void SoundCommandParser::cmdStopAllSounds(reg_t obj, int16 value) {
+#ifndef USE_OLD_MUSIC_FUNCTIONS
+ Common::StackLock(_music->_mutex);
+
+ const MusicList::iterator end = _music->getPlayListEnd();
+ for (MusicList::iterator i = _music->getPlayListStart(); i != end; ++i) {
+ if (_soundVersion <= SCI_VERSION_0_LATE)
+ PUT_SEL32V(_segMan, (*i)->soundObj, state, kSoundStopped);
+ else
+ PUT_SEL32V(_segMan, (*i)->soundObj, signal, SIGNAL_OFFSET);
+
+ (*i)->dataInc = 0;
+ _music->soundStop(*i);
+ }
+#endif
+}
+
+void SoundCommandParser::cmdSetSoundVolume(reg_t obj, int16 value) {
+ if (!obj.segment)
+ return;
+
+#ifndef USE_OLD_MUSIC_FUNCTIONS
+ MusicEntry *musicSlot = _music->getSlot(obj);
+ if (!musicSlot) {
+ // Do not throw a warning if the sound can't be found, as in some games
+ // this is called before the actual sound is loaded (e.g. SQ4CD, with the
+ // drum sounds of the energizer bunny at the beginning), so this is normal
+ // behavior
+ //warning("cmdSetSoundVolume: Slot not found (%04x:%04x)", PRINT_REG(obj));
+ return;
+ }
+
+ debugC(2, kDebugLevelSound, "cmdSetSoundVolume: %d", value);
+
+ value = CLIP<int>(value, 0, MUSIC_VOLUME_MAX);
+
+ if (musicSlot->volume != value) {
+ musicSlot->volume = value;
+ _music->soundSetVolume(musicSlot, value);
+ PUT_SEL32V(_segMan, obj, vol, value);
+ }
+#endif
+}
+
+void SoundCommandParser::cmdSetSoundPriority(reg_t obj, int16 value) {
+ if (!obj.segment)
+ return;
+
+#ifdef USE_OLD_MUSIC_FUNCTIONS
+ script_set_priority(_resMan, _segMan, _state, obj, value);
+#else
+ MusicEntry *musicSlot = _music->getSlot(obj);
+ if (!musicSlot) {
+ warning("cmdSetSoundPriority: Slot not found (%04x:%04x)", PRINT_REG(obj));
+ return;
+ }
+
+ if (value == -1) {
+ // Set priority from the song data
+ Resource *song = _resMan->findResource(ResourceId(kResourceTypeSound, musicSlot->resnum), 0);
+ if (song->data[0] == 0xf0)
+ _music->soundSetPriority(musicSlot, song->data[1]);
+ else
+ warning("cmdSetSoundPriority: Attempt to unset song priority when there is no built-in value");
+
+ //pSnd->prio=0;field_15B=0
+ PUT_SEL32V(_segMan, obj, flags, GET_SEL32V(_segMan, obj, flags) & 0xFD);
+ } else {
+ // Scripted priority
+
+ //pSnd->field_15B=1;
+ PUT_SEL32V(_segMan, obj, flags, GET_SEL32V(_segMan, obj, flags) | 2);
+ //DoSOund(0xF,hobj,w)
+ }
+#endif
+}
+
+void SoundCommandParser::cmdSetSoundLoop(reg_t obj, int16 value) {
+ if (!obj.segment)
+ return;
+
+#ifdef USE_OLD_MUSIC_FUNCTIONS
+ if (!GET_SEL32(_segMan, obj, nodePtr).isNull()) {
+ SongHandle handle = FROBNICATE_HANDLE(obj);
+ _state->sfx_song_set_loops(handle, value);
+ }
+#else
+ MusicEntry *musicSlot = _music->getSlot(obj);
+ if (!musicSlot) {
+ // Apparently, it's perfectly normal for a game to call cmdSetSoundLoop
+ // before actually initializing the sound and adding it to the playlist
+ // with cmdInitSound. Usually, it doesn't matter if the game doesn't
+ // request to loop the sound, so in this case, don't throw any warning,
+ // otherwise do, because the sound won't be looped
+ if (value == -1) {
+ warning("cmdSetSoundLoop: Slot not found (%04x:%04x) and the song was requested to be looped", PRINT_REG(obj));
+ } else {
+ // Doesn't really matter
+ }
+ return;
+ }
+ if (value == -1) {
+ musicSlot->loop = 0xFFFF;
+ } else {
+ musicSlot->loop = 1; // actually plays the music once
+ }
+
+ PUT_SEL32V(_segMan, obj, loop, musicSlot->loop);
+#endif
+}
+
+void SoundCommandParser::cmdSuspendSound(reg_t obj, int16 value) {
+ // TODO
+ warning("STUB: cmdSuspendSound");
+}
+
+#ifndef USE_OLD_MUSIC_FUNCTIONS
+
+void SoundCommandParser::updateSci0Cues() {
+ Common::StackLock(_music->_mutex);
+
+ const MusicList::iterator end = _music->getPlayListEnd();
+ for (MusicList::iterator i = _music->getPlayListStart(); i != end; ++i) {
+ // Is the sound stopped, and the sound object updated too? If yes, skip
+ // this sound, as SCI0 only allows one active song
+ if ((*i)->signal == 0 && (*i)->status != kSoundPlaying)
+ continue;
+
+ cmdUpdateCues((*i)->soundObj, 0);
+ }
+}
+
+#endif
+
+void SoundCommandParser::clearPlayList() {
+#ifndef USE_OLD_MUSIC_FUNCTIONS
+ _music->clearPlayList();
+#endif
+}
+
+void SoundCommandParser::syncPlayList(Common::Serializer &s) {
+#ifndef USE_OLD_MUSIC_FUNCTIONS
+ _music->saveLoadWithSerializer(s);
+#endif
+}
+
+void SoundCommandParser::reconstructPlayList(int savegame_version) {
+#ifndef USE_OLD_MUSIC_FUNCTIONS
+ Common::StackLock lock(_music->_mutex);
+
+ _music->resetDriver();
+
+ const MusicList::iterator end = _music->getPlayListEnd();
+ for (MusicList::iterator i = _music->getPlayListStart(); i != end; ++i) {
+ if (savegame_version < 14) {
+ (*i)->dataInc = GET_SEL32V(_segMan, (*i)->soundObj, dataInc);
+ (*i)->signal = GET_SEL32V(_segMan, (*i)->soundObj, signal);
+
+ if (_soundVersion >= SCI_VERSION_1_LATE)
+ (*i)->volume = GET_SEL32V(_segMan, (*i)->soundObj, vol);
+ }
+
+ if ((*i)->resnum && _resMan->testResource(ResourceId(kResourceTypeSound, (*i)->resnum))) {
+ (*i)->soundRes = new SoundResource((*i)->resnum, _resMan, _soundVersion);
+ _music->soundInitSnd(*i);
+ } else {
+ (*i)->soundRes = 0;
+ }
+ if ((*i)->status == kSoundPlaying)
+ cmdPlaySound((*i)->soundObj, 0);
+ }
+
+#endif
+}
+
+void SoundCommandParser::printPlayList(Console *con) {
+#ifndef USE_OLD_MUSIC_FUNCTIONS
+ _music->printPlayList(con);
+#endif
+}
+
+void SoundCommandParser::resetDriver() {
+#ifndef USE_OLD_MUSIC_FUNCTIONS
+ _music->resetDriver();
+#endif
+}
+
+} // End of namespace Sci
diff --git a/engines/sci/sound/soundcmd.h b/engines/sci/sound/soundcmd.h
new file mode 100644
index 0000000000..548dcf3889
--- /dev/null
+++ b/engines/sci/sound/soundcmd.h
@@ -0,0 +1,122 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef SCI_SOUNDCMD_H
+#define SCI_SOUNDCMD_H
+
+#include "sci/sci.h" // for USE_OLD_MUSIC_FUNCTIONS
+
+#include "common/list.h"
+#include "sci/engine/state.h"
+
+namespace Sci {
+
+class Console;
+class SciMusic;
+class SoundCommandParser;
+typedef void (SoundCommandParser::*SoundCommand)(reg_t obj, int16 value);
+
+struct MusicEntryCommand {
+ MusicEntryCommand(const char *d, SoundCommand c) : sndCmd(c), desc(d) {}
+ SoundCommand sndCmd;
+ const char *desc;
+};
+
+class SoundCommandParser {
+public:
+ SoundCommandParser(ResourceManager *resMan, SegManager *segMan, AudioPlayer *audio, SciVersion soundVersion);
+ ~SoundCommandParser();
+
+#ifdef USE_OLD_MUSIC_FUNCTIONS
+ void updateSfxState(SfxState *newState) { _state = newState; }
+#endif
+
+ reg_t parseCommand(int argc, reg_t *argv, reg_t acc);
+ void clearPlayList();
+ void syncPlayList(Common::Serializer &s);
+ void reconstructPlayList(int savegame_version);
+ void printPlayList(Console *con);
+ void resetDriver();
+
+#ifndef USE_OLD_MUSIC_FUNCTIONS
+ /**
+ * Synchronizes the current state of the music list to the rest of the engine, so that
+ * the changes that the sound thread makes to the music are registered with the engine
+ * scripts. In SCI0, we invoke this from kAnimate (which is called very often). SCI01
+ * and later have a specific callback function, cmdUpdateCues, which is called regularly
+ * by the engine scripts themselves, so the engine itself polls for changes to the music
+ */
+ void updateSci0Cues();
+#endif
+
+private:
+ Common::Array<MusicEntryCommand*> _soundCommands;
+ ResourceManager *_resMan;
+ SegManager *_segMan;
+#ifdef USE_OLD_MUSIC_FUNCTIONS
+ SfxState *_state;
+ int _midiCmd, _controller, _param;
+#else
+ SciMusic *_music;
+#endif
+ AudioPlayer *_audio;
+ SciVersion _soundVersion;
+ int _argc;
+ reg_t *_argv; // for cmdFadeSound
+ uint32 _midiCommand; // for cmdSendMidi
+ reg_t _acc;
+ int _cmdUpdateCuesIndex;
+
+ void cmdInitSound(reg_t obj, int16 value);
+ void cmdPlaySound(reg_t obj, int16 value);
+ void cmdDummy(reg_t obj, int16 value);
+ void cmdMuteSound(reg_t obj, int16 value);
+ void cmdPauseSound(reg_t obj, int16 value);
+ void cmdResumeSound(reg_t obj, int16 value);
+ void cmdStopSound(reg_t obj, int16 value);
+ void cmdDisposeSound(reg_t obj, int16 value);
+ void cmdMasterVolume(reg_t obj, int16 value);
+ void cmdFadeSound(reg_t obj, int16 value);
+ void cmdGetPolyphony(reg_t obj, int16 value);
+ void cmdStopAllSounds(reg_t obj, int16 value);
+ void cmdUpdateSound(reg_t obj, int16 value);
+ void cmdUpdateCues(reg_t obj, int16 value);
+ void cmdSendMidi(reg_t obj, int16 value);
+ void cmdReverb(reg_t obj, int16 value);
+ void cmdSetSoundHold(reg_t obj, int16 value);
+ void cmdGetAudioCapability(reg_t obj, int16 value);
+ void cmdSetSoundVolume(reg_t obj, int16 value);
+ void cmdSetSoundPriority(reg_t obj, int16 value);
+ void cmdSetSoundLoop(reg_t obj, int16 value);
+ void cmdSuspendSound(reg_t obj, int16 value);
+
+#ifdef USE_OLD_MUSIC_FUNCTIONS
+ void changeSoundStatus(reg_t obj, int newStatus);
+#endif
+};
+
+} // End of namespace Sci
+
+#endif // SCI_SOUNDCMD_H