/*************************************************************************** core.c Copyright (C) 2002 Christoph Reichenbach This program may be modified and copied freely according to the terms of the GNU general public license (GPL), as long as the above copyright notice and the licensing information contained herein are preserved. Please refer to www.gnu.org for licensing details. This work is provided AS IS, without warranty of any kind, expressed or implied, including but not limited to the warranties of merchantibility, noninfringement, and fitness for a specific purpose. The author will not be held liable for any damage caused by this work or derivatives of it. By using this source code, you agree to the licensing terms as stated above. Please contact the maintainer for bug reports or inquiries. Current Maintainer: Christoph Reichenbach (CR) ***************************************************************************/ /* 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" /*#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; 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, resource_mgr_t *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 = sfx_pcm_find_mixer(NULL); pcm_device = sfx_pcm_find_device(NULL); 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) { if (pcm_device && pcm_device->timer) timer = pcm_device->timer; else timer = sfx_find_timer(NULL); 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)) { fprintf(stderr, "[SFX] " __FILE__": Timer failed to initialize\n"); fprintf(stderr, "[SFX] Disabled sound support\n"); timer = NULL; pcm_device = NULL; player = NULL; mixer = NULL; return; } sciprintf("[SFX] Initialised timer '%s', v%s\n", timer->name, timer->version); } /* 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->exit(pcm_device); 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); if (pcm_device) { pcm_device->exit(pcm_device); pcm_device = NULL; } if (timer && timer->exit()) fprintf(stderr, "[SFX] Timer reported error on exit\n"); /* 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) { fprintf(stderr, "Unexpected (error): Song %ld still playing/suspended (%d)\n", 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)) { fprintf(stderr, "Looking up song handle %08lx failed in %s, L%d\n", 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.\n", 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) { fprintf(stderr, "FIXME: Implement volume\n"); return 0; } void sfx_set_volume(sfx_state_t *self, int volume) { fprintf(stderr, "FIXME: Implement volume\n"); } 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); }