scummvm/engines/sci/sfx/core.cpp

917 lines
22 KiB
C++
Raw Normal View History

/***************************************************************************
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) <jameson@linuxgames.com>
***************************************************************************/
/* Sound subsystem core: Event handler, sound player dispatching */
#include <stdio.h>
#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 = &not_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 == &not_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);
}