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/iterator | |
| 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/iterator')
| -rw-r--r-- | engines/sci/sound/iterator/core.cpp | 1015 | ||||
| -rw-r--r-- | engines/sci/sound/iterator/core.h | 209 | ||||
| -rw-r--r-- | engines/sci/sound/iterator/iterator.cpp | 1707 | ||||
| -rw-r--r-- | engines/sci/sound/iterator/iterator.h | 326 | ||||
| -rw-r--r-- | engines/sci/sound/iterator/iterator_internal.h | 276 | ||||
| -rw-r--r-- | engines/sci/sound/iterator/songlib.cpp | 189 | ||||
| -rw-r--r-- | engines/sci/sound/iterator/songlib.h | 171 | ||||
| -rw-r--r-- | engines/sci/sound/iterator/test-iterator.cpp | 423 |
8 files changed, 4316 insertions, 0 deletions
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); +} |
