/* 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 #include "sci/include/sfx_timer.h" #include "sci/include/sfx_iterator_internal.h" #include "sci/include/sfx_player.h" #include "sci/sfx/mixer.h" #include "sci/include/sci_midi.h" #include "common/mutex.h" namespace Sci { /*#define DEBUG_SONG_API*/ /*#define DEBUG_CUES*/ #ifdef DEBUG_CUES int sciprintf(char *msg, ...); #endif static sfx_player_t *player = NULL; sfx_pcm_mixer_t *mixer = NULL; static sfx_pcm_device_t *pcm_device = NULL; static sfx_timer_t *timer = NULL; extern sfx_timer_t sfx_timer_scummvm; extern sfx_pcm_device_t sfx_pcm_driver_scummvm; Common::Mutex* callbackMutex; #define MILLION 1000000 int sfx_pcm_available() { return (pcm_device != NULL); } void sfx_reset_player(void) { if (player) player->stop(); } tell_synth_func *sfx_get_player_tell_func(void) { if (player) return player->tell_synth; else return NULL; } int sfx_get_player_polyphony(void) { if (player) return player->polyphony; else return 0; } static long time_minus(GTimeVal t1, GTimeVal t2) { return (t1.tv_sec - t2.tv_sec) * MILLION + (t1.tv_usec - t2.tv_usec); } static GTimeVal time_plus(GTimeVal t1, long delta) { if (delta > 0) t1.tv_usec += delta % MILLION; else t1.tv_usec -= (-delta) % MILLION; t1.tv_sec += delta / MILLION; if (t1.tv_usec > MILLION) { t1.tv_sec++; t1.tv_usec -= MILLION; } return t1; } static void _freeze_time(sfx_state_t *self) { /* Freezes the top song delay time */ GTimeVal ctime; long delta; song_t *song = self->song; sci_get_current_time(&ctime); while (song) { delta = time_minus(song->wakeup_time, ctime); if (delta < 0) delta = 0; song->delay = delta; song = song->next_playing; } } #if 0 // Unreferenced - removed static void _dump_playing_list(sfx_state_t *self, char *msg) { song_t *song = self->song; fprintf(stderr, "[] Song list : [ "); song = *(self->songlib.lib); while (song) { fprintf(stderr, "%08lx:%d ", song->handle, song->status); song = song->next_playing; } fprintf(stderr, "]\n"); fprintf(stderr, "[] Play list (%s) : [ " , msg); while (song) { fprintf(stderr, "%08lx ", song->handle); song = song->next_playing; } fprintf(stderr, "]\n"); } #endif static void _dump_songs(sfx_state_t *self) { #if 0 song_t *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 (player) { fprintf(stderr, "Audio iterator:\n"); player->iterator_message(songit_make_message(0, SIMSG_PRINT(1))); } #endif } static void _thaw_time(sfx_state_t *self) { /* inverse of _freeze_time() */ GTimeVal ctime; song_t *song = self->song; sci_get_current_time(&ctime); while (song) { song->wakeup_time = time_plus(ctime, song->delay); song = song->next_playing; } } static int is_playing(sfx_state_t *self, song_t *song) { song_t *playing_song = self->song; /* _dump_playing_list(self, "is-playing");*/ while (playing_song) { if (playing_song == song) return 1; playing_song = playing_song->next_playing; } return 0; } static void _sfx_set_song_status(sfx_state_t *self, song_t *song, int status) { switch (status) { case SOUND_STATUS_STOPPED: /* Reset */ song->it->init(song->it); break; case SOUND_STATUS_SUSPENDED: case SOUND_STATUS_WAITING: if (song->status == SOUND_STATUS_PLAYING) { /* Update delay, set wakeup_time */ GTimeVal time; long delta; sci_get_current_time(&time); delta = time_minus(time, song->wakeup_time); song->delay -= delta; song->wakeup_time = time; } if (status == SOUND_STATUS_SUSPENDED) break; /* otherwise... */ case SOUND_STATUS_PLAYING: if (song->status == SOUND_STATUS_STOPPED) /* Starting anew */ sci_get_current_time(&song->wakeup_time); if (is_playing(self, 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 */ static void _update_single_song(sfx_state_t *self) { song_t *newsong = song_lib_find_active(self->songlib); if (newsong != self->song) { _freeze_time(self); /* 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) _sfx_set_song_status(self, newsong, SOUND_STATUS_PLAYING); /* Change instrument mappings */ } else { /* Turn off sound */ } if (self->song) { if (self->song->status == SOUND_STATUS_PLAYING) _sfx_set_song_status(self, newsong, SOUND_STATUS_WAITING); } if (self->debug & SFX_DEBUG_SONGS) { sciprintf("[SFX] Changing active song:"); if (!self->song) sciprintf(" New song:"); else sciprintf(" pausing %08lx, now playing", self->song->handle); if (newsong) sciprintf(" %08lx\n", newsong->handle); else sciprintf(" none\n"); } self->song = newsong; _thaw_time(self); /* Recover song delay time */ if (newsong && player) { song_iterator_t *clonesong = songit_clone(newsong->it, newsong->delay); player->add_iterator(clonesong, newsong->wakeup_time); } } } static void _update_multi_song(sfx_state_t *self) { song_t *oldfirst = self->song; song_t *oldseeker; song_t *newsong = song_lib_find_active(self->songlib); song_t *newseeker; song_t not_playing_anymore; /* Dummy object, referenced by ** songs which are no longer ** active. */ GTimeVal tv; sci_get_current_time(&tv); /* _dump_playing_list(self, "before");*/ _freeze_time(self); /* Store song delay time */ for (newseeker = newsong; newseeker; newseeker = newseeker->next_playing) { if (!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->next_stopping) { oldseeker->next_stopping = oldseeker->next_playing; oldseeker->next_playing = ¬_playing_anymore; if (oldseeker == oldseeker->next_playing) { BREAKPOINT(); } } /* Second, re-generate the new song queue. */ for (newseeker = newsong; newseeker; newseeker = newseeker->next_playing) { newseeker->next_playing = song_lib_find_next_active(self->songlib, newseeker); if (newseeker == newseeker->next_playing) { BREAKPOINT(); } } /* 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 is_playing(), indirectly */ self->song = newsong; /* Third, stop all old songs */ for (oldseeker = oldfirst; oldseeker; oldseeker = oldseeker->next_stopping) if (oldseeker->next_playing == ¬_playing_anymore) { _sfx_set_song_status(self, oldseeker, SOUND_STATUS_SUSPENDED); if (self->debug & SFX_DEBUG_SONGS) { sciprintf("[SFX] Stopping song %lx\n", oldseeker->handle); } if (player && oldseeker->it) player->iterator_message (songit_make_message(oldseeker->it->ID, SIMSG_STOP)); oldseeker->next_playing = NULL; /* Clear this pointer; we don't need the tag anymore */ } for (newseeker = newsong; newseeker; newseeker = newseeker->next_playing) { if (newseeker->status != SOUND_STATUS_PLAYING && player) { if (self->debug & SFX_DEBUG_SONGS) sciprintf("[SFX] Adding song %lx\n", newseeker->it->ID); player->add_iterator(songit_clone(newseeker->it, newseeker->delay), tv); } _sfx_set_song_status(self, newseeker, SOUND_STATUS_PLAYING); } self->song = newsong; _thaw_time(self); /* _dump_playing_list(self, "after");*/ } /* Update internal state */ static void _update(sfx_state_t *self) { if (self->flags & SFX_STATE_FLAG_MULTIPLAY) _update_multi_song(self); else _update_single_song(self); } static int _sfx_timer_active = 0; /* Timer toggle */ int sfx_play_iterator_pcm(song_iterator_t *it, song_handle_t handle) { #ifdef DEBUG_SONG_API fprintf(stderr, "[sfx-core] Playing PCM: %08lx\n", handle); #endif if (mixer) { sfx_pcm_feed_t *newfeed = it->get_pcm_feed(it); if (newfeed) { newfeed->debug_nr = (int) handle; mixer->subscribe(mixer, newfeed); return 1; } } return 0; } static void _sfx_timer_callback(void *data) { if (_sfx_timer_active) { Common::StackLock lock(*callbackMutex); /* First run the player, to give it a chance to fill ** the audio buffer */ if (player) player->maintenance(); assert(_sfx_timer_active); if (mixer) mixer->process(mixer); } } void sfx_init(sfx_state_t *self, ResourceManager *resmgr, int flags) { callbackMutex = new Common::Mutex(); song_lib_init(&self->songlib); self->song = NULL; self->flags = flags; self->debug = 0; /* Disable all debugging by default */ if (flags & SFX_STATE_FLAG_NOSOUND) { mixer = NULL; pcm_device = NULL; player = NULL; sciprintf("[SFX] Sound disabled.\n"); return; } mixer = getMixer(); pcm_device = &sfx_pcm_driver_scummvm; player = sfx_find_player(NULL); #ifdef DEBUG_SONG_API fprintf(stderr, "[sfx-core] Initialising: flags=%x\n", flags); #endif /*------------------*/ /* Initialise timer */ /*------------------*/ if (pcm_device || player->maintenance) { timer = &sfx_timer_scummvm; if (!timer) { fprintf(stderr, "[SFX] " __FILE__": Could not find timing mechanism\n"); fprintf(stderr, "[SFX] Disabled sound support\n"); pcm_device = NULL; player = NULL; mixer = NULL; return; } if (timer->init(_sfx_timer_callback, NULL)) { warning("[SFX] " __FILE__": Timer failed to initialize"); warning("[SFX] Disabled sound support"); timer = NULL; pcm_device = NULL; player = NULL; mixer = NULL; return; } } /* With no PCM device and no player, we don't need a timer */ /*----------------*/ /* Initialise PCM */ /*----------------*/ if (!pcm_device) { sciprintf("[SFX] No PCM device found, disabling PCM support\n"); mixer = NULL; } else { if (pcm_device->init(pcm_device)) { sciprintf("[SFX] Failed to open PCM device, disabling PCM support\n"); mixer = NULL; pcm_device = NULL; } else { if (mixer->init(mixer, pcm_device)) { sciprintf("[SFX] Failed to initialise PCM mixer; disabling PCM support\n"); mixer = NULL; pcm_device = NULL; } } } /*-------------------*/ /* Initialise player */ /*-------------------*/ if (!resmgr) { sciprintf("[SFX] Warning: No resource manager present, cannot initialise player\n"); player = NULL; } else if (player->init(resmgr, timer ? timer->delay_ms : 0)) { sciprintf("[SFX] Song player '%s' reported error, disabled\n", player->name); player = NULL; } if (!player) sciprintf("[SFX] No song player found\n"); else sciprintf("[SFX] Using song player '%s', v%s\n", player->name, player->version); _sfx_timer_active = 1; } void sfx_exit(sfx_state_t *self) { callbackMutex->lock(); _sfx_timer_active = 0; #ifdef DEBUG_SONG_API fprintf(stderr, "[sfx-core] Uninitialising\n"); #endif song_lib_free(self->songlib); pcm_device = NULL; if (timer && timer->exit()) warning("[SFX] Timer reported error on exit"); /* WARNING: The mixer may hold feeds from the ** player, so we must stop the mixer BEFORE ** stopping the player. */ if (mixer) { mixer->exit(mixer); mixer = NULL; } if (player) /* See above: This must happen AFTER stopping the mixer */ player->exit(); callbackMutex->unlock(); delete callbackMutex; callbackMutex = 0; } static inline int time_le(GTimeVal a, GTimeVal b) { return time_minus(a, b) <= 0; } void sfx_suspend(sfx_state_t *self, int suspend) { #ifdef DEBUG_SONG_API fprintf(stderr, "[sfx-core] Suspending? = %d\n", suspend); #endif if (suspend && (!self->suspended)) { /* suspend */ _freeze_time(self); if (player) player->pause(); /* Suspend song player */ } else if (!suspend && (self->suspended)) { /* unsuspend */ _thaw_time(self); if (player) player->resume(); /* Unsuspend song player */ } self->suspended = suspend; } int sfx_poll(sfx_state_t *self, song_handle_t *handle, int *cue) { /* Polls the sound server for cues etc. ** Returns : (int) 0 if the cue queue is empty, SI_LOOP, SI_CUE, or SI_FINISHED otherwise ** (song_handle_t) *handle: The affected handle ** (int) *cue: The sound cue number (if SI_CUE) */ if (!self->song) return 0; /* No milk today */ *handle = self->song->handle; #ifdef DEBUG_SONG_API fprintf(stderr, "[sfx-core] Polling any (%08lx)\n", *handle); #endif return sfx_poll_specific(self, *handle, cue); } int sfx_poll_specific(sfx_state_t *self, song_handle_t handle, int *cue) { GTimeVal ctime; song_t *song = self->song; sci_get_current_time(&ctime); while (song && song->handle != handle) song = song->next_playing; if (!song) return 0; /* Song not playing */ if (self->debug & SFX_DEBUG_CUES) { fprintf(stderr, "[SFX:CUE] Polled song %08lx ", handle); } while (1) { unsigned char buf[8]; int result; if (!time_le(song->wakeup_time, ctime)) return 0; /* Patience, young hacker! */ result = songit_next(&(song->it), buf, cue, IT_READER_MASK_ALL); switch (result) { case SI_FINISHED: _sfx_set_song_status(self, song, SOUND_STATUS_STOPPED); _update(self); /* ...fall through... */ case SI_LOOP: case SI_RELATIVE_CUE: case SI_ABSOLUTE_CUE: if (self->debug & SFX_DEBUG_CUES) { sciprintf(" => "); if (result == SI_FINISHED) sciprintf("finished\n"); else { if (result == SI_LOOP) sciprintf("Loop: "); else sciprintf("Cue: "); sciprintf("%d (0x%x)", *cue, *cue); } } return result; default: if (result > 0) song->wakeup_time = time_plus(song->wakeup_time, result * SOUND_TICK); /* Delay */ break; } } if (self->debug & SFX_DEBUG_CUES) { fprintf(stderr, "\n"); } } /*****************/ /* Song basics */ /*****************/ int sfx_add_song(sfx_state_t *self, song_iterator_t *it, int priority, song_handle_t handle, int number) { song_t *song = song_lib_find(self->songlib, handle); #ifdef DEBUG_SONG_API fprintf(stderr, "[sfx-core] Adding song: %08lx at %d, it=%p\n", handle, priority, it); #endif if (!it) { fprintf(stderr, "[SFX] Attempt to add empty song with handle %08lx\n", handle); return -1; } it->init(it); /* If we're already playing this, stop it */ /* Tell player to shut up */ _dump_songs(self); if (player) player->iterator_message(songit_make_message(handle, SIMSG_STOP)); if (song) { _sfx_set_song_status(self, song, SOUND_STATUS_STOPPED); fprintf(stderr, "Overwriting old song (%08lx) ...\n", handle); if (song->status == SOUND_STATUS_PLAYING || song->status == SOUND_STATUS_SUSPENDED) { warning("Unexpected (error): Song %ld still playing/suspended (%d)", handle, song->status); songit_free(it); return -1; } else song_lib_remove(self->songlib, handle); /* No duplicates */ } song = song_new(handle, it, priority); song->resource_num = number; song->hold = 0; song->loops = 0; sci_get_current_time(&song->wakeup_time); /* No need to delay */ song_lib_add(self->songlib, song); self->song = NULL; /* As above */ _update(self); return 0; } void sfx_remove_song(sfx_state_t *self, song_handle_t handle) { #ifdef DEBUG_SONG_API fprintf(stderr, "[sfx-core] Removing song: %08lx\n", handle); #endif if (self->song && self->song->handle == handle) self->song = NULL; song_lib_remove(self->songlib, handle); _update(self); } /**********************/ /* Song modifications */ /**********************/ #define ASSERT_SONG(s) if (!(s)) { warning("Looking up song handle %08lx failed in %s, L%d", handle, __FILE__, __LINE__); return; } void sfx_song_set_status(sfx_state_t *self, song_handle_t handle, int status) { song_t *song = song_lib_find(self->songlib, 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 _sfx_set_song_status(self, song, status); _update(self); } void sfx_song_set_fade(sfx_state_t *self, song_handle_t handle, fade_params_t *params) { #ifdef DEBUG_SONG_API static const char *stopmsg[] = {"??? Should not happen", "Do not stop afterwards", "Stop afterwards"}; #endif song_t *song = song_lib_find(self->songlib, 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(self); } void sfx_song_renice(sfx_state_t *self, song_handle_t handle, int priority) { song_t *song = song_lib_find(self->songlib, 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(self); } void sfx_song_set_loops(sfx_state_t *self, song_handle_t handle, int loops) { song_t *song = song_lib_find(self->songlib, handle); song_iterator_message_t msg = songit_make_message(handle, SIMSG_SET_LOOPS(loops)); ASSERT_SONG(song); #ifdef DEBUG_SONG_API fprintf(stderr, "[sfx-core] Setting loops on %08lx to %d\n", handle, loops); #endif songit_handle_message(&(song->it), msg); song->loops = ((base_song_iterator_t *) song->it)->loops; if (player/* && player->send_iterator_message*/) /* FIXME: The above should be optional! */ player->iterator_message(msg); } void sfx_song_set_hold(sfx_state_t *self, song_handle_t handle, int hold) { song_t *song = song_lib_find(self->songlib, handle); song_iterator_message_t msg = songit_make_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, loops); #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 song_handle_t midi_send_base = 0xffff0000; int sfx_send_midi(sfx_state_t *self, song_handle_t handle, int channel, int command, int arg1, int arg2) { byte buffer[5]; tell_synth_func *tell = sfx_get_player_tell_func(); /* 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) { sciprintf("TODO: channel mute (channel %d %s)!\n", channel, channel_state[arg2]); /* We need to have a GET_PLAYMASK interface to use here. SET_PLAYMASK we've got. */ return SFX_OK; } 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: sciprintf("Unexpected explicit MIDI command %02x\n", command); return SFX_ERROR; } if (tell) tell(MIDI_cmdlen[command >> 4], buffer); return SFX_OK; } int sfx_get_volume(sfx_state_t *self) { warning("FIXME: Implement volume"); return 0; } void sfx_set_volume(sfx_state_t *self, int volume) { warning("FIXME: Implement volume"); } void sfx_all_stop(sfx_state_t *self) { #ifdef DEBUG_SONG_API fprintf(stderr, "[sfx-core] All stop\n"); #endif song_lib_free(self->songlib); _update(self); } } // End of namespace Sci