diff options
author | Filippos Karapetis | 2010-01-05 01:22:16 +0000 |
---|---|---|
committer | Filippos Karapetis | 2010-01-05 01:22:16 +0000 |
commit | 84cd8d2dc7673bf883945cfdf600d98769817bc6 (patch) | |
tree | 9a57872c63fbf0a144b5fdd463d6bda43aa714df /engines/sci/sound | |
parent | d8c59f5baa386a6baaf62685b794c2531b9cdd64 (diff) | |
download | scummvm-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')
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 = ¬_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 == ¬_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 |