diff options
author | Jordi Vilalta Prat | 2009-02-15 06:10:59 +0000 |
---|---|---|
committer | Jordi Vilalta Prat | 2009-02-15 06:10:59 +0000 |
commit | fa6e10e9cec163845aa29e7940c86e9c9ab8a2bc (patch) | |
tree | ce87338830cc8c149e1de545246bcefe4f45da00 /engines/sci/sfx | |
parent | 7c148ddf021c990fa866b7600f979aac9a5b26c9 (diff) | |
download | scummvm-rg350-fa6e10e9cec163845aa29e7940c86e9c9ab8a2bc.tar.gz scummvm-rg350-fa6e10e9cec163845aa29e7940c86e9c9ab8a2bc.tar.bz2 scummvm-rg350-fa6e10e9cec163845aa29e7940c86e9c9ab8a2bc.zip |
Import the SCI engine sources from the FreeSCI Glutton branch (it doesn't compile yet)
svn-id: r38192
Diffstat (limited to 'engines/sci/sfx')
109 files changed, 25489 insertions, 0 deletions
diff --git a/engines/sci/sfx/Makefile.am b/engines/sci/sfx/Makefile.am new file mode 100644 index 0000000000..b085a7c917 --- /dev/null +++ b/engines/sci/sfx/Makefile.am @@ -0,0 +1,27 @@ +SUBDIRS = seq timer player device mixer pcm_device softseq +INCLUDES = -I$(top_srcdir)/src/include @EXTRA_INCLUDES@ +EXTRA_DIST = timetest.c adlib.h device.h mixer.h sequencer.h softseq.h +noinst_LIBRARIES = libscisound.a libscisoundlib.a +libscisound_a_SOURCES = iterator.c songlib.c core.c pcm-iterator.c +libscisoundlib_a_SOURCES = time.c adlib.c + +bin_PROGRAMS = test-iterator +test_iterator_SOURCE = test-iterator.c +test_iterator_LDFLAGS = @SCIV_LDFLAGS@ +LDADD = \ + libscisound.a \ + player/libsciplayer.a \ + seq/libsciseq.a \ + timer/libscitimer.a \ + pcm_device/libscipcm.a \ + mixer/libscimixer.a \ + softseq/libscisoftseq.a \ + libscisoundlib.a \ + device/libscisounddevice.a \ + ../scicore/libscicore.a \ + @LIB_M@ + + +TESTS = test-iterator + + diff --git a/engines/sci/sfx/adlib.c b/engines/sci/sfx/adlib.c new file mode 100644 index 0000000000..dab3902770 --- /dev/null +++ b/engines/sci/sfx/adlib.c @@ -0,0 +1,66 @@ +/*************************************************************************** + adlib.c Copyright (C) 2001 Solomon Peachy + + 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. + +***************************************************************************/ + +#include "adlib.h" + +adlib_instr adlib_sbi[96]; + +void +make_sbi(adlib_def *one, guint8 *buffer) +{ + memset(buffer, 0, sizeof(adlib_instr)); + +#if 0 + printf ("%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x ", one->keyscale1, one->freqmod1, one->feedback1, one->attackrate1, one->sustainvol1, one->envelope1, one->decayrate1, one->releaserate1, one->volume1, one->ampmod1, one->vibrato1, one->keybdscale1, one->algorithm1, one->waveform1); + + printf (" %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x ", one->keyscale2, one->freqmod2, one->feedback2, one->attackrate2, one->sustainvol2, one->envelope2, one->decayrate2, one->releaserate2, one->volume2, one->ampmod2, one->vibrato2, one->keybdscale2, one->algorithm2, one->waveform2); + + printf("\n"); +#endif + + buffer[0] |= ((one->ampmod1 & 0x1) << 7); + buffer[0] |= ((one->vibrato1 & 0x1) << 6); + buffer[0] |= ((one->envelope1 & 0x1) << 5); + buffer[0] |= ((one->keybdscale1 & 0x1) << 4); + buffer[0] |= (one->freqmod1 & 0xf); + buffer[1] |= ((one->ampmod2 & 0x1) << 7); + buffer[1] |= ((one->vibrato2 & 0x1) << 6); + buffer[1] |= ((one->envelope2 & 0x1) << 5); + buffer[1] |= ((one->keybdscale2 & 0x1) << 4); + buffer[1] |= (one->freqmod2 & 0xf); + buffer[2] |= ((one->keyscale1 & 0x3) << 6); + buffer[2] |= (one->volume1 & 0x3f); + buffer[3] |= ((one->keyscale2 & 0x3) << 6); + buffer[3] |= (one->volume2 & 0x3f); + buffer[4] |= ((one->attackrate1 & 0xf) << 4); + buffer[4] |= (one->decayrate1 & 0xf); + buffer[5] |= ((one->attackrate2 & 0xf) << 4); + buffer[5] |= (one->decayrate2 & 0xf); + buffer[6] |= ((one->sustainvol1 & 0xf) << 4); + buffer[6] |= (one->releaserate1 & 0xf); + buffer[7] |= ((one->sustainvol2 & 0xf) << 4); + buffer[7] |= (one->releaserate2 & 0xf); + buffer[8] |= (one->waveform1 & 0x3); + buffer[9] |= (one->waveform2 & 0x3); + + buffer[10] |= ((one->feedback1 & 0x7) << 1); + buffer[10] |= (1-(one->algorithm1 & 0x1)); + + return; +} diff --git a/engines/sci/sfx/adlib.h b/engines/sci/sfx/adlib.h new file mode 100644 index 0000000000..214b7100b7 --- /dev/null +++ b/engines/sci/sfx/adlib.h @@ -0,0 +1,69 @@ +/*************************************************************************** + sfx_adlib.h, from + midi_device.h Copyright (C) 2001 Solomon Peachy + Copytight (C) 2002..04 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. + +***************************************************************************/ + + +#ifndef _SFX_ADLIB_H_ +#define _SFX_ADLIB_H_ + +#include <resource.h> + + +#define ADLIB_VOICES 12 + +typedef struct _sci_adlib_def { + guint8 keyscale1; /* 0-3 !*/ + guint8 freqmod1; /* 0-15 !*/ + guint8 feedback1; /* 0-7 !*/ + guint8 attackrate1; /* 0-15 !*/ + guint8 sustainvol1; /* 0-15 !*/ + guint8 envelope1; /* t/f !*/ + guint8 decayrate1; /* 0-15 !*/ + guint8 releaserate1; /* 0-15 !*/ + guint8 volume1; /* 0-63 !*/ + guint8 ampmod1; /* t/f !*/ + guint8 vibrato1; /* t/f !*/ + guint8 keybdscale1; /* t/f !*/ + guint8 algorithm1; /* 0,1 REVERSED */ + guint8 keyscale2; /* 0-3 !*/ + guint8 freqmod2; /* 0-15 !*/ + guint8 feedback2; /* 0-7 UNUSED */ + guint8 attackrate2; /* 0-15 !*/ + guint8 sustainvol2; /* 0-15 !*/ + guint8 envelope2; /* t/f !*/ + guint8 decayrate2; /* 0-15 !*/ + guint8 releaserate2; /* 0-15 !*/ + guint8 volume2; /* 0-63 !*/ + guint8 ampmod2; /* t/f !*/ + guint8 vibrato2; /* t/f !*/ + guint8 keybdscale2; /* t/f !*/ + guint8 algorithm2; /* 0,1 UNUSED */ + guint8 waveform1; /* 0-3 !*/ + guint8 waveform2; /* 0-3 !*/ +} adlib_def; + +typedef unsigned char adlib_instr[12]; + +extern adlib_instr adlib_sbi[96]; + +void make_sbi(adlib_def *one, guint8 *buffer); +/* Converts a raw SCI adlib instrument into the adlib register format. */ + +#endif /* _SFX_ADLIB_H_ */ diff --git a/engines/sci/sfx/core.c b/engines/sci/sfx/core.c new file mode 100644 index 0000000000..9d55ad6bbf --- /dev/null +++ b/engines/sci/sfx/core.c @@ -0,0 +1,938 @@ +/*************************************************************************** + 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 <sfx_timer.h> +#include <sfx_iterator_internal.h> +#include <sfx_player.h> +#include "mixer.h" +#include <sci_midi.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; + +#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; + } +} + + +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"); +} + +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) { + /* First run the player, to give it a chance to fill + ** the audio buffer */ + + if (player) + player->maintenance(); + + if (mixer) + mixer->process(mixer); + } +} + +void +sfx_init(sfx_state_t *self, resource_mgr_t *resmgr, int flags) +{ + 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) +{ + _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(); + +} + +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; + +static song_handle_t midi_send_handle = 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 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); +} diff --git a/engines/sci/sfx/device.h b/engines/sci/sfx/device.h new file mode 100644 index 0000000000..49bc017c7b --- /dev/null +++ b/engines/sci/sfx/device.h @@ -0,0 +1,117 @@ +/*************************************************************************** + sfx_device.h Copyright (C) 2003,04 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> + +***************************************************************************/ +/* song player structure */ + +#ifndef _SFX_DEVICE_H +#define _SFX_DEVICE_H + +/* Various types of resources */ +#define SFX_DEVICE_NONE 0 +#define SFX_DEVICE_MIDI 1 /* midi writer */ +#define SFX_DEVICE_OPL2 2 /* OPL/2 sequencer */ + +struct _midi_device { + const char *name; + + int (*init)(struct _midi_device *self); + /* Initializes the device + ** Parameters: (midi_device_t *) self: Self reference + ** Returns : (int) SFX_OK on success, SFX_ERROR if the device could not be + ** opened + */ + + int (*set_option)(struct _midi_device *self, char *name, char *value); + /* Sets an option for the device + ** Parameters: (char *) name: Name of the option to set + ** (char *) value: Value of the option to set + ** Returns : (int) SFX_OK on success, SFX_ERROR otherwise (unsupported option) + */ +}; + +#define MIDI_WRITER_BODY \ + char *name; /* Name description of the device */ \ + \ + int (*init)(struct _midi_writer *self); \ + /* Initializes the writer \ + ** Parameters: (midi_writer_t *) self: Self reference \ + ** Returns : (int) SFX_OK on success, SFX_ERROR if the device could not be \ + ** opened \ + */ \ + \ + int (*set_option)(struct _midi_writer *self, char *name, char *value); \ + /* Sets an option for the writer \ + ** Parameters: (char *) name: Name of the option to set \ + ** (char *) value: Value of the option to set \ + ** Returns : (int) SFX_OK on success, SFX_ERROR otherwise (unsupported option) \ + */ \ + \ + int (*write)(struct _midi_writer *self, unsigned char *buf, int len); \ + /* Writes some bytes to the MIDI stream \ + ** Parameters: (char *) buf: The buffer to write \ + ** (int) len: Number of bytes to write \ + ** Returns : (int) SFX_OK on success, SFX_ERROR on failure \ + ** No delta time is expected here. \ + */ \ + \ + void (*delay)(struct _midi_writer *self, int ticks); \ + /* Introduces an explicit delay \ + ** Parameters: (int) ticks: Number of 60 Hz ticks to sleep \ + */ \ + \ + void (*flush)(struct _midi_writer *self); /* May be NULL */ \ + /* Flushes the MIDI file descriptor \ + ** Parameters: (midi_writer_t *) self: Self reference \ + */ \ + \ + void (*reset_timer)(struct _midi_writer *self); \ + /* Resets the timer associated with this device \ + ** Parameters: (midi_writer_t *) self: Self reference \ + ** This function makes sure that a subsequent write would have effect \ + ** immediately, and any delay() would be relative to the point in time \ + ** this function was invoked at. \ + */ \ + \ + void (*close)(struct _midi_writer *self); \ + /* Closes the associated MIDI device \ + ** Parameters: (midi_writer_t *) self: Self reference \ + */ + +typedef struct _midi_writer { + MIDI_WRITER_BODY +} midi_writer_t; + + +void * +sfx_find_device(int type, char *name); +/* Looks up a device by name +** Parameters: (int) type: Device type to look up +** (char *) name: Comma-separated list of devices to choose from +** (in the order specified), or NULL for default +** Returns : (void *) The device requested, or NULL if no match was found +*/ + +#endif /* !_SFX_PLAYER_H */ diff --git a/engines/sci/sfx/device/Makefile.am b/engines/sci/sfx/device/Makefile.am new file mode 100644 index 0000000000..4a4f023251 --- /dev/null +++ b/engines/sci/sfx/device/Makefile.am @@ -0,0 +1,3 @@ +noinst_LIBRARIES = libscisounddevice.a +INCLUDES = -I$(top_srcdir)/src/include @EXTRA_INCLUDES@ +libscisounddevice_a_SOURCES = devices.c alsa-midi.c unixraw-midi.c camd-midi.c diff --git a/engines/sci/sfx/device/alsa-midi.c b/engines/sci/sfx/device/alsa-midi.c new file mode 100644 index 0000000000..67ca995916 --- /dev/null +++ b/engines/sci/sfx/device/alsa-midi.c @@ -0,0 +1,227 @@ +/*************************************************************************** + alsa-midi.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> + +***************************************************************************/ + +#include <sfx_engine.h> +#include "../device.h" +#ifdef HAVE_ALSA + +#include <alsa/asoundlib.h> + +#define SCI_ALSA_MIDI_VERSION "0.1" + +static snd_midi_event_t *parser = NULL; +static snd_seq_t *seq = NULL; +static int queue = -1; +static int delta = 0; +static int port_out = -1; +static int port_nr = 128; +static int subport_nr = 0; + +static const char *seq_name = "default"; + +static void +_set_tempo(void) +{ + int resolution = 60; + int tempo = 1; + snd_seq_queue_tempo_t *queue_tempo; + + snd_seq_queue_tempo_malloc(&queue_tempo); + + memset(queue_tempo, 0, snd_seq_queue_tempo_sizeof()); + snd_seq_queue_tempo_set_ppq(queue_tempo, resolution); + snd_seq_queue_tempo_set_tempo(queue_tempo, 1000000/tempo); + + snd_seq_set_queue_tempo(seq, queue, queue_tempo); + + snd_seq_queue_tempo_free(queue_tempo); + +#if 0 + int tempo = 1000000 / 60; + snd_seq_queue_tempo_t *queue_tempo; + + snd_seq_queue_tempo_malloc(&queue_tempo); + snd_seq_queue_tempo_set_tempo(queue_tempo, tempo); + snd_seq_queue_tempo_set_ppq(queue_tempo, 1); + snd_seq_set_queue_tempo(seq, queue, queue_tempo); + snd_seq_queue_tempo_free(queue_tempo); +#endif +} + + +static int +am_subscribe_to_ports(void) +{ + if ((port_out = snd_seq_connect_to(seq, port_out, port_nr, subport_nr)) < 0) { + fprintf(stderr, "[SFX] Could not connect to ALSA sequencer port: %s\n", snd_strerror(port_out)); + return SFX_ERROR; + } + return SFX_OK; +} + + +static int +aminit(midi_writer_t *self) +{ + int err; + + snd_midi_event_new(4096, &parser); + snd_midi_event_init(parser); + + sciprintf("[SFX] Initialising ALSA MIDI backend, v%s\n", SCI_ALSA_MIDI_VERSION); + + if (snd_seq_open(&seq, seq_name, SND_SEQ_OPEN_OUTPUT, SND_SEQ_NONBLOCK)) { + fprintf(stderr, "[SFX] Failed to open ALSA MIDI sequencer '%s' for output\n", + seq_name); + return SFX_ERROR; + } + + if ((port_out = snd_seq_create_simple_port(seq, "FreeSCI", + SND_SEQ_PORT_CAP_WRITE | + SND_SEQ_PORT_CAP_SUBS_WRITE | + SND_SEQ_PORT_CAP_READ, + SND_SEQ_PORT_TYPE_MIDI_GENERIC)) < 0) { + fprintf(stderr, "[SFX] Could not create ALSA sequencer port\n"); + return SFX_ERROR; + } + + if (am_subscribe_to_ports()) + return SFX_ERROR; + + queue = snd_seq_alloc_queue(seq); + _set_tempo(); + + snd_seq_start_queue(seq, queue, NULL); + + if ((err = snd_seq_drain_output(seq))) { + fflush(NULL); + fprintf(stderr, "[SFX] Error while draining: %s\n", + snd_strerror(err)); + return SFX_ERROR; + } + + return SFX_OK; +} + +static int +amsetopt(midi_writer_t *self, char *name, char *value) +{ + return SFX_ERROR; +} + + +static int +amwrite(midi_writer_t *self, unsigned char *buf, int len) +{ + snd_seq_event_t evt; + +#if 0 + { + int i; + fprintf(stderr, "[MID] "); + for (i = 0; i < len; i++) + fprintf(stderr, " %02x", buf[i]); + fprintf(stderr, "\n"); + } +#endif + + snd_seq_ev_clear(&evt); + snd_seq_ev_set_source(&evt, port_out); + snd_seq_ev_set_subs(&evt); /* Broadcast to all subscribers */ + + snd_midi_event_encode(parser, buf, len, &evt); + snd_seq_ev_schedule_tick(&evt, queue, 0, delta); + + snd_seq_event_output_direct(seq, &evt); + +#if 0 + { + snd_seq_queue_status_t *status; + snd_seq_queue_status_malloc(&status); + + snd_seq_get_queue_status(seq, queue, status); + //snd_seq_tick_time_t snd_seq_queue_status_get_tick_time(const snd_seq_queue_status_t *info); + fprintf(stderr, "Queue at %d/%d\n", delta, snd_seq_queue_status_get_tick_time(status)); + + snd_seq_queue_status_free(status); + } +#endif + + + return SFX_OK; +} + +static void +amdelay(midi_writer_t *self, int ticks) +{ + delta += ticks; +} + +static void +amreset_timer(midi_writer_t *self) +{ + snd_seq_drain_output(seq); + snd_seq_stop_queue(seq, queue, NULL); + + + { + snd_seq_event_t evt; + snd_seq_ev_clear(&evt); + snd_seq_ev_set_source(&evt, port_out); + snd_seq_ev_set_subs(&evt); /* Broadcast to all subscribers */ + + snd_seq_ev_set_queue_pos_tick(&evt, queue, 0); + + snd_seq_event_output_direct(seq, &evt); + } + delta = 0; + + + + snd_seq_start_queue(seq, queue, NULL); +} + +static void +amclose(midi_writer_t *self) +{ + snd_midi_event_free(parser); + parser = NULL; +} + + +midi_writer_t sfx_device_midi_alsa = { + "alsa", + aminit, + amsetopt, + amwrite, + amdelay, + NULL, + amreset_timer, + amclose, +}; + +#endif diff --git a/engines/sci/sfx/device/camd-midi.c b/engines/sci/sfx/device/camd-midi.c new file mode 100644 index 0000000000..3af12f196e --- /dev/null +++ b/engines/sci/sfx/device/camd-midi.c @@ -0,0 +1,161 @@ +/*************************************************************************** + camd-midi.c Copyright (C) 2005--08 Walter van Niftrik, 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: + + Walter van Niftrik <w.f.b.w.v.niftrik@stud.tue.nl> + +***************************************************************************/ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif +#ifdef HAVE_PROTO_CAMD_H + +#include "sfx_engine.h" +#include "../device.h" + +#include <proto/camd.h> +#include <proto/dos.h> +#include <proto/exec.h> +#include <proto/intuition.h> +#include <stdio.h> + + +#define SWAP_BYTES +#define FILL_BYTES + +#define SCI_CAMD_MIDI_VERSION "0.1" +#define SYSEX_PREFIX 0xf0 + +static const char *devicename = "via686.out.0"; + +struct Library *CamdBase = NULL; +struct CamdIFace *ICamd = NULL; +static struct MidiLink *midi_link = NULL; +static struct MidiNode *midi_node = NULL; + +#define ABORT(m) { \ + if (CamdBase) \ + IExec->CloseLibrary(CamdBase); \ + sciprintf("[SFX] CAMD driver: "); \ + sciprintf(m); \ + sciprintf("\n"); \ + return SFX_ERROR; \ + } + +static int +camd_init(midi_writer_t *self) +{ + sciprintf("[SFX] Initialising CAMD raw MIDI backend, v%s\n", SCI_CAMD_MIDI_VERSION); + + CamdBase = IExec->OpenLibrary("camd.library", 36L); + if (!CamdBase) + ABORT("Could not open 'camd.library'"); + + ICamd = (struct CamdIFace *) IExec->GetInterface(CamdBase, "main", 1, NULL); + if (!ICamd) + ABORT("Error while retrieving CAMD interface\n"); + + midi_node = ICamd->CreateMidi(MIDI_MsgQueue, 0L, MIDI_SysExSize, 4096L, MIDI_Name, "freesci", TAG_END); + if (!midi_node) + ABORT("Could not create CAMD MIDI node"); + + midi_link = ICamd->AddMidiLink(midi_node, MLTYPE_Sender, MLINK_Location, devicename, TAG_END); + if (!midi_link) + ABORT(("Could not create CAMD MIDI link to '%s'", devicename)); + + sciprintf("[SFX] CAMD initialisation completed\n"); + + return SFX_OK; +} + +static int +camd_set_option(midi_writer_t *self, char *name, char *value) +{ + return SFX_ERROR; +} + +#define MAX_MIDI_LEN 3 + +static int +camd_write(midi_writer_t *self, unsigned char *buffer, int len) +{ + if (len == 0) + return SFX_OK; + + if (buffer[0] == SYSEX_PREFIX) { + /* Must send this as a SysEx */ + ICamd->PutSysEx(midi_link, buffer); + } else { + ULONG data = 0l; + int i; + int readlen = (len > MAX_MIDI_LEN) ? MAX_MIDI_LEN : len; + + for (i = 0; i < readlen; i++) + if (len >= i) { + data <<= 8; + data |= buffer[i]; + } + data <<= (8 * (sizeof(ULONG) - readlen)); + + if (len > MAX_MIDI_LEN) + sciprintf("[SFX] Warning: Truncated MIDI message to fit CAMD format (sent %d: %02x %02x %02x, real length %d)\n", + MAX_MIDI_LEN, buffer[0], buffer[1], buffer[2], len); + + ICamd->PutMidi(midi_link, data); + } + + return SFX_OK; +} + +static void +camd_delay(midi_writer_t *self, int ticks) +{ +} + +static void +camd_reset_timer(midi_writer_t *self) +{ +} + +static void +camd_close(midi_writer_t *self) +{ +#ifdef NO_OP + return; +#endif + if (CamdBase) + IExec->CloseLibrary(CamdBase); +} + +midi_writer_t sfx_device_midi_camd = { + "camd-midi", + &camd_init, + &camd_set_option, + &camd_write, + &camd_delay, + NULL, + &camd_reset_timer, + &camd_close +}; + +#endif /* HAVE_PROTO_CAMD_H */ diff --git a/engines/sci/sfx/device/devices.c b/engines/sci/sfx/device/devices.c new file mode 100644 index 0000000000..78d8c4279c --- /dev/null +++ b/engines/sci/sfx/device/devices.c @@ -0,0 +1,112 @@ +/*************************************************************************** + devices.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> + +***************************************************************************/ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif +#include "../device.h" +#include <stdio.h> + +#ifndef SCUMMVM +#ifdef HAVE_ALSA +extern struct _midi_device sfx_device_midi_alsa; +#endif +#if !defined(_DOS) && !defined(_WIN32) && !defined(_DREAMCAST) && !defined(__MORPHOS__) && !defined(ARM_WINCE) && !defined(_GP32) +extern struct _midi_device sfx_device_midi_unixraw; +#endif + +#ifdef HAVE_PROTO_CAMD_H +extern struct _midi_device sfx_device_midi_camd; +#endif +#endif // SCUMMVM + +#include <resource.h> + +static struct _midi_device *devices_midi[] = { +#ifndef SCUMMVM +#ifdef HAVE_PROTO_CAMD_H + &sfx_device_midi_camd, +#endif +#ifdef HAVE_ALSA + &sfx_device_midi_alsa, +#endif +#if !defined(_DOS) && !defined(_WIN32) && !defined(_DREAMCAST) && !defined(__MORPHOS__) && !defined(ARM_WINCE) && !defined(_GP32) + &sfx_device_midi_unixraw, +#endif +#endif // SCUMMVM + NULL +}; + +static struct _midi_device *devices_opl2[] = { + NULL +}; + + +/** -- **/ + +struct _midi_device **devices[] = { + NULL, /* No device */ + devices_midi, + devices_opl2, +}; + + +static struct _midi_device * +find_dev(int type, char *name) +{ + int i = 0; + + if (!type) + return NULL; + + if (!name) + return devices[type][0]; + + while (devices[type][i] && !strcmp(name, devices[type][i]->name)) + ++i; + + return devices[type][i]; +} + + +void * +sfx_find_device(int type, char *name) +{ + struct _midi_device *dev = find_dev(type, name); + + if (dev) { + if (dev->init(dev)) { + fprintf(stderr, "[SFX] Opening device '%s' failed\n", + dev->name); + return NULL; + } + + return dev; + }; + + return NULL; +} diff --git a/engines/sci/sfx/device/unixraw-midi.c b/engines/sci/sfx/device/unixraw-midi.c new file mode 100644 index 0000000000..69ce3890fc --- /dev/null +++ b/engines/sci/sfx/device/unixraw-midi.c @@ -0,0 +1,100 @@ +/*************************************************************************** + unixraw-midi.c Copyright (C) 2005 Walter van Niftrik + + + 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: + + Walter van Niftrik <w.f.b.w.v.niftrik@stud.tue.nl> + +***************************************************************************/ + +#include <sfx_engine.h> +#include "../device.h" + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#define SCI_UNIXRAW_MIDI_VERSION "0.1" + +#ifndef O_SYNC +# define O_SYNC 0 +#endif + +static int fd; +static const char *devicename = "/dev/midi"; + +static int +unixraw_init(midi_writer_t *self) +{ + sciprintf("[SFX] Initialising UNIX raw MIDI backend, v%s\n", SCI_UNIXRAW_MIDI_VERSION); + + fd = open(devicename, O_WRONLY|O_SYNC); + + if (!IS_VALID_FD(fd)) { + sciprintf("[SFX] Failed to open %s\n", devicename); + return SFX_ERROR; + } + + return SFX_OK; +} + +static int +unixraw_set_option(midi_writer_t *self, char *name, char *value) +{ + return SFX_ERROR; +} + +static int +unixraw_write(midi_writer_t *self, unsigned char *buffer, int len) +{ + if (write(fd, buffer, len) != len) { + sciprintf("[SFX] MIDI write error\n"); + return SFX_ERROR; + } + return SFX_OK; +} + +static void +unixraw_delay(midi_writer_t *self, int ticks) +{ +} + +static void +unixraw_reset_timer(midi_writer_t *self) +{ +} + +static void +unixraw_close(midi_writer_t *self) +{ + close(fd); +} + +midi_writer_t sfx_device_midi_unixraw = { + "unixraw-midi", + &unixraw_init, + &unixraw_set_option, + &unixraw_write, + &unixraw_delay, + NULL, + &unixraw_reset_timer, + &unixraw_close +}; diff --git a/engines/sci/sfx/doc/README b/engines/sci/sfx/doc/README new file mode 100644 index 0000000000..502ad7d619 --- /dev/null +++ b/engines/sci/sfx/doc/README @@ -0,0 +1,7 @@ +The files in this direcory are a bit outdated. There are some small non +critical errors in "patch.001", they are obvious if you bother to read +the source (which is correct). "sound01.txt" is just meant to give you a +basic understanding of the structures invovled in +SCI01/SCI1/SCI1.1/SCI32 sound resources. + +Ravi has made some really good documentation for SCI0. diff --git a/engines/sci/sfx/doc/patch001.txt b/engines/sci/sfx/doc/patch001.txt new file mode 100644 index 0000000000..0037eea9b4 --- /dev/null +++ b/engines/sci/sfx/doc/patch001.txt @@ -0,0 +1,118 @@ +SCI patch.001 Resource Format Revision 0.1 +Rickard Lind 1999-12-16 + +The patch.001 file for Roland MT-32, MT-100, LAPC-1, CM-32L and CM-64. + +This specification will sometimes look very incomprehensible without some +knowledge concerning the MT-32 MIDI implementation. +Have a look at http://members.xoom.com/_XMCM/TomLewandowski/docs.html + + +1. The header (494 bytes) which is always present. + +Offset Size Type Description +---------------------------------------------------------------------------- + 0x000 2 89 00 First Two Bytes + 0x002 20 ASCII String for MT-32 Display ("*It's Only A Model* ") + 0x016 20 ASCII String for MT-32 Display (" CAMELOT, CAMELOT! ") + 0x02a 20 ASCII String for MT-32 Display ("Ham & Jam & SpamAlot") + 0x03e 2 word MT-32 Master Volume + 0x040 1 Index in Predefined reverb settings at 0x04c (0-10) + 0x041 11 MT-32 SysEx block setting reverb + (last 3 bytes are dummies) + 0x04c 3 Predefined reverb setting #1 (Mode, Time, Level) + : : : + 0x06a 3 Predefined reverb setting #11 + 0x06d 8 MT-32 Patch Memory #1 (see Patch Memory description below) + : : : + 0x1e5 8 MT-32 Patch Memory #48 + 0x1ed 1 n = Number of Timbre Memory (0-64 userdefined instruments) +---------------------------------------------------------------------------- + + + Patch Memory description + + Offset Description + -------------------------------------------------------------------- + 0x00 Timbre Group (0 = Bank A, 1 = Bank B, 2 = Memory, 3 = Rythm) + 0x01 Timbre Number (0 - 63) + 0x02 Key Shift (0-48) [-24 - +24] + 0x03 Fine Tune (0-100) [-50 - +50] + 0x04 Bender Range (0-24) + 0x05 Assign Mode (0 = Poly 1, 1 = Poly 2, 2 = Poly 3, 3 = Poly 4) + 0x06 Reverb Switch (0 = OFF, 1 = ON) + 0x07 Dummy + -------------------------------------------------------------------- + + Mapping MT-32 to GM instruments is done with Timbre Group and + Timbre Number. + + Instrument 0-63: Bank A, 0-63 + Instrument 64-127: Bank B, 0-63 + + +2. The Timbre Memory block (if n > 0), offset relative to 0x1ee + +Offset Size Type Description +------------------------------------------------------------------------------ + 0x000 246 MT-32 Timbre Memory #1 (see Timbre Memory description below) + : : : + 0x??? 246 MT-32 Timbre Memory #n +------------------------------------------------------------------------------ + + + Timbre Memory description + + Offset Size Description + ----------------------------------------------------------------------- + 0x00 10 Timbre Name (ASCII String) + 0x0a See http://members.xoom.com/_XMCM/TomLewandowski/lapc1.txt + ----------------------------------------------------------------------- + + +3. Second MT-32 Patch Memory Block, offset realtive to 0x1ee + n * 246 + +Offset Size Description +--------------------------------------------------- + 0x000 2 0xab 0xcd (if this this is not present + there is no second block) + 0x002 8 MT-32 Patch Memory #49 + : : : + 0x17a 8 MT-32 Patch Memory #96 +--------------------------------------------------- + + +4. Block for setting up Patch Temporary Area (rythm part) and + System Area - Partial Reserve, offset relative to 0x370 + n * 246 + +Offset Size Description +--------------------------------------------------- + 0x000 2 0xdc 0xba (if this this is not present + this block is non existent) + 0x002 4 Rythm Setup for Key #24 (see below) + : : : + 0x0fe 4 Rythm Setup for Key #87 + 0x102 9 System Area - Partial Reserve +--------------------------------------------------- + + Rythm Setup description + See http://members.xoom.com/_XMCM/TomLewandowski/lapc1.txt + + +TODO: + + * Clearly describe which parts are interesting for a quick and dirty + GeneralMidi/patch.001/FreeSCI implementation + + * Describe how the Sierra MT-32 driver uses patch.001 + + * Make this readable to someone who has not been reading reference + manuals since early childhood + + * SGML + + +Revision history + + Revision 0.1 - 1999-12-16 + - First pre-release of the specification diff --git a/engines/sci/sfx/doc/sound01.txt b/engines/sci/sfx/doc/sound01.txt new file mode 100644 index 0000000000..8aae367da7 --- /dev/null +++ b/engines/sci/sfx/doc/sound01.txt @@ -0,0 +1,213 @@ +The SCI01+ sound resource format + +Originally written by Rickard Lind, 2000-01-05 +Extensively rewritten by Lars Skovlund, 2002-10-27 +Again updated by Lars Skovlund, 2005-10-12 + +Used in: +Quest for Glory II: Trial by Fire (QfG2) +Christmas greeting card 1990 (CC1990) + +The magic number (84 00) is left out, offset 0 is directly after these two +bytes. + +If you examine a SCI01 sound resource use "sciunpack --without-header" to +get the pointers within the file correct for your hex viewer. + +DESCRIPTION +----------- + +The SCI01 sound resource consists of a number of track lists and the +tracks themselves. There is one track list for (almost) every piece of +sound hardware supported by the game. Each track either contains track +data for one specific channel or a digital sample. + +SCI1 resources are the same, except that sample chunks are no longer +allowed (since they are now separate resources). + + Optional Priority Header + ------------------------ + + Some SCI1 songs contain an 8-byte header before the track list. At + least on PC platforms, its data is mostly unused. The priority value + is used if the script does not override it. + + offset size description + ------------------------------------------------------- + 0 byte 0xf0 Priority header marker byte + 1 byte Recommended priority for this song + 2 6 bytes Apparently unused + + Track List + ---------- + + The track list tells us which tracks are to be played on particular + hardware platforms. Each entry either terminates the previous list + or contains an entry for the current one. + + List Termination + ---------------- + + offset size description + ----------------------- + 0 byte 0xff + 1 byte Hardware ID of next list, 0xff if none + + List Entry + ---------- + + offset size description + ----------------------- + 0 byte 0 + 1 byte 0 + 2 word Data Chunk pointer + 4 word Data Chunk size + + The very first list in a file looks a little odd, in that it + starts with a single byte which tells us the hardware ID + associated with the first list (0 in all the cases I've seen) + followed by list entries as usual. + + Known Hardware IDs + ------------------ + + Some of these are used by multiple drivers, probably because they + support the same number of polyphonic voices. Note that the + hardware ID does not necessarily tell us whether samples are + supported - thus, the list for Roland MT-32 also contains sample + tracks, because the user may also have a Sound Blaster card + connected. SCI1 most likely has more hardware IDs than these. + + 0x00 - Sound Blaster, Adlib + 0x06 - MT-32 with Sound Blaster (for digital audio) + 0x09 - CMS/Game Blaster + 0x0c - Roland MT-32 + 0x12 - PC Speaker + 0x13 - IBM PS/1, Tandy 3-voice + + Data Chunks + ----------- + + In the sound resources of QfG2 and CC1990 I've seen two types of Data + Chunks, Sample and MIDI channel track. + + + Sample Chunk + ------------ + + offset size description + ----------------------- + 0 byte =0xfe + 1 byte !=0xfe (always 0 in QfG2 and CC1990) + 2 word Sample rate (Hz) + 4 word Sample length + 6 word Sample point 1 (begin?) + 8 word Sample point 2 (end?) + 10 Unsigned 8-bit mono sample data + + + MIDI channel track Chunk + ------------------------ + + This chunk begins with a 2 byte header. The low nibble of the + first byte indicates the channel number. The high nibble controls + certain aspects of (dynamic) track channel/hardware channel mapping. + + The second byte tells us how many notes will be + playing simultaneously in the channel. From the third byte onward + is the MIDI track data which looks just like normal SCI0 MIDI + track data, but all status bytes are targeted at one specific MIDI + channel. + +Example, sound.833 from QfG2 (--without-header) +----------------------------------------------- + +offset data description +------------------------ + 0000 00 Hardware ID for first track list + 0001 00 Track list continuation + 0002 00 Same hardware device + 0003 003F Data Chunk pointer (Little Endian) + 0005 0013 Data Chunk length (LE) + 0007 00 Track list continuation + 0008 00 Same hardware device + 0009 006A Data Chunk pointer (LE) + 000B 0015 Data Chunk length (LE) + 000D FF Next track list + 000E 09 for hardware device 0x09 + 000F 00 Track list continuation + 0010 00 Same hardware device + 0011 003F Data Chunk pointer (LE) + 0013 0013 Data Chunk length (LE) + 0015 00 Track list continuation + 0016 00 Same hardware device + 0017 0052 Data Chunk pointer (LE) + 0019 0018 Data Chunk length (LE) + 001B 00 Track list continuation + 001C 00 Same hardware device + 001D 0094 Data Chunk pointer (LE) + 001F 0012 Data Chunk length (LE) + 0021 FF Next track list + 0022 0C for hardware device 0x0C + 0023 00 Track list continuation + 0024 00 Same hardware device + 0025 003F Data Chunk pointer (LE) + 0027 0013 Data Chunk length (LE) + 0029 00 Track list continuation + 002A 00 Same hardware device + 002B 0052 Data Chunk pointer (LE) + 002D 0018 Data Chunk length (LE) + 002F FF Next track list + 0030 13 for hardware device 0x13 + 0031 00 Track list continuation + 0032 00 Same hardware device + 0033 003F Data Chunk pointer (LE) + 0035 0013 Data Chunk length (LE) + 0037 00 Track list continuation + 0038 00 Same hardware device + 0039 007F Data Chunk pointer (LE) + 003B 0015 Data Chunk length (LE) + + 003D FF FF Sequence Control - End of Sequence Blocks + ------------------------------------------------------------ + 003F 0F MIDI Track channel 15 (control channel) + 0040 01 One note playing on track (probably just to satisfy the + MIDI engine) + 0041 MIDI Track data like SCI0 + 0052 02 MIDI Track channel 2 + 0053 02 Two notes playing on track + 0054 MIDI Track data like SCI0 + 006A 03 MIDI Track channel 3 + 006B 01 One note playing on track + 006C MIDI Track data like SCI0 + 007F 0A MIDI Track channel 10 + 0080 01 One note playing on track + 0081 MIDI Track data like SCI0 + 0094 02 MIDI Track channel 2 + 0095 01 One note playing on track + 0096 MIDI Track data like SCI0 + +Addendum (provided by Lars Skovlund) +------------------------------------ + +First of all, tracks do not loop individually. No loop signals are +reported. + +Absolute cues are generally stored in the signal selector, and +cumulative cues are generally stored in the dataInc selector, with +some interesting twists: + +1. The server's record of the absolute cue value is reset as part of + UPDATE_CUES. +2. When a cumulative cue is reported to the VM object, it will be + placed in _both_ fields. In such a case, a constant of 0x7f will be + added to the _signal_ selector only, to be able to distinguish the + two kinds of cue (this has already been coded). +3. The above only happens if the sound does not use absolute cues + (i.e. if the signal is 0 a priori). Note that, because of 1) + above, this does not cause problems neither with successive + cumulative cues nor with mixed cumulative/absolute cues. +4. A signal of 0xff will stop the sound object playing. This may be + for purely internal purposes. +5. There no longer is a field indicating the amount of increment for + a cue. diff --git a/engines/sci/sfx/iterator.c b/engines/sci/sfx/iterator.c new file mode 100644 index 0000000000..f6c123a082 --- /dev/null +++ b/engines/sci/sfx/iterator.c @@ -0,0 +1,2113 @@ +/*************************************************************************** + iterator.c Copyright (C) 2001..04 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> + +***************************************************************************/ +/* Song iterators */ + +#include <stdio.h> +#include <sfx_iterator_internal.h> +#include <sfx_player.h> +#include <resource.h> +#include <sci_memory.h> + +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*/ + +void +print_tabs_id(int nr, songit_id_t id) +{ + while (nr-- > 0) + fprintf(stderr, "\t"); + + fprintf(stderr, "[%08lx] ", id); +} + +#ifndef HAVE_MEMCHR +static void * +memchr(void *_data, int c, int n) +{ + unsigned char *data = (unsigned char *) _data; + + while (n && !(*data == c)) { + ++data; + --n; + } + + if (n) + return data; + else + return NULL; +} +#endif + +static void +_common_init(base_song_iterator_t *self) +{ + self->fade.action = FADE_ACTION_NONE; + self->resetflag = 0; + self->loops = 0; + self->priority = 0; +} + + +/************************************/ +/*-- 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 > self->size) { \ + fprintf(stderr, SIPFX "Reached end of song without terminator (%x/%x) at %d!\n", offset, self->size, __LINE__); \ + return SI_FINISHED; \ + } + +#define CHECK_FOR_END(offset_augment) \ + if ((channel->offset + (offset_augment)) > channel->end) { \ + channel->state = SI_STATE_FINISHED; \ + fprintf(stderr, SIPFX "Reached end of track %d without terminator (%x+%x/%x) at %d!\n", channel->id, channel->offset, offset_augment, channel->end, __LINE__); \ + return SI_FINISHED; \ + } + + +static inline 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_read_next_command(sci0_song_iterator_t *self, + unsigned char *buf, int *result); + + +static int +_sci0_get_pcm_data(sci0_song_iterator_t *self, + sfx_pcm_config_t *format, + int *xoffset, + unsigned int *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 +_reset_synth_channels(base_song_iterator_t *self, song_iterator_channel_t *channel) +{ + int i; + byte buf[5]; + tell_synth_func *tell = sfx_get_player_tell_func(); + + for (i = 0; i < MIDI_CHANNELS; i++) + { + if (channel->saw_notes & (1 << i)) + { + buf[0] = 0xe0 | i; /* Pitch bend */ + buf[1] = 0x80; /* Wheel center */ + buf[2] = 0x40; + if (tell) + tell(3, buf); + /* TODO: Reset other controls? */ + } + } +} + +static int +_parse_sci_midi_command(base_song_iterator_t *self, unsigned char *buf, int *result, + song_iterator_channel_t *channel, + int flags) +{ + unsigned char cmd; + int paramsleft; + int midi_op; + int midi_channel; + + channel->state = SI_STATE_DELTA_TIME; + + cmd = self->data[channel->offset++]; + + if (!(cmd & 0x80)) { + /* 'Running status' mode */ + channel->offset--; + cmd = channel->last_cmd; + } + + if (cmd == 0xfe) + { + fprintf(stderr, "song iterator subsystem: Corrupted sound resource detected.\n"); + return SI_FINISHED; + } + + midi_op = cmd >> 4; + midi_channel = cmd & 0xf; + paramsleft = MIDI_cmdlen[midi_op]; + channel->saw_notes |= 1 << midi_channel; + +#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", + self->data[channel->offset-3], + self->data[channel->offset-2], + self->data[channel->offset-1], + self->data[channel->offset], + self->data[channel->offset+1], + self->data[channel->offset+2]); +} +#endif + + buf[0] = cmd; + + + CHECK_FOR_END(paramsleft); + memcpy(buf + 1, self->data + 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 */ + self->next((song_iterator_t *) self, buf, result); + + + if (cmd == SCI_MIDI_EOT) { + /* End of track? */ + _reset_synth_channels(self, channel); +/* fprintf(stderr, "eot; loops = %d, notesplayed=%d\n", self->loops, channel->notes_played);*/ + if (self->loops > 1 /* && channel->notes_played*/) { + /* If allowed, decrement the number of loops */ + if (!(flags & PARSE_FLAG_LOOPS_UNLIMITED)) + *result = --self->loops; + +#ifdef DEBUG_DECODING + fprintf(stderr, "%s L%d: (%p):%d Looping ", __FILE__, __LINE__, self, channel->id); + if (flags & PARSE_FLAG_LOOPS_UNLIMITED) + fprintf(stderr, "(indef.)"); + else + fprintf(stderr, "(%d)", self->loops); + fprintf(stderr, " %x -> %x\n", + channel->offset, channel->loop_offset); +#endif + channel->offset = channel->loop_offset; + channel->notes_played = 0; + channel->state = SI_STATE_DELTA_TIME; + channel->total_timepos = channel->loop_timepos; + channel->last_cmd = 0xfe; + fprintf(stderr, "Looping song iterator %08lx.\n", self->ID); + return SI_LOOP; + } else { + channel->state = SI_STATE_FINISHED; +#ifdef DEBUG_DECODING + fprintf(stderr, "%s L%d: (%p):%d EOT because" + " %d notes, %d loops\n", + __FILE__, __LINE__, self, channel->id, + channel->notes_played, self->loops); +#endif + 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 */ + self->next((song_iterator_t *) self, 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) + self->ccc += buf[2]; + else { /* No parameter to CC */ + self->ccc++; +/* channel->offset--; */ + } + *result = self->ccc; + return SI_RELATIVE_CUE; + + case SCI_MIDI_RESET_ON_SUSPEND: + self->resetflag = buf[2]; + break; + + case SCI_MIDI_SET_POLYPHONY: + self->polyphony[midi_channel] = buf[2]; + +#if 0 + { + int i; + int voices = 0; + for (i = 0; i < ((sci1_song_iterator_t *) self)->channels_nr; i++) + { + voices += self->polyphony[i]; + } + + sciprintf("SET_POLYPHONY(%d, %d) for a total of %d voices\n", midi_channel, buf[2], voices); + sciprintf("[iterator-1] DEBUG: Polyphony = [ "); + for (i = 0; i < ((sci1_song_iterator_t *) self)->channels_nr; i++) + sciprintf("%d ", self->polyphony[i]); + sciprintf("]\n"); + sciprintf("[iterator-1] DEBUG: Importance = [ "); + for (i = 0; i < ((sci1_song_iterator_t *) self)->channels_nr; i++) + sciprintf("%d ", self->importance[i]); + sciprintf("]\n"); + } +#endif + break; + + case SCI_MIDI_SET_REVERB: + break; + + case SCI_MIDI_CHANNEL_MUTE: + sciprintf("CHANNEL_MUTE(%d, %d)\n", midi_channel, buf[2]); + break; + + case SCI_MIDI_HOLD: + { + // Safe cast: This controller is only used in SCI1 + sci1_song_iterator_t *self1 = (sci1_song_iterator_t *) self; + + if (buf[2] == self1->hold) + { + channel->offset = channel->initial_offset; + channel->notes_played = 0; + channel->state = SI_STATE_COMMAND; + channel->total_timepos = 0; + + self1->channels_looped = self1->active_channels-1; + + 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 */ + self->next((song_iterator_t *) self, 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 ((cmd & 0xf0) == 0x90) /* note on? */ + channel->notes_played++; + + /* Process as normal MIDI operation */ + return 0; + } +} + + +static int +_sci_midi_process_state(base_song_iterator_t *self, unsigned char *buf, int *result, + song_iterator_channel_t *channel, + int flags) +{ + CHECK_FOR_END(0); + + switch (channel->state) { + + case SI_STATE_PCM: { + if (*(self->data + channel->offset) == 0 + && *(self->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: { + sfx_pcm_config_t format; + int offset; + unsigned int size; + int delay; + if (_sci0_get_pcm_data((sci0_song_iterator_t *) self, &format, &offset, &size)) + return SI_FINISHED; /* 'tis broken */ + channel->state = SI_STATE_FINISHED; + delay = (size * 50 + format.rate - 1) / format.rate; /* number of ticks to completion*/ + + fprintf(stderr, "delaying %d ticks\n", delay); + return delay; + } + + case SI_STATE_UNINITIALISED: + fprintf(stderr, SIPFX "Attempt to read command from uninitialized iterator!\n"); + self->init((song_iterator_t *) self); + return self->next((song_iterator_t *) self, buf, result); + + case SI_STATE_FINISHED: + return SI_FINISHED; + + case SI_STATE_DELTA_TIME: { + int offset; + int ticks = _parse_ticks(self->data + channel->offset, + &offset, + self->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 = _parse_sci_midi_command(self, buf, result, + channel, flags); + + if (retval == SI_FINISHED) { + if (self->active_channels) + --(self->active_channels); +#ifdef DEBUG_DECODING + fprintf(stderr, "%s L%d: (%p):%d Finished channel, %d channels left\n", + __FILE__, __LINE__, self, channel->id, + self->active_channels); +#endif + /* If we still have channels left... */ + if (self->active_channels) { + return self->next((song_iterator_t *) self, buf, result); + } + + /* Otherwise, we have reached the end */ + self->loops = 0; + } + + return retval; + } + + default: + fprintf(stderr, SIPFX "Invalid iterator state %d!\n", + channel->state); + BREAKPOINT(); + return SI_FINISHED; + } +} + +static inline int +_sci_midi_process(base_song_iterator_t *self, unsigned char *buf, int *result, + song_iterator_channel_t *channel, + int flags) +{ + return _sci_midi_process_state(self, buf, result, + channel, + flags); +} + +static int +_sci0_read_next_command(sci0_song_iterator_t *self, unsigned char *buf, + int *result) +{ + return _sci_midi_process((base_song_iterator_t *) self, buf, result, + &(self->channel), + PARSE_FLAG_PARAMETRIC_CUE); +} + + +static inline int +_sci0_header_magic_p(unsigned char *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(sci0_song_iterator_t *self, + sfx_pcm_config_t *format, + int *xoffset, + unsigned int *xsize) +{ + int tries = 2; + int found_it = 0; + unsigned char *pcm_data; + int size; + unsigned int offset = SCI0_MIDI_OFFSET; + + if (self->data[0] != 2) + return 1; + /* No such luck */ + + while ((tries--) && (offset < self->size) && (!found_it)) { + /* Search through the garbage manually */ + unsigned char *fc = (unsigned char*)memchr(self->data + offset, + SCI0_END_OF_SONG, + self->size - offset); + + if (!fc) { + fprintf(stderr, SIPFX "Warning: Playing unterminated" + " song!\n"); + return 1; + } + + /* add one to move it past the END_OF_SONG marker */ + offset = fc - self->data + 1; + + + if (_sci0_header_magic_p(self->data, offset, self->size)) + found_it = 1; + } + + if (!found_it) { + fprintf(stderr, SIPFX + "Warning: Song indicates presence of PCM, but" + " none found (finally at offset %04x)\n", offset); + + return 1; + } + + pcm_data = self->data + offset; + + size = getUInt16(pcm_data + SCI0_PCM_SIZE_OFFSET); + + /* Two of the format parameters are fixed by design: */ + format->format = SFX_PCM_FORMAT_U8; + format->stereo = SFX_PCM_MONO; + format->rate = getUInt16(pcm_data + SCI0_PCM_SAMPLE_RATE_OFFSET); + + if (offset + SCI0_PCM_DATA_OFFSET + size != self->size) { + int d = offset + SCI0_PCM_DATA_OFFSET + size - self->size; + + fprintf(stderr, SIPFX + "Warning: PCM advertizes %d bytes of data, but %d" + " bytes are trailing in the resource!\n", + size, self->size - (offset + SCI0_PCM_DATA_OFFSET)); + + if (d > 0) + size -= d; /* Fix this */ + } + + *xoffset = offset; + *xsize = size; + + return 0; + } + +static sfx_pcm_feed_t * +_sci0_check_pcm(sci0_song_iterator_t *self) +{ + sfx_pcm_config_t format; + int offset; + unsigned int size; + if (_sci0_get_pcm_data(self, &format, &offset, &size)) + return NULL; + + self->channel.state + = SI_STATE_FINISHED; /* Don't play both PCM and music */ + + return sfx_iterator_make_feed(self->data, + offset + SCI0_PCM_DATA_OFFSET, + size, + format); +} + +static song_iterator_t * +_sci0_handle_message(sci0_song_iterator_t *self, song_iterator_message_t msg) +{ + if (msg.recipient == _SIMSG_BASE) { + switch (msg.type) { + + case _SIMSG_BASEMSG_PRINT: + print_tabs_id(msg.args[0].i, self->ID); + fprintf(stderr, "SCI0: dev=%d, active-chan=%d, size=%d, loops=%d\n", + self->device_id, self->active_channels, self->size, + self->loops); + break; + + case _SIMSG_BASEMSG_SET_LOOPS: + self->loops = msg.args[0].i; + break; + + case _SIMSG_BASEMSG_CLONE: { + int tsize = sizeof(sci0_song_iterator_t); + base_song_iterator_t *mem = (base_song_iterator_t*)sci_malloc(tsize); + memcpy(mem, self, tsize); + sci_refcount_incref(mem->data); +#ifdef DEBUG_VERBOSE +fprintf(stderr, "** CLONE INCREF for new %p from %p at %p\n", mem, self, mem->data); +#endif + return (struct _song_iterator *) mem; /* Assume caller has another copy of this */ + } + + case _SIMSG_BASEMSG_STOP: { + songit_id_t sought_id = msg.ID; + + if (sought_id == self->ID) + self->channel.state = SI_STATE_FINISHED; + break; + } + + case _SIMSG_BASEMSG_SET_PLAYMASK: { + int i; + self->device_id = msg.args[0].i; + + /* Set all but the rhytm channel mask bits */ + self->channel.playmask &= ~(1 << MIDI_RHYTHM_CHANNEL); + + for (i = 0; i < MIDI_CHANNELS; i++) + if (self->data[2 + (i << 1)] & self->device_id + && i != MIDI_RHYTHM_CHANNEL) + self->channel.playmask |= (1 << i); + } + break; + + case _SIMSG_BASEMSG_SET_RHYTHM: + self->channel.playmask &= ~(1 << MIDI_RHYTHM_CHANNEL); + if (msg.args[0].i) + self->channel.playmask |= (1 << MIDI_RHYTHM_CHANNEL); + break; + + case _SIMSG_BASEMSG_SET_FADE: + { + fade_params_t *fp = (fade_params_t *) msg.args[0].p; + self->fade.action = fp->action; + self->fade.final_volume = fp->final_volume; + self->fade.ticks_per_step = fp->ticks_per_step; + self->fade.step_size = fp->step_size; + break; + } + + default: + return NULL; + } + + return (song_iterator_t *)self; + } + return NULL; +} + +static int +_sci0_get_timepos(sci0_song_iterator_t *self) +{ + return self->channel.total_timepos; +} + +static void +_base_init_channel(song_iterator_channel_t *channel, int id, int offset, + int end) +{ + channel->playmask = PLAYMASK_NONE; /* Disable all channels */ + channel->id = id; + channel->notes_played = 0; + channel->state = SI_STATE_DELTA_TIME; + channel->loop_timepos = 0; + channel->total_timepos = 0; + channel->timepos_increment = 0; + channel->delay = 0; /* Only used for more than one channel */ + channel->last_cmd = 0xfe; + + channel->offset + = channel->loop_offset + = channel->initial_offset + = offset; + channel->end = end; + channel->saw_notes = 0; +} + +static void +_sci0_init(sci0_song_iterator_t *self) +{ + _common_init((base_song_iterator_t *) self); + + self->ccc = 0; /* Reset cumulative cue counter */ + self->active_channels = 1; + _base_init_channel(&(self->channel), 0, SCI0_MIDI_OFFSET, self->size); + _reset_synth_channels((base_song_iterator_t *) self, + &(self->channel)); + self->delay_remaining = 0; + + if (self->data[0] == 2) /* Do we have an embedded PCM? */ + self->channel.state = SI_STATE_PCM; +} + + +static void +_sci0_cleanup(sci0_song_iterator_t *self) +{ +#ifdef DEBUG_VERBOSE +fprintf(stderr, "** FREEING it %p: data at %p\n", self, self->data); +#endif + if (self->data) + sci_refcount_decref(self->data); + self->data = NULL; +} + +/***************************/ +/*-- SCI1 song iterators --*/ +/***************************/ + +#define SCI01_INVALID_DEVICE 0xff + +/* First index determines whether DSP output is supported */ +static 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 */ + +#define SONGDATA(x) self->data[offset + (x)] +#define SCI1_CHANDATA(off) self->data[channel->offset + (off)] + +static int +_sci1_sample_init(sci1_song_iterator_t *self, int offset) +{ + sci1_sample_t *sample, **seekerp; + int rate; + int length; + int begin; + int end; + + CHECK_FOR_END_ABSOLUTE(offset + 10); + if (self->data[offset + 1] != 0) + sciprintf("[iterator-1] In sample at offset 0x04x: Byte #1 is %02x instead of zero\n", + self->data[offset + 1]); + + rate = getInt16(self->data + offset + 2); + length = getUInt16(self->data + offset + 4); + begin = getInt16(self->data + offset + 6); + end = getInt16(self->data + offset + 8); + + CHECK_FOR_END_ABSOLUTE(offset + 10 + length); + + sample = (sci1_sample_t*)sci_malloc(sizeof(sci1_sample_t)); + sample->delta = begin; + sample->size = length; + sample->data = self->data + offset + 10; + +#ifdef DEBUG_VERBOSE + fprintf(stderr, "[SAMPLE] %x/%x/%x/%x l=%x\n", + offset + 10, begin, end, self->size, length); +#endif + + sample->format.format = SFX_PCM_FORMAT_U8; + sample->format.stereo = SFX_PCM_MONO; + sample->format.rate = rate; + + sample->announced = 0; + + /* Perform insertion sort */ + seekerp = &(self->next_sample); + + while (*seekerp && (*seekerp)->delta < begin) + seekerp = &((*seekerp)->next); + + sample->next = *seekerp; + *seekerp = sample; + + return 0; /* Everything's fine */ +} + + +static int +_sci1_song_init(sci1_song_iterator_t *self) +{ + sci1_sample_t *seeker; + int last_time; + int offset = 0; + self->channels_nr = 0; + self->next_sample = 0; +// self->device_id = 0x0c; + + CHECK_FOR_END_ABSOLUTE(0); + if (SONGDATA(0) == 0xf0) + { + self->priority = SONGDATA(1); + + offset += 8; + } + + while (SONGDATA(0) != 0xff + && SONGDATA(0) != self->device_id) { + offset++; + CHECK_FOR_END_ABSOLUTE(offset + 1); + while (SONGDATA(0) != 0xff) { + CHECK_FOR_END_ABSOLUTE(offset + 7); + offset += 6; + } + offset++; + } + + if (SONGDATA(0) == 0xff) { + sciprintf("[iterator-1] Song does not support" + " hardware 0x%02x\n", + self->device_id); + return 1; + } + + offset++; + + while (SONGDATA(0) != 0xff) { /* End of list? */ + int track_offset; + int end; + offset += 2; + + CHECK_FOR_END_ABSOLUTE(offset + 4); + + track_offset = getUInt16(self->data + offset); + end = getUInt16(self->data + offset + 2); + + CHECK_FOR_END_ABSOLUTE(track_offset - 1); + + if (self->data[track_offset] == 0xfe) { + if (_sci1_sample_init(self, track_offset)) + return 1; /* Error */ + } else { + /* Regular MIDI channel */ + if (self->channels_nr >= MIDI_CHANNELS) { + sciprintf("[iterator-1] Warning: Song has more than %d channels, cutting them off\n", + MIDI_CHANNELS); + break; /* Scan for remaining samples */ + } else { + int channel_nr + = self->data[track_offset] & 0xf; + song_iterator_channel_t *channel = + &(self->channels[self->channels_nr++]); + + if (self->data[track_offset] & 0xf0) + printf("Channel %d has mapping bits %02x\n", + channel_nr, self->data[track_offset] & 0xf0); + + _base_init_channel(channel, + channel_nr, + /* Skip over header bytes: */ + track_offset + 2, + track_offset + end); + _reset_synth_channels((base_song_iterator_t *) self, + channel); + + self->polyphony[self->channels_nr - 1] + = SCI1_CHANDATA(-1); + self->importance[self->channels_nr - 1] + = self->polyphony[self->channels_nr - 1] >> 4; + self->polyphony[self->channels_nr - 1] &= 15; + + channel->playmask = ~0; /* Enable all */ + self->channel_mask |= (1 << channel_nr); + + CHECK_FOR_END_ABSOLUTE(offset + end); + } + } + offset += 4; + CHECK_FOR_END_ABSOLUTE(offset); + } + + /* Now ensure that sapmle deltas are relative to the previous sample */ + seeker = self->next_sample; + last_time = 0; + self->active_channels = self->channels_nr; + self->channels_looped = 0; + + while (seeker) { + int prev_last_time = last_time; + sciprintf("[iterator-1] 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; + seeker = seeker->next; + } + + return 0; /* Success */ +} + +#undef SONGDATA + +static inline int +_sci1_get_smallest_delta(sci1_song_iterator_t *self) +{ + int i, d = -1; + for (i = 0; i < self->channels_nr; i++) + if (self->channels[i].state == SI_STATE_COMMAND + && (d == -1 || self->channels[i].delay < d)) + d = self->channels[i].delay; + + if (self->next_sample && self->next_sample->delta < d) + return self->next_sample->delta; + else + return d; +} + +static inline void +_sci1_update_delta(sci1_song_iterator_t *self, int delta) +{ + int i; + + if (self->next_sample) + self->next_sample->delta -= delta; + + for (i = 0; i < self->channels_nr; i++) + if (self->channels[i].state == SI_STATE_COMMAND) + self->channels[i].delay -= delta; +} + +static inline int +_sci1_no_delta_time(sci1_song_iterator_t *self) +{ /* Checks that none of the channels is waiting for its delta to be read */ + int i; + + for (i = 0; i < self->channels_nr; i++) + if (self->channels[i].state == SI_STATE_DELTA_TIME) + return 0; + + return 1; +} + +static void +_sci1_dump_state(sci1_song_iterator_t *self) +{ + int i; + + sciprintf("-- [%p] ------------------------\n", self); + for (i = 0; i < self->channels_nr; i++) { + int j; + sciprintf("%d(s%02d): d-%d:\t(%x/%x) ", + self->channels[i].id, + self->channels[i].state, + self->channels[i].delay, + self->channels[i].offset, + self->channels[i].end); + for (j = -3; j < 9; j++) { + if (j == 0) + sciprintf(">"); + else + sciprintf(" "); + + sciprintf("%02x", self->data[self->channels[i].offset+j]); + + if (j == 0) + sciprintf("<"); + else + sciprintf(" "); + } + sciprintf("\n"); + } + if (self->next_sample) { + sciprintf("\t[sample %d]\n", + self->next_sample->delta); + } + sciprintf("------------------------------------------\n"); +} + +#define COMMAND_INDEX_NONE -1 +#define COMMAND_INDEX_PCM -2 + +static inline int /* Determine the channel # of the next active event, or -1 */ +_sci1_command_index(sci1_song_iterator_t *self) +{ + int i; + int base_delay = 0x7ffffff; + int best_chan = COMMAND_INDEX_NONE; + + for (i = 0; i < self->channels_nr; i++) + if ((self->channels[i].state != SI_STATE_PENDING) + && (self->channels[i].state != SI_STATE_FINISHED)) { + + if ((self->channels[i].state == SI_STATE_DELTA_TIME) + && (self->channels[i].delay == 0)) + return i; + /* First, read all unknown delta times */ + + if (self->channels[i].delay < base_delay) { + best_chan = i; + base_delay = self->channels[i].delay; + } + } + + if (self->next_sample && base_delay >= self->next_sample->delta) + return COMMAND_INDEX_PCM; + + return best_chan; +} + + +static sfx_pcm_feed_t * +_sci1_get_pcm(sci1_song_iterator_t *self) +{ + if (self->next_sample + && self->next_sample->delta <= 0) { + sci1_sample_t *sample = self->next_sample; + sfx_pcm_feed_t *feed + = sfx_iterator_make_feed(self->data, + sample->data - self->data, + sample->size, + sample->format); + + self->next_sample = self->next_sample->next; + + sci_free(sample); + + return feed; + } else + return NULL; +} + + +static int +_sci1_process_next_command(sci1_song_iterator_t *self, + unsigned char *buf, int *result) +{ + int retval = -42; /* Shouldn't happen, but gcc doesn't agree */ + int chan; + + if (!self->initialised) { + sciprintf("[iterator-1] DEBUG: Initialising for %d\n", + self->device_id); + self->initialised = 1; + if (_sci1_song_init(self)) + return SI_FINISHED; + } + + + if (self->delay_remaining) { + int delay = self->delay_remaining; + self->delay_remaining = 0; + return delay; + } + + do { + chan = _sci1_command_index(self); + + if (chan == COMMAND_INDEX_NONE) { + return SI_FINISHED; + } + + if (chan == COMMAND_INDEX_PCM) { + + if (self->next_sample->announced) { + /* Already announced; let's discard it */ + sfx_pcm_feed_t *feed + = _sci1_get_pcm(self); + feed->destroy(feed); + } else { + int delay = self->next_sample->delta; + + if (delay) { + _sci1_update_delta(self, delay); + return delay; + } + /* otherwise we're touching a PCM */ + self->next_sample->announced = 1; + return SI_PCM; + } + } else { /* Not a PCM */ + + retval = _sci_midi_process((base_song_iterator_t *) self, + buf, result, + &(self->channels[chan]), + PARSE_FLAG_LOOPS_UNLIMITED); + + if (retval == SI_LOOP) { + self->channels_looped++; + self->channels[chan].state = SI_STATE_PENDING; + self->channels[chan].delay = 0; + + if (self->channels_looped == self->active_channels) { + int i; + + /* Everyone's ready: Let's loop */ + for (i = 0; i < self->channels_nr; i++) + if (self->channels[i].state + == SI_STATE_PENDING) + self->channels[i].state + = SI_STATE_DELTA_TIME; + + self->channels_looped = 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 = _sci1_get_smallest_delta(self); + + if (_sci1_no_delta_time(self) && sd) { + /* No other channel is ready */ + _sci1_update_delta(self, sd); + + /* Only from here do we return delta times */ + return sd; + } + } + + } /* Not a PCM */ + + } while (retval > 0); /* All delays must be processed separately */ + + return retval; +} + +static struct _song_iterator * +_sci1_handle_message(sci1_song_iterator_t *self, + song_iterator_message_t msg) +{ + if (msg.recipient == _SIMSG_BASE) { /* May extend this in the future */ + switch (msg.type) { + + case _SIMSG_BASEMSG_PRINT: { + int playmask = 0; + int i; + + for (i = 0; i < self->channels_nr; i++) + playmask |= self->channels[i].playmask; + + print_tabs_id(msg.args[0].i, self->ID); + fprintf(stderr, "SCI1: chan-nr=%d, playmask=%04x\n", + self->channels_nr, playmask); + } + break; + + case _SIMSG_BASEMSG_CLONE: { + int tsize = sizeof(sci1_song_iterator_t); + sci1_song_iterator_t *mem = (sci1_song_iterator_t*)sci_malloc(tsize); + sci1_sample_t **samplep; + int delta = msg.args[0].i; /* Delay until next step */ + + memcpy(mem, self, tsize); + samplep = &(mem->next_sample); + + sci_refcount_incref(mem->data); + + mem->delay_remaining += delta; + + /* Clone chain of samples */ + while (*samplep) { + sci1_sample_t *newsample + = (sci1_sample_t*)sci_malloc(sizeof(sci1_sample_t)); + memcpy(newsample, *samplep, + sizeof(sci1_sample_t)); + *samplep = newsample; + samplep = &(newsample->next); + } + + return (struct _song_iterator *) mem; /* Assume caller has another copy of this */ + } + + case _SIMSG_BASEMSG_STOP: { + songit_id_t sought_id = msg.ID; + int i; + + if (sought_id == self->ID) { + self->ID = 0; + + for (i = 0; i < self->channels_nr; i++) + self->channels[i].state = SI_STATE_FINISHED; + } + break; + } + + case _SIMSG_BASEMSG_SET_PLAYMASK: if (msg.ID == self->ID) { + self->channel_mask = 0; + + self->device_id + = sci0_to_sci1_device_map + [sci_ffs(msg.args[0].i & 0xff) - 1] + [sfx_pcm_available()] + ; + + if (self->device_id == 0xff) { + sciprintf("[iterator-1] Warning: Device %d(%d) not supported", + msg.args[0].i & 0xff, sfx_pcm_available()); + } + if (self->initialised) { + int i; + int toffset = -1; + + for (i = 0; i < self->channels_nr; i++) + if (self->channels[i].state != SI_STATE_FINISHED + && self->channels[i].total_timepos > toffset) { + toffset = self->channels[i].total_timepos + + self->channels[i].timepos_increment + - self->channels[i].delay; + } + + /* Find an active channel so that we can + ** get the correct time offset */ + + _sci1_song_init(self); + + toffset -= self->delay_remaining; + self->delay_remaining = 0; + + if (toffset > 0) + return new_fast_forward_iterator((song_iterator_t *) self, + toffset); + } else { + _sci1_song_init(self); + self->initialised = 1; + } + + break; + + } + + case _SIMSG_BASEMSG_SET_LOOPS: + if (msg.ID == self->ID) + self->loops = (msg.args[0].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: + self->hold = msg.args[0].i; + break; + case _SIMSG_BASEMSG_SET_RHYTHM: + /* Ignore */ + break; + + case _SIMSG_BASEMSG_SET_FADE: + { + fade_params_t *fp = (fade_params_t *) msg.args[0].p; + self->fade.action = fp->action; + self->fade.final_volume = fp->final_volume; + self->fade.ticks_per_step = fp->ticks_per_step; + self->fade.step_size = fp->step_size; + break; + } + + default: + fprintf(stderr, SIPFX "Unsupported command %d to" + " SCI1 iterator", msg.type); + } + return (song_iterator_t *) self; + } + return NULL; +} + + +static int +_sci1_read_next_command(sci1_song_iterator_t *self, + unsigned char *buf, int *result) +{ + return _sci1_process_next_command(self, buf, result); +} + + +static void +_sci1_init(sci1_song_iterator_t *self) +{ + _common_init((base_song_iterator_t *) self); + self->ccc = 127; + self->device_id = 0x00; /* Default to Sound Blaster/Adlib for purposes + ** of cue computation */ + self->next_sample = NULL; + self->channels_nr = 0; + self->initialised = 0; + self->delay_remaining = 0; + self->loops = 0; + self->hold = 0; + memset(self->polyphony, 0, sizeof(self->polyphony)); + memset(self->importance, 0, sizeof(self->importance)); +} + +static void +_sci1_cleanup(sci1_song_iterator_t *it) +{ + sci1_sample_t *sample_seeker = it->next_sample; + while (sample_seeker) { + sci1_sample_t *old_sample = sample_seeker; + sample_seeker = sample_seeker->next; + sci_free(old_sample); + } + + _sci0_cleanup((sci0_song_iterator_t *)it); +} + +static int +_sci1_get_timepos(sci1_song_iterator_t *self) +{ + int max = 0; + int i; + + for (i = 0; i < self->channels_nr; i++) + if (self->channels[i].total_timepos > max) + max = self->channels[i].total_timepos; + + return max; +} + +/*****************************/ +/*-- Cleanup song iterator --*/ +/*****************************/ + + +static void +_cleanup_iterator_init(song_iterator_t *it) +{ +} + +static song_iterator_t * +_cleanup_iterator_handle_message(song_iterator_t *i, song_iterator_message_t msg) +{ + if (msg.recipient == _SIMSG_BASEMSG_PRINT + && msg.type == _SIMSG_BASEMSG_PRINT) { + print_tabs_id(msg.args[0].i, i->ID); + fprintf(stderr, "CLEANUP\n"); + } + + return NULL; +} + +static int +_cleanup_iterator_next(song_iterator_t *self, unsigned char *buf, int *result) +{ + /* Task: Return channel-notes-off for each channel */ + if (self->channel_mask) { + int bs = sci_ffs(self->channel_mask) - 1; + + self->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; +} + +song_iterator_t * +new_cleanup_iterator(unsigned int channels) +{ + song_iterator_t *it = (song_iterator_t*)sci_malloc(sizeof(song_iterator_t)); + it->channel_mask = channels; + it->ID = 17; + it->flags = 0; + it->death_listeners_nr = 0; + + it->cleanup = NULL; + it->get_pcm_feed = NULL; + it->init = _cleanup_iterator_init; + it->handle_message = _cleanup_iterator_handle_message; + it->get_timepos = NULL; + it->next = _cleanup_iterator_next; + return it; +} + +/**********************************/ +/*-- Fast-forward song iterator --*/ +/**********************************/ + +static int +_ff_read_next_command(fast_forward_song_iterator_t *self, + byte *buf, int *result) +{ + int rv; + + if (self->delta <= 0) + return SI_MORPH; /* Did our duty */ + + while (1) { + rv = self->delegate->next(self->delegate, buf, result); + + if (rv > 0) { + /* Subtract from the delta we want to wait */ + self->delta -= rv; + + /* Done */ + if (self->delta < 0) + return -self->delta; + } + + if (rv <= 0) + return rv; + } +} + +static sfx_pcm_feed_t * +_ff_check_pcm(fast_forward_song_iterator_t *self) +{ + return self->delegate->get_pcm_feed(self->delegate); +} + +static song_iterator_t * +_ff_handle_message(fast_forward_song_iterator_t *self, + song_iterator_message_t msg) +{ + if (msg.recipient == _SIMSG_PLASTICWRAP) + switch (msg.type) { + + case _SIMSG_PLASTICWRAP_ACK_MORPH: + if (self->delta <= 0) { + song_iterator_t *it = self->delegate; + sci_free(self); + return it; + } + break; + + default: + BREAKPOINT(); + } + else if (msg.recipient == _SIMSG_BASE) { + switch (msg.type) { + + case _SIMSG_BASEMSG_CLONE: { + int tsize = sizeof(fast_forward_song_iterator_t); + fast_forward_song_iterator_t *clone = (fast_forward_song_iterator_t *)sci_malloc(tsize); + memcpy(clone, self, tsize); + songit_handle_message(&clone->delegate, msg); + return (song_iterator_t *) clone; + } + + case _SIMSG_BASEMSG_PRINT: + print_tabs_id(msg.args[0].i, self->ID); + fprintf(stderr, "PLASTICWRAP:\n"); + msg.args[0].i++; + songit_handle_message(&(self->delegate), msg); + break; + + default: + songit_handle_message(&(self->delegate), msg); + } + } else + songit_handle_message(&(self->delegate), msg); + + return NULL; +} + + +static void +_ff_init(fast_forward_song_iterator_t *self) +{ + return; +} + +static int +_ff_get_timepos(fast_forward_song_iterator_t *self) +{ + return self->delegate->get_timepos(self->delegate); +} + +song_iterator_t * +new_fast_forward_iterator(song_iterator_t *capsit, int delta) +{ + fast_forward_song_iterator_t *it = + (fast_forward_song_iterator_t*)sci_malloc(sizeof(fast_forward_song_iterator_t)); + + if (capsit == NULL) + { + free(it); + return NULL; + } + it->ID = 0; + + it->delegate = capsit; + it->delta = delta; + it->death_listeners_nr = 0; + + it->next = (int(*)(song_iterator_t *, unsigned char *, int *)) + _ff_read_next_command; + it->get_pcm_feed = (sfx_pcm_feed_t *(*)(song_iterator_t *)) + _ff_check_pcm; + it->handle_message = (song_iterator_t *(*)(song_iterator_t *, + song_iterator_message_t)) + _ff_handle_message; + it->get_timepos = (int(*)(song_iterator_t *))_ff_get_timepos; + it->init = (void(*)(song_iterator_t *)) + _ff_init; + it->cleanup = NULL; + it->channel_mask = capsit->channel_mask; + + + return (song_iterator_t *) it; +} + + +/********************/ +/*-- Tee iterator --*/ +/********************/ + + +static int +_tee_read_next_command(tee_song_iterator_t *it, unsigned char *buf, + int *result) +{ + static int ready_masks[2] = {TEE_LEFT_READY, TEE_RIGHT_READY}; + static int active_masks[2] = {TEE_LEFT_ACTIVE, TEE_RIGHT_ACTIVE}; + static int pcm_masks[2] = {TEE_LEFT_PCM, TEE_RIGHT_PCM}; + int i; + int retid; + +#ifdef DEBUG_TEE_ITERATOR + fprintf(stderr, "[Tee] %02x\n", it->status); +#endif + + if (!(it->status & (TEE_LEFT_ACTIVE | TEE_RIGHT_ACTIVE))) + /* None is active? */ + return SI_FINISHED; + + if (it->morph_deferred == TEE_MORPH_READY) + return SI_MORPH; + + if ((it->status & (TEE_LEFT_ACTIVE | TEE_RIGHT_ACTIVE)) + != (TEE_LEFT_ACTIVE | TEE_RIGHT_ACTIVE)) { + /* Not all are is active? */ + int which; +#ifdef DEBUG_TEE_ITERATOR + fprintf(stderr, "\tRequesting transformation...\n"); +#endif + if (it->status & TEE_LEFT_ACTIVE) + which = TEE_LEFT; + else if (it->status & TEE_RIGHT_ACTIVE) + which = TEE_RIGHT; + memcpy(buf, it->children[which].buf, MAX_BUF_SIZE); + *result = it->children[which].result; + it->morph_deferred = TEE_MORPH_READY; + return it->children[which].retval; + } + + /* First, check for unreported PCMs */ + for (i = TEE_LEFT; i <= TEE_RIGHT; i++) + if ((it->status & (ready_masks[i] | pcm_masks[i])) + == (ready_masks[i] | pcm_masks[i])) { + it->status &= ~ready_masks[i]; + return SI_PCM; + } + + for (i = TEE_LEFT; i <= TEE_RIGHT; i++) + if (!(it->status & ready_masks[i])) { + + /* Buffers aren't ready yet */ + it->children[i].retval = + songit_next(&(it->children[i].it), + it->children[i].buf, + &(it->children[i].result), + IT_READER_MASK_ALL + | IT_READER_MAY_FREE + | IT_READER_MAY_CLEAN); + + it->status |= ready_masks[i]; +#ifdef DEBUG_TEE_ITERATOR + fprintf(stderr, "\t Must check %d: %d\n", i, + it->children[i].retval); +#endif + + if (it->children[i].retval == SI_ABSOLUTE_CUE || + it->children[i].retval == SI_RELATIVE_CUE) + return it->children[i].retval; + if (it->children[i].retval == SI_FINISHED) { + it->status &= ~active_masks[i]; + /* Recurse to complete */ +#ifdef DEBUG_TEE_ITERATOR +fprintf(stderr, "\t Child %d signalled completion, recursing w/ status %02x\n", i, it->status); +#endif + return _tee_read_next_command(it, buf, result); + } else if (it->children[i].retval == SI_PCM) { + it->status |= pcm_masks[i]; + it->status &= ~ready_masks[i]; + return SI_PCM; + } + } + + + /* We've already handled PCM, MORPH and FINISHED, CUEs & LOOP remain */ + + retid = TEE_LEFT; + if ((it->children[TEE_LEFT].retval > 0) + /* Asked to delay */ + && (it->children[TEE_RIGHT].retval <= it->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", + it->children[TEE_LEFT].retval, it->children[TEE_RIGHT].retval, retid); +#endif +#if 0 + if (it->children[retid].retval == 0) { + /* Perform remapping, if neccessary */ + byte *buf = it->children[retid].buf; + if (*buf != SCI_MIDI_SET_SIGNAL + && *buf < 0xf0) { /* Not a generic command */ + int chan = *buf & 0xf; + int op = *buf & 0xf0; + + chan = it->children[retid].channel_remap[chan]; + + *buf = chan | op; + } + } +#endif + + /* Adjust delta times */ + if (it->children[retid].retval > 0 + && it->children[1-retid].retval > 0) { + if (it->children[1-retid].retval + == it->children[retid].retval) + /* If both children wait the same amount of time, + ** we have to re-fetch commands from both */ + it->status &= ~ready_masks[1-retid]; + else + /* If they don't, we can/must re-use the other + ** child's delay time */ + it->children[1-retid].retval + -= it->children[retid].retval; + } + + it->status &= ~ready_masks[retid]; + memcpy(buf, it->children[retid].buf, MAX_BUF_SIZE); + *result = it->children[retid].result; + + return it->children[retid].retval; +} + +static sfx_pcm_feed_t * +_tee_check_pcm(tee_song_iterator_t *it) +{ + static int pcm_masks[2] = {TEE_LEFT_PCM, TEE_RIGHT_PCM}; + int i; + + for (i = TEE_LEFT; i <= TEE_RIGHT; i++) + if (it->status & pcm_masks[i]) { + + it->status &= ~pcm_masks[i]; + return it->children[i].it-> + get_pcm_feed(it->children[i].it); + } + + return NULL; /* No iterator */ +} + +static song_iterator_t * +_tee_handle_message(tee_song_iterator_t *self, song_iterator_message_t msg) +{ + if (msg.recipient == _SIMSG_BASE) { + switch (msg.type) { + + case _SIMSG_BASEMSG_PRINT: + print_tabs_id(msg.args[0].i, self->ID); + fprintf(stderr, "TEE:\n"); + msg.args[0].i++; + break; /* And continue with our children */ + + case _SIMSG_BASEMSG_CLONE: { + tee_song_iterator_t *newit + = (tee_song_iterator_t*)sci_malloc(sizeof(tee_song_iterator_t)); + memcpy(newit, self, sizeof(tee_song_iterator_t)); + + if (newit->children[TEE_LEFT].it) + newit->children[TEE_LEFT].it = + songit_clone(newit->children[TEE_LEFT].it, msg.args[0].i); + if (newit->children[TEE_RIGHT].it) + newit->children[TEE_RIGHT].it = + songit_clone(newit->children[TEE_RIGHT].it, msg.args[0].i); + + return (song_iterator_t *) newit; + } + + default: + break; + } + } + + if (msg.recipient == _SIMSG_PLASTICWRAP) { + song_iterator_t *old_it; + switch (msg.type) { + + case _SIMSG_PLASTICWRAP_ACK_MORPH: + if (!(self->status & (TEE_LEFT_ACTIVE | TEE_RIGHT_ACTIVE))) { + songit_free((song_iterator_t *) self); + return NULL; + } else if (!(self->status & TEE_LEFT_ACTIVE)) { + if (self->may_destroy) + songit_free(self->children[TEE_LEFT].it); + old_it = self->children[TEE_RIGHT].it; + sci_free(self); + return old_it; + } else if (!(self->status & TEE_RIGHT_ACTIVE)) { + if (self->may_destroy) + songit_free(self->children[TEE_RIGHT].it); + old_it = self->children[TEE_LEFT].it; + sci_free(self); + return old_it; + } else { + sciprintf("[tee-iterator] WARNING:" + " Morphing without need\n"); + return (song_iterator_t *) self; + } + + default: + BREAKPOINT(); + } + } + + if (self->children[TEE_LEFT].it) + songit_handle_message(&(self->children[TEE_LEFT].it), msg); + if (self->children[TEE_RIGHT].it) + songit_handle_message(&(self->children[TEE_RIGHT].it), msg); + + return NULL; +} + +static void +_tee_init(tee_song_iterator_t *it) +{ + it->status = TEE_LEFT_ACTIVE | TEE_RIGHT_ACTIVE; + it->children[TEE_LEFT].it->init(it->children[TEE_LEFT].it); + it->children[TEE_RIGHT].it->init(it->children[TEE_RIGHT].it); +} + +static void +_tee_free(tee_song_iterator_t *it) +{ + int i; + for (i = TEE_LEFT; i <= TEE_RIGHT; i++) + if (it->children[i].it && it->may_destroy) + songit_free(it->children[i].it); +} + +static void +songit_tee_death_notification(tee_song_iterator_t *self, + song_iterator_t *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 { + BREAKPOINT(); + } +} + + +song_iterator_t * +songit_new_tee(song_iterator_t *left, song_iterator_t *right, int may_destroy) +{ + int i; + int firstfree = 1; /* First free channel */ + int incomplete_map = 0; + tee_song_iterator_t *it = (tee_song_iterator_t*)sci_malloc(sizeof(tee_song_iterator_t)); + + it->ID = 0; + + it->morph_deferred = TEE_MORPH_NONE; + it->status = TEE_LEFT_ACTIVE | TEE_RIGHT_ACTIVE; + it->may_destroy = may_destroy; + + it->children[TEE_LEFT].it = left; + it->children[TEE_RIGHT].it = right; + it->death_listeners_nr = 0; + + /* By default, don't remap */ + for (i = 0; i < 16; i++) + it->children[TEE_LEFT].channel_remap[i] + = it->children[TEE_RIGHT].channel_remap[i] = i; + + /* Default to lhs channels */ + it->channel_mask = left->channel_mask; + for (i = 0; i < 16; i++) + if (it->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) & it->channel_mask))) + ++firstfree; + + if (firstfree == MIDI_CHANNELS) { + incomplete_map = 1; + fprintf(stderr, "[songit-tee <%08lx,%08lx>] " + "Could not remap right channel #%d:" + " Out of channels\n", + left->ID, right->ID, i); + } else { + it->children[TEE_RIGHT].channel_remap[i] + = firstfree; + + it->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, + it->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, it->children[c].channel_remap[i]); + } +#endif + + + it->next = (int(*)(song_iterator_t *, unsigned char *, int *)) + _tee_read_next_command; + + it->get_pcm_feed = (sfx_pcm_feed_t*(*)(song_iterator_t *)) + _tee_check_pcm; + + it->handle_message = (song_iterator_t *(*)(song_iterator_t *, + song_iterator_message_t)) + _tee_handle_message; + + it->init = (void(*)(song_iterator_t *)) + _tee_init; + + it->get_timepos = NULL; + + song_iterator_add_death_listener((song_iterator_t *)it, + left, (void (*)(void *, void*)) + songit_tee_death_notification); + song_iterator_add_death_listener((song_iterator_t *)it, + right, (void (*)(void *, void*)) + songit_tee_death_notification); + + it->cleanup = NULL; + + return (song_iterator_t *) it; +} + + +/*************************************/ +/*-- General purpose functionality --*/ +/*************************************/ + +int +songit_next(song_iterator_t **it, unsigned char *buf, int *result, int mask) +{ + int retval; + + if (!*it) + return SI_FINISHED; + + do { + retval = (*it)->next(*it, buf, result); + if (retval == SI_MORPH) { + fprintf(stderr, " Morphing %p (stored at %p)\n", *it, it); + if (!SIMSG_SEND((*it), SIMSG_ACK_MORPH)) { + BREAKPOINT(); + } else fprintf(stderr, "SI_MORPH successful\n"); + } + + if (retval == SI_FINISHED) + fprintf(stderr, "[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; + + if (mask & IT_READER_MAY_FREE) + songit_free(*it); + *it = new_cleanup_iterator(channel_mask); + 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)) { + songit_free(*it); + *it = NULL; + } + + return retval; +} + + + +song_iterator_t * +songit_new(unsigned char *data, unsigned int size, int type, songit_id_t id) +{ + base_song_iterator_t *it; + int i; + + if (!data || size < 22) { + fprintf(stderr, SIPFX "Attempt to instantiate song iterator for null" + " song data\n"); + return NULL; + } + + + switch (type) { + + case SCI_SONG_ITERATOR_TYPE_SCI0: + /**-- Playing SCI0 sound resources --**/ + it = (base_song_iterator_t*)sci_malloc(sizeof(sci0_song_iterator_t)); + it->channel_mask = 0xffff; /* Allocate all channels by default */ + + for (i = 0; i < MIDI_CHANNELS; i++) + it->polyphony[i] = data[1 + (i << 1)]; + + it->next = (int(*)(song_iterator_t *, unsigned char *, int *)) + _sci0_read_next_command; + it->get_pcm_feed = (sfx_pcm_feed_t*(*)(song_iterator_t *)) + _sci0_check_pcm; + it->handle_message = (song_iterator_t *(*)(song_iterator_t *, song_iterator_message_t)) + _sci0_handle_message; + it->init = (void(*)(song_iterator_t *))_sci0_init; + it->cleanup = (void(*)(song_iterator_t *))_sci0_cleanup; + ((sci0_song_iterator_t *)it)->channel.state + = SI_STATE_UNINITIALISED; + it->get_timepos = (int(*)(song_iterator_t *))_sci0_get_timepos; + break; + + case SCI_SONG_ITERATOR_TYPE_SCI1: + /**-- SCI01 or later sound resource --**/ + it = (base_song_iterator_t*)sci_malloc(sizeof(sci1_song_iterator_t)); + it->channel_mask = 0; /* Defer channel allocation */ + + for (i = 0; i < MIDI_CHANNELS; i++) + it->polyphony[i] = 0; /* Unknown */ + + it->next = (int(*)(song_iterator_t *, unsigned char *, int *)) + _sci1_read_next_command; + it->get_pcm_feed = (sfx_pcm_feed_t*(*)(song_iterator_t *)) + _sci1_get_pcm; + it->handle_message = (song_iterator_t *(*)(song_iterator_t *, song_iterator_message_t)) + _sci1_handle_message; + it->init = (void(*)(song_iterator_t *))_sci1_init; + it->cleanup = (void(*)(song_iterator_t *))_sci1_cleanup; + it->get_timepos = (int(*)(song_iterator_t *))_sci1_get_timepos; + break; + + default: + /**-- Invalid/unsupported sound resources --**/ + fprintf(stderr, SIPFX "Attempt to instantiate invalid/unknown" + " song iterator type %d\n", type); + return NULL; + } + it->ID = id; + + it->death_listeners_nr = 0; + + it->data = (unsigned char*)sci_refcount_memdup(data, size); + it->size = size; + + it->init((song_iterator_t *) it); + + return (song_iterator_t *) it; +} + +void +songit_free(song_iterator_t *it) +{ + if (it) { + int i; + + if (it->cleanup) + it->cleanup(it); + + for (i = 0; i < it->death_listeners_nr; i++) + it->death_listeners[i].notify(it->death_listeners[i].self, it); + + sci_free(it); + } +} + +song_iterator_message_t +songit_make_message(songit_id_t id, int recipient, int type, int a1, int a2) +{ + song_iterator_message_t rv; + rv.ID = id; + rv.recipient = recipient; + rv.type = type; + rv.args[0].i = a1; + rv.args[1].i = a2; + + return rv; +} + +song_iterator_message_t +songit_make_ptr_message(songit_id_t id, int recipient, int type, void * a1, int a2) +{ + song_iterator_message_t rv; + rv.ID = id; + rv.recipient = recipient; + rv.type = type; + rv.args[0].p = a1; + rv.args[1].i = a2; + + return rv; +} + + +int +songit_handle_message(song_iterator_t **it_reg_p, song_iterator_message_t msg) +{ + song_iterator_t *it = *it_reg_p; + song_iterator_t *newit; + + newit = it->handle_message(it, msg); + + if (!newit) + return 0; /* Couldn't handle */ + + *it_reg_p = newit; /* Might have self-morphed */ + return 1; +} + +song_iterator_t * +songit_clone(song_iterator_t *it, int delta) +{ + SIMSG_SEND(it, SIMSG_CLONE(delta)); + it->death_listeners_nr = 0; + it->flags |= SONGIT_FLAG_CLONE; + return it; +} + +void +song_iterator_add_death_listener(song_iterator_t *it, + void *client, + void (*notify) (void *self, void *notifier)) +{ + if (it->death_listeners_nr >= SONGIT_MAX_LISTENERS) { + fprintf(stderr, "FATAL: Too many death listeners for song" + " iterator\n"); + BREAKPOINT(); + exit(1); + } + + it->death_listeners[it->death_listeners_nr].notify = notify; + it->death_listeners[it->death_listeners_nr].self = client; + + it->death_listeners_nr++; +} + +void +song_iterator_remove_death_listener(song_iterator_t *it, + void *client) +{ + int i; + for (i = 0; i < it->death_listeners_nr; i++) { + if (it->death_listeners[i].self == client) { + --it->death_listeners_nr; + + /* Overwrite, if this wasn't the last one */ + if (i+1 < it->death_listeners_nr) + it->death_listeners[i] + = it->death_listeners[it->death_listeners_nr]; + + return; + } + } + + fprintf(stderr, "FATAL: Could not remove death listener from " + "song iterator\n"); + BREAKPOINT(); + exit(1); +} + + +song_iterator_t * +sfx_iterator_combine(song_iterator_t *it1, song_iterator_t *it2) +{ + if (it1 == NULL) + return it2; + if (it2 == NULL) + return it1; + + /* Both are non-NULL: */ + return songit_new_tee(it1, it2, 1); /* 'may destroy' */ +} diff --git a/engines/sci/sfx/lists/GM.txt b/engines/sci/sfx/lists/GM.txt new file mode 100644 index 0000000000..eea2510447 --- /dev/null +++ b/engines/sci/sfx/lists/GM.txt @@ -0,0 +1,177 @@ +/*000 00*/ "Acoustic Grand Piano", +/*001 01*/ "Bright Acoustic Piano", +/*002 02*/ "Electric Grand Piano", +/*003 03*/ "Honky-tonk Piano", +/*004 04*/ "Electric Piano 1", +/*005 05*/ "Electric Piano 2", +/*006 06*/ "Harpsichord", +/*007 07*/ "Clavinet", +/*008 08*/ "Celesta", +/*009 09*/ "Glockenspiel", +/*010 0A*/ "Music Box", +/*011 0B*/ "Vibraphone", +/*012 0C*/ "Marimba", +/*013 0D*/ "Xylophone", +/*014 0E*/ "Tubular Bells", +/*015 0F*/ "Dulcimer", +/*016 10*/ "Drawbar Organ", +/*017 11*/ "Percussive Organ", +/*018 12*/ "Rock Organ", +/*019 13*/ "Church Organ", +/*020 14*/ "Reed Organ", +/*021 15*/ "Accordion", +/*022 16*/ "Harmonica", +/*023 17*/ "Tango Accordion", +/*024 18*/ "Acoustic Guitar (nylon)", +/*025 19*/ "Acoustic Guitar (steel)", +/*026 1A*/ "Electric Guitar (jazz)", +/*027 1B*/ "Electric Guitar (clean)", +/*028 1C*/ "Electric Guitar (muted)", +/*029 1D*/ "Overdriven Guitar", +/*030 1E*/ "Distortion Guitar", +/*031 1F*/ "Guitar Harmonics", +/*032 20*/ "Acoustic Bass", +/*033 21*/ "Electric Bass (finger)", +/*034 22*/ "Electric Bass (pick)", +/*035 23*/ "Fretless Bass", +/*036 24*/ "Slap Bass 1", +/*037 25*/ "Slap Bass 2", +/*038 26*/ "Synth Bass 1", +/*039 27*/ "Synth Bass 2", +/*040 28*/ "Violin", +/*041 29*/ "Viola", +/*042 2A*/ "Cello", +/*043 2B*/ "Contrabass", +/*044 2C*/ "Tremolo Strings", +/*045 2D*/ "Pizzicato Strings", +/*046 2E*/ "Orchestral Harp", +/*047 2F*/ "Timpani", +/*048 30*/ "String Ensemble 1", +/*049 31*/ "String Ensemble 2", +/*050 32*/ "SynthStrings 1", +/*051 33*/ "SynthStrings 2", +/*052 34*/ "Choir Aahs", +/*053 35*/ "Voice Oohs", +/*054 36*/ "Synth Voice", +/*055 37*/ "Orchestra Hit", +/*056 38*/ "Trumpet", +/*057 39*/ "Trombone", +/*058 3A*/ "Tuba", +/*059 3B*/ "Muted Trumpet", +/*060 3C*/ "French Horn", +/*061 3D*/ "Brass Section", +/*062 3E*/ "SynthBrass 1", +/*063 3F*/ "SynthBrass 2", + +/*064 40*/ "Soprano Sax", +/*065 41*/ "Alto Sax", +/*066 42*/ "Tenor Sax", +/*067 43*/ "Baritone Sax", +/*068 44*/ "Oboe", +/*069 45*/ "English Horn", +/*070 46*/ "Bassoon", +/*071 47*/ "Clarinet", +/*072 48*/ "Piccolo", +/*073 49*/ "Flute", +/*074 4A*/ "Recorder", +/*075 4B*/ "Pan Flute", +/*076 4C*/ "Blown Bottle", +/*077 4D*/ "Shakuhachi", +/*078 4E*/ "Whistle", +/*079 4F*/ "Ocarina", +/*080 50*/ "Lead 1 (square)", +/*081 51*/ "Lead 2 (sawtooth)", +/*082 52*/ "Lead 3 (calliope)", +/*083 53*/ "Lead 4 (chiff)", +/*084 54*/ "Lead 5 (charang)", +/*085 55*/ "Lead 6 (voice)", +/*086 56*/ "Lead 7 (fifths)", +/*087 57*/ "Lead 8 (bass+lead)", +/*088 58*/ "Pad 1 (new age)", +/*089 59*/ "Pad 2 (warm)", +/*090 5A*/ "Pad 3 (polysynth)", +/*091 5B*/ "Pad 4 (choir)", +/*092 5C*/ "Pad 5 (bowed)", +/*093 5D*/ "Pad 6 (metallic)", +/*094 5E*/ "Pad 7 (halo)", +/*095 5F*/ "Pad 8 (sweep)", +/*096 60*/ "FX 1 (rain)", +/*097 61*/ "FX 2 (soundtrack)", +/*098 62*/ "FX 3 (crystal)", +/*099 63*/ "FX 4 (atmosphere)", +/*100 64*/ "FX 5 (brightness)", +/*101 65*/ "FX 6 (goblins)", +/*102 66*/ "FX 7 (echoes)", +/*103 67*/ "FX 8 (sci-fi)", +/*104 68*/ "Sitar", +/*105 69*/ "Banjo", +/*106 6A*/ "Shamisen", +/*107 6B*/ "Koto", +/*108 6C*/ "Kalimba", +/*109 6D*/ "Bag pipe", +/*110 6E*/ "Fiddle", +/*111 6F*/ "Shannai", +/*112 70*/ "Tinkle Bell", +/*113 71*/ "Agogo", +/*114 72*/ "Steel Drums", +/*115 73*/ "Woodblock", +/*116 74*/ "Taiko Drum", +/*117 75*/ "Melodic Tom", +/*118 76*/ "Synth Drum", +/*119 77*/ "Reverse Cymbal", +/*120 78*/ "Guitar Fret Noise", +/*121 79*/ "Breath Noise", +/*122 7A*/ "Seashore", +/*123 7B*/ "Bird Tweet", +/*124 7C*/ "Telephone Ring", +/*125 7D*/ "Helicopter", +/*126 7E*/ "Applause", +/*127 7F*/ "Gunshot" + +/*035 23*/ "Acoustic Bass Drum", +/*036 24*/ "Bass Drum 1", +/*037 25*/ "Side Stick", +/*038 26*/ "Acoustic Snare", +/*039 27*/ "Hand Clap", +/*040 28*/ "Electric Snare", +/*041 29*/ "Low Floor Tom", +/*042 2A*/ "Closed Hi-Hat", +/*043 2B*/ "High Floor Tom", +/*044 2C*/ "Pedal Hi-Hat", +/*045 2D*/ "Low Tom", +/*046 2E*/ "Open Hi-Hat", +/*047 2F*/ "Low-Mid Tom", +/*048 30*/ "Hi-Mid Tom", +/*049 31*/ "Crash Cymbal 1", +/*050 32*/ "High Tom", +/*051 33*/ "Ride Cymbal 1", +/*052 34*/ "Chinese Cymbal", +/*053 35*/ "Ride Bell", +/*054 36*/ "Tambourine", +/*055 37*/ "Splash Cymbal", +/*056 38*/ "Cowbell", +/*057 39*/ "Crash Cymbal 2", +/*058 3A*/ "Vibraslap", +/*059 3B*/ "Ride Cymbal 2", +/*060 3C*/ "Hi Bongo", +/*061 3D*/ "Low Bongo", +/*062 3E*/ "Mute Hi Conga", +/*063 3F*/ "Open Hi Conga", +/*064 40*/ "Low Conga", +/*065 41*/ "High Timbale", +/*066 42*/ "Low Timbale", +/*067 43*/ "High Agogo", +/*068 44*/ "Low Agogo", +/*069 45*/ "Cabasa", +/*070 46*/ "Maracas", +/*071 47*/ "Short Whistle", +/*072 48*/ "Long Whistle", +/*073 49*/ "Short Guiro", +/*074 4A*/ "Long Guiro", +/*075 4B*/ "Claves", +/*076 4C*/ "Hi Wood Block", +/*077 4D*/ "Low Wood Block", +/*078 4E*/ "Mute Cuica", +/*079 4F*/ "Open Cuica", +/*080 50*/ "Mute Triangle", +/*081 51*/ "Open Triangle" diff --git a/engines/sci/sfx/lists/gm_patches.c b/engines/sci/sfx/lists/gm_patches.c new file mode 100644 index 0000000000..b959a87461 --- /dev/null +++ b/engines/sci/sfx/lists/gm_patches.c @@ -0,0 +1,198 @@ +/*************************************************************************** + gm_patches.c Copyright (C) 2000 Rickard Lind + + + 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. + +***************************************************************************/ + +char *GM_Patch[128] = { +/*000*/ "Acoustic Grand Piano", +/*001*/ "Bright Acoustic Piano", +/*002*/ "Electric Grand Piano", +/*003*/ "Honky-tonk Piano", +/*004*/ "Electric Piano 1", +/*005*/ "Electric Piano 2", +/*006*/ "Harpsichord", +/*007*/ "Clavinet", +/*008*/ "Celesta", +/*009*/ "Glockenspiel", +/*010*/ "Music Box", +/*011*/ "Vibraphone", +/*012*/ "Marimba", +/*013*/ "Xylophone", +/*014*/ "Tubular Bells", +/*015*/ "Dulcimer", +/*016*/ "Drawbar Organ", +/*017*/ "Percussive Organ", +/*018*/ "Rock Organ", +/*019*/ "Church Organ", +/*020*/ "Reed Organ", +/*021*/ "Accordion", +/*022*/ "Harmonica", +/*023*/ "Tango Accordion", +/*024*/ "Acoustic Guitar (nylon)", +/*025*/ "Acoustic Guitar (steel)", +/*026*/ "Electric Guitar (jazz)", +/*027*/ "Electric Guitar (clean)", +/*028*/ "Electric Guitar (muted)", +/*029*/ "Overdriven Guitar", +/*030*/ "Distortion Guitar", +/*031*/ "Guitar Harmonics", +/*032*/ "Acoustic Bass", +/*033*/ "Electric Bass (finger)", +/*034*/ "Electric Bass (pick)", +/*035*/ "Fretless Bass", +/*036*/ "Slap Bass 1", +/*037*/ "Slap Bass 2", +/*038*/ "Synth Bass 1", +/*039*/ "Synth Bass 2", +/*040*/ "Violin", +/*041*/ "Viola", +/*042*/ "Cello", +/*043*/ "Contrabass", +/*044*/ "Tremolo Strings", +/*045*/ "Pizzicato Strings", +/*046*/ "Orchestral Harp", +/*047*/ "Timpani", +/*048*/ "String Ensemble 1", +/*049*/ "String Ensemble 2", +/*050*/ "SynthStrings 1", +/*051*/ "SynthStrings 2", +/*052*/ "Choir Aahs", +/*053*/ "Voice Oohs", +/*054*/ "Synth Voice", +/*055*/ "Orchestra Hit", +/*056*/ "Trumpet", +/*057*/ "Trombone", +/*058*/ "Tuba", +/*059*/ "Muted Trumpet", +/*060*/ "French Horn", +/*061*/ "Brass Section", +/*062*/ "SynthBrass 1", +/*063*/ "SynthBrass 2", +/*064*/ "Soprano Sax", +/*065*/ "Alto Sax", +/*066*/ "Tenor Sax", +/*067*/ "Baritone Sax", +/*068*/ "Oboe", +/*069*/ "English Horn", +/*070*/ "Bassoon", +/*071*/ "Clarinet", +/*072*/ "Piccolo", +/*073*/ "Flute", +/*074*/ "Recorder", +/*075*/ "Pan Flute", +/*076*/ "Blown Bottle", +/*077*/ "Shakuhachi", +/*078*/ "Whistle", +/*079*/ "Ocarina", +/*080*/ "Lead 1 (square)", +/*081*/ "Lead 2 (sawtooth)", +/*082*/ "Lead 3 (calliope)", +/*083*/ "Lead 4 (chiff)", +/*084*/ "Lead 5 (charang)", +/*085*/ "Lead 6 (voice)", +/*086*/ "Lead 7 (fifths)", +/*087*/ "Lead 8 (bass+lead)", +/*088*/ "Pad 1 (new age)", +/*089*/ "Pad 2 (warm)", +/*090*/ "Pad 3 (polysynth)", +/*091*/ "Pad 4 (choir)", +/*092*/ "Pad 5 (bowed)", +/*093*/ "Pad 6 (metallic)", +/*094*/ "Pad 7 (halo)", +/*095*/ "Pad 8 (sweep)", +/*096*/ "FX 1 (rain)", +/*097*/ "FX 2 (soundtrack)", +/*098*/ "FX 3 (crystal)", +/*099*/ "FX 4 (atmosphere)", +/*100*/ "FX 5 (brightness)", +/*101*/ "FX 6 (goblins)", +/*102*/ "FX 7 (echoes)", +/*103*/ "FX 8 (sci-fi)", +/*104*/ "Sitar", +/*105*/ "Banjo", +/*106*/ "Shamisen", +/*107*/ "Koto", +/*108*/ "Kalimba", +/*109*/ "Bag pipe", +/*110*/ "Fiddle", +/*111*/ "Shannai", +/*112*/ "Tinkle Bell", +/*113*/ "Agogo", +/*114*/ "Steel Drums", +/*115*/ "Woodblock", +/*116*/ "Taiko Drum", +/*117*/ "Melodic Tom", +/*118*/ "Synth Drum", +/*119*/ "Reverse Cymbal", +/*120*/ "Guitar Fret Noise", +/*121*/ "Breath Noise", +/*122*/ "Seashore", +/*123*/ "Bird Tweet", +/*124*/ "Telephone Ring", +/*125*/ "Helicopter", +/*126*/ "Applause", +/*127*/ "Gunshot" }; + +char *GM_RhythmKey[47] = { +/*035*/ "Acoustic Bass Drum", +/*036*/ "Bass Drum 1", +/*037*/ "Side Stick", +/*038*/ "Acoustic Snare", +/*039*/ "Hand Clap", +/*040*/ "Electric Snare", +/*041*/ "Low Floor Tom", +/*042*/ "Closed Hi-Hat", +/*043*/ "High Floor Tom", +/*044*/ "Pedal Hi-Hat", +/*045*/ "Low Tom", +/*046*/ "Open Hi-Hat", +/*047*/ "Low-Mid Tom", +/*048*/ "Hi-Mid Tom", +/*049*/ "Crash Cymbal 1", +/*050*/ "High Tom", +/*051*/ "Ride Cymbal 1", +/*052*/ "Chinese Cymbal", +/*053*/ "Ride Bell", +/*054*/ "Tambourine", +/*055*/ "Splash Cymbal", +/*056*/ "Cowbell", +/*057*/ "Crash Cymbal 2", +/*058*/ "Vibraslap", +/*059*/ "Ride Cymbal 2", +/*060*/ "Hi Bongo", +/*061*/ "Low Bongo", +/*062*/ "Mute Hi Conga", +/*063*/ "Open Hi Conga", +/*064*/ "Low Conga", +/*065*/ "High Timbale", +/*066*/ "Low Timbale", +/*067*/ "High Agogo", +/*068*/ "Low Agogo", +/*069*/ "Cabasa", +/*070*/ "Maracas", +/*071*/ "Short Whistle", +/*072*/ "Long Whistle", +/*073*/ "Short Guiro", +/*074*/ "Long Guiro", +/*075*/ "Claves", +/*076*/ "Hi Wood Block", +/*077*/ "Low Wood Block", +/*078*/ "Mute Cuica", +/*079*/ "Open Cuica", +/*080*/ "Mute Triangle" +/*081*/ "Open Triangle" }; diff --git a/engines/sci/sfx/lists/mt32_timbres.c b/engines/sci/sfx/lists/mt32_timbres.c new file mode 100644 index 0000000000..776ff5dbb7 --- /dev/null +++ b/engines/sci/sfx/lists/mt32_timbres.c @@ -0,0 +1,181 @@ +/*************************************************************************** + mt_32_timbres.c Copyright (C) 2000 Rickard Lind + + + 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. + +***************************************************************************/ + +char *MT32_Timbre[128] = { +/*000*/ "AcouPiano1", +/*001*/ "AcouPiano2", +/*002*/ "AcouPiano3", +/*003*/ "ElecPiano1", +/*004*/ "ElecPiano2", +/*005*/ "ElecPiano3", +/*006*/ "ElecPiano4", +/*007*/ "Honkytonk ", +/*008*/ "Elec Org 1", +/*009*/ "Elec Org 2", +/*010*/ "Elec Org 3", +/*011*/ "Elec Org 4", +/*012*/ "Pipe Org 1", +/*013*/ "Pipe Org 2", +/*014*/ "Pipe Org 3", +/*015*/ "Accordion ", +/*016*/ "Harpsi 1 ", +/*017*/ "Harpsi 2 ", +/*018*/ "Harpsi 3 ", +/*019*/ "Clavi 1 ", +/*020*/ "Clavi 2 ", +/*021*/ "Clavi 3 ", +/*022*/ "Celesta 1 ", +/*023*/ "Celesta 2 ", +/*024*/ "Syn Brass1", +/*025*/ "Syn Brass2", +/*026*/ "Syn Brass3", +/*027*/ "Syn Brass4", +/*028*/ "Syn Bass 1", +/*029*/ "Syn Bass 2", +/*030*/ "Syn Bass 3", +/*031*/ "Syn Bass 4", +/*032*/ "Fantasy ", +/*033*/ "Harmo Pan ", +/*034*/ "Chorale ", +/*035*/ "Glasses ", +/*036*/ "Soundtrack", +/*037*/ "Atmosphere", +/*038*/ "Warm Bell ", +/*039*/ "Funny Vox ", +/*040*/ "Echo Bell ", +/*041*/ "Ice Rain ", +/*042*/ "Oboe 2001 ", +/*043*/ "Echo Pan ", +/*044*/ "DoctorSolo", +/*045*/ "Schooldaze", +/*046*/ "BellSinger", +/*047*/ "SquareWave", +/*048*/ "Str Sect 1", +/*049*/ "Str Sect 2", +/*050*/ "Str Sect 3", +/*051*/ "Pizzicato ", +/*052*/ "Violin 1 ", +/*053*/ "Violin 2 ", +/*054*/ "Cello 1 ", +/*055*/ "Cello 2 ", +/*056*/ "Contrabass", +/*057*/ "Harp 1 ", +/*058*/ "Harp 2 ", +/*059*/ "Guitar 1 ", +/*060*/ "Guitar 2 ", +/*061*/ "Elec Gtr 1", +/*062*/ "Elec Gtr 2", +/*063*/ "Sitar ", +/*064*/ "Acou Bass1", +/*065*/ "Acou Bass2", +/*066*/ "Elec Bass1", +/*067*/ "Elec Bass2", +/*068*/ "Slap Bass1", +/*069*/ "Slap Bass2", +/*070*/ "Fretless 1", +/*071*/ "Fretless 2", +/*072*/ "Flute 1 ", +/*073*/ "Flute 2 ", +/*074*/ "Piccolo 1 ", +/*075*/ "Piccolo 2 ", +/*076*/ "Recorder ", +/*077*/ "Panpipes ", +/*078*/ "Sax 1 ", +/*079*/ "Sax 2 ", +/*080*/ "Sax 3 ", +/*081*/ "Sax 4 ", +/*082*/ "Clarinet 1", +/*083*/ "Clarinet 2", +/*084*/ "Oboe ", +/*085*/ "Engl Horn ", +/*086*/ "Bassoon ", +/*087*/ "Harmonica ", +/*088*/ "Trumpet 1 ", +/*089*/ "Trumpet 2 ", +/*090*/ "Trombone 1", +/*091*/ "Trombone 2", +/*092*/ "Fr Horn 1 ", +/*093*/ "Fr Horn 2 ", +/*094*/ "Tuba ", +/*095*/ "Brs Sect 1", +/*096*/ "Brs Sect 2", +/*097*/ "Vibe 1 ", +/*098*/ "Vibe 2 ", +/*099*/ "Syn Mallet", +/*100*/ "Wind Bell ", +/*101*/ "Glock ", +/*102*/ "Tube Bell ", +/*103*/ "Xylophone ", +/*104*/ "Marimba ", +/*105*/ "Koto ", +/*106*/ "Sho ", +/*107*/ "Shakuhachi", +/*108*/ "Whistle 1 ", +/*109*/ "Whistle 2 ", +/*110*/ "BottleBlow", +/*111*/ "BreathPipe", +/*112*/ "Timpani ", +/*113*/ "MelodicTom", +/*114*/ "Deep Snare", +/*115*/ "Elec Perc1", +/*116*/ "Elec Perc2", +/*117*/ "Taiko ", +/*118*/ "Taiko Rim ", +/*119*/ "Cymbal ", +/*120*/ "Castanets ", +/*121*/ "Triangle ", +/*122*/ "Orche Hit ", +/*123*/ "Telephone ", +/*124*/ "Bird Tweet", +/*125*/ "OneNoteJam", +/*126*/ "WaterBells", +/*127*/ "JungleTune" }; + +char *MT32_RhythmTimbre[30] = { +/*00*/ "Acou BD ", +/*01*/ "Acou SD ", +/*02*/ "Acou HiTom", +/*03*/ "AcouMidTom", +/*04*/ "AcouLowTom", +/*05*/ "Elec SD ", +/*06*/ "Clsd HiHat", +/*07*/ "OpenHiHat1", +/*08*/ "Crash Cym ", +/*09*/ "Ride Cym ", +/*10*/ "Rim Shot ", +/*11*/ "Hand Clap ", +/*12*/ "Cowbell ", +/*13*/ "Mt HiConga", +/*14*/ "High Conga", +/*15*/ "Low Conga ", +/*16*/ "Hi Timbale", +/*17*/ "LowTimbale", +/*18*/ "High Bongo", +/*19*/ "Low Bongo ", +/*20*/ "High Agogo", +/*21*/ "Low Agogo ", +/*22*/ "Tambourine", +/*23*/ "Claves ", +/*24*/ "Maracas ", +/*25*/ "SmbaWhis L", +/*26*/ "SmbaWhis S", +/*27*/ "Cabasa ", +/*28*/ "Quijada ", +/*29*/ "OpenHiHat2" }; diff --git a/engines/sci/sfx/mixer.h b/engines/sci/sfx/mixer.h new file mode 100644 index 0000000000..7cf2aae397 --- /dev/null +++ b/engines/sci/sfx/mixer.h @@ -0,0 +1,119 @@ +/*************************************************************************** + sfx_mixer.h Copyright (C) 2003,04 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> + +***************************************************************************/ + +#ifndef _SFX_MIXER_H_ +#define _SFX_MIXER_H_ + +#include <sfx_pcm.h> + + +#define SFX_PCM_FEED_MODE_ALIVE 0 +#define SFX_PCM_FEED_MODE_DEAD 1 + +struct twochannel_data { + int left, right; +}; + +typedef struct { + sfx_pcm_feed_t *feed; + + /* The following fields are for use by the mixer only and must not be + ** touched by pcm_feed code. */ + byte *buf; /* dynamically allocated buffer for this feed, used in some circumstances. */ + int buf_size; /* Number of frames that fit into the buffer */ + sfx_pcm_urat_t spd; /* source frames per destination frames */ + sfx_pcm_urat_t scount; /* Frame counter, backed up in between calls */ + int frame_bufstart; /* Left-over frames at the beginning of the buffer */ + int mode; /* Whether the feed is alive or pending destruction */ + + int pending_review; /* Timestamp needs to be checked for this stream */ + struct twochannel_data ch_old, ch_new; /* Intermediate results of output computation */ +} sfx_pcm_feed_state_t; + + +typedef struct _sfx_pcm_mixer { + /* Mixers are the heart of all matters PCM. They take PCM data from subscribed feeds, + ** mix it (hence the name) and ask the pcm device they are attached to to play the + ** result. */ + + const char *name; + const char *version; + + int (*init)(struct _sfx_pcm_mixer *self, sfx_pcm_device_t *device); + /* Initialises the mixer + ** Parameters: (sfx_pcm_mixer_t *) self: Self reference + ** (sfx_pcm_device_t *) device: An _already initialised_ PCM output driver + ** Returns : (int) SFX_OK on success, SFX_ERROR otherwise + */ + + void (*exit)(struct _sfx_pcm_mixer *self); + /* Uninitialises the mixer + ** Parameters: (sfx_pcm_mixer_t *) self: Self reference + ** Also uninitialises all feeds and the attached output device. + */ + + void (*subscribe)(struct _sfx_pcm_mixer *self, sfx_pcm_feed_t *feed); + /* Subscribes the mixer to a new feed + ** Parameters: (sfx_pcm_mixer_t *) self: Self reference + ** (sfx_pcm_feed_t *) feed: The feed to subscribe to + */ + + void (*pause)(struct _sfx_pcm_mixer *self); + /* Pauses the processing of input and output + */ + + void (*resume)(struct _sfx_pcm_mixer *self); + /* Resumes the processing of input and output after a pause + */ + + int (*process)(struct _sfx_pcm_mixer *self); + /* Processes all feeds, mixes their results, and passes everything to the output device + ** Returns : (int) SFX_OK on success, SFX_ERROR otherwise (output device error or + ** internal assertion failure) + ** Effects : All feeds are poll()ed, and the device is asked to output(). Buffer size + ** depends on the time that has passed since the last call to process(), if + ** any. + */ + + int feeds_nr; + int feeds_allocd; + sfx_pcm_feed_state_t *feeds; + sfx_pcm_device_t *dev; + + void *private_bits; +} sfx_pcm_mixer_t; + +sfx_pcm_mixer_t * +sfx_pcm_find_mixer(char *name); +/* Looks up a mixer by name, or a default mixer +** Parameters: (char *) name: Name of the mixer to look for, or NULL to +** take a default +*/ + +extern sfx_pcm_mixer_t *mixer; /* _THE_ global pcm mixer */ + +#endif /* !defined(_SFX_MIXER_H_) */ diff --git a/engines/sci/sfx/mixer/Makefile.am b/engines/sci/sfx/mixer/Makefile.am new file mode 100644 index 0000000000..684d40a4ba --- /dev/null +++ b/engines/sci/sfx/mixer/Makefile.am @@ -0,0 +1,6 @@ +EXTRA_DIST = dc.c +noinst_LIBRARIES = libscimixer.a +INCLUDES = -I$(top_srcdir)/src/include @EXTRA_INCLUDES@ +libscimixer_a_SOURCES = mixers.c soft.c +test_LDADD = libscimixer.a ../../scicore/libscicore.a +check_PROGRAMS = test diff --git a/engines/sci/sfx/mixer/dc.c b/engines/sci/sfx/mixer/dc.c new file mode 100644 index 0000000000..52b3ab5cbb --- /dev/null +++ b/engines/sci/sfx/mixer/dc.c @@ -0,0 +1,329 @@ +/*************************************************************************** + dc.c Copyright (C) 2005 Walter van Niftrik + + + 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: + + Walter van Niftrik <w.f.b.w.v.niftrik@stud.tue.nl> + +***************************************************************************/ + +#include "../mixer.h" +#include <sci_memory.h> +#include <dc/sound/sound.h> +#include <stream.h> +#include <sys/queue.h> + +#define FEED_MODE_ALIVE 0 +#define FEED_MODE_IDLE 1 +#define FEED_MODE_DIEING 2 +#define FEED_MODE_DEAD 3 +#define FEED_MODE_RESTART 4 + +typedef struct feed_state { + /* Queue entry. */ + TAILQ_ENTRY(feed_state) entry; + + /* Whether feed is alive or dead. */ + int mode; + + /* Blank gap in frames. */ + int gap; + + /* Stream handle. */ + snd_stream_hnd_t handle; + + /* Feed. */ + sfx_pcm_feed_t *feed; + + /* Timestamp of next frame requested by stream driver. */ + sfx_timestamp_t time; +} feed_state_t; + +TAILQ_HEAD(feed_list, feed_state) feeds; + +/* Buffer size in samples. */ +#define BUF_SIZE 0x4000 + +static char buf[BUF_SIZE * 2]; + +static feed_state_t * +find_feed_state(snd_stream_hnd_t hnd) +{ + feed_state_t *state; + TAILQ_FOREACH(state, &feeds, entry) { + if (state->handle == hnd) + return state; + } + + return NULL; +} + +static void +query_timestamp(feed_state_t *state) +{ + sfx_pcm_feed_t *feed = state->feed; + + if (feed->get_timestamp) { + sfx_timestamp_t stamp; + int val = feed->get_timestamp(feed, &stamp); + + switch (val) { + case PCM_FEED_TIMESTAMP: + state->gap = sfx_timestamp_frame_diff(stamp, state->time); + + if (state->gap >= 0) + state->mode = FEED_MODE_ALIVE; + else { + long secs, usecs; + + state->mode = FEED_MODE_RESTART; + sci_gettime(&secs, &usecs); + state->time = sfx_new_timestamp(secs, usecs, feed->conf.rate); + state->gap = sfx_timestamp_frame_diff(stamp, state->time); + + if (state->gap < 0) + state->gap = 0; + } + break; + case PCM_FEED_IDLE: + state->mode = FEED_MODE_IDLE; + break; + case PCM_FEED_EMPTY: + state->mode = FEED_MODE_DIEING; + state->gap = BUF_SIZE; + } + } else { + state->mode = FEED_MODE_DIEING; + state->gap = BUF_SIZE; + } +} + +void +U8_to_S16(char *buf, int frames, int stereo) +{ + int samples = frames * (stereo ? 2 : 1); + int i; + + for (i = samples - 1; i >= 0; i--) { + buf[i * 2 + 1] = (unsigned char) buf[i] - 128; + buf[i * 2] = 0; + } +} + +static void * +callback(snd_stream_hnd_t hnd, sfx_timestamp_t timestamp, int bytes_req, int *bytes_recv) +{ + feed_state_t *state = find_feed_state(hnd); + sfx_pcm_feed_t *feed; + int channels, frames_req; + int frames_recv = 0; + + assert(state); + + state->time = timestamp; + feed = state->feed; + channels = feed->conf.stereo == SFX_PCM_MONO ? 1 : 2; + frames_req = bytes_req / 2 / channels; + + while (frames_req != frames_recv) { + int frames_left = frames_req - frames_recv; + char *buf_pos = buf + frames_recv * channels * 2; + + if (state->mode == FEED_MODE_IDLE) + query_timestamp(state); + + if (state->mode == FEED_MODE_IDLE) { + memset(buf_pos, 0, frames_left * channels * 2); + + state->time = sfx_timestamp_add(state->time, frames_left); + break; + } + + if (state->gap) { + int frames = state->gap; + + if (frames > frames_left) + frames = frames_left; + + memset(buf_pos, 0, frames * channels * 2); + + state->gap -= frames; + frames_recv += frames; + state->time = sfx_timestamp_add(state->time, frames); + if (!state->gap && state->mode == FEED_MODE_DIEING) { + state->mode = FEED_MODE_DEAD; + break; + } + } else { + int frames = feed->poll(feed, buf_pos, frames_left); + + if (feed->conf.format == SFX_PCM_FORMAT_U8) + U8_to_S16(buf_pos, frames, feed->conf.stereo != SFX_PCM_MONO); + + frames_recv += frames; + state->time = sfx_timestamp_add(state->time, frames); + + if (frames < frames_left) + query_timestamp(state); + } + } + + *bytes_recv = bytes_req; + return buf; +} + +static int +mix_init(sfx_pcm_mixer_t *self, sfx_pcm_device_t *device) +{ + if (snd_stream_init() < 0) { + fprintf(stderr, "[dc-mixer] Failed to initialize streaming sound driver\n"); + return SFX_ERROR; + } + + TAILQ_INIT(&feeds); + + return SFX_OK; +} + +static void +mix_subscribe(sfx_pcm_mixer_t *self, sfx_pcm_feed_t *feed) +{ + feed_state_t *state = sci_malloc(sizeof(feed_state_t)); + long secs, usecs; + + if ((feed->conf.format != SFX_PCM_FORMAT_S16_LE) && + (feed->conf.format != SFX_PCM_FORMAT_U8)) { + fprintf(stderr, "[dc-mixer] Unsupported feed format\n"); + feed->destroy(feed); + return; + } + + state->handle = snd_stream_alloc(callback, BUF_SIZE); + + if (state->handle == SND_STREAM_INVALID) { + fprintf(stderr, "[dc-mixer] Failed to allocate stream handle\n"); + feed->destroy(feed); + return; + } + + feed->frame_size = SFX_PCM_FRAME_SIZE(feed->conf); + state->mode = FEED_MODE_ALIVE; + state->feed = feed; + state->gap = 0; + + TAILQ_INSERT_TAIL(&feeds, state, entry); + + sci_gettime(&secs, &usecs); + state->time = sfx_new_timestamp(secs, usecs, feed->conf.rate); + snd_stream_start(state->handle, feed->conf.rate, + feed->conf.stereo != SFX_PCM_MONO); +} + +static void +mix_exit(sfx_pcm_mixer_t *self) +{ + snd_stream_shutdown(); +} + +static int +mix_process(sfx_pcm_mixer_t *self) +{ + feed_state_t *state, *state_next; + + TAILQ_FOREACH(state, &feeds, entry) { + snd_stream_poll(state->handle); + } + + state = TAILQ_FIRST(&feeds); + while (state) { + state_next = TAILQ_NEXT(state, entry); + if (state->mode == FEED_MODE_DEAD) { + snd_stream_stop(state->handle); + snd_stream_destroy(state->handle); + state->feed->destroy(state->feed); + TAILQ_REMOVE(&feeds, state, entry); + } + else if (state->mode == FEED_MODE_RESTART) { + snd_stream_stop(state->handle); + snd_stream_start(state->handle, state->feed->conf.rate, + state->feed->conf.stereo != SFX_PCM_MONO); + state->mode = FEED_MODE_ALIVE; + } + state = state_next; + } + + return SFX_OK; +} + +static void +mix_pause(sfx_pcm_mixer_t *self) +{ +} + +static void +mix_resume(sfx_pcm_mixer_t *self) +{ +} + +static int +pcm_init(sfx_pcm_device_t *self) +{ + return SFX_OK; +} + +static void +pcm_exit(sfx_pcm_device_t *self) +{ +} + +sfx_pcm_device_t sfx_pcm_driver_dc = { + "dc", + "0.1", + + pcm_init, + pcm_exit, + NULL, + NULL, + NULL, + + {0, 0, 0}, + 0, + NULL, + NULL +}; + +sfx_pcm_mixer_t sfx_pcm_mixer_dc = { + "dc", + "0.1", + + mix_init, + mix_exit, + mix_subscribe, + mix_pause, + mix_resume, + mix_process, + + 0, + 0, + NULL, + NULL, + NULL +}; diff --git a/engines/sci/sfx/mixer/mixers.c b/engines/sci/sfx/mixer/mixers.c new file mode 100644 index 0000000000..1c7e27ab17 --- /dev/null +++ b/engines/sci/sfx/mixer/mixers.c @@ -0,0 +1,55 @@ +/*************************************************************************** + mixers.c Copyright (C) 2003 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> + +***************************************************************************/ + +#include "../mixer.h" +#include <resource.h> + +extern sfx_pcm_mixer_t sfx_pcm_mixer_soft_linear; + +#ifdef _DREAMCAST +extern sfx_pcm_mixer_t sfx_pcm_mixer_dc; +#endif + +static sfx_pcm_mixer_t *mixers[] = { +#ifdef _DREAMCAST + &sfx_pcm_mixer_dc, +#endif + &sfx_pcm_mixer_soft_linear, + NULL +}; + +sfx_pcm_mixer_t * +sfx_pcm_find_mixer(char *name) +{ + int i = 0; + + if (name) + while (mixers[i] && strcmp(name, mixers[i]->name)) + ++i; + + return mixers[i]; +} diff --git a/engines/sci/sfx/mixer/soft.c b/engines/sci/sfx/mixer/soft.c new file mode 100644 index 0000000000..9f833017df --- /dev/null +++ b/engines/sci/sfx/mixer/soft.c @@ -0,0 +1,988 @@ +/*************************************************************************** + mixer.c Copyright (C) 2003 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> + +***************************************************************************/ + +#include "../mixer.h" +#include <sci_memory.h> + +/* Max. number of microseconds in difference allowed between independent audio streams */ +#define TIMESTAMP_MAX_ALLOWED_DELTA 2000 + +/*#define DEBUG 3*/ +/* Set DEBUG to one of the following: +** anything -- high-level debugging (feed subscriptions/deletions etc.) +** >= 1 -- rough input and output analysis (once per call) +** >= 2 -- more detailed input analysis (once per call and feed) +** >= 3 -- fully detailed input and output analysis (once per frame and feed) +*/ + +/*#define DEBUG 0*/ + +#define MIN_DELTA_OBSERVATIONS 100 /* Number of times the mixer is called before it starts trying to improve latency */ +#define MAX_DELTA_OBSERVATIONS 1000000 /* Number of times the mixer is called before we assume we truly understand timing */ + +static int diagnosed_too_slow = 0; + +static volatile int mixer_lock = 0; + +/*#define DEBUG_LOCKS*/ +#ifdef DEBUG_LOCKS +# define DEBUG_ACQUIRE fprintf(stderr, "[ -LOCK -] ACKQ %d: %d\n", __LINE__, mixer_lock) +# define DEBUG_WAIT fprintf(stderr, "[ -LOCK -] WAIT %d: %d\n", __LINE__, mixer_lock); +# define DEBUG_RELEASE ; fprintf(stderr, "[ -LOCK -] REL %d: %d\n", __LINE__, mixer_lock); +#else +# define DEBUG_ACQUIRE +# define DEBUG_WAIT +# define DEBUG_RELEASE +#endif + +#define ACQUIRE_LOCK() ++mixer_lock; while (mixer_lock != 1) { DEBUG_WAIT sci_sched_yield(); } DEBUG_ACQUIRE +#define RELEASE_LOCK() --mixer_lock DEBUG_RELEASE + +struct mixer_private { + byte *outbuf; /* Output buffer to write to the PCM device next time */ + sfx_timestamp_t outbuf_timestamp; /* Timestamp associated with the output buffer */ + int have_outbuf_timestamp; /* Whether we really _have_ an associated timestamp */ + byte *writebuf; /* Buffer we're supposed to write to */ + gint32 *compbuf_l, *compbuf_r; /* Intermediate buffers for computation */ + int lastbuf_len; /* Number of frames stored in the last buffer */ + + long skew; /* Millisecond relative to which we compute time. This is the millisecond + ** part of the first time we emitted sound, to simplify some computations. */ + long lsec; /* Last point in time we updated buffers, if any (seconds since the epoch) */ + int played_this_second; /* Number of frames emitted so far in second lsec */ + + int max_delta; /* maximum observed time delta (using 'frames' as a metric unit) */ + int delta_observations; /* Number of times we played; confidence measure for max_delta */ + + /* Pause data */ + int paused; +}; + +#define P ((struct mixer_private *)(self->private_bits)) + + +static int +mix_init(sfx_pcm_mixer_t *self, sfx_pcm_device_t *device) +{ + self->dev = device; + self->private_bits /* = P */ = sci_malloc(sizeof(struct mixer_private)); + P->outbuf = P->writebuf = NULL; + P->lastbuf_len = 0; + P->compbuf_l = (gint32*)sci_malloc(sizeof(gint32) * device->buf_size); + P->compbuf_r = (gint32*)sci_malloc(sizeof(gint32) * device->buf_size); + P->played_this_second = 0; + P->paused = 0; +#ifdef DEBUG + sciprintf("[soft-mixer] Initialised device %s v%s (%d Hz, %d/%x)\n", + device->name, device->version, + device->conf.rate, device->conf.stereo, device->conf.format); +#endif + return SFX_OK; +} + +static inline unsigned int +gcd(unsigned int a, unsigned int b) +{ + if (a == b) + return a; + + if (a < b) { + unsigned int c = b % a; + + if (!c) + return a; + + return gcd(c, a); + } else + return gcd(b, a); +} + +static sfx_pcm_urat_t +urat(unsigned int nom, unsigned int denom) +{ + sfx_pcm_urat_t rv; + unsigned int g; + + rv.val = nom / denom; + nom -= rv.val * denom; + if (nom == 0) + g = 1; + else + g = gcd(nom, denom); + + rv.nom = nom / g; + rv.den = denom / g; + + return rv; +} + +static void +mix_subscribe(sfx_pcm_mixer_t *self, sfx_pcm_feed_t *feed) +{ + sfx_pcm_feed_state_t *fs; + ACQUIRE_LOCK(); + if (!self->feeds) { + self->feeds_allocd = 2; + self->feeds = (sfx_pcm_feed_state_t*)sci_malloc(sizeof(sfx_pcm_feed_state_t) + * self->feeds_allocd); + } else if (self->feeds_allocd == self->feeds_nr) { + self->feeds_allocd += 2; + self->feeds = (sfx_pcm_feed_state_t*)sci_realloc(self->feeds, + sizeof(sfx_pcm_feed_state_t) + * self->feeds_allocd); + } + + fs = self->feeds + self->feeds_nr++; + fs->feed = feed; + + feed->frame_size = SFX_PCM_FRAME_SIZE(feed->conf); + + /* fs->buf_size = (self->dev->buf_size + * (feed->conf.rate + + self->dev->conf.rate - 1)) + / self->dev->conf.rate; + */ + /* For the sake of people without 64 bit CPUs: */ + fs->buf_size = 2 + /* Additional safety */ + (self->dev->buf_size * + (1 + (feed->conf.rate / self->dev->conf.rate))); +fprintf(stderr, " ---> %d/%d/%d/%d = %d\n", + self->dev->buf_size, + feed->conf.rate, + self->dev->conf.rate, + feed->frame_size, + fs->buf_size); + + fs->buf = (byte*)sci_malloc(fs->buf_size * feed->frame_size); +fprintf(stderr, " ---> --> %d for %p at %p\n", fs->buf_size * feed->frame_size, (void *)fs, (void *)fs->buf); +{int i; for (i = 0; i < fs->buf_size * feed->frame_size; i++) +fs->buf[i] = 0xa5; } + fs->scount = urat(0, 1); + fs->spd = urat(feed->conf.rate, self->dev->conf.rate); + fs->scount.den = fs->spd.den; + fs->ch_old.left = 0; + fs->ch_old.right = 0; + fs->ch_new.left = 0; + fs->ch_new.right = 0; + fs->mode = SFX_PCM_FEED_MODE_ALIVE; + + /* If the feed can't provide us with timestamps, we don't need to wait for it to do so */ + fs->pending_review = (feed->get_timestamp)? 1 : 0; + + fs->frame_bufstart = 0; + +#ifdef DEBUG + sciprintf("[soft-mixer] Subscribed %s-%x (%d Hz, %d/%x) at %d+%d/%d, buffer size %d\n", + feed->debug_name, feed->debug_nr, feed->conf.rate, feed->conf.stereo, feed->conf.format, + fs->spd.val, fs->spd.nom, fs->spd.den, fs->buf_size); +#endif + RELEASE_LOCK(); +} + + +static void +_mix_unsubscribe(sfx_pcm_mixer_t *self, sfx_pcm_feed_t *feed) +{ + int i; +#ifdef DEBUG + sciprintf("[soft-mixer] Unsubscribing %s-%x\n", feed->debug_name, feed->debug_nr); +#endif + for (i = 0; i < self->feeds_nr; i++) { + sfx_pcm_feed_state_t *fs = self->feeds + i; + + if (fs->feed == feed) { + feed->destroy(feed); + + if (fs->buf) + sci_free(fs->buf); + + self->feeds_nr--; + + /* Copy topmost into deleted so that we don't have any holes */ + if (i != self->feeds_nr) + self->feeds[i] = self->feeds[self->feeds_nr]; + + if (self->feeds_allocd > 8 && self->feeds_allocd > (self->feeds_nr << 1)) { + /* Limit memory waste */ + self->feeds_allocd >>= 1; + self->feeds + = (sfx_pcm_feed_state_t*)sci_realloc(self->feeds, + sizeof(sfx_pcm_feed_state_t) + * self->feeds_allocd); + } + + for (i = 0; i < self->feeds_nr; i++) + fprintf(stderr, " Feed #%d: %s-%x\n", + i, self->feeds[i].feed->debug_name, + self->feeds[i].feed->debug_nr); + + return; + } + } + + fprintf(stderr, "[sfx-mixer] Assertion failed: Deleting invalid feed %p out of %d\n", + (void *)feed, self->feeds_nr); + + BREAKPOINT(); +} + +static void +mix_unsubscribe(sfx_pcm_mixer_t *self, sfx_pcm_feed_t *feed) +{ + ACQUIRE_LOCK(); + _mix_unsubscribe(self, feed); + RELEASE_LOCK(); +} + + +static void +mix_exit(sfx_pcm_mixer_t *self) +{ + ACQUIRE_LOCK(); + while (self->feeds_nr) + _mix_unsubscribe(self, self->feeds[0].feed); + + if (P->outbuf) + sci_free(P->outbuf); + if (P->writebuf) + sci_free(P->writebuf); + + if (P->compbuf_l) + sci_free(P->compbuf_l); + if (P->compbuf_l) + sci_free(P->compbuf_r); + + sci_free(P); + self->private_bits /* = P */ = NULL; + RELEASE_LOCK(); + +#ifdef DEBUG + sciprintf("[soft-mixer] Uninitialising mixer\n"); +#endif +} + + +#define LIMIT_16_BITS(v) \ + if (v < -32767) \ + v = -32768; \ + else if (v > 32766) \ + v = 32767 + +static inline void +mix_compute_output(sfx_pcm_mixer_t *self, int outplen) +{ + int frame_i; + sfx_pcm_config_t conf = self->dev->conf; + int use_16 = conf.format & SFX_PCM_FORMAT_16; + int bias = conf.format & ~SFX_PCM_FORMAT_LMASK; + byte *lchan, *rchan = NULL; + /* Don't see how this could possibly wind up being + ** used w/o initialisation, but you never know... */ + gint32 *lsrc = P->compbuf_l; + gint32 *rsrc = P->compbuf_r; + int frame_size = SFX_PCM_FRAME_SIZE(conf); + + + if (!P->writebuf) + P->writebuf = (byte*)sci_malloc(self->dev->buf_size * frame_size + 4); + + if (conf.stereo) { + if (conf.stereo == SFX_PCM_STEREO_RL) { + lchan = P->writebuf + ((use_16)? 2 : 1); + rchan = P->writebuf; + } else { + lchan = P->writebuf; + rchan = P->writebuf + ((use_16)? 2 : 1); + } + } else + lchan = P->writebuf; + + + for (frame_i = 0; frame_i < outplen; frame_i++) { + int left = *lsrc++; + int right = *rsrc++; + + if (conf.stereo) { + LIMIT_16_BITS(left); + LIMIT_16_BITS(right); + + if (!use_16) { + left >>= 8; + right >>= 8; + } + + left += bias; + right += bias; + + if (use_16) { + if (SFX_PCM_FORMAT_LE == (conf.format & SFX_PCM_FORMAT_ENDIANNESS)) { + lchan[0] = left & 0xff; + lchan[1] = (left >> 8) & 0xff; + rchan[0] = right & 0xff; + rchan[1] = (right >> 8) & 0xff; + } else { + lchan[1] = left & 0xff; + lchan[0] = (left >> 8) & 0xff; + rchan[1] = right & 0xff; + rchan[0] = (right >> 8) & 0xff; + } + + lchan += 4; + rchan += 4; + } else { + *lchan = left & 0xff; + *rchan = right & 0xff; + + lchan += 2; + rchan += 2; + } + + } else { + left += right; + left >>= 1; + LIMIT_16_BITS(left); + if (!use_16) + left >>= 8; + + left += bias; + + if (use_16) { + if (SFX_PCM_FORMAT_LE == (conf.format & SFX_PCM_FORMAT_ENDIANNESS)) { + lchan[0] = left & 0xff; + lchan[1] = (left >> 8) & 0xff; + } else { + lchan[1] = left & 0xff; + lchan[0] = (left >> 8) & 0xff; + } + + lchan += 2; + } else { + *lchan = left & 0xff; + lchan += 1; + } + } + } +} + +static inline void +mix_swap_buffers(sfx_pcm_mixer_t *self) +{ /* Swap buffers */ + byte *tmp = P->outbuf; + P->outbuf = P->writebuf; + P->writebuf = tmp; +} + + +#define FRAME_OFFSET(usecs) \ + ((usecs >> 7) /* approximate, since uint32 is too small */ \ + * ((long) self->dev->conf.rate)) \ + / (1000000L >> 7) + +static inline int +mix_compute_buf_len(sfx_pcm_mixer_t *self, int *skip_frames) + /* Computes the number of frames we ought to write. It tries to minimise the number, + ** in order to reduce latency. */ + /* It sets 'skip_frames' to the number of frames to assume lost by latency, effectively + ** skipping them. */ +{ + int free_frames; + int played_frames = 0; /* since the last call */ + long secs, usecs; + int frame_pos; + int result_frames; + + sci_gettime(&secs, &usecs); + + if (!P->outbuf) { + /* Called for the first time ever? */ + P->skew = usecs; + P->lsec = secs; + P->max_delta = 0; + P->delta_observations = 0; + P->played_this_second = 0; + *skip_frames = 0; + return self->dev->buf_size; + } + + /* fprintf(stderr, "[%d:%d]S%d ", secs, usecs, P->skew);*/ + + if (P->skew > usecs) { + secs--; + usecs += (1000000 - P->skew); + } + else + usecs -= P->skew; + + frame_pos = FRAME_OFFSET(usecs); + + played_frames = frame_pos - P->played_this_second + + ((secs - P->lsec) * self->dev->conf.rate); + /* + fprintf(stderr, "%d:%d - %d:%d => %d\n", secs, frame_pos, + P->lsec, P->played_this_second, played_frames); + */ + + if (played_frames > self->dev->buf_size) + played_frames = self->dev->buf_size; + + /* + fprintf(stderr, "Between %d:? offset=%d and %d:%d offset=%d: Played %d at %d\n", P->lsec, P->played_this_second, + secs, usecs, frame_pos, played_frames, self->dev->conf.rate); + */ + + + if (played_frames > P->max_delta) + P->max_delta = played_frames; + + free_frames = played_frames; + + if (free_frames > self->dev->buf_size) { + if (!diagnosed_too_slow) { + sciprintf("[sfx-mixer] Your timer is too slow for your PCM output device (%d/%d), free=%d.\n" + "[sfx-mixer] You might want to try changing the device, timer, or mixer, if possible.\n", + played_frames, self->dev->buf_size, free_frames); + } + diagnosed_too_slow = 1; + + *skip_frames = free_frames - self->dev->buf_size; + free_frames = self->dev->buf_size; + } else + *skip_frames = 0; + + ++P->delta_observations; + if (P->delta_observations > MAX_DELTA_OBSERVATIONS) + P->delta_observations = MAX_DELTA_OBSERVATIONS; + +/* /\* Disabled, broken *\/ */ +/* if (0 && P->delta_observations > MIN_DELTA_OBSERVATIONS) { /\* Start improving after a while *\/ */ +/* int diff = self->dev->conf.rate - P->max_delta; */ + +/* /\* log-approximate P->max_delta over time *\/ */ +/* recommended_frames = P->max_delta + */ +/* ((diff * MIN_DELTA_OBSERVATIONS) / P->delta_observations); */ +/* /\* WTF? *\/ */ +/* } else */ +/* recommended_frames = self->dev->buf_size; /\* Initially, keep the buffer full *\/ */ + +#if (DEBUG >= 1) + sciprintf("[soft-mixer] played since last time: %d, recommended: %d, free: %d\n", + played_frames, recommended_frames, free_frames); +#endif + + result_frames = free_frames; + + if (result_frames < 0) + result_frames = 0; + + P->played_this_second += result_frames; + while (P->played_this_second >= self->dev->conf.rate) { + /* Won't normally happen more than once */ + P->played_this_second -= self->dev->conf.rate; + P->lsec++; + } + + if (result_frames > self->dev->buf_size) { + fprintf(stderr, "[soft-mixer] Internal assertion failed: frames-to-write %d > %d\n", + result_frames, self->dev->buf_size); + } + return result_frames; +} + + + +#define READ_NEW_VALUES() \ + if (frames_left > 0) { \ + if (bias) { /* unsigned data */ \ + if (!use_16) { \ + c_new.left = (*lsrc) << 8; \ + c_new.right = (*rsrc) << 8; \ + } else { \ + if (conf.format & SFX_PCM_FORMAT_LE) { \ + c_new.left = lsrc[0] | lsrc[1] << 8; \ + c_new.right = rsrc[0] | rsrc[1] << 8; \ + } else { \ + c_new.left = lsrc[1] | lsrc[0] << 8; \ + c_new.right = rsrc[1] | rsrc[0] << 8; \ + } \ + } \ + } else { /* signed data */ \ + if (!use_16) { \ + c_new.left = (*((signed char *)lsrc)) << 8; \ + c_new.right = (*((signed char *)rsrc)) << 8; \ + } else { \ + if (conf.format & SFX_PCM_FORMAT_LE) { \ + c_new.left = lsrc[0] | ((signed char *)lsrc)[1] << 8; \ + c_new.right = rsrc[0] | ((signed char *)rsrc)[1] << 8; \ + } else { \ + c_new.left = lsrc[1] | ((signed char *)lsrc)[0] << 8; \ + c_new.right = rsrc[1] | ((signed char *)rsrc)[0] << 8; \ + } \ + } \ + } \ + \ + c_new.left -= bias; \ + c_new.right -= bias; \ + \ + lsrc += frame_size; \ + rsrc += frame_size; \ + } else { \ + c_new.left = c_new.right = 0; \ + break; \ + } + + +static volatile int xx_offset; +static volatile int xx_size; + +static void +mix_compute_input_linear(sfx_pcm_mixer_t *self, int add_result, + int len, sfx_timestamp_t *ts, sfx_timestamp_t base_ts) + /* if add_result is non-zero, P->outbuf should be added to rather than overwritten. */ + /* base_ts is the timestamp for the first frame */ +{ + sfx_pcm_feed_state_t *fs = self->feeds + add_result; + sfx_pcm_feed_t *f = fs->feed; + sfx_pcm_config_t conf = f->conf; + int use_16 = conf.format & SFX_PCM_FORMAT_16; + gint32 *lchan = P->compbuf_l; + gint32 *rchan = P->compbuf_r; + int frame_size = f->frame_size; + byte *wr_dest = fs->buf + (frame_size * fs->frame_bufstart); + byte *lsrc = fs->buf; + byte *rsrc = fs->buf; + /* Location to write to */ + int frames_nr; + int bias = (conf.format & ~SFX_PCM_FORMAT_LMASK)? 0x8000 : 0; + /* We use this only on a 16 bit level here */ + + /* The two most extreme source frames we consider for a + ** destination frame */ + struct twochannel_data c_old = fs->ch_old; + struct twochannel_data c_new = fs->ch_new; + + int frames_read = 0; + int frames_left; + int write_offset; /* Iterator for translation */ + int delay_frames = 0; /* Number of frames (dest buffer) at the beginning we skip */ + + /* First, compute the number of frames we want to retreive */ + frames_nr = fs->spd.val * len; + /* A little complicated since we must consider partial frames */ + frames_nr += (fs->spd.nom * len + + (fs->scount.den - fs->scount.nom) /* remember that we may have leftovers */ + + (fs->spd.den - 1 /* round up */) + ) + / fs->spd.den; + + ts->secs = -1; + + if (frames_nr > fs->buf_size) { + fprintf(stderr, "%d (%d*%d + somethign) bytes, but only %d allowed!!!!!\n", + frames_nr * f->frame_size, + fs->spd.val, len, + fs->buf_size); + BREAKPOINT(); + } + + if (fs->pending_review) { + int newmode = PCM_FEED_EMPTY; /* empty unless a get_timestamp() tells otherwise */ + + RELEASE_LOCK(); + /* Retrieve timestamp */ + if (f->get_timestamp) + newmode = f->get_timestamp(f, ts); + ACQUIRE_LOCK(); + + fs = self->feeds + add_result; + /* Reset in case of status update */ + + switch (newmode) { + + case PCM_FEED_TIMESTAMP: { + /* Compute the number of frames the returned timestamp is in the future: */ + delay_frames = + sfx_timestamp_frame_diff(sfx_timestamp_renormalise(*ts, base_ts.frame_rate), + base_ts); + + if (delay_frames <= 0) + /* Start ASAP, even if it's too late */ + delay_frames = 0; + else + if (delay_frames > len) + delay_frames = len; + fs->pending_review = 0; + } + break; + + case PCM_FEED_EMPTY: + fs->mode = SFX_PCM_FEED_MODE_DEAD; + + /* ...fall through... */ + + case PCM_FEED_IDLE: + /* Clear audio buffer, if neccessary, and return */ + if (!add_result) { + memset(P->compbuf_l, 0, sizeof(gint32) * len); + memset(P->compbuf_r, 0, sizeof(gint32) * len); + } + return; + + default: + fprintf(stderr, "[soft-mixer] Fatal: Invalid mode returned by PCM feed %s-%d's get_timestamp(): %d\n", + f->debug_name, f->debug_nr, newmode); + exit(1); + } + } + + RELEASE_LOCK(); + /* Make sure we have sufficient information */ + if (frames_nr > delay_frames + fs->frame_bufstart) + frames_read = + f->poll(f, wr_dest, + frames_nr + - delay_frames + - fs->frame_bufstart); + + ACQUIRE_LOCK(); + fs = self->feeds + add_result; + + frames_read += fs->frame_bufstart; + frames_left = frames_read; + + /* Reset in case of status update */ + + /* Skip at the beginning: */ + if (delay_frames) { + if (!add_result) { + memset(lchan, 0, sizeof(gint32) * delay_frames); + memset(rchan, 0, sizeof(gint32) * delay_frames); + } + lchan += delay_frames; + rchan += delay_frames; + + len -= delay_frames; + } + + +#if (DEBUG >= 2) + sciprintf("[soft-mixer] Examining %s-%x (frame size %d); read %d/%d/%d, re-using %d frames\n", + f->debug_name, f->debug_nr, frame_size, frames_read, frames_nr, + fs->buf_size, fs->frame_bufstart); +#endif + + + if (conf.stereo == SFX_PCM_STEREO_LR) + rsrc += (use_16)? 2 : 1; + else if (conf.stereo == SFX_PCM_STEREO_RL) + lsrc += (use_16)? 2 : 1; + /* Otherwise, we let both point to the same place */ + +#if (DEBUG >= 2) + sciprintf("[soft-mixer] Stretching theoretical %d (physical %d) results to %d\n", frames_nr, frames_left, len); +#endif + for (write_offset = 0; write_offset < len; write_offset++) { + int leftsum = 0; /* Sum of any complete frames we used */ + int rightsum = 0; + + int left; /* Sum of the two most extreme source frames + ** we considered, i.e. the oldest and newest + ** one corresponding to the output frame we are + ** computing */ + int right; + + int frame_steps = fs->spd.val; + int j; + + if (fs->scount.nom >= fs->scount.den) { + fs->scount.nom -= fs->scount.den; /* Ensure fractional part < 1 */ + ++frame_steps; + } + if (frame_steps) + c_old = c_new; + +#if 0 + if (write_offset == 0) { + READ_NEW_VALUES(); + --frames_left; +#if (DEBUG >= 3) + sciprintf("[soft-mixer] Initial read %d:%d\n", c_new.left, c_new.right); +#endif + c_old = c_new; + } +#endif + + for (j = 0; j < frame_steps; j++) { + READ_NEW_VALUES(); + --frames_left; +#if (DEBUG >= 3) + sciprintf("[soft-mixer] Step %d/%d made %d:%d\n", j, frame_steps, c_new.left, c_new.right); +#endif + + /* The last frame will be subject to the fractional + ** part analysis, so we add it to 'left' and 'right' + ** later-- all others are added to (leftsum, rightsum). + */ + if (j+1 < frame_steps) { + leftsum += c_new.left; + rightsum += c_new.right; + } + } + + left = c_new.left * fs->scount.nom + + c_old.left * (fs->scount.den - fs->scount.nom); + right = c_new.right * fs->scount.nom + + c_old.right * (fs->scount.den - fs->scount.nom); + + /* Normalise */ + left /= fs->spd.den; + right /= fs->spd.den; + + + leftsum += left; + rightsum += right; + + + /* Make sure to divide by the number of frames we added here */ + if (frame_steps > 1) { + leftsum /= (frame_steps); + rightsum /= (frame_steps); + } + + +#if (DEBUG >= 3) + sciprintf("[soft-mixer] Ultimate result: %d:%d (frac %d:%d)\n", leftsum, rightsum, left, right); +#endif + + if (add_result) { + *(lchan++) += leftsum; + *(rchan++) += rightsum; + } else { + *(lchan++) = leftsum; + *(rchan++) = rightsum; + } + + fs->scount.nom += fs->spd.nom; /* Count up fractional part */ + } + + fs->ch_old = c_old; + fs->ch_new = c_new; + + /* If neccessary, zero out the rest */ + if (write_offset < len && !add_result) { + memset(lchan, 0, sizeof(gint32) * (len - write_offset)); + memset(rchan, 0, sizeof(gint32) * (len - write_offset)); + } + + /* Save whether we have a partial frame still stored */ + fs->frame_bufstart = frames_left; + + if (frames_left) { + xx_offset = ((frames_read - frames_left) * f->frame_size); + xx_size = frames_left * f->frame_size; + if (xx_offset + xx_size + >= fs->buf_size * f->frame_size) { + fprintf(stderr, "offset %d >= max %d!\n", + (xx_offset + xx_size), fs->buf_size * f->frame_size); + BREAKPOINT(); + } + + memmove(fs->buf, + fs->buf + ((frames_read - frames_left) * f->frame_size), + frames_left * f->frame_size); + } +#if (DEBUG >= 2) + sciprintf("[soft-mixer] Leaving %d over\n", fs->frame_bufstart); +#endif + + if (frames_read + delay_frames < frames_nr) { + if (f->get_timestamp) /* Can resume? */ + fs->pending_review = 1; + else + fs->mode = SFX_PCM_FEED_MODE_DEAD; /* Done. */ + } +} + +static int +mix_process_linear(sfx_pcm_mixer_t *self) +{ +ACQUIRE_LOCK(); +{ + int src_i; /* source feed index counter */ + int frames_skip; /* Number of frames to discard, rather than to emit */ + int buflen = mix_compute_buf_len(self, &frames_skip); /* Compute # of frames we must compute and write */ + int fake_buflen; + int timestamp_max_delta = 0; + int have_timestamp = 0; + sfx_timestamp_t start_timestamp; /* The timestamp at which the first frame will be played */ + sfx_timestamp_t min_timestamp; + sfx_timestamp_t timestamp; + + if (self->dev->get_output_timestamp) + start_timestamp = self->dev->get_output_timestamp(self->dev); + else { + long sec, usec; + sci_gettime(&sec, &usec); + start_timestamp = sfx_new_timestamp(sec, usec, self->dev->conf.rate); + } + + if ((P->outbuf) && (P->lastbuf_len)) { + sfx_timestamp_t ts; + int rv; + + if (P->have_outbuf_timestamp) { + ts = sfx_timestamp_renormalise(P->outbuf_timestamp, self->dev->conf.rate); + } + + rv = self->dev->output(self->dev, P->outbuf, + P->lastbuf_len, + (P->have_outbuf_timestamp)? &ts : NULL); + + if (rv == SFX_ERROR) { + RELEASE_LOCK(); + return rv; /* error */ + } + } + +#if (DEBUG >= 1) + if (self->feeds_nr) + sciprintf("[soft-mixer] Mixing %d output frames on %d input feeds\n", buflen, self->feeds_nr); +#endif + if (self->feeds_nr && !P->paused) { + /* Below, we read out all feeds in case we have to skip frames first, then get the + ** most current sound. 'fake_buflen' is either the actual buflen (for the last iteration) + ** or a fraction of the buf length to discard. */ + do { + if (frames_skip) { + if (frames_skip > self->dev->buf_size) + fake_buflen = self->dev->buf_size; + else + fake_buflen = frames_skip; + + frames_skip -= fake_buflen; + } else { + fake_buflen = buflen; + frames_skip = -1; /* Mark us as being completely done */ + } + + for (src_i = 0; src_i < self->feeds_nr; src_i++) { + mix_compute_input_linear(self, src_i, + fake_buflen, ×tamp, + start_timestamp); + + if (timestamp.secs >= 0) { + if (have_timestamp) { + int diff = sfx_timestamp_usecs_diff(min_timestamp, timestamp); + if (diff > 0) { + /* New earlier timestamp */ + timestamp = min_timestamp; + timestamp_max_delta += diff; + } else if (diff > timestamp_max_delta) + timestamp_max_delta = diff; + /* New max delta for timestamp */ + } else { + min_timestamp = timestamp; + have_timestamp = 1; + } + } + } + /* Destroy all feeds we finished */ + for (src_i = 0; src_i < self->feeds_nr; src_i++) + if (self->feeds[src_i].mode == SFX_PCM_FEED_MODE_DEAD) + _mix_unsubscribe(self, self->feeds[src_i].feed); + } while (frames_skip >= 0); + + } else { /* Zero it out */ + memset(P->compbuf_l, 0, sizeof(gint32) * buflen); + memset(P->compbuf_r, 0, sizeof(gint32) * buflen); + } + +#if (DEBUG >= 1) + if (self->feeds_nr) + sciprintf("[soft-mixer] Done mixing for this session, the result will be our next output buffer\n"); +#endif + +#if (DEBUG >= 3) + if (self->feeds_nr) { + int i; + sciprintf("[soft-mixer] Intermediate representation:\n"); + for (i = 0; i < buflen; i++) + sciprintf("[soft-mixer] Offset %d:\t[%04x:%04x]\t%d:%d\n", i, + P->compbuf_l[i] & 0xffff, P->compbuf_r[i] & 0xffff, + P->compbuf_l[i], P->compbuf_r[i]); + } +#endif + + if (timestamp_max_delta > TIMESTAMP_MAX_ALLOWED_DELTA) + sciprintf("[soft-mixer] Warning: Difference in timestamps between audio feeds is %d us\n", timestamp_max_delta); + + mix_compute_output(self, buflen); + P->lastbuf_len = buflen; + + /* Finalize */ + mix_swap_buffers(self); + if (have_timestamp) + P->outbuf_timestamp = sfx_timestamp_add(min_timestamp, + timestamp_max_delta >> 1); + P->have_outbuf_timestamp = have_timestamp; + +} RELEASE_LOCK(); + return SFX_OK; +} + +static void +mix_pause(sfx_pcm_mixer_t *self) +{ + ACQUIRE_LOCK(); + P->paused = 1; + RELEASE_LOCK(); +} + +static void +mix_resume(sfx_pcm_mixer_t *self) +{ + ACQUIRE_LOCK(); + P->paused = 0; + RELEASE_LOCK(); +} + +sfx_pcm_mixer_t sfx_pcm_mixer_soft_linear = { + "soft-linear", + "0.1", + + mix_init, + mix_exit, + mix_subscribe, + mix_pause, + mix_resume, + mix_process_linear, + + 0, + 0, + NULL, + NULL, + NULL +}; diff --git a/engines/sci/sfx/mixer/test.c b/engines/sci/sfx/mixer/test.c new file mode 100644 index 0000000000..20b3e952e1 --- /dev/null +++ b/engines/sci/sfx/mixer/test.c @@ -0,0 +1,351 @@ +/*************************************************************************** + test.c Copyright (C) 2003 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> + +***************************************************************************/ +/* Mixer inspection/test program */ + + +#include "../mixer.h" +#include <time.h> + +#if 0 +sfx_pcm_mixer_t *mix; + +int dev_init(sfx_pcm_device_t *self); +void dev_exit(sfx_pcm_device_t *self); +int dev_option(sfx_pcm_device_t *self, char *name, char *value); +int dev_output(sfx_pcm_device_t *self, byte *buf, int count); + +#define MIN_OUTPUT 128 +/* Min amount of output to compute */ + +#define DEVICES_NR 10 + +sfx_pcm_device_t devices[DEVICES_NR] = { + { "test-1", "0", dev_init, dev_exit, dev_option, dev_output, + { 200, SFX_PCM_MONO, SFX_PCM_FORMAT_U8 }, 1024, NULL }, +#if (DEVICES_NR > 1) + { "test-2", "0", dev_init, dev_exit, dev_option, dev_output, + { 200, SFX_PCM_STEREO_LR, SFX_PCM_FORMAT_U8 }, 1024, NULL }, + { "test-3", "0", dev_init, dev_exit, dev_option, dev_output, + { 200, SFX_PCM_STEREO_RL, SFX_PCM_FORMAT_U8 }, 1024, NULL }, + { "test-4", "0", dev_init, dev_exit, dev_option, dev_output, + { 200, SFX_PCM_MONO, SFX_PCM_FORMAT_S8 }, 1024, NULL }, + { "test-5", "0", dev_init, dev_exit, dev_option, dev_output, + { 200, SFX_PCM_MONO, SFX_PCM_FORMAT_U16_LE }, 1024, NULL }, + { "test-6", "0", dev_init, dev_exit, dev_option, dev_output, + { 200, SFX_PCM_MONO, SFX_PCM_FORMAT_U16_BE }, 1024, NULL }, + { "test-7", "0", dev_init, dev_exit, dev_option, dev_output, + { 200, SFX_PCM_MONO, SFX_PCM_FORMAT_S16_LE }, 1024, NULL }, + { "test-8", "0", dev_init, dev_exit, dev_option, dev_output, + { 200, SFX_PCM_MONO, SFX_PCM_FORMAT_S16_BE }, 1024, NULL }, + { "test-9", "0", dev_init, dev_exit, dev_option, dev_output, + { 200, SFX_PCM_STEREO_RL, SFX_PCM_FORMAT_S16_LE }, 1024, NULL }, + { "test-10", "0", dev_init, dev_exit, dev_option, dev_output, + { 200, SFX_PCM_STEREO_LR, SFX_PCM_FORMAT_U16_BE }, 1024, NULL } +#endif +}; + +int output_count; + +int dev_init(sfx_pcm_device_t *self) +{ + output_count = 0; + + fprintf(stderr, "[DEV] Initialised device %p as follows:\n" + "\trate = %d\n" + "\tstereo = %s\n" + "\tbias = %x\n" + "\tbytes/sample = %d\n" + "\tendianness = %s\n", + self, + self->conf.rate, + self->conf.stereo? ((self->conf.stereo == SFX_PCM_STEREO_LR)? "Left, Right" : "Right, Left") : "No", + self->conf.format & ~SFX_PCM_FORMAT_LMASK, + (self->conf.format & SFX_PCM_FORMAT_16)? 2 : 1, + ((self->conf.format & SFX_PCM_FORMAT_ENDIANNESS) == SFX_PCM_FORMAT_BE)? "big" : "little"); + return 0; +} + +void dev_exit(sfx_pcm_device_t *self) +{ + fprintf(stderr, "[DEV] Uninitialising device\n"); +} + +int dev_option(sfx_pcm_device_t *self, char *name, char *value) +{ + fprintf(stderr, "[DEV] Set option '%s' to '%s'\n", name, value); + return 0; +} + +int dev_output_enabled = 0; + +int dev_output(sfx_pcm_device_t *self, byte *buf, int count) +{ + int mono_sample_size = ((self->conf.format & SFX_PCM_FORMAT_16)? 2 : 1); + int sample_size = (self->conf.stereo? 2 : 1) * mono_sample_size; + int bias = self->conf.format & ~SFX_PCM_FORMAT_LMASK; + int is_bigendian = (self->conf.format & SFX_PCM_FORMAT_ENDIANNESS) == SFX_PCM_FORMAT_BE; + byte *left_channel = buf; + byte *right_channel = buf; + + if (!dev_output_enabled) + return 0; + + if (self->conf.format & SFX_PCM_FORMAT_16) + bias <<= 8; + + if (self->conf.stereo == SFX_PCM_STEREO_LR) + right_channel += mono_sample_size; + if (self->conf.stereo == SFX_PCM_STEREO_RL) + left_channel += mono_sample_size; + + while (count--) { + int right = right_channel[0]; + int left = left_channel[0]; + int second_byte = ((self->conf.format & SFX_PCM_FORMAT_16)? 1 : 0); + + if (second_byte) { + + if (is_bigendian) { + left = left << 8 | left_channel[1]; + right = right << 8 | right_channel[1]; + } else { + left = left | left_channel[1] << 8; + right = right | right_channel[1] << 8; + } + } + + left -= bias; + right -= bias; + + if (!second_byte) { + left <<= 8; + right <<= 8; + } + + fprintf(stderr, "[DEV] %p play %04x:\t%04x %04x\n", self, output_count++, left & 0xffff, right & 0xffff); + + left_channel += sample_size; + right_channel += sample_size; + } + return 0; +} + +/* Feeds for debugging */ + +typedef struct { + int i; +} int_struct; + +int feed_poll(sfx_pcm_feed_t *self, byte *dest, int size); +void feed_destroy(sfx_pcm_feed_t *self); + +int_struct private_bits[10] = { + {0}, + {0}, + {0}, + {0}, + {0}, + {0}, + {0}, + {0}, + {0} +}; + + +typedef struct { + int start; + int samples_nr; + byte *data; +} sample_feed_t; + +#define FEEDS_NR 4 + +sfx_pcm_feed_t feeds[FEEDS_NR] = { + { feed_poll, feed_destroy, &(private_bits[0]), + { 200, SFX_PCM_MONO, SFX_PCM_FORMAT_S8 }, "test-feed", 0, 0} +#if FEEDS_NR > 1 + ,{ feed_poll, feed_destroy, &(private_bits[1]), + { 400, SFX_PCM_MONO, SFX_PCM_FORMAT_U8 }, "test-feed", 1, 0} +#endif +#if FEEDS_NR > 2 + ,{ feed_poll, feed_destroy, &(private_bits[2]), + { 20, SFX_PCM_MONO, SFX_PCM_FORMAT_S16_LE }, "test-feed", 2, 0} +#endif +#if FEEDS_NR > 3 + ,{ feed_poll, feed_destroy, &(private_bits[3]), + { 150, SFX_PCM_STEREO_LR, SFX_PCM_FORMAT_S8 }, "test-feed", 3, 0} +#endif + /* + ,{ feed_poll, feed_destroy, &(private_bits[4]), + {}, "test-feed", 4, 0} + ,{ feed_poll, feed_destroy, &(private_bits[5]), + {}, "test-feed", 5, 0} + */ +}; + +byte feed_data_0[] = {0xfd, 0xfe, 0xff, 0, 1, 2, 3, 4, 5, 6}; +byte feed_data_1[] = {0x80, 0x90, 0xA0, 0xB0, 0xC0, + 0xD0, 0xD0, 0xC0, 0xB0, 0xA0, 0x90, 0x80}; +byte feed_data_2[] = {0x00, 0x00, + 0x00, 0x80, + 0xe8, 0x03}; +byte feed_data_3[] = {0x00, 0x10, + 0x01, 0x20, + 0x02, 0x30}; + +sample_feed_t sample_feeds[FEEDS_NR] = { + { 1, 10, feed_data_0 } +#if FEEDS_NR > 1 + ,{ 21, 12, feed_data_1 } +#endif +#if FEEDS_NR > 2 + ,{ 0, 3, feed_data_2 } +#endif +#if FEEDS_NR > 3 + ,{ 40, 3, feed_data_3 } +#endif +}; + +void +feed_destroy(sfx_pcm_feed_t *self) +{ + int_struct *s = (int_struct *) self->internal; + s->i = 0; /* reset */ +} + + +int +feed_poll(sfx_pcm_feed_t *self, byte *dest, int size) +{ + int_struct *s = (int_struct *) self->internal; + int sample_size = self->sample_size; + sample_feed_t *data = &(sample_feeds[self->debug_nr]); + int bias = self->conf.format & ~SFX_PCM_FORMAT_LMASK; + byte neutral[4] = {0, 0, 0, 0}; + int i; +fprintf(stderr, "[feed] Asked for %d at %p, ss=%d\n", size, dest, sample_size); + if (bias) { + byte first = bias >> 8; + byte second = bias & 0xff; + + if ((self->conf.format & SFX_PCM_FORMAT_ENDIANNESS) == SFX_PCM_FORMAT_LE) { + int t = first; + first = second; + second = t; + } + + if (self->conf.format & SFX_PCM_FORMAT_16) { + neutral[0] = first; + neutral[1] = second; + neutral[2] = first; + neutral[3] = second; + } else { + neutral[0] = bias; + neutral[1] = bias; + } + } + + for (i = 0; i < size; i++) { + int t = s->i - data->start; + + if (t >= data->samples_nr) + return i; + + if (t >= 0) + memcpy(dest, data->data + t * sample_size, sample_size); + else + memcpy(dest, neutral, sample_size); + + dest += sample_size; + s->i++; + } + return size; +} + + + + +extern FILE *con_file; + +#define DELAY usleep((rand() / (RAND_MAX / 250L))) + + +int +main(int argc, char **argv) +{ + int dev_nr; + + mix = sfx_pcm_find_mixer(NULL); + + if (!mix) { + fprintf(stderr, "Error: Could not find a mixer!\n"); + return 1; + } else { + fprintf(stderr, "Running %s, v%s\n", + mix->name, mix->version); + } + con_file = stderr; + + srand(time(NULL)); + + for (dev_nr = 0; dev_nr < DEVICES_NR; dev_nr++) { + sfx_pcm_device_t *dev = &(devices[dev_nr++]); + int j; + dev->init(dev); + mix->init(mix, dev); + + dev_output_enabled = 0; + /* Prime it to our timing */ + for (j = 0; j < 250; j++) { + DELAY; + mix->process(mix); + } + dev_output_enabled = 1; + + fprintf(stderr, "[test] Subscribing...\n"); + + for (j = 0; j < FEEDS_NR; j++) + mix->subscribe(mix, &(feeds[j])); + + fprintf(stderr, "[test] Subscribed %d feeds.\n", + FEEDS_NR); + + while (output_count < MIN_OUTPUT) { + DELAY; + mix->process(mix); + fprintf(stderr, "<tick>\n"); + } + + fprintf(stderr, "[test] Preparing finalisation\n"); + mix->exit(mix); + fprintf(stderr, "[test] Mixer uninitialised\n"); + } +} + +#else +int main() {} +#endif diff --git a/engines/sci/sfx/mt32_GM_mapping/Makefile b/engines/sci/sfx/mt32_GM_mapping/Makefile new file mode 100644 index 0000000000..2d40cb25b0 --- /dev/null +++ b/engines/sci/sfx/mt32_GM_mapping/Makefile @@ -0,0 +1,15 @@ +CC =gcc +CFLAGS =-O2 -Wall +objects =main.o + +mtgm: $(objects) + $(CC) -o mtgm $(objects) + +main.o: main.c + $(CC) $(CFLAGS) -c main.c + +.PHONY: clean distclean +clean: + rm -f mtgm $(objects) +distclean: + rm -f mtgm $(objects) *~ diff --git a/engines/sci/sfx/mt32_GM_mapping/README b/engines/sci/sfx/mt32_GM_mapping/README new file mode 100644 index 0000000000..8fa94ec4fd --- /dev/null +++ b/engines/sci/sfx/mt32_GM_mapping/README @@ -0,0 +1,2 @@ +The sourcecode in this directory is not intended to be included in FreeSCI. + diff --git a/engines/sci/sfx/mt32_GM_mapping/gm_patches.c b/engines/sci/sfx/mt32_GM_mapping/gm_patches.c new file mode 100644 index 0000000000..b959a87461 --- /dev/null +++ b/engines/sci/sfx/mt32_GM_mapping/gm_patches.c @@ -0,0 +1,198 @@ +/*************************************************************************** + gm_patches.c Copyright (C) 2000 Rickard Lind + + + 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. + +***************************************************************************/ + +char *GM_Patch[128] = { +/*000*/ "Acoustic Grand Piano", +/*001*/ "Bright Acoustic Piano", +/*002*/ "Electric Grand Piano", +/*003*/ "Honky-tonk Piano", +/*004*/ "Electric Piano 1", +/*005*/ "Electric Piano 2", +/*006*/ "Harpsichord", +/*007*/ "Clavinet", +/*008*/ "Celesta", +/*009*/ "Glockenspiel", +/*010*/ "Music Box", +/*011*/ "Vibraphone", +/*012*/ "Marimba", +/*013*/ "Xylophone", +/*014*/ "Tubular Bells", +/*015*/ "Dulcimer", +/*016*/ "Drawbar Organ", +/*017*/ "Percussive Organ", +/*018*/ "Rock Organ", +/*019*/ "Church Organ", +/*020*/ "Reed Organ", +/*021*/ "Accordion", +/*022*/ "Harmonica", +/*023*/ "Tango Accordion", +/*024*/ "Acoustic Guitar (nylon)", +/*025*/ "Acoustic Guitar (steel)", +/*026*/ "Electric Guitar (jazz)", +/*027*/ "Electric Guitar (clean)", +/*028*/ "Electric Guitar (muted)", +/*029*/ "Overdriven Guitar", +/*030*/ "Distortion Guitar", +/*031*/ "Guitar Harmonics", +/*032*/ "Acoustic Bass", +/*033*/ "Electric Bass (finger)", +/*034*/ "Electric Bass (pick)", +/*035*/ "Fretless Bass", +/*036*/ "Slap Bass 1", +/*037*/ "Slap Bass 2", +/*038*/ "Synth Bass 1", +/*039*/ "Synth Bass 2", +/*040*/ "Violin", +/*041*/ "Viola", +/*042*/ "Cello", +/*043*/ "Contrabass", +/*044*/ "Tremolo Strings", +/*045*/ "Pizzicato Strings", +/*046*/ "Orchestral Harp", +/*047*/ "Timpani", +/*048*/ "String Ensemble 1", +/*049*/ "String Ensemble 2", +/*050*/ "SynthStrings 1", +/*051*/ "SynthStrings 2", +/*052*/ "Choir Aahs", +/*053*/ "Voice Oohs", +/*054*/ "Synth Voice", +/*055*/ "Orchestra Hit", +/*056*/ "Trumpet", +/*057*/ "Trombone", +/*058*/ "Tuba", +/*059*/ "Muted Trumpet", +/*060*/ "French Horn", +/*061*/ "Brass Section", +/*062*/ "SynthBrass 1", +/*063*/ "SynthBrass 2", +/*064*/ "Soprano Sax", +/*065*/ "Alto Sax", +/*066*/ "Tenor Sax", +/*067*/ "Baritone Sax", +/*068*/ "Oboe", +/*069*/ "English Horn", +/*070*/ "Bassoon", +/*071*/ "Clarinet", +/*072*/ "Piccolo", +/*073*/ "Flute", +/*074*/ "Recorder", +/*075*/ "Pan Flute", +/*076*/ "Blown Bottle", +/*077*/ "Shakuhachi", +/*078*/ "Whistle", +/*079*/ "Ocarina", +/*080*/ "Lead 1 (square)", +/*081*/ "Lead 2 (sawtooth)", +/*082*/ "Lead 3 (calliope)", +/*083*/ "Lead 4 (chiff)", +/*084*/ "Lead 5 (charang)", +/*085*/ "Lead 6 (voice)", +/*086*/ "Lead 7 (fifths)", +/*087*/ "Lead 8 (bass+lead)", +/*088*/ "Pad 1 (new age)", +/*089*/ "Pad 2 (warm)", +/*090*/ "Pad 3 (polysynth)", +/*091*/ "Pad 4 (choir)", +/*092*/ "Pad 5 (bowed)", +/*093*/ "Pad 6 (metallic)", +/*094*/ "Pad 7 (halo)", +/*095*/ "Pad 8 (sweep)", +/*096*/ "FX 1 (rain)", +/*097*/ "FX 2 (soundtrack)", +/*098*/ "FX 3 (crystal)", +/*099*/ "FX 4 (atmosphere)", +/*100*/ "FX 5 (brightness)", +/*101*/ "FX 6 (goblins)", +/*102*/ "FX 7 (echoes)", +/*103*/ "FX 8 (sci-fi)", +/*104*/ "Sitar", +/*105*/ "Banjo", +/*106*/ "Shamisen", +/*107*/ "Koto", +/*108*/ "Kalimba", +/*109*/ "Bag pipe", +/*110*/ "Fiddle", +/*111*/ "Shannai", +/*112*/ "Tinkle Bell", +/*113*/ "Agogo", +/*114*/ "Steel Drums", +/*115*/ "Woodblock", +/*116*/ "Taiko Drum", +/*117*/ "Melodic Tom", +/*118*/ "Synth Drum", +/*119*/ "Reverse Cymbal", +/*120*/ "Guitar Fret Noise", +/*121*/ "Breath Noise", +/*122*/ "Seashore", +/*123*/ "Bird Tweet", +/*124*/ "Telephone Ring", +/*125*/ "Helicopter", +/*126*/ "Applause", +/*127*/ "Gunshot" }; + +char *GM_RhythmKey[47] = { +/*035*/ "Acoustic Bass Drum", +/*036*/ "Bass Drum 1", +/*037*/ "Side Stick", +/*038*/ "Acoustic Snare", +/*039*/ "Hand Clap", +/*040*/ "Electric Snare", +/*041*/ "Low Floor Tom", +/*042*/ "Closed Hi-Hat", +/*043*/ "High Floor Tom", +/*044*/ "Pedal Hi-Hat", +/*045*/ "Low Tom", +/*046*/ "Open Hi-Hat", +/*047*/ "Low-Mid Tom", +/*048*/ "Hi-Mid Tom", +/*049*/ "Crash Cymbal 1", +/*050*/ "High Tom", +/*051*/ "Ride Cymbal 1", +/*052*/ "Chinese Cymbal", +/*053*/ "Ride Bell", +/*054*/ "Tambourine", +/*055*/ "Splash Cymbal", +/*056*/ "Cowbell", +/*057*/ "Crash Cymbal 2", +/*058*/ "Vibraslap", +/*059*/ "Ride Cymbal 2", +/*060*/ "Hi Bongo", +/*061*/ "Low Bongo", +/*062*/ "Mute Hi Conga", +/*063*/ "Open Hi Conga", +/*064*/ "Low Conga", +/*065*/ "High Timbale", +/*066*/ "Low Timbale", +/*067*/ "High Agogo", +/*068*/ "Low Agogo", +/*069*/ "Cabasa", +/*070*/ "Maracas", +/*071*/ "Short Whistle", +/*072*/ "Long Whistle", +/*073*/ "Short Guiro", +/*074*/ "Long Guiro", +/*075*/ "Claves", +/*076*/ "Hi Wood Block", +/*077*/ "Low Wood Block", +/*078*/ "Mute Cuica", +/*079*/ "Open Cuica", +/*080*/ "Mute Triangle" +/*081*/ "Open Triangle" }; diff --git a/engines/sci/sfx/mt32_GM_mapping/lb2map.txt b/engines/sci/sfx/mt32_GM_mapping/lb2map.txt new file mode 100644 index 0000000000..89c60aa552 --- /dev/null +++ b/engines/sci/sfx/mt32_GM_mapping/lb2map.txt @@ -0,0 +1,118 @@ + ---------------------------------------------------------------------- + | Dagger of Amon Ra | Boop boop be doop | Wahoo! | +-------------------------------------------------------------------------- +| ## | MT-32 Timbre | KSh FTn BR | General MIDI Patch | KSh VolA V | +-------------------------------------------------------------------------- +| 04 | AcouPiano3 | 00 00 12 | Acoustic Grand Piano | 000 000 3 | +| 05 | Honkytonk | 00 00 12 | Honky-tonk Piano | 000 000 3 | +| 06 | m ClarinetMS | 00 00 12 | Clarinet | 000 -020 3 | +| 07 | Acou Bass1 | 00 00 12 | Acoustic Bass | 000 -015 3 | +| 08 | Trombone 2 | 00 00 12 | Tuba | 000 005 3 | +| 09 | m StrSect1MS | 00 00 12 | String Ensemble 1 | 000 020 3 | +| 10 | m Fantasy2MS | 00 00 12 | Pad 1 (new age) | 000 010 3 | +| 11 | Flute 2 | 00 00 12 | Flute | 000 -020 3 | +| 12 | Timpani | 00 00 12 | Timpani | 000 -015 1 | +| 13 | Trumpet 1 | 00 00 12 | Trumpet | 000 030 3 | +| 14 | m FrHorn1MS2 | 00 00 12 | French Horn | 000 000 3 | +| 15 | m Oboe MS | 00 00 12 | Oboe | 000 -020 3 | +| 16 | m Pizz MS | 00 00 12 | Pizzicato Strings | 000 000 3 | +| 17 | m CymSwellMS | 00 00 12 | Reverse Cymbal | 000 000 3 | +| 18 | Xylophone | 00 00 12 | Xylophone | 000 000 3 | +| 19 | Bassoon | 00 00 12 | Bassoon | 000 020 3 | +| 20 | Accordion | 00 00 12 | Accordion | 000 000 3 | +| 21 | m BanjoLB2 | 00 00 12 | Banjo | 000 -020 3 | +| 22 | Marimba | 00 00 12 | Marimba | 000 000 3 | +| 23 | m WarmPadStr | 00 00 12 | String Ensemble 2 | 000 015 3 | +| 24 | m BassPizzMS | 00 00 12 | Pizzicato Strings | -012 000 3 | +| 25 | m WoodBlox | 00 00 12 | Woodblock | 000 000 3 | +| 26 | Vibe 1 | 00 00 00 | Vibraphone | 000 -010 3 | +| 27 | Sax 4 | 00 00 12 | Tenor Sax | 000 000 3 | +| 28 | m Glock MS | 00 00 12 | Glockenspiel | 000 000 3 | +| 29 | Koto | 00 00 12 | Koto | 000 000 3 | +| 30 | m Taiko | 00 00 12 | Taiko Drum | 012 -010 1 | +| 31 | Guitar 1 | 00 00 12 | Acoustic Guitar (nylon) | 000 -035 3 | +| 32 | m Bell Tree | 00 00 12 | Glockenspiel | 000 000 3 | +| 33 | Sitar | 00 00 12 | Sitar | 000 -010 3 | +| 34 | Harp 1 | 00 00 12 | Orchestral Harp | 000 -025 3 | +| 45 | m Fantasy2MS | 00 00 12 | Tubular Bells | 012 010 3 | +| 46 | m Window | 00 00 12 | Reverse Cymbal | -048 000 3 | +| 47 | m Snare | 00 00 00 | Woodblock | 000 000 0 | +| 48 | m CracklesMS | 00 00 12 | Woodblock | 000 000 0 | +| 49 | m TireSqueal | 00 00 12 | | | +| 50 | m Gurgle | 00 00 12 | | | +| 51 | m Toilet | 00 00 12 | | | +| 52 | m hiss | 00 00 12 | | | +| 53 | m IceBreakMS | 00 00 12 | | | +| 54 | m DoorSlamMS | 00 00 12 | Woodblock | -012 000 0 | +| 55 | m CreakyDLL1 | 00 00 12 | | | +| 56 | m Armor MS | 00 00 12 | Agogo | 012 -020 0 | +| 57 | m RatSqueek | 00 00 12 | Guitar Fret Noise | 012 000 3 | +| 58 | m StoneDr MS | 00 00 12 | Reverse Cymbal | -048 000 3 | +| 59 | m NewSplatMS | 00 00 12 | Melodic Tom | 000 000 2 | +| 60 | m Splash MS | 00 00 12 | | | +| 61 | m Bubbles | 00 00 12 | | | +| 62 | m ChurchB MS | 00 00 12 | Tubular Bells | 000 000 3 | +| 63 | m Thud MS | 00 00 12 | Taiko Drum | -012 000 2 | +| 64 | m TYPIMG | 00 00 12 | | | +| 65 | m Lock MS | 00 00 12 | Woodblock | 000 000 0 | +| 66 | m Window | 00 00 12 | Reverse Cymbal | 000 000 3 | +| 67 | m CabEngine | 00 00 12 | Tenor Sax | -060 050 3 | +| 68 | m Ocean MS | 00 00 12 | | | +| 69 | m Wind MS | 00 00 12 | | | +| 70 | Telephone | 00 00 12 | Telephone Ring | 000 000 3 | +| 71 | Bird Tweet | 00 00 12 | Bird Tweet | 000 000 3 | +| 72 | m Explode MS | 00 00 12 | Gunshot | -012 -015 3 | +| 73 | m SwmpBackgr | 00 00 12 | | | +| 74 | m Toing | 00 00 12 | | | +| 75 | m Lone Wolf | 00 00 12 | | | +| 76 | Whistle 2 | 00 00 12 | Whistle | 000 000 3 | +| 77 | m seagulls | 00 00 12 | Bird Tweet | 000 000 3 | +| 78 | m Scrubin'MS | 00 00 12 | Reverse Cymbal | 000 000 3 | +| 79 | m SqurWaveMS | 00 00 12 | Lead 1 (square) | 000 000 3 | +| 80 | m InHale MS | 00 00 12 | Breath Noise | 000 025 3 | +| 81 | m Arena2 MS | 00 00 12 | Applause | 000 000 3 | +| 82 | m ArenaNoSus | 00 00 12 | Applause | 000 000 3 | +| 92 | AcouPiano1 | 00 00 02 | | | +| 93 | AcouPiano1 | 00 00 02 | | | +| 94 | AcouPiano1 | 00 00 02 | | | +| 95 | AcouPiano1 | 00 00 02 | | | +-------------------------------------------------------------------------- + | ## | MT-32 Timbre | OL PP | General MIDI Rhythm Key | + -------------------------------------------------------- + | 35 | r Acou BD | 100 07 | Acoustic Bass Drum | + | 36 | r Acou BD | 100 07 | Acoustic Bass Drum | + | 37 | r Rim Shot | 100 06 | Side Stick | + | 38 | r Acou SD | 100 07 | Electric Snare | + | 39 | r Hand Clap | 100 08 | Hand Clap | + | 40 | r Acou SD | 100 06 | Electric Snare | + | 41 | r AcouLowTom | 100 11 | Low Floor Tom | + | 42 | r Clsd HiHat | 100 06 | Closed Hi-Hat | + | 43 | r AcouLowTom | 100 11 | High Floor Tom | + | 44 | r OpenHiHat2 | 100 06 | Pedal Hi-Hat | + | 45 | r AcouMidTom | 100 08 | Low Tom | + | 46 | r OpenHiHat1 | 100 06 | Open Hi-Hat | + | 47 | r AcouMidTom | 100 08 | Low-Mid Tom | + | 48 | r Acou HiTom | 100 03 | Hi-Mid Tom | + | 49 | r Crash Cym | 100 06 | Crash Cymbal 1 | + | 50 | r Acou HiTom | 100 03 | High Tom | + | 51 | r Ride Cym | 100 08 | Ride Cymbal 1 | + | 52 | m CymSwellMS | 100 07 | | + | 54 | r Tambourine | 100 09 | Tambourine | + | 55 | m ChokeCrash | 100 07 | Crash Cymbal 2 | + | 56 | r Cowbell | 100 07 | Cowbell | + | 60 | r High Bongo | 100 02 | Hi Bongo | + | 61 | r Low Bongo | 100 04 | Low Bongo | + | 62 | r Mt HiConga | 100 08 | Mute Hi Conga | + | 63 | r High Conga | 100 09 | Open Hi Conga | + | 64 | r Low Conga | 100 10 | Low Conga | + | 65 | r Hi Timbale | 100 07 | High Timbale | + | 66 | r LowTimbale | 100 05 | Low Timbale | + | 67 | r High Agogo | 100 02 | High Agogo | + | 68 | r Low Agogo | 100 02 | Low Agogo | + | 69 | r Cabasa | 100 09 | Cabasa | + | 70 | r Maracas | 100 04 | Maracas | + | 71 | r SmbaWhis S | 100 09 | Short Whistle | + | 72 | r SmbaWhis L | 100 09 | Long Whistle | + | 73 | r Quijada | 100 00 | | + | 75 | r Claves | 100 12 | Claves | + -------------------------------------------------------- diff --git a/engines/sci/sfx/mt32_GM_mapping/main.c b/engines/sci/sfx/mt32_GM_mapping/main.c new file mode 100644 index 0000000000..1db2b584b4 --- /dev/null +++ b/engines/sci/sfx/mt32_GM_mapping/main.c @@ -0,0 +1,157 @@ +/*************************************************************************** + main.c Copyright (C) 2000 Rickard Lind + + + 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. + +***************************************************************************/ + +#include <stdio.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include <stdlib.h> + +#include "mt32_timbres.c" +#include "gm_patches.c" + +void analyze(unsigned char *patch001, unsigned int length001, + unsigned char *patch004, unsigned int length004); + +int main(int argc, char **argv) +{ + int fd1, fd2; + unsigned char *patch001; + unsigned char *patch004; + unsigned int length001, length004; + + if (argc < 2) + return -1; + + if ((fd1 = open(argv[1], O_RDONLY)) < 0) + return -1; + if ((fd2 = open(argv[2], O_RDONLY)) < 0) { + close(fd1); + return -1; + } + + patch001 = (unsigned char *)sci_malloc(65536); + length001 = read(fd1, patch001, 65536); + close(fd1); + + patch004 = (unsigned char *)sci_malloc(65536); + length004 = read(fd2, patch004, 65536); + close(fd2); + + if (patch001[0] == 0x89 && patch001[1] == 0x00) + if (patch004[0] == 0x89 && patch004[1] == 0x00) + analyze(patch001 + 2, length001 - 2, patch004 + 2, length004 - 2); + else + analyze(patch001 + 2, length001 - 2, patch004, length004); + else + if (patch004[0] == 0x89 && patch004[1] == 0x00) + analyze(patch001, length001, patch004 + 2, length004 - 2); + else + analyze(patch001, length001, patch004, length004); + + free(patch001); + free(patch004); + + return 0; +} + +void analyze(unsigned char *patch001, unsigned int length001, + unsigned char *patch004, unsigned int length004) +{ + int i; + unsigned char *mt32patch; + unsigned char *mt32rhythm; + + printf(" ----------------------------------------------------------------------\n"); + printf(" | %.20s | %.20s | %.20s |\n", patch001, patch001 + 20, patch001 + 40); + printf("--------------------------------------------------------------------------\n"); + printf("| ## | MT-32 Timbre | KSh FTn BR | General MIDI Patch | KSh VolA V |\n"); + printf("--------------------------------------------------------------------------\n"); + for (i = 0; i < 96; i++) { + if (i < 48) + mt32patch = patch001 + 107 + i * 8; + else + mt32patch = patch001 + 110 + i * 8 + patch001[491] * 246; + + if (!((mt32patch[0] == 0) && + (mt32patch[1] == 0) && + (mt32patch[2] == 0) && + (mt32patch[3] == 0) && + (mt32patch[4] == 0) && + (mt32patch[5] == 0) && + (mt32patch[6] == 0) && + (mt32patch[7] == 0))) { + printf("| %02i |", i); + if (mt32patch[0] < 2) + if (mt32patch[0] == 0) + printf(" %.10s", MT32_Timbre[mt32patch[1]]); + else + printf(" %.10s", MT32_Timbre[mt32patch[1] + 64]); + else if (mt32patch[0] == 2) + printf(" m %.10s", patch001 + 492 + mt32patch[1] * 246); + else if (mt32patch[0] == 3) + printf(" r %.10s", MT32_RhythmTimbre[mt32patch[1]]); + printf(" | % 03i % 03i %02i | ", + mt32patch[2] - 24, + mt32patch[3] - 50, + mt32patch[4]); + if (patch004[i] != 0xFF) { + printf("%-23s ", GM_Patch[patch004[i]]); + printf("| % 04i % 04i %i |", + *((signed char *)(patch004) + i + 128), + *((signed char *)(patch004) + i + 256), + patch004[i + 513]); + } else + printf(" | |"); + printf("\n"); + } + } + printf("--------------------------------------------------------------------------\n"); + printf(" | ## | MT-32 Timbre | OL PP | General MIDI Rhythm Key |\n"); + printf(" --------------------------------------------------------\n"); + for (i = 0; i < 64; i++) + { + mt32rhythm = patch001 + 880 + i * 4 + patch001[491] * 246; + if ((mt32rhythm[0] < 94) && + !((mt32rhythm[0] == 0) && + (mt32rhythm[1] == 0) && + (mt32rhythm[2] == 0) && + (mt32rhythm[3] == 0)) && + !((mt32rhythm[0] == 1) && + (mt32rhythm[1] == 1) && + (mt32rhythm[2] == 1) && + (mt32rhythm[3] == 1))) { + printf(" | %02i |", i + 24); + if (mt32rhythm[0] < 64) + printf(" m %.10s", patch001 + 492 + mt32rhythm[0] * 246); + else + printf(" r %.10s", MT32_RhythmTimbre[mt32rhythm[0] - 64]); + printf(" | %03i %02i | ", mt32rhythm[1], mt32rhythm[2]); + if (patch004[384 + i + 24] != 0xFF) + printf("%-23s |", GM_RhythmKey[patch004[384 + i + 24] - 35]); + else + printf(" |"); + printf("\n"); + } + } + printf(" --------------------------------------------------------\n"); + return; +} diff --git a/engines/sci/sfx/mt32_GM_mapping/mt32_timbres.c b/engines/sci/sfx/mt32_GM_mapping/mt32_timbres.c new file mode 100644 index 0000000000..beb5fa97e3 --- /dev/null +++ b/engines/sci/sfx/mt32_GM_mapping/mt32_timbres.c @@ -0,0 +1,181 @@ +/*************************************************************************** + mt32_timbres.c Copyright (C) 2000 Rickard Lind + + + 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. + +***************************************************************************/ + +char *MT32_Timbre[128] = { +/*000*/ "AcouPiano1", +/*001*/ "AcouPiano2", +/*002*/ "AcouPiano3", +/*003*/ "ElecPiano1", +/*004*/ "ElecPiano2", +/*005*/ "ElecPiano3", +/*006*/ "ElecPiano4", +/*007*/ "Honkytonk ", +/*008*/ "Elec Org 1", +/*009*/ "Elec Org 2", +/*010*/ "Elec Org 3", +/*011*/ "Elec Org 4", +/*012*/ "Pipe Org 1", +/*013*/ "Pipe Org 2", +/*014*/ "Pipe Org 3", +/*015*/ "Accordion ", +/*016*/ "Harpsi 1 ", +/*017*/ "Harpsi 2 ", +/*018*/ "Harpsi 3 ", +/*019*/ "Clavi 1 ", +/*020*/ "Clavi 2 ", +/*021*/ "Clavi 3 ", +/*022*/ "Celesta 1 ", +/*023*/ "Celesta 2 ", +/*024*/ "Syn Brass1", +/*025*/ "Syn Brass2", +/*026*/ "Syn Brass3", +/*027*/ "Syn Brass4", +/*028*/ "Syn Bass 1", +/*029*/ "Syn Bass 2", +/*030*/ "Syn Bass 3", +/*031*/ "Syn Bass 4", +/*032*/ "Fantasy ", +/*033*/ "Harmo Pan ", +/*034*/ "Chorale ", +/*035*/ "Glasses ", +/*036*/ "Soundtrack", +/*037*/ "Atmosphere", +/*038*/ "Warm Bell ", +/*039*/ "Funny Vox ", +/*040*/ "Echo Bell ", +/*041*/ "Ice Rain ", +/*042*/ "Oboe 2001 ", +/*043*/ "Echo Pan ", +/*044*/ "DoctorSolo", +/*045*/ "Schooldaze", +/*046*/ "BellSinger", +/*047*/ "SquareWave", +/*048*/ "Str Sect 1", +/*049*/ "Str Sect 2", +/*050*/ "Str Sect 3", +/*051*/ "Pizzicato ", +/*052*/ "Violin 1 ", +/*053*/ "Violin 2 ", +/*054*/ "Cello 1 ", +/*055*/ "Cello 2 ", +/*056*/ "Contrabass", +/*057*/ "Harp 1 ", +/*058*/ "Harp 2 ", +/*059*/ "Guitar 1 ", +/*060*/ "Guitar 2 ", +/*061*/ "Elec Gtr 1", +/*062*/ "Elec Gtr 2", +/*063*/ "Sitar ", +/*064*/ "Acou Bass1", +/*065*/ "Acou Bass2", +/*066*/ "Elec Bass1", +/*067*/ "Elec Bass2", +/*068*/ "Slap Bass1", +/*069*/ "Slap Bass2", +/*070*/ "Fretless 1", +/*071*/ "Fretless 2", +/*072*/ "Flute 1 ", +/*073*/ "Flute 2 ", +/*074*/ "Piccolo 1 ", +/*075*/ "Piccolo 2 ", +/*076*/ "Recorder ", +/*077*/ "Panpipes ", +/*078*/ "Sax 1 ", +/*079*/ "Sax 2 ", +/*080*/ "Sax 3 ", +/*081*/ "Sax 4 ", +/*082*/ "Clarinet 1", +/*083*/ "Clarinet 2", +/*084*/ "Oboe ", +/*085*/ "Engl Horn ", +/*086*/ "Bassoon ", +/*087*/ "Harmonica ", +/*088*/ "Trumpet 1 ", +/*089*/ "Trumpet 2 ", +/*090*/ "Trombone 1", +/*091*/ "Trombone 2", +/*092*/ "Fr Horn 1 ", +/*093*/ "Fr Horn 2 ", +/*094*/ "Tuba ", +/*095*/ "Brs Sect 1", +/*096*/ "Brs Sect 2", +/*097*/ "Vibe 1 ", +/*098*/ "Vibe 2 ", +/*099*/ "Syn Mallet", +/*100*/ "Wind Bell ", +/*101*/ "Glock ", +/*102*/ "Tube Bell ", +/*103*/ "Xylophone ", +/*104*/ "Marimba ", +/*105*/ "Koto ", +/*106*/ "Sho ", +/*107*/ "Shakuhachi", +/*108*/ "Whistle 1 ", +/*109*/ "Whistle 2 ", +/*110*/ "BottleBlow", +/*111*/ "BreathPipe", +/*112*/ "Timpani ", +/*113*/ "MelodicTom", +/*114*/ "Deep Snare", +/*115*/ "Elec Perc1", +/*116*/ "Elec Perc2", +/*117*/ "Taiko ", +/*118*/ "Taiko Rim ", +/*119*/ "Cymbal ", +/*120*/ "Castanets ", +/*121*/ "Triangle ", +/*122*/ "Orche Hit ", +/*123*/ "Telephone ", +/*124*/ "Bird Tweet", +/*125*/ "OneNoteJam", +/*126*/ "WaterBells", +/*127*/ "JungleTune" }; + +char *MT32_RhythmTimbre[30] = { +/*00*/ "Acou BD ", +/*01*/ "Acou SD ", +/*02*/ "Acou HiTom", +/*03*/ "AcouMidTom", +/*04*/ "AcouLowTom", +/*05*/ "Elec SD ", +/*06*/ "Clsd HiHat", +/*07*/ "OpenHiHat1", +/*08*/ "Crash Cym ", +/*09*/ "Ride Cym ", +/*10*/ "Rim Shot ", +/*11*/ "Hand Clap ", +/*12*/ "Cowbell ", +/*13*/ "Mt HiConga", +/*14*/ "High Conga", +/*15*/ "Low Conga ", +/*16*/ "Hi Timbale", +/*17*/ "LowTimbale", +/*18*/ "High Bongo", +/*19*/ "Low Bongo ", +/*20*/ "High Agogo", +/*21*/ "Low Agogo ", +/*22*/ "Tambourine", +/*23*/ "Claves ", +/*24*/ "Maracas ", +/*25*/ "SmbaWhis L", +/*26*/ "SmbaWhis S", +/*27*/ "Cabasa ", +/*28*/ "Quijada ", +/*29*/ "OpenHiHat2" }; diff --git a/engines/sci/sfx/mt32_GM_mapping/pq1map.txt b/engines/sci/sfx/mt32_GM_mapping/pq1map.txt new file mode 100644 index 0000000000..b2040b54f7 --- /dev/null +++ b/engines/sci/sfx/mt32_GM_mapping/pq1map.txt @@ -0,0 +1,135 @@ + ---------------------------------------------------------------------- + | ..THE DEATH ANGEL.. | POLICE QUEST I | <THANKS FOR PLAYING> | +-------------------------------------------------------------------------- +| ## | MT-32 Timbre | KSh FTn BR | General MIDI Patch | KSh VolA V | +-------------------------------------------------------------------------- +| 00 | m FEEDBAK AX | 00 00 12 | Distortion Guitar | -012 065 3 | +| 01 | m REV CYMBAL | 00 00 12 | Reverse Cymbal | 000 000 0 | +| 02 | m ANALOG SYN | 00 00 12 | Pad 3 (polysynth) | 000 127 3 | +| 03 | m STACKBASS | 00 00 12 | Slap Bass 2 | -012 100 3 | +| 04 | m ORGAN B | 00 00 12 | Drawbar Organ | 000 055 3 | +| 05 | Syn Mallet | 00 00 12 | Dulcimer | 000 000 0 | +| 06 | m HARD RIDE | 00 00 12 | | | +| 07 | Orche Hit | 00 00 12 | Orchestra Hit | 000 000 0 | +| 08 | m HEFTY BASS | 00 00 12 | Electric Bass (finger) | -024 100 3 | +| 09 | AcouPiano1 | 00 00 12 | Acoustic Grand Piano | 000 000 0 | +| 10 | m SpaceVibes | 00 00 12 | Vibraphone | 000 000 0 | +| 11 | m CIGARETTE | -24 00 12 | | | +| 12 | AcouPiano1 | 00 00 12 | Acoustic Grand Piano | 000 000 0 | +| 13 | Castanets | 00 00 12 | | | +| 14 | AcouPiano1 | 00 00 12 | Acoustic Grand Piano | 000 000 0 | +| 15 | m LUSH STRNG | 00 00 12 | String Ensemble 1 | 000 117 3 | +| 16 | Fantasy | 00 00 12 | Pad 1 (new age) | 000 000 0 | +| 17 | AcouPiano1 | 00 00 12 | Acoustic Grand Piano | 000 000 0 | +| 18 | m BIG BANJO | 00 00 12 | FX 3 (crystal) | 000 -010 3 | +| 19 | Soundtrack | 00 00 12 | FX 2 (soundtrack) | 000 000 0 | +| 20 | Timpani | 00 00 12 | Timpani | 000 000 0 | +| 21 | Fr Horn 1 | 00 00 12 | French Horn | 000 000 0 | +| 22 | Trumpet 2 | 00 00 12 | Trumpet | 000 000 3 | +| 23 | m SMOKING | 00 00 12 | | | +| 24 | m F VoxStrg | 00 00 12 | String Ensemble 1 | 000 000 0 | +| 25 | m SqurWaveMS | 00 00 12 | | | +| 26 | m TYPIMG | 00 00 12 | | | +| 27 | m SnglVox MS | 00 00 12 | | | +| 28 | Str Sect 2 | 00 00 12 | String Ensemble 1 | 000 000 0 | +| 29 | Guitar 2 | 00 00 12 | Electric Guitar (clean) | 000 000 0 | +| 30 | Elec Gtr 2 | 00 00 12 | Electric Guitar (clean) | 000 000 0 | +| 31 | Harmonica | 00 00 12 | Harmonica | 000 000 0 | +| 35 | Sax 3 | 00 00 12 | Alto Sax | 000 000 0 | +| 36 | Slap Bass1 | 00 00 12 | Slap Bass 1 | 000 000 0 | +| 37 | Fretless 1 | 00 00 12 | Fretless Bass | 000 000 0 | +| 38 | Acou Bass1 | 00 00 12 | Acoustic Bass | 000 000 0 | +| 41 | r Crash Cym | 00 00 12 | | | +| 42 | r Acou BD | 00 00 12 | | | +| 43 | m RAP SNARE | 00 00 12 | | | +| 44 | m BIG SNARE | 00 00 00 | | | +| 45 | m CLOSED HAT | 00 00 12 | | | +| 46 | m SLOSH HAT | 00 00 12 | | | +| 47 | m Gun MS | 00 00 12 | Gunshot | 000 000 0 | +| 48 | m IceBreakMS | 00 00 12 | | | +| 49 | m SHOWER | 00 00 12 | | | +| 50 | m Pft MS | 00 00 12 | | | +| 51 | m Chicago MS | 00 00 12 | Bright Acoustic Piano | 000 030 0 | +| 52 | m ROCK GUIT1 | 00 00 12 | Distortion Guitar | -012 127 3 | +| 53 | m Dog MS | 00 00 12 | | | +| 54 | m TriangleMS | 00 00 12 | | | +| 55 | m CLICKS | 00 00 12 | Woodblock | 000 000 0 | +| 56 | Bird Tweet | 00 00 12 | Bird Tweet | 000 000 0 | +| 57 | AcouPiano1 | 00 00 12 | Acoustic Grand Piano | 000 000 0 | +| 58 | m CARHORN3+4 | 00 00 12 | | | +| 59 | m CUFFS | 00 00 12 | | | +| 60 | m ELEC PHONE | 00 00 12 | | | +| 61 | m KongHit | 00 00 12 | | | +| 62 | m SCUFFLE | 00 00 12 | | | +| 63 | m FUNK PING | 00 00 12 | Orchestral Harp | 027 -010 0 | +| 64 | m BIG TOMS | 00 00 12 | Taiko Drum | 000 000 0 | +| 65 | m ElecGtr MS | 00 00 12 | Electric Guitar (clean) | -012 -005 3 | +| 66 | m Lock MS | 00 00 12 | | | +| 67 | Fretless 1 | 00 00 02 | Fretless Bass | 000 000 0 | +| 70 | m Armor MS | 00 00 12 | | | +| 71 | m ElecGtr MS | 00 00 02 | Electric Guitar (clean) | 000 000 0 | +| 77 | Telephone | 00 00 00 | Telephone Ring | 000 000 0 | +| 78 | m StoneDr MS | 00 00 12 | | | +| 79 | m Shuffle | 00 00 12 | | | +| 80 | m Buzzer | 00 00 12 | | | +| 81 | Vibe 1 | 00 00 12 | Vibraphone | 000 000 0 | +| 82 | m ElecGtr MS | 00 00 12 | Electric Guitar (clean) | 000 000 0 | +| 83 | r Hand Clap | 00 00 12 | | | +| 84 | m Drip MS | 00 00 12 | | | +| 85 | m CracklesMS | 00 00 12 | | | +| 86 | m TireSqueal | 00 00 12 | Bag pipe | 000 025 3 | +| 87 | m Explode MS | 00 00 12 | | | +| 88 | m DRAWER | 00 00 12 | | | +| 89 | m KABOOM | 00 00 12 | | | +| 90 | m GUN/STATIC | 00 00 12 | | | +| 91 | m TRAFFIC | 00 00 12 | Seashore | 000 000 0 | +| 92 | m DOOR SLAM | 00 00 12 | | | +| 93 | m CAR DOOR | 00 00 12 | | | +| 94 | m STEPS | 00 00 12 | | | +| 95 | m DIESEL | 00 00 12 | | | +-------------------------------------------------------------------------- + | ## | MT-32 Timbre | OL PP | General MIDI Rhythm Key | + -------------------------------------------------------- + | 34 | r Acou SD | 100 07 | Electric Snare | + | 35 | r Acou BD | 100 07 | Acoustic Bass Drum | + | 36 | m BIG KICK | 095 07 | Bass Drum 1 | + | 37 | r Rim Shot | 100 06 | Side Stick | + | 38 | m RAP SNARE | 100 07 | Electric Snare | + | 39 | m SLAPSTICK | 096 08 | Hand Clap | + | 40 | m BIG SNARE | 100 06 | Acoustic Snare | + | 41 | m ACOU TOMS | 080 07 | Low Floor Tom | + | 42 | m CLOSED HAT | 090 06 | Closed Hi-Hat | + | 43 | m ACOU TOMS | 080 11 | High Floor Tom | + | 44 | m SLOSH HAT | 082 06 | Pedal Hi-Hat | + | 45 | m ACOU TOMS | 085 13 | Low Tom | + | 46 | m CHOKE HAT | 072 06 | Open Hi-Hat | + | 47 | m ACOU TOMS | 080 08 | Low-Mid Tom | + | 48 | m ACOU TOMS | 069 06 | Hi-Mid Tom | + | 49 | r Crash Cym | 095 06 | Crash Cymbal 1 | + | 50 | m HARD RIDE | 100 08 | Ride Cymbal 2 | + | 51 | m RIDE CYM | 090 08 | Ride Cymbal 1 | + | 52 | m CRASH CYM | 077 11 | Crash Cymbal 1 | + | 53 | m HEFTY BASS | 082 04 | | + | 54 | r Tambourine | 100 09 | Tambourine | + | 56 | r Cowbell | 100 07 | Cowbell | + | 57 | m StoneDr MS | 100 07 | | + | 58 | r Tambourine | 100 09 | Tambourine | + | 60 | r High Bongo | 100 02 | Hi Bongo | + | 61 | r Low Bongo | 100 04 | Low Bongo | + | 62 | r Mt HiConga | 100 08 | Mute Hi Conga | + | 63 | r High Conga | 100 09 | Open Hi Conga | + | 64 | r Low Conga | 100 10 | Low Conga | + | 65 | r Hi Timbale | 100 07 | High Timbale | + | 66 | r LowTimbale | 100 05 | Low Timbale | + | 67 | r High Agogo | 100 02 | High Agogo | + | 68 | r Low Agogo | 100 02 | Low Agogo | + | 69 | r Cabasa | 100 09 | Cabasa | + | 70 | r Maracas | 100 04 | Maracas | + | 71 | r SmbaWhis S | 100 09 | Short Whistle | + | 72 | r SmbaWhis L | 100 09 | Long Whistle | + | 73 | r Quijada | 100 10 | Short Guiro | + | 75 | r Claves | 100 12 | Claves | + | 76 | m Stir | 100 07 | Long Guiro | + | 77 | m Hit | 100 07 | Acoustic Snare | + | 78 | m Brushcym | 090 07 | Ride Cymbal 1 | + -------------------------------------------------------- diff --git a/engines/sci/sfx/mt32_GM_mapping/qfg1map.txt b/engines/sci/sfx/mt32_GM_mapping/qfg1map.txt new file mode 100644 index 0000000000..c1816786b1 --- /dev/null +++ b/engines/sci/sfx/mt32_GM_mapping/qfg1map.txt @@ -0,0 +1,123 @@ + ---------------------------------------------------------------------- + | *QUEST FOR GLORY I* | So U Want 2 B A HERO | QUEST FOR GLORY I | +-------------------------------------------------------------------------- +| ## | MT-32 Timbre | KSh FTn BR | General MIDI Patch | KSh VolA V | +-------------------------------------------------------------------------- +| 03 | m Toms MS | 00 00 12 | Melodic Tom | 000 000 1 | +| 04 | Trumpet 2 | 00 00 12 | Trumpet | 000 022 2 | +| 05 | m T-Bone2 MS | 00 00 12 | Trombone | 000 065 2 | +| 06 | m FrHorn1 MS | 00 00 12 | French Horn | 000 040 3 | +| 07 | m StrSect1MS | 00 00 12 | String Ensemble 1 | 000 127 2 | +| 08 | Timpani | 00 00 12 | Timpani | 000 000 2 | +| 09 | Contrabass | 00 00 12 | Contrabass | 000 080 2 | +| 10 | m Glock MS | 00 00 12 | Glockenspiel | 012 080 3 | +| 11 | m Flute MS | 00 00 12 | Flute | 000 045 3 | +| 12 | Tuba | 00 00 12 | Tuba | 000 127 3 | +| 13 | Brs Sect 1 | 00 00 12 | Brass Section | 000 100 3 | +| 14 | Fr Horn 1 | 00 00 12 | French Horn | 000 040 3 | +| 15 | Pizzicato | 00 00 12 | Pizzicato Strings | 000 000 2 | +| 17 | Acou Bass1 | -12 00 00 | Acoustic Bass | 000 050 2 | +| 18 | Sax 2 | 00 00 00 | Alto Sax | 000 040 1 | +| 19 | m EnglHornMS | 00 00 12 | English Horn | 012 040 3 | +| 20 | Pipe Org 1 | 00 00 00 | Church Organ | 000 000 3 | +| 21 | Panpipes | 00 00 12 | Pan Flute | 000 020 1 | +| 22 | Clarinet 1 | 00 00 12 | Clarinet | 000 020 3 | +| 23 | m BassPizzMS | 00 00 12 | Orchestral Harp | -012 050 1 | +| 24 | m Conga MS | 00 00 12 | Taiko Drum | 000 000 1 | +| 25 | Sitar | 00 00 12 | Sitar | 000 080 3 | +| 26 | m Snare MS | 00 00 00 | Taiko Drum | 000 000 1 | +| 27 | m ClarinetMS | 00 00 12 | Clarinet | 000 020 3 | +| 28 | m Fantasy2MS | 00 00 12 | Pad 4 (choir) | 000 050 1 | +| 29 | Guitar 1 | 00 00 12 | Acoustic Guitar (nylon) | 000 000 1 | +| 30 | Chorale | 00 00 12 | Pad 4 (choir) | 000 020 2 | +| 31 | Syn Bass 1 | 00 00 12 | Synth Bass 1 | 000 020 1 | +| 32 | m GameSnd MS | 00 00 12 | Lead 1 (square) | 000 000 1 | +| 33 | m Calliope | 00 00 12 | Drawbar Organ | 000 000 1 | +| 34 | Whistle 1 | 00 00 12 | Whistle | 000 040 3 | +| 47 | m SKATE2 | 00 00 24 | | | +| 48 | m Claw MS | 00 00 12 | Synth Drum | 000 000 1 | +| 49 | m Flames MS | 00 00 12 | Breath Noise | 000 000 1 | +| 50 | m Swords MS | 00 00 12 | | | +| 51 | m Armor MS | 00 00 12 | | | +| 52 | m Splat MS | 00 00 00 | Synth Drum | 000 000 1 | +| 53 | m Fall MS | 00 00 00 | | | +| 54 | m Crash MS | 00 00 00 | Gunshot | 000 127 3 | +| 55 | m Laser MS | 00 00 00 | Lead 2 (sawtooth) | 000 -040 1 | +| 56 | m Vase MS | 00 00 00 | Gunshot | -012 000 1 | +| 57 | m Spit MS | 00 00 12 | Woodblock | -012 000 1 | +| 58 | m Bubbles | 00 00 12 | | | +| 59 | m Ninga MS | 00 00 12 | Breath Noise | 000 100 1 | +| 60 | m Flame2 MS | 00 00 12 | Breath Noise | 000 100 1 | +| 61 | m CracklesMS | 00 00 12 | Woodblock | 000 040 1 | +| 62 | m Gulp MS | 00 00 12 | | | +| 63 | m KnifeStkMS | 00 00 12 | Woodblock | 000 110 1 | +| 64 | m Pft MS | 00 00 12 | Helicopter | 000 110 1 | +| 65 | m Poof MS | 00 00 12 | | | +| 66 | Bird Tweet | 00 00 12 | Bird Tweet | 000 000 1 | +| 67 | m Owl MS | 00 00 12 | Bird Tweet | -012 000 1 | +| 68 | m Wind MS | 00 00 12 | | | +| 69 | m Cricket | 00 00 12 | Guitar Fret Noise | 000 000 1 | +| 70 | m SwmpBackgr | 00 00 12 | Guitar Fret Noise | 000 000 1 | +| 71 | m FireDartMS | 00 00 00 | Seashore | 000 120 1 | +| 72 | m LghtboltMS | 00 00 12 | Seashore | 000 120 1 | +| 73 | m Raspbry MS | 00 00 12 | Lead 2 (sawtooth) | 000 000 1 | +| 74 | m Explode MS | 00 00 13 | Helicopter | 000 000 1 | +| 75 | m Lock MS | 00 00 12 | Gunshot | 080 000 1 | +| 76 | m Buildup MS | 00 00 12 | | | +| 77 | m Boing MS | 00 00 24 | Taiko Drum | 000 000 1 | +| 78 | m Flames3 MS | 00 00 12 | Helicopter | 000 080 1 | +| 79 | m Thunder MS | 00 00 12 | Helicopter | -012 100 1 | +| 80 | m Meeps MS | 00 00 12 | SynthBrass 1 | 000 000 1 | +| 81 | m Bells MS | 00 00 12 | Tinkle Bell | 012 080 1 | +| 82 | m Skid MS | 00 00 12 | Helicopter | 000 000 1 | +| 83 | m RimShot MS | 00 00 12 | Woodblock | 000 000 1 | +| 84 | m WtrFall MS | 00 00 12 | | | +| 85 | m DoorSlamMS | 00 00 12 | Taiko Drum | 000 000 1 | +| 86 | m SmileFacMS | 00 00 12 | Helicopter | 000 040 1 | +| 87 | m Tumble MS | 00 00 12 | Synth Drum | 000 040 1 | +| 88 | m Punch MS | 00 00 12 | Synth Drum | 000 080 1 | +| 89 | m CreakyD MS | 00 00 12 | | | +| 90 | m CstlGateMS | 00 00 12 | Gunshot | -050 050 1 | +| 91 | m Hammer MS | 00 00 12 | | | +| 92 | m Horse1 MS | 00 00 12 | Woodblock | 000 000 1 | +| 93 | m Horse2 MS | 00 00 12 | Woodblock | 000 000 1 | +| 94 | m Kiss MS | 00 00 12 | Gunshot | 100 000 1 | +| 95 | m StrSect1MS | 00 00 12 | String Ensemble 1 | 000 127 2 | +-------------------------------------------------------------------------- + | ## | MT-32 Timbre | OL PP | General MIDI Rhythm Key | + -------------------------------------------------------- + | 36 | r Acou BD | 100 07 | Bass Drum 1 | + | 37 | r Rim Shot | 100 06 | Side Stick | + | 38 | r Acou SD | 100 07 | Acoustic Snare | + | 39 | r Hand Clap | 100 08 | Hand Clap | + | 40 | r Elec SD | 100 06 | Electric Snare | + | 41 | r AcouLowTom | 100 11 | Low Floor Tom | + | 42 | r Clsd HiHat | 100 06 | Closed Hi-Hat | + | 43 | r AcouLowTom | 100 11 | Low Floor Tom | + | 44 | r OpenHiHat2 | 100 06 | Open Hi-Hat | + | 45 | r AcouMidTom | 100 08 | Low-Mid Tom | + | 46 | r OpenHiHat1 | 100 06 | Open Hi-Hat | + | 47 | r AcouMidTom | 100 08 | Hi-Mid Tom | + | 48 | r Acou HiTom | 100 03 | High Tom | + | 49 | r Crash Cym | 100 06 | Crash Cymbal 1 | + | 50 | r Acou HiTom | 100 03 | High Tom | + | 51 | r Ride Cym | 100 08 | Ride Cymbal 1 | + | 52 | m CymSwellMS | 100 07 | | + | 54 | r Tambourine | 100 09 | Tambourine | + | 56 | r Cowbell | 100 07 | Cowbell | + | 60 | r High Bongo | 100 02 | Hi Bongo | + | 61 | r Low Bongo | 100 04 | Low Bongo | + | 62 | r Mt HiConga | 100 08 | Mute Hi Conga | + | 63 | r High Conga | 100 09 | Open Hi Conga | + | 64 | r Low Conga | 100 10 | Low Conga | + | 65 | r Hi Timbale | 100 07 | High Timbale | + | 66 | r LowTimbale | 100 05 | Low Timbale | + | 67 | r High Agogo | 100 02 | High Agogo | + | 68 | r Low Agogo | 100 02 | Low Agogo | + | 69 | r Cabasa | 100 09 | Cabasa | + | 70 | r Maracas | 100 04 | Maracas | + | 71 | r SmbaWhis S | 100 09 | Short Whistle | + | 72 | r SmbaWhis L | 100 09 | Long Whistle | + | 73 | r Quijada | 100 00 | Short Guiro | + | 75 | r Claves | 100 12 | Claves | + -------------------------------------------------------- diff --git a/engines/sci/sfx/old/Makefile b/engines/sci/sfx/old/Makefile new file mode 100644 index 0000000000..f7235d945b --- /dev/null +++ b/engines/sci/sfx/old/Makefile @@ -0,0 +1,24 @@ +CC = gcc +CFLAGS = -O2 -Wall +LIBS = -L/usr/lib -lasound +objects = main.o midi_mt32.o midiout.o midiout_unixraw.o midiout_alsaraw.o + +sciplaymidi: $(objects) + $(CC) $(LIBS) -o sciplaymidi $(objects) + +main.o: main.c + $(CC) $(CFLAGS) -c main.c +midi_mt32.o: midi_mt32.c midi_mt32.h midiout.h + $(CC) $(CFLAGS) -c midi_mt32.c +midiout.o: midiout.c midiout.h midiout_unixraw.h midiout_alsaraw.h + $(CC) $(CFLAGS) -c midiout.c +midiout_unixraw.o: midiout_unixraw.c midiout_unixraw.h + $(CC) $(CFLAGS) -c midiout_unixraw.c +midiout_alsaraw.o: midiout_alsaraw.c midiout_alsaraw.h + $(CC) $(CFLAGS) -c midiout_alsaraw.c + +.PHONY: clean distclean +clean: + rm -f sciplaymidi $(objects) +distclean: + rm -f sciplaymidi $(objects) *~ diff --git a/engines/sci/sfx/old/README b/engines/sci/sfx/old/README new file mode 100644 index 0000000000..c652175761 --- /dev/null +++ b/engines/sci/sfx/old/README @@ -0,0 +1,6 @@ +type 'make' and './sciplaymidi' to program your MT-32 with the file +'patch.001' in the current directory + +Only tested with Linux and ALSA... + +Rickard Lind <rpl@dd.chalmers.se> diff --git a/engines/sci/sfx/old/ROADMAP b/engines/sci/sfx/old/ROADMAP new file mode 100644 index 0000000000..9a6ec1b4da --- /dev/null +++ b/engines/sci/sfx/old/ROADMAP @@ -0,0 +1,72 @@ +The way I would have made things if I had the time to do it +Rickard Lind <rpl@dd.chalmers.se>, 2000-12-30 +------------------------------------------------------------------------------- + +Step 1: + +D rename "src/sound/midi.c" in freesci to "oldmidi.c" and still use it +D move my "midi*" and "midiout*" to "src/sound" +D change all "glib.h" to the real thing + +Step 2: + +D implement all note-playing, volume changing, reverb etc in "midi_mt32.*" + use disassembled sierra SCI1.1 driver as the main source of inspiration +D change "soundserver_null.c" to use the new device driver for MT-32 +* use "~/.freesci/config" to set ALSA/OSS and other options + +Step 3: + +* Implement a GM translation driver "midi_gm.*" using a parsed textfile + for instrument mapping +* Improve instrument mappings using new features such as keyshift, + volume adjust and velocity remap + +Step 4: + +* Reimplement a SCI0 soundserver using the new sound sub sytem +* PCM support (samples) with ALSA and possibly DirectX +* MPU-401 UART midiout driver for DOS/Windows + +Step 5: + +* SCI01, SCI1, SCI32 soundserver +* Adlib support "midi_opl2.*", "oplout_alsaraw.*", "oplout_pcmemu.*" +* PCM support Sound Blaster DOS + +Step 6: + +* Make it possible to play samples and use opl2 emulation (found in MAME + I think) at the same time through the same sound device + +Step 7: + +* All those other little nifty things... + +------------------------------------------------------------------------------- + +My idea concerning naming of the files: + +src/sound/midi.* wrapper for MIDI device drivers +src/sound/midiout.* wrapper for different methods to output + MIDI data +src/sound/pcm.* wrapper for PCM device drivers +src/sound/pcmout.* wrapper for different methods to output + PCM data +src/sound/midi_mt32.* Roland MT-32 device driver +src/sound/midi_opl2.* Adlib/OPL2 device driver +src/sound/midiout_alsaraw.* rawmidi output using alsalib +src/sound/midiout_unixraw.* rawmidi output using unix filesystem +src/sound/oplout_alsaraw.* opl output using alsa + +------------------------------------------------------------------------------- + +Use Linux and ALSA 0.5.x (or later) and Roland MT-32 while developing. +Don't implement supremely stupid abstract frameworks without consulting +experienced people on the mailinglist first. There are infinite ways to +implement the sound subsystem the VERY VERY WRONG way. Don't make +everything too much of a hack either. + +Use the files is in "lists/" when doing text output for debugging purposes. + +Good luck! diff --git a/engines/sci/sfx/old/main.c b/engines/sci/sfx/old/main.c new file mode 100644 index 0000000000..73daebd1bb --- /dev/null +++ b/engines/sci/sfx/old/main.c @@ -0,0 +1,49 @@ +/*************************************************************************** + main.c Copyright (C) 2000 Rickard Lind + + + 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. + +***************************************************************************/ + +#include <stdlib.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include "midi_mt32.h" + +int main() +{ + int fd; + unsigned char *patch; + unsigned int length; + + patch = (unsigned char *)sci_malloc(65536); + + fd = open("patch.001", O_RDONLY); + length = read(fd, patch, 65536); + close(fd); + + if (patch[0] == 0x89 && patch[1] == 0x00) + midi_mt32_open(patch + 2, length - 2); + else + midi_mt32_open(patch, length); + + midi_mt32_close(); + + free(patch); + return 0; +} diff --git a/engines/sci/sfx/old/midi.c b/engines/sci/sfx/old/midi.c new file mode 100644 index 0000000000..1be951befb --- /dev/null +++ b/engines/sci/sfx/old/midi.c @@ -0,0 +1,30 @@ +/*************************************************************************** + midi.c Copyright (C) 2000 Rickard Lind + + + 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. + +***************************************************************************/ + +#ifndef _MIDI_MT32_H_ +#define _MIDI_MT32_H_ + +int midi_mt32_open(guint8 *data_ptr, unsigned int data_length); +int midi_mt32_close(); + +int midi_mt32_noteoff(guint8 channel, guint8 note); +int midi_mt32_noteon(guint8 channel, guint8 note, guint8 velocity); + +#endif /* _MIDI_MT32_H_ */ diff --git a/engines/sci/sfx/old/midi.h b/engines/sci/sfx/old/midi.h new file mode 100644 index 0000000000..9a816781d3 --- /dev/null +++ b/engines/sci/sfx/old/midi.h @@ -0,0 +1,30 @@ +/*************************************************************************** + midi.h Copyright (C) 2000 Rickard Lind + + + 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. + +***************************************************************************/ + +#ifndef _MIDI_H_ +#define _MIDI_H_ + +int midi_open(guint8 *data_ptr, unsigned int data_length); +int midi_close(); + +int midi_noteoff(guint8 channel, guint8 note); +int midi_noteon(guint8 channel, guint8 note, guint8 velocity); + +#endif /* _MIDI_H_ */ diff --git a/engines/sci/sfx/old/midi_mt32.c b/engines/sci/sfx/old/midi_mt32.c new file mode 100644 index 0000000000..be5550bd93 --- /dev/null +++ b/engines/sci/sfx/old/midi_mt32.c @@ -0,0 +1,341 @@ +/*************************************************************************** + midi_mt32.c Copyright (C) 2000 Rickard Lind + + + 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. + +***************************************************************************/ + +#include "glib.h" +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include "midi_mt32.h" +#include "midiout.h" + +#define RHYTHM_CHANNEL 9 + +int midi_mt32_poke(guint32 address, guint8 *data, unsigned int n); +int midi_mt32_poke_gather(guint32 address, guint8 *data1, unsigned int count1, + guint8 *data2, unsigned int count2); +int midi_mt32_write_block(guint8 *data, unsigned int count); +int midi_mt32_patch001_type(guint8 *data, unsigned int length); +int midi_mt32_patch001_type0_length(guint8 *data, unsigned int length); +int midi_mt32_patch001_type1_length(guint8 *data, unsigned int length); +int midi_mt32_sysex_delay(); + +static guint8 *data; +static unsigned int length; +static int type; +static guint8 sysex_buffer[266] = {0xF0, 0x41, 0x10, 0x16, 0x12}; + +static gint8 patch_map[128] = { + 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15, + 16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31, + 32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47, + 48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63, + 64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79, + 80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95, + 96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111, + 112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127}; +static gint8 keyshift[128] = { + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; +static gint8 volume_adjust[128] = { + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; +static guint8 velocity_map_index[128] = { + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; +static guint8 velocity_map[4][128] = { + {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15, + 16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31, + 32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47, + 48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63, + 64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79, + 80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95, + 96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111, + 112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127}, + {0,32,32,33,33,34,34,35,35,36,36,37,37,38,38,39, + 39,40,40,41,41,42,42,43,43,44,44,45,45,46,46,47, + 47,48,48,49,49,50,50,51,51,52,52,53,53,54,54,55, + 55,56,56,57,57,58,58,59,59,60,60,61,61,62,62,63, + 64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79, + 80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95, + 96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111, + 112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127}, + {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15, + 16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31, + 32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47, + 48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63, + 64,65,66,66,67,67,68,68,69,69,70,70,71,71,72,72, + 73,73,74,74,75,75,76,76,77,77,78,78,79,79,80,80, + 81,81,82,82,83,83,84,84,85,85,86,86,87,87,88,88, + 89,89,90,90,91,91,92,92,93,93,94,94,95,95,96,96}, + {0,32,32,33,33,34,34,35,35,36,36,37,37,38,38,39, + 39,40,40,41,41,42,42,43,43,44,44,45,45,46,46,47, + 47,48,48,49,49,50,50,51,51,52,52,53,53,54,54,55, + 55,56,56,57,57,58,58,59,59,60,60,61,61,62,62,63, + 64,65,66,66,67,67,68,68,69,69,70,70,71,71,72,72, + 73,73,74,74,75,75,76,76,77,77,78,78,79,79,80,80, + 81,81,82,82,83,83,84,84,85,85,86,86,87,87,88,88, + 89,89,90,90,91,91,92,92,93,93,94,94,95,95,96,96}}; +static gint8 rhythmkey_map[128] = { + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,35,36,37,38,39,40,41,42,43,44,45,46,47, + 48,49,50,51,-1,-1,54,-1,56,-1,-1,-1,60,61,62,63, + 64,65,66,67,68,69,70,71,72,73,-1,75,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}; +/* static struct { + gint8 sci_patch; + gint8 sci_volume; + gint8 sci_pitchwheel; + gint8 patch; + gint8 keyshift; + gint8 volume_adjust; + guint8 velocity_map_index; + guint8 +} channel[16]; */ +static guint8 master_volume; + +int midi_mt32_open(guint8 *data_ptr, unsigned int data_length) +{ + guint8 unknown_sysex[6] = {0x16, 0x16, 0x16, 0x16, 0x16, 0x16}; + guint8 i, memtimbres; + unsigned int block2, block3; + + if (midiout_open() < 0) + return -1; + + data = data_ptr; + length = data_length; + + type = midi_mt32_patch001_type(data, length); + printf("MT-32: Programming Roland MT-32 with patch.001 (v%i)\n", type); + + if (type == 0) { + /* Display MT-32 initialization message */ + printf("MT-32: Displaying Text: \"%.20s\"\n", data + 20); + midi_mt32_poke(0x200000, data + 20, 20); + midi_mt32_sysex_delay(); + + /* Write Patches (48 or 96) */ + memtimbres = data[491]; + block2 = (memtimbres * 246) + 492; + printf("MT-32: Writing Patches #01 - #32\n"); + midi_mt32_poke(0x050000, data + 107, 256); + midi_mt32_sysex_delay(); + if ((length > block2) && + data[block2] == 0xAB && + data[block2 + 1] == 0xCD) { + printf("MT-32: Writing Patches #33 - #64\n"); + midi_mt32_poke_gather(0x050200, data + 363, 128, data + block2 + 2, 128); + midi_mt32_sysex_delay(); + printf("MT-32: Writing Patches #65 - #96\n"); + midi_mt32_poke(0x050400, data + block2 + 130, 256); + midi_mt32_sysex_delay(); + block3 = block2 + 386; + } else { + printf("MT-32: Writing Patches #33 - #48\n"); + midi_mt32_poke(0x050200, data + 363, 128); + midi_mt32_sysex_delay(); + block3 = block2; + } + /* Write Memory Timbres */ + for (i = 0; i < memtimbres; i++) { + printf("MT-32: Writing Memory Timbre #%02d: \"%.10s\"\n", + i + 1, data + 492 + i * 246); + midi_mt32_poke(0x080000 + (i << 9), data + 492 + i * 246, 246); + midi_mt32_sysex_delay(); + } + /* Write Rhythm key map and Partial Reserve */ + if ((length > block3) && + data[block3] == 0xDC && + data[block3 + 1] == 0xBA) { + printf("MT-32: Writing Rhythm key map\n"); + midi_mt32_poke(0x030110, data + block3 + 2, 256); + midi_mt32_sysex_delay(); + printf("MT-32: Writing Partial Reserve\n"); + midi_mt32_poke(0x100004, data + block3 + 258, 9); + midi_mt32_sysex_delay(); + } + /* Display MT-32 initialization done message */ + printf("MT-32: Displaying Text: \"%.20s\"\n", data); + midi_mt32_poke(0x200000, data, 20); + midi_mt32_sysex_delay(); + /* Write undocumented MT-32(?) SysEx */ + printf("MT-32: Writing {F0 41 10 16 12 52 00 0A 16 16 16 16 16 16 20 F7}\n"); + midi_mt32_poke(0x52000A, unknown_sysex, 6); + midi_mt32_sysex_delay(); + return 0; + } else if (type == 1) { + midi_mt32_write_block(data + 1155, (data[1154] << 8) + data[1153]); + memcpy(patch_map, data, 128); + memcpy(keyshift, data + 128, 128); + memcpy(volume_adjust, data + 256, 128); + memcpy(velocity_map_index, data + 513, 128); + for (i = 0; i < 4; i++) + memcpy(velocity_map[i], data + 641 + i * 128, 128); + memcpy(rhythmkey_map, data + 384, 128); + return 0; + } + return -1; +} + +int midi_mt32_close() +{ +if (type == 0) { + printf("MT-32: Displaying Text: \"%.20s\"\n", data + 40); + midi_mt32_poke(0x200000, data + 40, 20); + midi_mt32_sysex_delay(); +} +return midiout_close(); +} + +int midi_mt32_noteoff(guint8 channel, guint8 note) +{ + return 0; +} + +int midi_mt32_noteon(guint8 channel, guint8 note, guint8 velocity) +{ +/* guint8 buffer[3] = {0x90}; + if (channel == RHYTHM_CHANNEL) + if (rhythmkey_map[note] == -1) + return 0; + else { + buffer[0] |= channel + buffer[1] = rhythmkey_map[note]; + buffer[2] = velo + midi_write_event(buffer, 3); + }; */ + return 0; +} + +int midi_mt32_poke(guint32 address, guint8 *data, unsigned int count) +{ + guint8 checksum = 0; + unsigned int i; + + if (count > 256) return -1; + + checksum -= (sysex_buffer[5] = (char)((address >> 16) & 0x7F)); + checksum -= (sysex_buffer[6] = (char)((address >> 8) & 0x7F)); + checksum -= (sysex_buffer[7] = (char)(address & 0x7F)); + + for (i = 0; i < count; i++) + checksum -= (sysex_buffer[i + 8] = data[i]); + + sysex_buffer[count + 8] = checksum & 0x7F; + sysex_buffer[count + 9] = 0xF7; + + return midiout_write_block(sysex_buffer, count + 10); +} + +int midi_mt32_poke_gather(guint32 address, guint8 *data1, unsigned int count1, + guint8 *data2, unsigned int count2) +{ + guint8 checksum = 0; + unsigned int i; + + if ((count1 + count2) > 256) return -1; + + checksum -= (sysex_buffer[5] = (char)((address >> 16) & 0x7F)); + checksum -= (sysex_buffer[6] = (char)((address >> 8) & 0x7F)); + checksum -= (sysex_buffer[7] = (char)(address & 0x7F)); + + for (i = 0; i < count1; i++) + checksum -= (sysex_buffer[i + 8] = data1[i]); + for (i = 0; i < count2; i++) + checksum -= (sysex_buffer[i + 8 + count1] = data2[i]); + + sysex_buffer[count1 + count2 + 8] = checksum & 0x7F; + sysex_buffer[count1 + count2 + 9] = 0xF7; + + return midiout_write_block(sysex_buffer, count1 + count2 + 10); +} + +int midi_mt32_write_block(guint8 *data, unsigned int count) +{ + unsigned int block_start = 0; + unsigned int i = 0; + + while (i < count) { + if ((data[i] == 0xF0) && (i != block_start)) { + midiout_write_block(data + block_start, i - block_start); + block_start = i; + } + if (data[i] == 0xF7) { + midiout_write_block(data + block_start, i - block_start + 1); + midi_mt32_sysex_delay(); + block_start = i + 1; + } + i++; + } + if (count >= block_start) + midiout_write_block(data + block_start, count - block_start); + return 0; +} + +int midi_mt32_patch001_type(guint8 *data, unsigned int length) +{ + /* length test */ + if (length < 1155) + return 0; + if (length > 16889) + return 1; + if (midi_mt32_patch001_type0_length(data, length) && + !midi_mt32_patch001_type1_length(data, length)) + return 0; + if (midi_mt32_patch001_type1_length(data, length) && + !midi_mt32_patch001_type0_length(data, length)) + return 1; + return -1; +} + +int midi_mt32_patch001_type0_length(guint8 *data, unsigned int length) +{ + unsigned int pos = 492 + 246 * data[491]; + + if ((length >= (pos + 386)) && (data[pos] == 0xAB) && (data[pos + 1] == 0xCD)) + pos += 386; + if ((length >= (pos + 267)) && (data[pos] == 0xDC) && (data[pos + 1] == 0xBA)) + pos += 267; + if (pos == length) + return 1; + return 0; +} + +int midi_mt32_patch001_type1_length(guint8 *data, unsigned int length) +{ + if ((length >= 1155) && (((data[1154] << 8) + data[1153] + 1155) == length)) + return 1; + return 0; +} + +int midi_mt32_sysex_delay() +{ + usleep(320 * 63); /* One MIDI byte is 320us, 320us * 63 > 20ms */ + return 0; +} diff --git a/engines/sci/sfx/old/midi_mt32.h b/engines/sci/sfx/old/midi_mt32.h new file mode 100644 index 0000000000..0e14efd41d --- /dev/null +++ b/engines/sci/sfx/old/midi_mt32.h @@ -0,0 +1,29 @@ +/*************************************************************************** + midi_mt32.h Copyright (C) 2000 Rickard Lind + + + 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. + +***************************************************************************/ + +#ifndef _MIDI_MT32_H_ +#define _MIDI_MT32_H_ + +#include "glib.h" + +int midi_mt32_open(guint8 *data_ptr, unsigned int data_length); +int midi_mt32_close(); + +#endif /* _MIDI_MT32_H_ */ diff --git a/engines/sci/sfx/old/midiout.c b/engines/sci/sfx/old/midiout.c new file mode 100644 index 0000000000..262836baba --- /dev/null +++ b/engines/sci/sfx/old/midiout.c @@ -0,0 +1,63 @@ +/*************************************************************************** + midiout.c Copyright (C) 2000 Rickard Lind + + + 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. + +***************************************************************************/ + +#include "glib.h" +#include "midiout.h" +#include "midiout_alsaraw.h" +#include "midiout_unixraw.h" + +static int (*midiout_ptr_close)(); +static int (*midiout_ptr_write)(guint8 *, unsigned int); + +static unsigned char running_status = 0; + +int midiout_open() +{ + midiout_ptr_close = midiout_alsaraw_close; + midiout_ptr_write = midiout_alsaraw_write; + return midiout_alsaraw_open(0, 0); + + /* + midiout_ptr_close = midiout_unixraw_close; + midiout_ptr_write = midiout_unixraw_write; + return midiout_unixraw_open("/dev/midi00"); + */ +} + +int midiout_close() +{ + return midiout_ptr_close(); +} + +int midiout_write_event(guint8 *buffer, unsigned int count) +{ + if (buffer[0] == running_status) + return midiout_ptr_write(buffer + 1, count - 1); + else { + running_status = buffer[0]; + return midiout_ptr_write(buffer, count); + } +} + +int midiout_write_block(guint8 *buffer, unsigned int count) +{ + running_status = 0; + return midiout_ptr_write(buffer, count); +} diff --git a/engines/sci/sfx/old/midiout.h b/engines/sci/sfx/old/midiout.h new file mode 100644 index 0000000000..e0ce3915ae --- /dev/null +++ b/engines/sci/sfx/old/midiout.h @@ -0,0 +1,29 @@ +/*************************************************************************** + midiout.h Copyright (C) 2000 Rickard Lind + + + 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. + +***************************************************************************/ + +#ifndef _MIDIOUT_H_ +#define _MIDIOUT_H_ + +int midiout_open(); +int midiout_close(); +int midiout_write_event(guint8 *buffer, unsigned int count); +int midiout_write_block(guint8 *buffer, unsigned int count); + +#endif /* _MIDIOUT_H_ */ diff --git a/engines/sci/sfx/old/midiout_alsaraw.c b/engines/sci/sfx/old/midiout_alsaraw.c new file mode 100644 index 0000000000..e95aa28425 --- /dev/null +++ b/engines/sci/sfx/old/midiout_alsaraw.c @@ -0,0 +1,51 @@ +/*************************************************************************** + midiout_alsaraw.c Copyright (C) 2000 Rickard Lind + + + 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. + +***************************************************************************/ + +#include "glib.h" +#include <stdio.h> +#include <sys/asoundlib.h> +#include "midiout_alsaraw.h" + +static snd_rawmidi_t *handle; + +int midiout_alsaraw_open(int card, int device) +{ + int err; + + if ((err = snd_rawmidi_open(&handle, card, device, SND_RAWMIDI_OPEN_OUTPUT)) < 0) { + fprintf(stderr, "Open failed (%i): /dev/snd/midiC%iD%i\n", err, card, device); + return -1; + } + return 0; +} + +int midiout_alsaraw_close() +{ + if (snd_rawmidi_close(handle) < 0) + return -1; + return 0; +} + +int midiout_alsaraw_write(guint8 *buffer, unsigned int count) +{ + if (snd_rawmidi_write(handle, buffer, count) != count) + return -1; + return 0; +} diff --git a/engines/sci/sfx/old/midiout_alsaraw.h b/engines/sci/sfx/old/midiout_alsaraw.h new file mode 100644 index 0000000000..cbf9da44c1 --- /dev/null +++ b/engines/sci/sfx/old/midiout_alsaraw.h @@ -0,0 +1,28 @@ +/*************************************************************************** + midiout_alsaraw.h Copyright (C) 2000 Rickard Lind + + + 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. + +***************************************************************************/ + +#ifndef _MIDIOUT_ALSARAW_H_ +#define _MIDIOUT_ALSARAW_H_ + +int midiout_alsaraw_open(int card, int device); +int midiout_alsaraw_close(); +int midiout_alsaraw_write(guint8 *buffer, unsigned int count); + +#endif /* _MIDIOUT_ALSARAW_H_ */ diff --git a/engines/sci/sfx/old/midiout_unixraw.c b/engines/sci/sfx/old/midiout_unixraw.c new file mode 100644 index 0000000000..a0dc85955f --- /dev/null +++ b/engines/sci/sfx/old/midiout_unixraw.c @@ -0,0 +1,52 @@ +/*************************************************************************** + midiout_unixraw.c Copyright (C) 2000 Rickard Lind + + + 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. + +***************************************************************************/ + +#include "glib.h" +#include <stdio.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include "midiout_unixraw.h" + +static int fd; + +int midiout_unixraw_open(char *devicename) +{ + if (!IS_VALID_FD(fd = open(devicename, O_WRONLY|O_SYNC))) { + fprintf(stderr, "Open failed (%i): %s\n", fd, devicename); + return -1; + } + return 0; +} + +int midiout_unixraw_close() +{ + if (close(fd) < 0) + return -1; + return 0; +} + +int midiout_unixraw_write(guint8 *buffer, unsigned int count) +{ + if (write(fd, buffer, count) != count) + return -1; + return 0; +} diff --git a/engines/sci/sfx/old/midiout_unixraw.h b/engines/sci/sfx/old/midiout_unixraw.h new file mode 100644 index 0000000000..c5980696e9 --- /dev/null +++ b/engines/sci/sfx/old/midiout_unixraw.h @@ -0,0 +1,28 @@ +/*************************************************************************** + midiout_unixraw.h Copyright (C) 2000 Rickard Lind + + + 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. + +***************************************************************************/ + +#ifndef _MIDIOUT_UNIXRAW_H_ +#define _MIDIOUT_UNIXRAW_H_ + +int midiout_unixraw_open(char *devicename); +int midiout_unixraw_close(); +int midiout_unixraw_write(guint8 *buffer, unsigned int count); + +#endif /* _MIDIOUT_UNIXRAW_H_ */ diff --git a/engines/sci/sfx/pcm-iterator.c b/engines/sci/sfx/pcm-iterator.c new file mode 100644 index 0000000000..8d9b86ed0d --- /dev/null +++ b/engines/sci/sfx/pcm-iterator.c @@ -0,0 +1,117 @@ +/*************************************************************************** + pcm-iterator.c Copyright (C) 2002 Christoph Reichenbach + + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public Licence as + published by the Free Software Foundaton; either version 2 of the + Licence, or (at your option) any later version. + + It is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + merchantibility or fitness for a particular purpose. See the + GNU General Public Licence for more details. + + You should have received a copy of the GNU General Public Licence + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + + Please contact the maintainer for any program-related bug reports or + inquiries. + + Current Maintainer: + + Christoph Reichenbach (CR) <jameson@linuxgames.com> + +***************************************************************************/ + +#include <sfx_iterator.h> +#include <resource.h> /* for BREAKPOINT */ +#include <sci_memory.h> + +#define D ((pcm_data_internal_t *)self->internal) + +static int +pi_poll(sfx_pcm_feed_t *self, byte *dest, int size); +static void +pi_destroy(sfx_pcm_feed_t *self); + +typedef struct { + byte *base_data; + byte *data; + int frames_left; +} pcm_data_internal_t; + + +static sfx_pcm_feed_t pcm_it_prototype = { + pi_poll, + pi_destroy, + NULL, /* No timestamp getter */ + NULL, /* Internal data goes here */ + {0,0,0}, /* Must fill in configuration */ + "song-iterator", + 0, /* Ideally the resource number should go here */ + 0 /* The mixer computes this for us */ +}; + + +sfx_pcm_feed_t * +sfx_iterator_make_feed(byte *base_data, + int offset, + int size, + sfx_pcm_config_t conf) +{ + sfx_pcm_feed_t *feed; + pcm_data_internal_t *idat; + byte *data = base_data + offset; + + if (!data) { + /* Now this is silly; why'd you call this function in the first place? */ + return NULL; + } + sci_refcount_incref(base_data); + + idat = (pcm_data_internal_t*)sci_malloc(sizeof(pcm_data_internal_t)); + idat->base_data = base_data; + idat->data = data; + idat->frames_left = size; + feed = (sfx_pcm_feed_t*)sci_malloc(sizeof(sfx_pcm_feed_t)); + *feed = pcm_it_prototype; + feed->internal = idat; + feed->conf = conf; + + return feed; +} + + +static int +pi_poll(sfx_pcm_feed_t *self, byte *dest, int size) +{ + int data_len; + + if (size >= D->frames_left) + size = D->frames_left; + + D->frames_left -= size; + + data_len = size * self->frame_size; + + memcpy(dest, D->data, data_len); +#if 0 +memset(dest, 0xff, data_len); +#endif + + D->data += data_len; + + return size; +} + +static void +pi_destroy(sfx_pcm_feed_t *self) +{ + sci_refcount_decref(D->base_data); + sci_free(D); + sci_free(self); +} diff --git a/engines/sci/sfx/pcm_device/Makefile.am b/engines/sci/sfx/pcm_device/Makefile.am new file mode 100644 index 0000000000..b13a3aab0b --- /dev/null +++ b/engines/sci/sfx/pcm_device/Makefile.am @@ -0,0 +1,7 @@ +noinst_LIBRARIES = libscipcm.a +INCLUDES = -I$(top_srcdir)/src/include @EXTRA_INCLUDES@ +AM_CFLAGS = $(SDL_CFLAGS) +EXTRA_DIST = audiobuf.h +libscipcm_a_SOURCES = audiobuf.c pcm_devices.c sdl.c alsa.c +audbuf_test_LDADD = libscipcm.a ../../scicore/libscicore.a +check_PROGRAMS = audbuf_test diff --git a/engines/sci/sfx/pcm_device/alsa.c b/engines/sci/sfx/pcm_device/alsa.c new file mode 100644 index 0000000000..26d4c35044 --- /dev/null +++ b/engines/sci/sfx/pcm_device/alsa.c @@ -0,0 +1,387 @@ +/*************************************************************************** + alsa.c Copyright (C) 2004 Walter van Niftrik + + + 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: + + Walter van Niftrik <w.f.b.w.v.niftrik@stud.tue.nl> + +***************************************************************************/ + +/* Based on ALSA's pcm.c example. */ + +#include <sfx_pcm.h> +#include "audiobuf.h" + +#ifdef HAVE_ALSA +#ifdef HAVE_PTHREAD + +#define ALSA_PCM_NEW_HW_PARAMS_API +#define ALSA_PCM_NEW_SW_PARAMS_API + +#include <alsa/asoundlib.h> +#include <pthread.h> + +static const char *device = "default"; /* FIXME */ +static snd_pcm_format_t format = SND_PCM_FORMAT_S16; +static unsigned int rate = 44100; /* FIXME */ +static unsigned int channels = 2; /* FIXME */ +static unsigned int buffer_time = 100000; /* FIXME */ +static unsigned int period_time = 1000000/60; /* 60Hz */ /* FIXME */ + +static snd_pcm_sframes_t buffer_size; +static snd_pcm_sframes_t period_size; + +static int frame_size; +static long last_callback_secs, last_callback_usecs; + +static sfx_audio_buf_t audio_buffer; +static void (*alsa_sfx_timer_callback)(void *data); +static void *alsa_sfx_timer_data; + +static snd_pcm_t *handle; + +static pthread_t thread; +static volatile byte run_thread; + +static pthread_mutex_t mutex; + +static int +xrun_recovery(snd_pcm_t *handle, int err) +{ + if (err == -EPIPE) { /* under-run */ + err = snd_pcm_prepare(handle); + if (err < 0) + fprintf(stderr, "Can't recovery from underrun, prepare failed: %s\n", snd_strerror(err)); + return 0; + } else if (err == -ESTRPIPE) { + while ((err = snd_pcm_resume(handle)) == -EAGAIN) + sleep(1); /* wait until the suspend flag is released */ + if (err < 0) { + err = snd_pcm_prepare(handle); + if (err < 0) + fprintf(stderr, "Can't recovery from suspend, prepare failed: %s\n", snd_strerror(err)); + } + return 0; + } + return err; +} + +static void * +alsa_thread(void *arg) +{ + gint16 *ptr; + int err, cptr; + guint8 *buf; + sfx_pcm_device_t *self = (sfx_pcm_device_t *) arg; + + buf = (guint8 *) malloc(period_size * frame_size); + + while (run_thread) { + ptr = (gint16 *) buf; + cptr = period_size; + + sci_gettime(&last_callback_secs, &last_callback_usecs); + + self->timer->block(); + + if (alsa_sfx_timer_callback) + alsa_sfx_timer_callback(alsa_sfx_timer_data); + + self->timer->unblock(); + + sfx_audbuf_read(&audio_buffer, buf, period_size); + + while (cptr > 0) { + err = snd_pcm_writei(handle, ptr, cptr); + if (err == -EAGAIN) + continue; + if (err < 0) { + if (xrun_recovery(handle, err) < 0) { + fprintf(stderr, "[SND:ALSA] Write error: %s\n", snd_strerror(err)); + run_thread = 0; + } + break; /* skip one period */ + } + ptr += err * channels; + cptr -= err; + } + } + + free(buf); + return NULL; +} + +static sfx_timestamp_t +pcmout_alsa_output_timestamp(sfx_pcm_device_t *self) +{ + /* Number of frames enqueued in the output device: */ + int delta = (buffer_size - period_size) / frame_size + /* Number of frames enqueued in the internal audio buffer: */ + + audio_buffer.frames_nr; + + return sfx_timestamp_add(sfx_new_timestamp(last_callback_secs, + last_callback_usecs, + rate), + delta); +} + +static int +pcmout_alsa_init(sfx_pcm_device_t *self) +{ + unsigned int rrate; + int err, dir; + snd_pcm_hw_params_t *hwparams; + snd_pcm_sw_params_t *swparams; + pthread_attr_t attr; + + snd_pcm_hw_params_alloca(&hwparams); + snd_pcm_sw_params_alloca(&swparams); + + err = snd_pcm_open(&handle, device, SND_PCM_STREAM_PLAYBACK, 0); + if (err < 0) { + sciprintf("[SND:ALSA] Playback open error: %s\n", snd_strerror(err)); + return SFX_ERROR; + } + err = snd_pcm_hw_params_any(handle, hwparams); + if (err < 0) { + sciprintf("[SND:ALSA] Broken configuration for playback: no configurations available: %s\n", snd_strerror(err)); + return SFX_ERROR; + } + err = snd_pcm_hw_params_set_access(handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED); + if (err < 0) { + sciprintf("[SND:ALSA] Access type not available for playback: %s\n", snd_strerror(err)); + return SFX_ERROR; + } + err = snd_pcm_hw_params_set_format(handle, hwparams, format); + if (err < 0) { + sciprintf("[SND:ALSA] Sample format not available for playback: %s\n", snd_strerror(err)); + return SFX_ERROR; + } + err = snd_pcm_hw_params_set_channels(handle, hwparams, channels); + if (err < 0) { + sciprintf("[SND:ALSA] Channels count (%i) not available for playback: %s\n", channels, snd_strerror(err)); + return SFX_ERROR; + } + rrate = rate; + err = snd_pcm_hw_params_set_rate_near(handle, hwparams, &rrate, 0); + if (err < 0) { + sciprintf("[SND:ALSA] Rate %iHz not available for playback: %s\n", rate, snd_strerror(err)); + return SFX_ERROR; + } + if (rrate != rate) { + sciprintf("[SND:ALSA] Rate doesn't match (requested %iHz, get %iHz)\n", rate, err); + return SFX_ERROR; + } + err = snd_pcm_hw_params_set_buffer_time_near(handle, hwparams, &buffer_time, &dir); + if (err < 0) { + sciprintf("[SND:ALSA] Unable to set buffer time %i for playback: %s\n", buffer_time, snd_strerror(err)); + return SFX_ERROR; + } + err = snd_pcm_hw_params_get_buffer_size(hwparams, (snd_pcm_uframes_t*)&buffer_size); + if (err < 0) { + sciprintf("[SND:ALSA] Unable to get buffer size for playback: %s\n", snd_strerror(err)); + return SFX_ERROR; + } + err = snd_pcm_hw_params_set_period_time_near(handle, hwparams, &period_time, &dir); + if (err < 0) { + sciprintf("[SND:ALSA] Unable to set period time %i for playback: %s\n", period_time, snd_strerror(err)); + return SFX_ERROR; + } + err = snd_pcm_hw_params_get_period_size(hwparams, (snd_pcm_uframes_t*)&period_size, &dir); + if (err < 0) { + sciprintf("[SND:ALSA] Unable to get period size for playback: %s\n", snd_strerror(err)); + return SFX_ERROR; + } + if (period_size >= buffer_size) { + sciprintf("[SND:ALSA] Period size %i matches or exceeds buffer size %i\n", period_size, buffer_size); + return SFX_ERROR; + } + err = snd_pcm_hw_params(handle, hwparams); + if (err < 0) { + sciprintf("[SND:ALSA] Unable to set hw params for playback: %s\n", snd_strerror(err)); + return SFX_ERROR; + } + err = snd_pcm_sw_params_current(handle, swparams); + if (err < 0) { + sciprintf("[SND:ALSA] Unable to determine current swparams for playback: %s\n", snd_strerror(err)); + return SFX_ERROR; + } + err = snd_pcm_sw_params_set_start_threshold(handle, swparams, buffer_size); + if (err < 0) { + sciprintf("[SND:ALSA] Unable to set start threshold mode for playback: %s\n", snd_strerror(err)); + return SFX_ERROR; + } + err = snd_pcm_sw_params_set_avail_min(handle, swparams, period_size); + if (err < 0) { + sciprintf("[SND:ALSA] Unable to set avail min for playback: %s\n", snd_strerror(err)); + return SFX_ERROR; + } + err = snd_pcm_sw_params_set_xfer_align(handle, swparams, 1); + if (err < 0) { + sciprintf("[SND:ALSA] Unable to set transfer align for playback: %s\n", snd_strerror(err)); + return SFX_ERROR; + } + err = snd_pcm_sw_params(handle, swparams); + if (err < 0) { + sciprintf("[SND:ALSA] Unable to set sw params for playback: %s\n", snd_strerror(err)); + return SFX_ERROR; + } + + self->buf_size = buffer_size; + self->conf.rate = rate; + self->conf.stereo = channels > 1; + self->conf.format = SFX_PCM_FORMAT_S16_NATIVE; + + frame_size = SFX_PCM_FRAME_SIZE(self->conf); + + sfx_audbuf_init(&audio_buffer, self->conf); + + if (pthread_mutex_init(&mutex, NULL) != 0) { + sciprintf("[SND:ALSA] Failed to create mutex\n"); + return SFX_ERROR; + } + + run_thread = 1; + if (pthread_create(&thread, NULL, alsa_thread, self) != 0) { + sciprintf("[SND:ALSA] Failed to create thread\n"); + return SFX_ERROR; + } + + return SFX_OK; +} + +static int +pcmout_alsa_output(sfx_pcm_device_t *self, byte *buf, + int count, sfx_timestamp_t *ts) +{ + if (ts) + sfx_audbuf_write_timestamp(&audio_buffer, *ts); + + sfx_audbuf_write(&audio_buffer, buf, count); + return SFX_OK; +} + +static int +pcmout_alsa_set_option(sfx_pcm_device_t *self, char *name, char *value) +{ + return SFX_ERROR; +} + +static void +pcmout_alsa_exit(sfx_pcm_device_t *self) +{ + int err; + + run_thread = 0; + sciprintf("[SND:ALSA] Waiting for PCM thread to exit... "); + if (!pthread_join(thread, NULL)) + sciprintf("OK\n"); + else + sciprintf("Failed\n"); + + pthread_mutex_destroy(&mutex); + + if ((err = snd_pcm_drop(handle)) < 0) { + sciprintf("[SND:ALSA] Can't stop PCM device: %s\n", snd_strerror(err)); + } + if ((err = snd_pcm_close(handle)) < 0) { + sciprintf("[SND:ALSA] Can't close PCM device: %s\n", snd_strerror(err)); + } + + sfx_audbuf_free(&audio_buffer); +} + +static int +timer_alsa_set_option(char *name, char *value) +{ + return SFX_ERROR; +} + + +static int +timer_alsa_init(void (*callback)(void *data), void *data) +{ + alsa_sfx_timer_callback = callback; + alsa_sfx_timer_data = data; + + return SFX_OK; +} + +static int +timer_alsa_stop(void) +{ + alsa_sfx_timer_callback = NULL; + + return SFX_OK; +} + +static int +timer_alsa_block(void) +{ + if (pthread_mutex_lock(&mutex) != 0) { + fprintf(stderr, "[SND:ALSA] Failed to lock mutex\n"); + return SFX_ERROR; + } + + return SFX_OK; +} + +static int +timer_alsa_unblock(void) +{ + if (pthread_mutex_unlock(&mutex) != 0) { + fprintf(stderr, "[SND:ALSA] Failed to unlock mutex\n"); + return SFX_ERROR; + } + + return SFX_OK; +} + +#define ALSA_PCM_VERSION "0.2" + +sfx_timer_t pcmout_alsa_timer = { + "alsa-pcm-timer", + ALSA_PCM_VERSION, + 0, + 0, + timer_alsa_set_option, + timer_alsa_init, + timer_alsa_stop, + timer_alsa_block, + timer_alsa_unblock +}; + +sfx_pcm_device_t sfx_pcm_driver_alsa = { + "alsa", + ALSA_PCM_VERSION, + pcmout_alsa_init, + pcmout_alsa_exit, + pcmout_alsa_set_option, + pcmout_alsa_output, + pcmout_alsa_output_timestamp, + {0, 0, 0}, + 0, + &pcmout_alsa_timer, + NULL +}; + +#endif /* HAVE_PTHREAD */ +#endif /* HAVE_ALSA */ diff --git a/engines/sci/sfx/pcm_device/audbuf_test.c b/engines/sci/sfx/pcm_device/audbuf_test.c new file mode 100644 index 0000000000..216aa9079e --- /dev/null +++ b/engines/sci/sfx/pcm_device/audbuf_test.c @@ -0,0 +1,190 @@ +/*************************************************************************** + audbuf_test.c Copyright (C) 2002 Christoph Reichenbach + + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public Licence as + published by the Free Software Foundaton; either version 2 of the + Licence, or (at your option) any later version. + + It is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + merchantibility or fitness for a particular purpose. See the + GNU General Public Licence for more details. + + You should have received a copy of the GNU General Public Licence + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + + Please contact the maintainer for any program-related bug reports or + inquiries. + + Current Maintainer: + + Christoph Reichenbach (CR) <jameson@linuxgames.com> + +***************************************************************************/ + +#include "audiobuf.h" +#if 0 +sfx_audio_buf_t buf; + +#define MIN_FRAMESIZE 1 +#define MAX_FRAMESIZE 8 + + +void +tester_write(unsigned char *data, int datalen, int framesize, int gran) +{ + int i; + + for (i = 0; i < datalen; i += gran) { + int size = (i + gran < datalen)? gran : datalen - i; + + sfx_audbuf_write(&buf, data + (i * framesize), framesize, size); + } +} + + +void +tester_read(unsigned char *data, int datalen, int framesize, int gran) +{ + unsigned char *readdata = malloc(datalen * framesize); + int i; + + for (i = 0; i < datalen; i += gran) { + int size = (i + gran < datalen)? gran : datalen - i; + int j; + + sfx_audbuf_read(&buf, readdata + (i * framesize), framesize, size); + for (j = 0; j < gran * framesize; j++) { + int offset = i*framesize + j; + + if (data[i] != readdata[i]) { + fprintf(stderr, "[ERROR] Mismatch at offset %08x (sample #%d): Expected %02x, got %02x\n", + offset, i, readdata[i], data[i]); + } + } + } + + free(readdata); +} + + +void +test1(unsigned char *data, int len) + /* Test the 'regular' case */ +{ + int framesize; + int stepsize; + + fprintf(stderr, "[Test-1] Commenced; len=%d.\n", len); + + for (framesize = MAX_FRAMESIZE; framesize >= MIN_FRAMESIZE; framesize >>= 1) { + fprintf(stderr, "[Test-1] Writing frame size %d\n", framesize); + for (stepsize = 1; stepsize <= len; stepsize++) + tester_write(data, len / framesize, framesize, stepsize); + } + + for (framesize = MAX_FRAMESIZE; framesize >= MIN_FRAMESIZE; framesize >>= 1) { + fprintf(stderr, "[Test-1] Reading frame size %d\n", framesize); + for (stepsize = len; stepsize >= 1; stepsize--) + tester_read(data, len / framesize, framesize, stepsize); + } + + fprintf(stderr, "[Test-1] Completed.\n"); +} + +#define TEST2_COUNT 10 +#define TEST2_LEN 3 + +void +test2(unsigned char *data, int framesize) + /* Test whether buffer underrun repeats are handled correctly */ +{ + int i; + unsigned char *src; + + fprintf(stderr, "[Test-2] Commenced; framesize=%d.\n", framesize); + + sfx_audbuf_write(&buf, data, framesize, 1); + src = malloc(framesize * TEST2_LEN); + + for (i = 0; i < TEST2_COUNT + 1; i++) { + int inst; + sfx_audbuf_read(&buf, src, framesize, TEST2_LEN); + + for (inst = 0; inst < TEST2_LEN; inst++) { + int offset = inst * framesize; + int j; + + for (j = 0; j < framesize; j++) + if (src[j + offset] != data[j]) { + fprintf(stderr, "[ERROR] At copy sample %d, frame %d, offset %d: Expected %02x, got %02x\n", + i, inst, j, data[j], src[j+offset]); + } + } + memset(src, 0xbf, framesize * TEST2_LEN); + } + + free(src); + fprintf(stderr, "[Test-1] Completed.\n"); +} + + +#define CHUNKS_NR 4 + +#define CHUNK_LEN_0 8 +#define CHUNK_LEN_1 20 +#define CHUNK_LEN_2 16 +#define CHUNK_LEN_3 40 + +unsigned char test_data_0[CHUNK_LEN_0] = + { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 }; +unsigned char test_data_1[CHUNK_LEN_1] = + { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, + 0xff, 0x00, 0xff, 0x00}; +unsigned char test_data_2[CHUNK_LEN_2] = + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; +unsigned char test_data_3[CHUNK_LEN_3] = + { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, + 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, + 0x8f, 0x8e, 0x8d, 0x8c, 0x8b, 0x8a, 0x89, 0x88 }; + +struct { + int len; + unsigned char *data; +} test_chunks[CHUNKS_NR] = { + { CHUNK_LEN_0, test_data_0 }, + { CHUNK_LEN_1, test_data_1 }, + { CHUNK_LEN_2, test_data_2 }, + { CHUNK_LEN_3, test_data_3 } +}; + +int +main(int argc, char **argv) +{ + int i; + + sfx_audbuf_init(&buf); + for (i = 0; i < CHUNKS_NR; i++) { + int k; + + /* for (k = MAX_FRAMESIZE; k >= MIN_FRAMESIZE; k >>= 1) + test2(test_chunks[i].data, k);*/ + + test1(test_chunks[i].data, test_chunks[i].len); + } + sfx_audbuf_exit(&buf); + + return 0; +} +#else +int main() {} +#endif diff --git a/engines/sci/sfx/pcm_device/audiobuf.c b/engines/sci/sfx/pcm_device/audiobuf.c new file mode 100644 index 0000000000..fa23708ce4 --- /dev/null +++ b/engines/sci/sfx/pcm_device/audiobuf.c @@ -0,0 +1,348 @@ +/*************************************************************************** + audiobuf.c Copyright (C) 2003 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> + +***************************************************************************/ + +#include "audiobuf.h" + +#define NO_BUFFER_UNDERRUN 0 +#define SAW_BUFFER_UNDERRUN 1 +#define REPORTED_BUFFER_UNDERRUN 2 + +static int +buffer_underrun_status = NO_BUFFER_UNDERRUN; + + +static sfx_audio_buf_chunk_t * +sfx_audbuf_alloc_chunk(void) +{ + sfx_audio_buf_chunk_t *ch = (sfx_audio_buf_chunk_t*)sci_malloc(sizeof(sfx_audio_buf_chunk_t)); + ch->used = 0; + ch->next = NULL; + ch->prev = NULL; + + return ch; +} + +void +sfx_audbuf_init(sfx_audio_buf_t *buf, sfx_pcm_config_t pcm_conf) +{ + int framesize = SFX_PCM_FRAME_SIZE(pcm_conf); + byte silence[16]; + int silencew = pcm_conf.format & ~SFX_PCM_FORMAT_LMASK; + + /* Determine the correct 'silence' for the channel and install it */ + /* Conservatively assume stereo */ + if (pcm_conf.format & SFX_PCM_FORMAT_16) { + if (pcm_conf.format & SFX_PCM_FORMAT_LE) { + silence[0] = silencew & 0xff; + silence[1] = (silencew >> 8) & 0xff; + } else { + silence[0] = (silencew >> 8) & 0xff; + silence[1] = silencew & 0xff; + } + memcpy(silence + 2, silence, 2); + } else { + silence[0] = silencew; + silence[1] = silencew; + } + + buf->last = buf->first = sfx_audbuf_alloc_chunk(); + buf->unused = NULL; + memcpy(buf->last_frame, silence, framesize); /* Initialise, in case we + ** underrun before the + ** first write */ + buf->read_offset = 0; + buf->framesize = framesize; + buf->read_timestamp.secs = -1; /* Mark as inactive */ + buf->frames_nr = 0; +} + +static void +sfx_audbuf_free_chain(sfx_audio_buf_chunk_t *b) +{ + while (b) { + sfx_audio_buf_chunk_t *n = b->next; + sci_free(b); + b = n; + } +} + +void +sfx_audbuf_free(sfx_audio_buf_t *buf) +{ + sfx_audbuf_free_chain(buf->first); + sfx_audbuf_free_chain(buf->unused); + buf->first = buf->last = buf->unused = NULL; + buf->read_offset = (int) 0xdeadbeef; +} + +void +sfx_audbuf_write(sfx_audio_buf_t *buf, unsigned char *src, int frames) +{ + /* In here, we compute PER BYTE */ + int data_left = buf->framesize * frames; + + if (!buf->last) { + fprintf(stderr, "FATAL: Violation of audiobuf.h usage protocol: Must use 'init' before 'write'\n"); + exit(1); + } + + if (buffer_underrun_status == SAW_BUFFER_UNDERRUN) { + /* Print here to avoid threadsafeness issues */ + sciprintf("[audiobuf] Buffer underrun\n"); + buffer_underrun_status = REPORTED_BUFFER_UNDERRUN; + } + + buf->frames_nr += frames; + + while (data_left) { + int cpsize; + int buf_free; + buf_free = SFX_AUDIO_BUF_SIZE - buf->last->used; + + + if (buf_free >= data_left) + cpsize = data_left; + else + cpsize = buf_free; + + /* Copy and advance pointers */ + memcpy(buf->last->data + buf->last->used, src, cpsize); + data_left -= cpsize; + buf->last->used += cpsize; + src += cpsize; + + if (buf->last->used == SFX_AUDIO_BUF_SIZE) { + if (!buf->last->next) { + sfx_audio_buf_chunk_t *old = buf->last; + if (buf->unused) { /* Re-use old chunks */ + sfx_audio_buf_chunk_t *buf_next_unused = buf->unused->next; + buf->unused->next = NULL; + buf->unused->used = 0; + + buf->last->next = buf->unused; + buf->unused = buf_next_unused; + } else /* Allocate */ + buf->last->next = + sfx_audbuf_alloc_chunk(); + + buf->last->prev = old; + } + + buf->last = buf->last->next; + } + } + +#ifdef TRACE_BUFFER + { + sfx_audio_buf_chunk_t *c = buf->first; + int t = buf->read_offset; + + while (c) { + fprintf(stderr, "-> ["); + for (; t < c->used; t++) + fprintf(stderr, " %02x", c->data[t]); + t = 0; + fprintf(stderr, " ] "); + c = c->next; + } + fprintf(stderr, "\n"); + } +#endif + + if (frames && (src - buf->framesize) != buf->last_frame) + /* Backup last frame, unless we're already filling from it */ + memcpy(buf->last_frame, src - buf->framesize, buf->framesize); +} + +int +sfx_audbuf_read(sfx_audio_buf_t *buf, unsigned char *dest, int frames) +{ + int written = 0; + + if (frames <= 0) + return 0; + + if (buf->read_timestamp.secs >= 0) { + /* Have a timestamp? Must update it! */ + buf->read_timestamp = + sfx_timestamp_add(buf->read_timestamp, + frames); + + } + + buf->frames_nr -= frames; + if (buf->frames_nr < 0) + buf->frames_nr = 0; + +#ifdef TRACE_BUFFER + { + sfx_audio_buf_chunk_t *c = buf->first; + int t = buf->read_offset; + + while (c) { + fprintf(stderr, "-> ["); + for (; t < c->used; t++) + fprintf(stderr, " %02x", c->data[t]); + t = 0; + fprintf(stderr, " ] "); + c = c->next; + } + fprintf(stderr, "\n"); + } +#endif + + while (frames) { + int data_needed = frames * buf->framesize; + int rdbytes = data_needed; + int rdframes; + + if (rdbytes > buf->first->used - buf->read_offset) + rdbytes = buf->first->used - buf->read_offset; + + memcpy(dest, buf->first->data + buf->read_offset, rdbytes); + + buf->read_offset += rdbytes; + dest += rdbytes; + + if (buf->read_offset == SFX_AUDIO_BUF_SIZE) { + /* Continue to next, enqueue the current chunk as + ** being unused */ + sfx_audio_buf_chunk_t *lastfirst = buf->first; + + buf->first = buf->first->next; + lastfirst->next = buf->unused; + buf->unused = lastfirst; + + buf->read_offset = 0; + } + + rdframes = (rdbytes / buf->framesize); + frames -= rdframes; + written += rdframes; + + if (frames && + (!buf->first || buf->read_offset == buf->first->used)) { + fprintf(stderr, "Underrun by %d frames at %d\n", frames, + buf->read_timestamp.frame_rate); + /* Buffer underrun! */ + if (!buffer_underrun_status == NO_BUFFER_UNDERRUN) { + buffer_underrun_status = SAW_BUFFER_UNDERRUN; + } + do { + memcpy(dest, buf->last_frame, buf->framesize); + dest += buf->framesize; + } while (--frames); + } + + } + + return written; +} + +#if 0 +static void +_sfx_audbuf_rewind_stream(sfx_audio_buf_t *buf, int delta) +{ + if (delta > buf->frames_nr) + delta = buf->frames_nr; + + + fprintf(stderr, "Rewinding %d\n", delta); + buf->frames_nr -= delta; + + /* From here on, 'delta' means the number of BYTES to remove */ + delta *= buf->framesize; + + while (delta) { + if (buf->last->used >= delta) { + fprintf(stderr, "Subtracting from %d %d\n", buf->last->used, delta); + buf->last->used -= delta; + delta = 0; + } else { + fprintf(stderr, "Must do block-unuse\n"); + delta -= buf->last->used; + buf->last->used = 0; + buf->last->next = buf->unused; + buf->unused = buf->last; + buf->last = buf->unused->prev; + buf->unused->prev = NULL; + } + } +} +#endif + +void +sfx_audbuf_write_timestamp(sfx_audio_buf_t *buf, sfx_timestamp_t ts) +{ + sfx_timestamp_t newstamp; + + newstamp = sfx_timestamp_add(ts, -buf->frames_nr); + + + if (buf->read_timestamp.secs <= 0) + /* Initial stamp */ + buf->read_timestamp = newstamp; + else { + int delta = sfx_timestamp_frame_diff(newstamp, buf->read_timestamp); + long s1,s2,s3,u1,u2,u3; + sfx_timestamp_gettime(&(buf->read_timestamp), &s1, &u1); + sfx_timestamp_gettime(&(newstamp), &s2, &u2); + sfx_timestamp_gettime(&(ts), &s3, &u3); + + if (delta < 0) { +#if 0 + /* fprintf(stderr, "[SFX-BUF] audiobuf.c: Timestamp delta %d at %d: Must rewind (not implemented yet)\n", + delta, buf->read_timestamp.frame_rate);*/ + _sfx_audbuf_rewind_stream(buf, -delta); + buf->read_timestamp = newstamp; +#endif + } else if (delta > 0) { + fprintf(stderr, "[SFX-BUF] audiobuf.c: Timestamp delta %d at %d: Filling in as silence frames\n", + delta, buf->read_timestamp.frame_rate); + /* Fill up with silence */ + while (delta--) { + sfx_audbuf_write(buf, buf->last_frame, 1); + } + buf->read_timestamp = newstamp; + } + } +} + +int +sfx_audbuf_read_timestamp(sfx_audio_buf_t *buf, sfx_timestamp_t *ts) +{ + if (buf->read_timestamp.secs > 0) { + *ts = buf->read_timestamp; + return 0; + } else { + ts->secs = -1; + ts->usecs = -1; + ts->frame_offset = -1; + ts->frame_rate = -1; + return 1; /* No timestamp */ + } +} diff --git a/engines/sci/sfx/pcm_device/audiobuf.h b/engines/sci/sfx/pcm_device/audiobuf.h new file mode 100644 index 0000000000..b2019aec17 --- /dev/null +++ b/engines/sci/sfx/pcm_device/audiobuf.h @@ -0,0 +1,140 @@ +/*************************************************************************** + audiobuf.h Copyright (C) 2003 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> + +***************************************************************************/ + +/* Auxiliary audio buffer for PCM devices +** Polled PCM devices must store data written to them until it is explicitly +** requiested. This is facilitated by the structures and functions defined +** here. +** This is generic for all PCM devices; it implies no specific requirements. +** +** Usage: Introduce an sfx_audio_buf_t into your state and make sure to use +** each of the functions provided here at least once in the appropriate +** places. +*/ + + +#ifndef _AUDIOBUF_H_ +#define _AUDIOBUF_H_ + +#include <resource.h> +#include <sfx_time.h> +#include <sci_memory.h> +#include <sfx_pcm.h> + + +#define SFX_AUDIO_BUF_SIZE 8192 /* Must be multiple of framesize */ +#define SFX_AUDIO_MAX_FRAME 8 /* Max. individual frame size */ + +typedef struct _sfx_audio_buf_chunk { + unsigned char data[SFX_AUDIO_BUF_SIZE]; + int used; + struct _sfx_audio_buf_chunk *prev; + struct _sfx_audio_buf_chunk *next; +} sfx_audio_buf_chunk_t; + +typedef struct { + int read_offset; + sfx_audio_buf_chunk_t *first; /* Next to read-- can be = last */ + sfx_audio_buf_chunk_t *last; /* Next to write-- can be = first */ + sfx_audio_buf_chunk_t *unused; /* Unused chunk list, can be NULL */ + unsigned char last_frame[SFX_AUDIO_MAX_FRAME]; + /* Contains the last frame successfully read; used for buffer + ** underruns to avoid crack before silance */ + sfx_timestamp_t read_timestamp; /* Timestamp for reading */ + int frames_nr; /* Total number of frames currently in between reading and writing */ + int framesize; +} sfx_audio_buf_t; + + +void +sfx_audbuf_init(sfx_audio_buf_t *buf, sfx_pcm_config_t conf); +/* Initialises an audio buffer +** Parameters: (sfx_audio_buf_t *) buf: The buffer to initialise +** (sfx_pcm_config_t) conf: The configuration for which the buffer should +** be set up +** Modifies : (sfx_audio_buf_t) *buf +*/ + +void +sfx_audbuf_free(sfx_audio_buf_t *buf); +/* Frees all memory associated with an audio buffer +** Parameters: (sfx_audio_buf_t *) buf: The buffer whose associated memory +** should be freed +** Modifies : (sfx_audio_buf_t) *buf +*/ + +void +sfx_audbuf_write(sfx_audio_buf_t *buf, unsigned char *src, int frames); +/* Store data in an audion buffer +** Parameters: (sfx_audio_buf_t *) buf: The buffer to write to +** (unsigned char *) src: Pointer to the data that should be +** written +** (int) frames: Number of frames to write +** Modifies : (sfx_audio_buf_t) *buf +*/ + + +void +sfx_audbuf_write_timestamp(sfx_audio_buf_t *buf, sfx_timestamp_t ts); +/* Sets the most recently written timestamp for the buffer +** Parameters: (sfx_audio_buf_t *) buf: The buffer to operate on +** (sfx_timestamp_t) ts: The timestamp to set +** If a timestamp is already set, 'ts' is checked for consistency and +** 'silent' frames are introduced as padding for future writes. +*/ + + +int +sfx_audbuf_read_timestamp(sfx_audio_buf_t *buf, sfx_timestamp_t *ts); +/* Reads the timestamp describing the time right before the next frame being read +** Parameters: (sfx_audio_buf_t *) buf: The buffer to read from +** Returns : (sfx_timestamp_t) *ts: The requested timestamp, or nothing +** (int) zero on success, nonzero if no timestamp is known +*/ + + +int +sfx_audbuf_read(sfx_audio_buf_t *buf, unsigned char *dest, int frames); +/* Read data from audio buffer +** Parameters: (sfx_audio_buf_t *) buf: The buffer to write to +** (unsigned char *) dest: Pointer to the place the read data +** should be written to +** (int) frames: Number of frames to write +** Returns : (int) Number of frames actually read +** Affects : (sfx_audio_buf_t) *buf +** (unsigned char ) *dest +** global error stream +** If the returned number of frames is smaller than the number of frames +** requested to be written, this function will issue a buffer underrun +** warning and fill up the remaining space with the last frame it en-- +** countered, or a block of '0' if no such frame is known. +*/ + + + + +#endif /* !_AUDIOBUF_H_ */ diff --git a/engines/sci/sfx/pcm_device/pcm_devices.c b/engines/sci/sfx/pcm_device/pcm_devices.c new file mode 100644 index 0000000000..913b59fb08 --- /dev/null +++ b/engines/sci/sfx/pcm_device/pcm_devices.c @@ -0,0 +1,73 @@ +/*************************************************************************** + pcmout.c Copyright (C) 2002 Solomon Peachy + + 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. + +***************************************************************************/ + +#include <sfx_pcm.h> +#include <resource.h> + +#ifndef NO_PCMOUT +#ifdef SCUMMVM +extern sfx_pcm_device_t sfx_pcm_driver_scummvm; +#else // SCUMMVM +# ifdef HAVE_SDL +extern sfx_pcm_device_t sfx_pcm_driver_sdl; +# endif +# ifdef HAVE_ALSA +extern sfx_pcm_device_t sfx_pcm_driver_alsa; +# endif +# ifdef _DREAMCAST +extern sfx_pcm_device_t sfx_pcm_driver_dc; +# endif +#endif // SCUMMVM +#endif + +sfx_pcm_device_t *pcmout_drivers[] = { +#ifndef NO_PCMOUT +#ifdef SCUMMVM + &sfx_pcm_driver_scummvm, +#else // SCUMMVM +# ifdef HAVE_SDL + &sfx_pcm_driver_sdl, +# endif +# ifdef HAVE_ALSA + &sfx_pcm_driver_alsa, +# endif +# ifdef _DREAMCAST + &sfx_pcm_driver_dc, +# endif +#endif // SCUMMVM +#endif + NULL +}; + +sfx_pcm_device_t * +sfx_pcm_find_device(char *name) +{ + int retval = 0; + + if (!name) { /* Find default driver */ + return pcmout_drivers[0]; + } + + while (pcmout_drivers[retval] && + strcasecmp(name, pcmout_drivers[retval]->name)) + retval++; + + return pcmout_drivers[retval]; +} + diff --git a/engines/sci/sfx/pcm_device/scummvm.cpp b/engines/sci/sfx/pcm_device/scummvm.cpp new file mode 100644 index 0000000000..1b64dd40d3 --- /dev/null +++ b/engines/sci/sfx/pcm_device/scummvm.cpp @@ -0,0 +1,60 @@ +#include "sfx_time.h" +#include "sfx_pcm.h" +#include "engines/engine.h" +#include "sound/audiostream.h" +#include "sound/mixer.h" + + +static int pcmout_scummvm_framesize; +static Audio::AppendableAudioStream * pcmout_scummvm_audiostream; +static Audio::SoundHandle pcmout_scummvm_sound_handle; + + +static int pcmout_scummvm_init(sfx_pcm_device_t *self) { + int pcmout_scummvm_audiostream_flags = Audio::Mixer::FLAG_16BITS | Audio::Mixer::FLAG_STEREO; + +#ifdef SCUMM_LITTLE_ENDIAN + pcmout_scummvm_audiostream_flags |= Audio::Mixer::FLAG_LITTLE_ENDIAN; +#endif + + self->buf_size = 2048 << 1; + self->conf.rate = g_engine->_mixer->getOutputRate(); + self->conf.stereo = SFX_PCM_STEREO_LR; + self->conf.format = SFX_PCM_FORMAT_S16_NATIVE; + pcmout_scummvm_framesize = SFX_PCM_FRAME_SIZE(self->conf); + + pcmout_scummvm_audiostream = Audio::makeAppendableAudioStream(self->conf.rate, pcmout_scummvm_audiostream_flags); + ::g_engine->_mixer->playInputStream(Audio::Mixer::kSFXSoundType, &pcmout_scummvm_sound_handle, pcmout_scummvm_audiostream); + + return SFX_OK; +} + +static void pcmout_scummvm_exit(sfx_pcm_device_t *self) { +} + +static int pcmout_scummvm_output(sfx_pcm_device_t *self, byte *buf, int count, + sfx_timestamp_t *timestamp) { + + byte *__buf = new byte[count * pcmout_scummvm_framesize]; + + memcpy(__buf, buf, count * pcmout_scummvm_framesize); + + pcmout_scummvm_audiostream->queueBuffer(__buf, count * pcmout_scummvm_framesize); + + return SFX_OK; +} + + +sfx_pcm_device_t sfx_pcm_driver_scummvm = { + "ScummVM", + "0.1", + &pcmout_scummvm_init, + &pcmout_scummvm_exit, + NULL, + &pcmout_scummvm_output, + NULL, + {0, 0, 0}, + 0, + NULL, + NULL + }; diff --git a/engines/sci/sfx/pcm_device/sdl.c b/engines/sci/sfx/pcm_device/sdl.c new file mode 100644 index 0000000000..bd125835cb --- /dev/null +++ b/engines/sci/sfx/pcm_device/sdl.c @@ -0,0 +1,272 @@ +/*************************************************************************** + sdl.c Copyright (C) 2002 Solomon Peachy, Claudio Matsuoka, + 2003,04 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. + +***************************************************************************/ + +#include <sfx_pcm.h> +#include "audiobuf.h" + +#ifdef HAVE_SDL + +#if !defined(_MSC_VER) +# include <sys/time.h> +#endif + +#include <SDL_config.h> +#undef HAVE_ICONV +#undef HAVE_ICONV_H +#undef HAVE_ALLOCA_H + +#include <SDL.h> + +#define DELTA_TIME_LIMIT 10000 /* Report errors above this delta time */ + +static sfx_audio_buf_t audio_buffer; +static void (*sdl_sfx_timer_callback)(void *data); +static void *sdl_sfx_timer_data; +static int frame_size; +static int buf_size; +static int rate; +static long last_callback_secs, last_callback_usecs; +static int last_callback_len; + +static sfx_timestamp_t +pcmout_sdl_output_timestamp(sfx_pcm_device_t *self) +{ + /* Number of frames enqueued in the output device: */ + int delta = (buf_size - last_callback_len) / frame_size + /* Number of frames enqueued in the internal audio buffer: */ + + audio_buffer.frames_nr; + + return sfx_timestamp_add(sfx_new_timestamp(last_callback_secs, + last_callback_usecs, + rate), + delta); +} + +FILE *fil = NULL; + +static void +timer_sdl_internal_callback(void *userdata, byte *dest, int len) +{ + sci_gettime(&last_callback_secs, &last_callback_usecs); + last_callback_len = len; + + if (sdl_sfx_timer_callback) + sdl_sfx_timer_callback(sdl_sfx_timer_data); + +#if 0 + if (!sfx_audbuf_read_timestamp(&audio_buffer, &ts)) { + int delta = (buf_size - len) / frame_size; + sfx_timestamp_t real_ts; + long deltatime; + long sec2, usec2; + sci_gettime(&sec2, &usec2); + + real_ts = sfx_timestamp_add(sfx_new_timestamp(sec, usec, rate), delta); + + deltatime = sfx_timestamp_usecs_diff(ts, real_ts); + + fprintf(stderr, "[SDL] Frames requested: %d Playing %ld too late. Needed %ldus for computations.\n", + len / frame_size, deltatime, + (usec2-usec) + (sec2-sec)*1000000); + + if (abs(deltatime) > DELTA_TIME_LIMIT) + sciprintf("[SND:SDL] Very high delta time for PCM playback: %ld too late (%d frames in the future)\n", + deltatime, sfx_timestamp_frame_diff(sfx_new_timestamp(sec, usec, rate), ts)); + +#if 0 + if (deltatime < 0) { + /* Read and discard frames, explicitly underrunning */ + int max_read = len / frame_size; + int frames_to_kill = sfx_timestamp_frame_diff(real_ts, ts); + + while (frames_to_kill) { + int d = frames_to_kill > max_read? max_read : frames_to_kill; + sfx_audbuf_read(&audio_buffer, dest, d); + frames_to_kill -= d; + } + } +#endif + } +#endif + sfx_audbuf_read(&audio_buffer, dest, len / frame_size); + +#if 0 + if (!fil) { + fil = fopen("/tmp/sdl.log", "w"); + } + { + int i; + int end = len / frame_size; + gint16 *d = dest; + fprintf(fil, "Writing %d/%d\n", len, frame_size); + for (i = 0; i < end; i++) { + fprintf(fil, "\t%d\t%d\n", d[0], d[1]); + d += 2; + } + } +#endif +} + + +static int +pcmout_sdl_init(sfx_pcm_device_t *self) +{ + SDL_AudioSpec a; + + if (SDL_Init(SDL_INIT_AUDIO|SDL_INIT_NOPARACHUTE) != 0) { + fprintf (stderr, "[SND:SDL] Error while initialising: %s\n", SDL_GetError()); + return -1; + } + + a.freq = 44100; /* FIXME */ +#ifdef WORDS_BIGENDIAN + a.format = AUDIO_S16MSB; /* FIXME */ +#else + a.format = AUDIO_S16LSB; /* FIXME */ +#endif + a.channels = 2; /* FIXME */ + a.samples = 2048; /* FIXME */ + a.callback = timer_sdl_internal_callback; + a.userdata = NULL; + + if (SDL_OpenAudio (&a, NULL) < 0) { + fprintf (stderr, "[SND:SDL] Error while opening audio: %s\n", SDL_GetError()); + return SFX_ERROR; + } + + buf_size = a.samples; + rate = a.freq; + + self->buf_size = a.samples << 1; /* Looks like they're using double size */ + self->conf.rate = a.freq; + self->conf.stereo = a.channels > 1; + self->conf.format = SFX_PCM_FORMAT_S16_NATIVE; + + frame_size = SFX_PCM_FRAME_SIZE(self->conf); + + sfx_audbuf_init(&audio_buffer, self->conf); + SDL_PauseAudio (0); + + return SFX_OK; +} + +int +pcmout_sdl_output(sfx_pcm_device_t *self, byte *buf, + int count, sfx_timestamp_t *ts) +{ + if (ts) + sfx_audbuf_write_timestamp(&audio_buffer, *ts); + sfx_audbuf_write(&audio_buffer, buf, count); + return SFX_OK; +} + + +static int +pcmout_sdl_set_option(sfx_pcm_device_t *self, char *name, char *value) +{ + return SFX_ERROR; /* Option not supported */ +} + +static void +pcmout_sdl_exit(sfx_pcm_device_t *self) +{ + SDL_PauseAudio (1); + SDL_CloseAudio(); + sfx_audbuf_free(&audio_buffer); + + SDL_QuitSubSystem(SDL_INIT_AUDIO); + + if (!SDL_WasInit(SDL_INIT_EVERYTHING)) { + sciprintf("[SND:SDL] No active SDL subsystems found.. shutting down SDL\n"); + SDL_Quit(); + } +} + + +static int +timer_sdl_set_option(char *name, char *value) +{ + return SFX_ERROR; +} + + +static int +timer_sdl_init(void (*callback)(void *data), void *data) +{ + sdl_sfx_timer_callback = callback; + sdl_sfx_timer_data = data; + + + return SFX_OK; +} + +static int +timer_sdl_stop(void) +{ + sdl_sfx_timer_callback = NULL; + + return SFX_OK; +} + +static int +timer_sdl_block(void) +{ + SDL_LockAudio(); + + return SFX_OK; +} + +static int +timer_sdl_unblock(void) +{ + SDL_UnlockAudio(); + + return SFX_OK; +} + +#define SDL_PCM_VERSION "0.1" + +sfx_timer_t pcmout_sdl_timer = { + "sdl-pcm-timer", + SDL_PCM_VERSION, + 0, + 0, + timer_sdl_set_option, + timer_sdl_init, + timer_sdl_stop, + timer_sdl_block, + timer_sdl_unblock +}; + +sfx_pcm_device_t sfx_pcm_driver_sdl = { + "sdl", + SDL_PCM_VERSION, + pcmout_sdl_init, + pcmout_sdl_exit, + pcmout_sdl_set_option, + pcmout_sdl_output, + pcmout_sdl_output_timestamp, + {0, 0, 0}, + 0, + &pcmout_sdl_timer, + NULL +}; + +#endif diff --git a/engines/sci/sfx/player/Makefile.am b/engines/sci/sfx/player/Makefile.am new file mode 100644 index 0000000000..34ae8e8427 --- /dev/null +++ b/engines/sci/sfx/player/Makefile.am @@ -0,0 +1,3 @@ +noinst_LIBRARIES = libsciplayer.a +INCLUDES = -I$(top_srcdir)/src/include @EXTRA_INCLUDES@ +libsciplayer_a_SOURCES = players.c realtime.c polled.c diff --git a/engines/sci/sfx/player/players.c b/engines/sci/sfx/player/players.c new file mode 100644 index 0000000000..d6d7cbaf17 --- /dev/null +++ b/engines/sci/sfx/player/players.c @@ -0,0 +1,54 @@ +/*************************************************************************** + players.c Copyright (C) 2002..04 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> + +***************************************************************************/ + +#include <sfx_player.h> + +extern sfx_player_t sfx_player_realtime; +extern sfx_player_t sfx_player_polled; + +sfx_player_t *sfx_players[] = { + &sfx_player_polled, + &sfx_player_realtime, + NULL +}; + +sfx_player_t * +sfx_find_player(char *name) +{ + if (!name) { + /* Implement platform policy here */ + + return sfx_players[0]; + } else { + int n = 0; + while (sfx_players[n] && + strcasecmp(sfx_players[n]->name, name)) + ++n; + + return sfx_players[n]; + } +} diff --git a/engines/sci/sfx/player/polled.c b/engines/sci/sfx/player/polled.c new file mode 100644 index 0000000000..59eeceaac2 --- /dev/null +++ b/engines/sci/sfx/player/polled.c @@ -0,0 +1,335 @@ +/*************************************************************************** + polled.c Copyright (C) 2004 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> + +***************************************************************************/ +/* Polled player, mostly for PCM-based thingies (which _can_ poll, after all) */ + +#include <sfx_player.h> +#include "../softseq.h" +#include "../mixer.h" + +static song_iterator_t *play_it; +static int play_paused = 0; +static sfx_softseq_t *seq; +static int volume = 100; +static sfx_timestamp_t new_timestamp; +static int new_song = 0; + +/* The time counter is used to determine how close to the end of a tick we are. +** For each frame played, it is decreased by 60. */ +#define TIME_INC 60 +static int time_counter = 0; + +static void +pp_tell_synth(int buf_nr, byte *buf) +{ + seq->handle_command(seq, buf[0], buf_nr-1, buf+1); +} + + +/*----------------------*/ +/* Mixer implementation */ +/*----------------------*/ +int +ppf_poll(sfx_pcm_feed_t *self, byte *dest, int size) +{ + int written = 0; + byte buf[4]; + int buf_nr; + + if (!play_it) + return 0; + + if (play_paused) + return 0; + + while (written < size) { + int can_play; + int do_play; + + while (time_counter <= TIME_INC) { + int next_stat = songit_next(&play_it, + &(buf[0]), &buf_nr, + IT_READER_MASK_ALL + | IT_READER_MAY_FREE + | IT_READER_MAY_CLEAN); + + switch (next_stat) { + case SI_PCM: + sfx_play_iterator_pcm(play_it, 0); + break; + + case SI_FINISHED: + songit_free(play_it); + play_it = NULL; + return written; /* We're done... */ + + case SI_IGNORE: + case SI_LOOP: + case SI_RELATIVE_CUE: + case SI_ABSOLUTE_CUE: + break; /* Boooring... .*/ + + case 0: /* MIDI command */ + + seq->handle_command(seq, buf[0], buf_nr - 1, buf+1); + break; + + default: + time_counter += next_stat * seq->pcm_conf.rate; + } + } + + can_play = time_counter / TIME_INC; + do_play = (can_play > (size - written))? (size - written) : can_play; + + time_counter -= do_play * TIME_INC; + + seq->poll(seq, dest + written * self->frame_size, do_play); + written += do_play; + } + + return size; /* Apparently, we wrote all that was requested */ +} + +void +ppf_destroy(sfx_pcm_feed_t *self) +{ + /* no-op */ +} + +int +ppf_get_timestamp(sfx_pcm_feed_t *self, sfx_timestamp_t *timestamp) +{ + if (!new_song) + return PCM_FEED_IDLE; + + /* Otherwise, we have a timestamp: */ + + *timestamp = new_timestamp; + new_song = 0; + return PCM_FEED_TIMESTAMP; +} + +extern sfx_player_t sfx_player_polled; +static +sfx_pcm_feed_t pcmfeed = { + ppf_poll, + ppf_destroy, + ppf_get_timestamp, + NULL, + {0, 0, 0}, + "polled-player-feed", + 0, + 0 /* filled in by the mixer */ +}; + +/*=======================*/ +/* Player implementation */ +/*=======================*/ + + +/*--------------------*/ +/* API implementation */ +/*--------------------*/ + +static void +pp_timer_callback(void) +{ + /* Hey, we're polled anyway ;-) */ +} + +static int +pp_set_option(char *name, char *value) +{ + return SFX_ERROR; +} + +static int +pp_init(resource_mgr_t *resmgr, int expected_latency) +{ + resource_t *res = NULL, *res2 = NULL; + int fd; + + if (!mixer) + return SFX_ERROR; + + /* FIXME Temporary hack to detect Amiga games. */ + fd = sci_open("bank.001", O_RDONLY); + + if (fd == SCI_INVALID_FD) + seq = sfx_find_softseq(NULL); + else { + close(fd); + seq = sfx_find_softseq("amiga"); + } + + if (!seq) { + sciprintf("[sfx:seq:polled] Initialisation failed: Could not find software sequencer\n"); + return SFX_ERROR; + } + + if (seq->patch_nr != SFX_SEQ_PATCHFILE_NONE) { + res = scir_find_resource(resmgr, sci_patch, seq->patch_nr, 0); + } + + if (seq->patch2_nr != SFX_SEQ_PATCHFILE_NONE) { + res2 = scir_find_resource(resmgr, sci_patch, seq->patch2_nr, 0); + } + + if (seq->init(seq, + (res)? res->data : NULL, + (res)? res->size : 0, + (res2)? res2->data : NULL, + (res2)? res2->size : 0)) { + sciprintf("[sfx:seq:polled] Initialisation failed: Sequencer '%s', v%s failed to initialise\n", + seq->name, seq->version); + return SFX_ERROR; + } + + pcmfeed.conf = seq->pcm_conf; + + seq->set_volume(seq, volume); + mixer->subscribe(mixer, &pcmfeed); + + sfx_player_polled.polyphony = seq->polyphony; + return SFX_OK; +} + +static int +pp_add_iterator(song_iterator_t *it, GTimeVal start_time) +{ + song_iterator_t *old = play_it; + + SIMSG_SEND(it, SIMSG_SET_PLAYMASK(seq->playmask)); + SIMSG_SEND(it, SIMSG_SET_RHYTHM(seq->play_rhythm)); + + if (play_it == NULL) + seq->allstop(seq); + + play_it = sfx_iterator_combine(play_it, it); + + seq->set_volume(seq, volume); + + /* The check must happen HERE, and not at the beginning of the + function, to avoid a race condition with the mixer. */ + if (old == NULL) { + new_timestamp = sfx_new_timestamp(start_time.tv_sec, + start_time.tv_usec, + seq->pcm_conf.rate); + /* ASAP otherwise */ + time_counter = 0; + new_song = 1; + } + + return SFX_OK; +} + +static int +pp_fade_out(void) +{ + fprintf(stderr, __FILE__": Attempt to fade out- not implemented yet\n"); + return SFX_ERROR; +} + +static int +pp_stop(void) +{ + song_iterator_t *it = play_it; + + play_it = NULL; +fprintf(stderr, "[play] Now stopping it %p\n", (void *)it); + if (it) + songit_free(it); + + seq->allstop(seq); + + return SFX_OK; +} + +static int +pp_send_iterator_message(song_iterator_message_t msg) +{ + if (!play_it) + return SFX_ERROR; + + songit_handle_message(&play_it, msg); + return SFX_OK; +} + +static int +pp_pause(void) +{ + play_paused = 1; + seq->set_volume(seq, 0); + + return SFX_OK; +} + +static int +pp_resume(void) +{ + if (!play_it) + { + play_paused = 0; + return SFX_OK; /* Nothing to resume */ + } + + if (play_paused) + new_song = 1; /* Fake starting a new song, re-using the old + ** time stamp (now long in the past) to indicate + ** resuming ASAP */ + + play_paused = 0; + seq->set_volume(seq, volume); + return SFX_OK; +} + +static int +pp_exit(void) +{ + seq->exit(seq); + songit_free(play_it); + play_it = NULL; + + return SFX_OK; +} + +sfx_player_t sfx_player_polled = { + "polled", + "0.1", + &pp_set_option, + &pp_init, + &pp_add_iterator, + &pp_fade_out, + &pp_stop, + &pp_send_iterator_message, + &pp_pause, + &pp_resume, + &pp_exit, + &pp_timer_callback, + &pp_tell_synth, + 0 /* polyphony */ +}; diff --git a/engines/sci/sfx/player/realtime.c b/engines/sci/sfx/player/realtime.c new file mode 100644 index 0000000000..752b0b816b --- /dev/null +++ b/engines/sci/sfx/player/realtime.c @@ -0,0 +1,328 @@ +/*************************************************************************** + realtime.c Copyright (C) 2002..04 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> + +***************************************************************************/ +/* OK... 'realtime' may be a little too euphemistic, as this one just +** prays for some reasonable amount of soft real-time, but it's close +** enough, I guess. */ + +#include <sfx_player.h> +#include "../sequencer.h" + +static sfx_sequencer_t *seq; + +extern sfx_player_t sfx_player_realtime; + +/* Playing mechanism */ + +static inline GTimeVal +current_time(void) +{ + GTimeVal tv; + sci_get_current_time(&tv); + return tv; +} + +static inline GTimeVal +add_time_delta(GTimeVal tv, long delta) +{ + int sec_d; + + tv.tv_usec += delta; + sec_d = tv.tv_usec / 1000000; + tv.tv_usec -= sec_d * 1000000; + + tv.tv_sec += sec_d; + + return tv; +} + +static inline long +delta_time(GTimeVal comp_tv, GTimeVal base) +{ + GTimeVal tv; + sci_get_current_time(&tv); + return (comp_tv.tv_sec - tv.tv_sec) * 1000000 + + (comp_tv.tv_usec - tv.tv_usec); +} + +static song_iterator_t *play_it = NULL; +static GTimeVal play_last_time; +static GTimeVal play_pause_started; /* Beginning of the last pause */ +static GTimeVal play_pause_counter; /* Last point in time to mark a + ** play position augmentation */ +static int play_paused = 0; +static int play_it_done = 0; +static int play_writeahead = 0; +static int play_moredelay = 0; + +static void +play_song(song_iterator_t *it, GTimeVal *wakeup_time, int writeahead_time) +{ + unsigned char buf[8]; + int result; + + if (play_paused) { + GTimeVal ct; + sci_get_current_time(&ct); + + *wakeup_time = + add_time_delta(*wakeup_time, delta_time(play_pause_counter, ct)); + play_pause_counter = ct; + } else + /* Not paused: */ + while (play_it && delta_time(*wakeup_time, current_time()) + < writeahead_time) { + int delay; + + switch ((delay = songit_next(&(play_it), + &(buf[0]), &result, + IT_READER_MASK_ALL + | IT_READER_MAY_FREE + | IT_READER_MAY_CLEAN))) { + + case SI_FINISHED: + play_it_done = 1; + return; + + case SI_IGNORE: + case SI_LOOP: + case SI_RELATIVE_CUE: + case SI_ABSOLUTE_CUE: + break; + + case SI_PCM: + sfx_play_iterator_pcm(play_it, 0); + break; + + case 0: + seq->event(buf[0], result-1, buf+1); + + break; + + default: + play_moredelay = delay - 1; + *wakeup_time = song_next_wakeup_time(wakeup_time, delay); + if (seq->delay) + seq->delay(delay); + } + } +} + +static void +rt_tell_synth(int buf_nr, byte *buf) +{ + seq->event(buf[0], buf_nr-1, buf+1); +} + +static void +rt_timer_callback(void) +{ + if (play_it && !play_it_done) { + if (!play_moredelay) { + long delta = delta_time(play_last_time, current_time()); + + if (delta < 0) { + play_writeahead -= (int)((double)delta * 1.2); /* Adjust upwards */ + } else if (delta > 15000) { + play_writeahead -= 2500; /* Adjust downwards */ + } + } else --play_moredelay; + + if (play_writeahead < seq->min_write_ahead_ms) + play_writeahead = seq->min_write_ahead_ms; + + play_song(play_it, &play_last_time, play_writeahead); + } +} + +static resource_t * +find_patch(resource_mgr_t *resmgr, const char *seq, int patchfile) +{ + resource_t *res = NULL; + + if (patchfile != SFX_SEQ_PATCHFILE_NONE) { + res = scir_find_resource(resmgr, sci_patch, patchfile, 0); + if (!res) { + fprintf(stderr, "[SFX] " __FILE__": patch.%03d requested by sequencer (%s), but not found\n", + patchfile, seq); + } + } + + return res; +} + +/* API implementation */ + +static int +rt_set_option(char *name, char *value) +{ + return SFX_ERROR; +} + +static int +rt_init(resource_mgr_t *resmgr, int expected_latency) +{ + resource_t *res = NULL, *res2 = NULL; + void *seq_dev = NULL; + GTimeVal foo = {0,0}; + + seq = sfx_find_sequencer(NULL); + + if (!seq) { + fprintf(stderr, "[SFX] " __FILE__": Could not find sequencer\n"); + return SFX_ERROR; + } + + sfx_player_realtime.polyphony = seq->polyphony; + + res = find_patch(resmgr, seq->name, seq->patchfile); + res2 = find_patch(resmgr, seq->name, seq->patchfile2); + + if (seq->device) + seq_dev = sfx_find_device(seq->device, NULL); + + if (seq->open(res? res->size : 0, + res? res->data : NULL, + res2? res2->size : 0, + res2? res2->data : NULL, + seq_dev)) { + fprintf(stderr, "[SFX] " __FILE__": Sequencer failed to initialize\n"); + return SFX_ERROR; + } + + play_writeahead = expected_latency; + if (play_writeahead < seq->min_write_ahead_ms) + play_writeahead = seq->min_write_ahead_ms; + + play_writeahead *= 1000; /* microseconds */ + + if (seq->reset_timer) + seq->reset_timer(foo); + + return SFX_OK; +} + +static int +rt_add_iterator(song_iterator_t *it, GTimeVal start_time) +{ + if (seq->reset_timer) /* Restart timer counting if possible */ + seq->reset_timer(start_time); + + SIMSG_SEND(it, SIMSG_SET_PLAYMASK(seq->playmask)); + SIMSG_SEND(it, SIMSG_SET_RHYTHM(seq->play_rhythm)); + + play_last_time = start_time; + play_it = sfx_iterator_combine(play_it, it); + play_it_done = 0; + play_moredelay = 0; + + return SFX_OK; +} + +static int +rt_fade_out(void) +{ + fprintf(stderr, __FILE__": Attempt to fade out- not implemented yet\n"); + return SFX_ERROR; +} + +static int +rt_stop(void) +{ + song_iterator_t *it = play_it; + + play_it = NULL; + + if (it) + songit_free(it); + if (seq && seq->allstop) + seq->allstop(); + + return SFX_OK; +} + +static int +rt_send_iterator_message(song_iterator_message_t msg) +{ + if (!play_it) + return SFX_ERROR; + + songit_handle_message(&play_it, msg); + return SFX_OK; +} + +static int +rt_pause(void) +{ + sci_get_current_time(&play_pause_started); + /* Also, indicate that we haven't modified the time counter + ** yet */ + play_pause_counter = play_pause_started; + + play_paused = 1; + if (!seq->allstop) { + sciprintf("[SFX] Cannot suspend sequencer, sound will continue for a bit\n"); + return SFX_OK; + } else + return seq->allstop(); +} + +static int +rt_resume(void) +{ + play_paused = 0; + return SFX_OK; +} + +static int +rt_exit(void) +{ + int retval = SFX_OK; + + if(seq->close()) { + fprintf(stderr, "[SFX] Sequencer reported error on close\n"); + retval = SFX_ERROR; + } + + return retval; +} + +sfx_player_t sfx_player_realtime = { + "realtime", + "0.1", + &rt_set_option, + &rt_init, + &rt_add_iterator, + &rt_fade_out, + &rt_stop, + &rt_send_iterator_message, + &rt_pause, + &rt_resume, + &rt_exit, + &rt_timer_callback, + &rt_tell_synth, + 0 /* polyphony */ +}; diff --git a/engines/sci/sfx/seq/Makefile.am b/engines/sci/sfx/seq/Makefile.am new file mode 100644 index 0000000000..74fb4fda3f --- /dev/null +++ b/engines/sci/sfx/seq/Makefile.am @@ -0,0 +1,5 @@ +noinst_LIBRARIES = libsciseq.a +INCLUDES = -I$(top_srcdir)/src/include @EXTRA_INCLUDES@ +EXTRA_DIST = instrument-map.h +libsciseq_a_SOURCES = sequencers.c oss-adlib.c mt32.c gm.c instrument-map.c \ + map-mt32-to-gm.c diff --git a/engines/sci/sfx/seq/gm.c b/engines/sci/sfx/seq/gm.c new file mode 100644 index 0000000000..4889e76ea8 --- /dev/null +++ b/engines/sci/sfx/seq/gm.c @@ -0,0 +1,185 @@ +/*************************************************************************** + Copyright (C) 2008 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 merchantability, + 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) <creichen@gmail.com> + +***************************************************************************/ + +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <stdio.h> +#ifdef HAVE_UNISTD_H +# include <unistd.h> +#endif +#include "../sequencer.h" +#include "../device.h" +#include "instrument-map.h" +#include <resource.h> + +static midi_writer_t *writer = NULL; + +static int +midi_gm_open(int patch_len, byte *data, int patch2_len, byte *data2, void *device) +{ + sfx_instrument_map_t *instrument_map = sfx_instrument_map_load_sci(data, patch_len); + + if (!instrument_map) { + fprintf(stderr, "[GM] No GM instrument map found, trying MT-32 instrument map..\n"); + instrument_map = sfx_instrument_map_mt32_to_gm(data2, patch2_len); + } + + writer = sfx_mapped_writer((midi_writer_t *) device, instrument_map); + + if (!writer) + return SFX_ERROR; + + if (writer->reset_timer) + writer->reset_timer(writer); + + return SFX_OK; +} + +static int +midi_gm_close(void) +{ + return SFX_OK; +} + +static int +midi_gm_event(byte command, int argc, byte *argv) +{ + byte data[4]; + + assert (argc < 4); + data[0] = command; + memcpy(data + 1, argv, argc); + + writer->write(writer, data, argc + 1); + + return SFX_OK; +} + +static int +midi_gm_delay(int ticks) +{ + writer->delay(writer, ticks); + + return SFX_OK; +} + +static int +midi_gm_reset_timer(GTimeVal ts) +{ + writer->reset_timer(writer); + + return SFX_OK; +} + +#define MIDI_MASTER_VOLUME_LEN 8 + +static int +midi_gm_volume(guint8 volume) +{ + byte data[MIDI_MASTER_VOLUME_LEN] = { + 0xf0, + 0x7f, + 0x7f, + 0x04, + 0x01, + volume, + volume, + 0xf7}; + + writer->write(writer, data, MIDI_MASTER_VOLUME_LEN); + if (writer->flush) + writer->flush(writer); + + return SFX_OK; +} + +static int +midi_gm_allstop(void) +{ + byte data[3] = { 0xb0, + 0x78, /* all sound off */ + 0 }; + int i; + + /* All sound off on all channels */ + for (i = 0; i < 16; i++) { + data[0] = 0xb0 | i; + writer->write(writer, data, 3); + } + if (writer->flush) + writer->flush(writer); + + return SFX_OK; +} + +static int +midi_gm_reverb(int reverb) +{ + byte data[3] = { 0xb0, + 91, /* set reverb */ + reverb }; + int i; + + /* Set reverb on all channels */ + for (i = 0; i < 16; i++) + if (i != 9) { + data[0] = 0xb0 | i; + writer->write(writer, data, 3); + } + if (writer->flush) + writer->flush(writer); + + return SFX_OK; +} + +static int +midi_gm_set_option(char *x, char *y) +{ + return SFX_ERROR; +} + +sfx_sequencer_t sfx_sequencer_gm = { + "General MIDI", + "0.1", + SFX_DEVICE_MIDI, + &midi_gm_set_option, + &midi_gm_open, + &midi_gm_close, + &midi_gm_event, + &midi_gm_delay, + &midi_gm_reset_timer, + &midi_gm_allstop, + &midi_gm_volume, + &midi_gm_reverb, + 004, /* patch.004 */ + 001, /* patch.001 */ + 0x01, /* playflag */ + 1, /* do play rhythm */ + 64, /* max polyphony */ + 0 /* no write-ahead needed inherently */ +}; diff --git a/engines/sci/sfx/seq/instrument-map.c b/engines/sci/sfx/seq/instrument-map.c new file mode 100644 index 0000000000..0d829a0582 --- /dev/null +++ b/engines/sci/sfx/seq/instrument-map.c @@ -0,0 +1,539 @@ +/*************************************************************************** + Copyright (C) 2008 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 merchantability, + 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) <creichen@gmail.com> + +***************************************************************************/ + +#include <assert.h> +#include "sci_midi.h" +#include "sci_memory.h" +#include "instrument-map.h" +#include "sfx_engine.h" + +sfx_instrument_map_t * +sfx_instrument_map_new(int velocity_maps_nr) +{ + sfx_instrument_map_t *map = (sfx_instrument_map_t *)sci_malloc(sizeof (sfx_instrument_map_t)); + int i; + + map->initialisation_block_size = 0; + map->initialisation_block = NULL; + + map->velocity_maps_nr = velocity_maps_nr; + map->percussion_velocity_map_index = SFX_NO_VELOCITY_MAP; + + if (velocity_maps_nr == 0) + map->velocity_map = NULL; /* Yes, this complicates control flow needlessly, but it avoids some of the pointless + ** warnings that certain memory tools seem to find appropriate. */ + else { + map->velocity_map = (byte **)sci_malloc(sizeof (byte *) * velocity_maps_nr); + for (i = 0; i < velocity_maps_nr; ++i) + map->velocity_map[i] = (byte *)sci_malloc(SFX_VELOCITIES_NR); + } + for (i = 0; i < SFX_INSTRUMENTS_NR; ++i) + map->velocity_map_index[i] = SFX_NO_VELOCITY_MAP; + + map->percussion_volume_adjust = 0; + for (i = 0; i < SFX_RHYTHM_NR; ++i) + map->percussion_map[i] = i; + + + for (i = 0; i < SFX_INSTRUMENTS_NR; ++i) { + map->patch_map[i].patch = i; + map->patch_key_shift[i] = 0; + map->patch_volume_adjust[i] = 0; + } + + return map; +} + +void +sfx_instrument_map_free(sfx_instrument_map_t *map) +{ + if (!map) + return; + + if (map->velocity_map) { + int i; + for (i = 0; i < map->velocity_maps_nr; i++) + sci_free(map->velocity_map[i]); + sci_free(map->velocity_map); + map->velocity_map = NULL; + } + + if (map->initialisation_block) { + sci_free(map->initialisation_block); + map->initialisation_block = NULL; + } + + sci_free(map); +} + +#define PATCH_MAP_OFFSET 0x0000 +#define PATCH_KEY_SHIFT_OFFSET 0x0080 +#define PATCH_VOLUME_ADJUST_OFFSET 0x0100 +#define PATCH_PERCUSSION_MAP_OFFSET 0x0180 +#define PATCH_PERCUSSION_VOLUME_ADJUST 0x0200 +#define PATCH_VELOCITY_MAP_INDEX 0x0201 +#define PATCH_VELOCITY_MAP(i) (0x0281 + (0x80 * i)) +#define PATCH_INIT_DATA_SIZE_LE 0x0481 +#define PATCH_INIT_DATA 0x0483 + +#define PATCH_INSTRUMENT_MAPS_NR 4 + +#define PATCH_MIN_SIZE PATCH_INIT_DATA + + +static int +patch001_type0_length(byte *data, size_t length) +{ + unsigned int pos = 492 + 246 * data[491]; + +/* printf("timbres %d (post = %04x)\n",data[491], pos);*/ + + if ((length >= (pos + 386)) && (data[pos] == 0xAB) && (data[pos + 1] == 0xCD)) + pos += 386; + +/* printf("pos = %04x (%02x %02x)\n", pos, data[pos], data[pos + 1]); */ + + if ((length >= (pos + 267)) && (data[pos] == 0xDC) && (data[pos + 1] == 0xBA)) + pos += 267; + +/* printf("pos = %04x %04x (%d)\n", pos, length, pos-length); */ + + + if (pos == length) + return 1; + return 0; +} + +static int +patch001_type1_length(byte *data, size_t length) +{ + if ((length >= 1155) && (((data[1154] << 8) + data[1153] + 1155) == length)) + return 1; + return 0; +} + +int +sfx_instrument_map_detect(byte *data, size_t length) +{ + /* length test */ + if (length < 1155) + return SFX_MAP_MT32; + if (length > 16889) + return SFX_MAP_MT32_GM; + if (patch001_type0_length(data, length) && + !patch001_type1_length(data, length)) + return SFX_MAP_MT32; + if (patch001_type1_length(data, length) && + !patch001_type0_length(data, length)) + return SFX_MAP_MT32_GM; + return SFX_MAP_UNKNOWN; +} + + +sfx_instrument_map_t * +sfx_instrument_map_load_sci(byte *data, size_t size) +{ + sfx_instrument_map_t * map; + int i, m; + + if (data == NULL) + return NULL; + + if (size < PATCH_MIN_SIZE) { + fprintf(stderr, "[instrument-map] Instrument map too small: %d of %d\n", (int) size, PATCH_MIN_SIZE); + return NULL; + } + + map = sfx_instrument_map_new(PATCH_INSTRUMENT_MAPS_NR); + + /* Set up MIDI intialisation data */ + map->initialisation_block_size = getInt16(data + PATCH_INIT_DATA_SIZE_LE); + if (map->initialisation_block_size) { + if (size < PATCH_MIN_SIZE + map->initialisation_block_size) { + fprintf(stderr, "[instrument-map] Instrument map too small for initialisation block: %d of %d\n", (int) size, PATCH_MIN_SIZE); + return NULL; + } + + if (size > PATCH_MIN_SIZE + map->initialisation_block_size) + fprintf(stderr, "[instrument-map] Instrument larger than required by initialisation block: %d of %d\n", (int) size, PATCH_MIN_SIZE); + + if (map->initialisation_block_size != 0) { + map->initialisation_block = (byte *)sci_malloc(map->initialisation_block_size); + memcpy(map->initialisation_block, data + PATCH_INIT_DATA, map->initialisation_block_size); + } + } + + /* Set up basic instrument info */ + for (i = 0; i < SFX_INSTRUMENTS_NR; i++) { + map->patch_map[i].patch = (char)data[PATCH_MAP_OFFSET + i]; + map->patch_key_shift[i] = (char)data[PATCH_KEY_SHIFT_OFFSET + i]; + map->patch_volume_adjust[i] = (char)data[PATCH_VOLUME_ADJUST_OFFSET + i]; + map->patch_bend_range[i] = SFX_UNMAPPED; + map->velocity_map_index[i] = data[PATCH_VELOCITY_MAP_INDEX + i]; + } + + /* Set up percussion maps */ + map->percussion_volume_adjust = data[PATCH_PERCUSSION_VOLUME_ADJUST]; + for (i = 0; i < SFX_RHYTHM_NR; i++) { + map->percussion_map[i] = data[PATCH_PERCUSSION_MAP_OFFSET + i]; + map->percussion_velocity_scale[i] = SFX_MAX_VELOCITY; + } + + /* Set up velocity maps */ + for (m = 0; m < PATCH_INSTRUMENT_MAPS_NR; m++) { + byte *velocity_map = map->velocity_map[m]; + for (i = 0; i < SFX_VELOCITIES_NR; i++) + velocity_map[i] = data[PATCH_VELOCITY_MAP(m) + i]; + } + + map->percussion_velocity_map_index = 0; + + return map; +} + + +/* Output with the instrument map */ +#define MIDI_CHANNELS_NR 0x10 + +typedef struct decorated_midi_writer { + MIDI_WRITER_BODY + + midi_writer_t *writer; + sfx_patch_map_t patches[MIDI_CHANNELS_NR]; + sfx_instrument_map_t *map; +} decorated_midi_writer_t; + + +static void +init_decorated(struct _midi_writer *self_) +{ + decorated_midi_writer_t *self = (decorated_midi_writer_t *) self_; + self->writer->init(self->writer); +} + +static void +set_option_decorated(struct _midi_writer *self_, char *name, char *value) +{ + decorated_midi_writer_t *self = (decorated_midi_writer_t *) self_; + self->writer->set_option(self->writer, name, value); +} + +static void +delay_decorated(struct _midi_writer *self_, int ticks) +{ + decorated_midi_writer_t *self = (decorated_midi_writer_t *) self_; + self->writer->delay(self->writer, ticks); +} + +static void +flush_decorated(struct _midi_writer *self_) +{ + decorated_midi_writer_t *self = (decorated_midi_writer_t *) self_; + if (self->writer->flush) + self->writer->flush(self->writer); +} + +static void +reset_timer_decorated(struct _midi_writer *self_) +{ + decorated_midi_writer_t *self = (decorated_midi_writer_t *) self_; + self->writer->reset_timer(self->writer); +} + + +static void +close_decorated(decorated_midi_writer_t *self) +{ + sfx_instrument_map_free(self->map); + self->map = NULL; + self->writer->close(self->writer); + sci_free(self->name); + self->name = NULL; + sci_free(self); +} + +#define BOUND_127(x) (((x) < 0)? 0 : (((x) > 0x7f)? 0x7f : (x))) + +static int +bound_hard_127(int i, char *descr) +{ + int r = BOUND_127(i); + if (r != i) + fprintf(stderr, "[instrument-map] Hard-clipping %02x to %02x in %s\n", i, r, descr); + return r; +} + +static int +set_bend_range(midi_writer_t *writer, int channel, int range) +{ + byte buf[3] = {0xb0, 0x65, 0x00}; + + buf[0] |= channel & 0xf; + if (writer->write(writer, buf, 3) != SFX_OK) + return SFX_ERROR; + + buf[1] = 0x64; + if (writer->write(writer, buf, 3) != SFX_OK) + return SFX_ERROR; + + buf[1] = 0x06; + buf[2] = BOUND_127(range); + if (writer->write(writer, buf, 3) != SFX_OK) + return SFX_ERROR; + + buf[1] = 0x26; + buf[2] = 0; + if (writer->write(writer, buf, 3) != SFX_OK) + return SFX_ERROR; + + return SFX_OK; +} + +static int +write_decorated(decorated_midi_writer_t *self, byte *buf, int len) +{ + sfx_instrument_map_t *map = self->map; + int op = *buf & 0xf0; + int chan = *buf & 0x0f; + int patch = self->patches[chan].patch; + int rhythm = self->patches[chan].rhythm; + + assert (len >= 1); + + if (op == 0xC0 && chan != MIDI_RHYTHM_CHANNEL) { /* Program change */ + int patch = bound_hard_127(buf[1], "program change"); + int instrument = map->patch_map[patch].patch; + int bend_range = map->patch_bend_range[patch]; + + self->patches[chan] = map->patch_map[patch]; + + if (instrument == SFX_UNMAPPED || instrument == SFX_MAPPED_TO_RHYTHM) + return SFX_OK; + + assert (len >= 2); + buf[1] = bound_hard_127(instrument, "patch lookup"); + + if (self->writer->write(self->writer, buf, len) != SFX_OK) + return SFX_ERROR; + + if (bend_range != SFX_UNMAPPED) + return set_bend_range(self->writer, chan, bend_range); + + return SFX_OK; + } + + if (chan == MIDI_RHYTHM_CHANNEL || patch == SFX_MAPPED_TO_RHYTHM) { + /* Rhythm channel handling */ + switch (op) { + case 0x80: + case 0x90: { /* Note off / note on */ + int velocity, instrument, velocity_map_index, velocity_scale; + + if (patch == SFX_MAPPED_TO_RHYTHM) { + buf[0] = (buf[0] & ~0x0f) | MIDI_RHYTHM_CHANNEL; + instrument = rhythm; + velocity_scale = SFX_MAX_VELOCITY; + } else { + int instrument_index = bound_hard_127(buf[1], "rhythm instrument index"); + instrument = map->percussion_map[instrument_index]; + velocity_scale = map->percussion_velocity_scale[instrument_index]; + } + + if (instrument == SFX_UNMAPPED) + return SFX_OK; + + assert (len >= 3); + + velocity = bound_hard_127(buf[2], "rhythm velocity"); + velocity_map_index = map->percussion_velocity_map_index; + + if (velocity_map_index != SFX_NO_VELOCITY_MAP) + velocity = BOUND_127(velocity + map->velocity_map[velocity_map_index][velocity]); + + velocity = BOUND_127(velocity * velocity_scale / SFX_MAX_VELOCITY); + + buf[1] = bound_hard_127(instrument, "rhythm instrument"); + buf[2] = velocity; + + break; + } + + case 0xB0: { /* Controller change */ + assert (len >= 3); + if (buf[1] == 0x7) /* Volume change */ + buf[2] = BOUND_127(buf[2] + map->percussion_volume_adjust); + break; + } + + default: break; + } + + } else { + /* Instrument channel handling */ + + if (patch == SFX_UNMAPPED) + return SFX_OK; + + switch (op) { + case 0x80: + case 0x90: { /* Note off / note on */ + int note = bound_hard_127(buf[1], "note"); + int velocity = bound_hard_127(buf[2], "velocity"); + int velocity_map_index = map->velocity_map_index[patch]; + assert (len >= 3); + + note += map->patch_key_shift[patch]; + /* Not the most efficient solutions, but the least error-prone */ + while (note < 0) + note += 12; + while (note > 0x7f) + note -= 12; + + if (velocity_map_index != SFX_NO_VELOCITY_MAP) + velocity = BOUND_127(velocity + map->velocity_map[velocity_map_index][velocity]); + + buf[1] = note; + buf[2] = velocity; + break; + } + + case 0xB0: /* Controller change */ + assert (len >= 3); + if (buf[1] == 0x7) /* Volume change */ + buf[2] = BOUND_127(buf[2] + map->patch_volume_adjust[patch]); + break; + + default: break; + } + } + + return self->writer->write(self->writer, buf, len); +} + +#define MIDI_BYTES_PER_SECOND 3250 /* This seems to be the minimum guarantee by the standard */ +#define MAX_PER_TICK (MIDI_BYTES_PER_SECOND / 60) /* After this, we ought to issue one tick of pause */ + +static void +init(midi_writer_t *writer, byte *data, size_t len) +{ + int offset = 0; + byte status = 0; + + /* Send init data as separate MIDI commands */ + while (offset < len) { + int args; + byte op = data[offset]; + byte msg[3]; + int i; + + if (op == 0xf0) { + int msg_len; + byte *find = (byte *) memchr(data + offset, 0xf7, len - offset); + + if (!find) { + fprintf(stderr, "[instrument-map] Failed to find end of sysex message\n"); + return; + } + + msg_len = find - data - offset + 1; + writer->write(writer, data + offset, msg_len); + + /* Wait at least 40ms after sysex */ + writer->delay(writer, 3); + offset += msg_len; + continue; + } + + if (op < 0x80) + op = status; + else { + status = op; + offset++; + } + + msg[0] = op; + + switch (op & 0xf0) { + case 0xc0: + case 0xd0: + args = 1; + break; + default: + args = 2; + } + + if (args > len - offset) { + fprintf(stderr, "[instrument-map] Insufficient bytes remaining for MIDI command %02x\n", op); + return; + } + + for (i = 0; i < args; i++) + msg[i + 1] = data[offset + i]; + + writer->write(writer, msg, args + 1); + offset += args; + + if (writer->flush) + writer->flush(writer); + } +} + +#define NAME_SUFFIX "+instruments" + +midi_writer_t * +sfx_mapped_writer(midi_writer_t *writer, sfx_instrument_map_t *map) +{ + int i; + decorated_midi_writer_t *retval; + + if (map == NULL) + return writer; + + retval = (decorated_midi_writer_t *)sci_malloc(sizeof(decorated_midi_writer_t)); + retval->writer = writer; + retval->name = (char *)sci_malloc(strlen(writer->name) + strlen(NAME_SUFFIX) + 1); + strcpy(retval->name, writer->name); + strcat(retval->name, NAME_SUFFIX); + + retval->init = (int (*)(midi_writer_t *)) init_decorated; + retval->set_option = (int (*)(midi_writer_t *, char *, char *)) set_option_decorated; + retval->write = (int (*)(midi_writer_t *, byte *, int)) write_decorated; + retval->delay = (void (*)(midi_writer_t *, int)) delay_decorated; + retval->flush = (void (*)(midi_writer_t *)) flush_decorated; + retval->reset_timer = (void (*)(midi_writer_t *)) reset_timer_decorated; + retval->close = (void (*)(midi_writer_t *)) close_decorated; + + retval->map = map; + + init(writer, map->initialisation_block, map->initialisation_block_size); + + for (i = 0; i < MIDI_CHANNELS_NR; i++) + retval->patches[i].patch = SFX_UNMAPPED; + + return (midi_writer_t *) retval; +} + diff --git a/engines/sci/sfx/seq/instrument-map.h b/engines/sci/sfx/seq/instrument-map.h new file mode 100644 index 0000000000..85f654d60b --- /dev/null +++ b/engines/sci/sfx/seq/instrument-map.h @@ -0,0 +1,132 @@ +/*************************************************************************** + Copyright (C) 2008 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 merchantability, + 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) <creichen@gmail.com> + +***************************************************************************/ + +/* Implementation of SCI instrument maps for GM and MT-32. */ + +#ifndef SCI_INSTRUMENT_MAP_ +#define SCI_INSTRUMENT_MAP_ + +#include <stdlib.h> +#include "resource.h" +#include "../device.h" + +#define SFX_INSTRUMENTS_NR 0x80 +#define SFX_RHYTHM_NR 0x80 +#define SFX_VELOCITIES_NR 0x80 +#define SFX_NO_VELOCITY_MAP -1 /* use in velocity_map_index to indicate that no map should be used */ + +/* Instrument map types */ +#define SFX_MAP_UNKNOWN 0 +#define SFX_MAP_MT32 1 /* Original MT-32 map format */ +#define SFX_MAP_MT32_GM 2 /* More recent map format used for both MT-32 and GM */ + +/* Patch not mapped */ +#define SFX_UNMAPPED -1 +/* Patch mapped to rhythm key */ +#define SFX_MAPPED_TO_RHYTHM -2 + +/* Maximum velocity (used for scaling) */ +#define SFX_MAX_VELOCITY 128 + +typedef struct { + int patch; /* Native instrument, SFX_UNMAPPED or SFX_MAPPED_TO_RHYTHM */ + int rhythm; /* Rhythm key when patch == SFX_MAPPED_TO_RHYTHM */ +} sfx_patch_map_t; + +typedef struct { + sfx_patch_map_t patch_map[SFX_INSTRUMENTS_NR]; /* Map patch nr to which native instrument or rhythm key */ + int patch_key_shift[SFX_INSTRUMENTS_NR]; /* Shift patch key by how much? */ + int patch_volume_adjust[SFX_INSTRUMENTS_NR]; /* Adjust controller 7 by how much? */ + int patch_bend_range[SFX_INSTRUMENTS_NR]; /* Bend range in semitones or SFX_UNMAPPED for default */ + + int percussion_map[SFX_RHYTHM_NR]; /* Map percussion instrument (RHYTH_CHANNEL) to what native 'key'? */ + int percussion_volume_adjust; /* unused in SCI patches */ + + int velocity_map_index[SFX_INSTRUMENTS_NR]; /* Velocity translation map to use for that instrument */ + int velocity_maps_nr; /* How many velocity translation maps do we have? */ + byte **velocity_map; /* velocity_maps_nr entries, each of size SFX_VELOCITIES_NR */ + int percussion_velocity_map_index; /* Special index for the percussion map */ + int percussion_velocity_scale[SFX_INSTRUMENTS_NR]; /* Velocity scale (0 - SFX_PERC_MAX_VOL) */ + + size_t initialisation_block_size; + byte *initialisation_block; /* Initial MIDI commands to set up the device */ +} sfx_instrument_map_t; + +sfx_instrument_map_t * +sfx_instrument_map_new(int velocity_maps_nr); +/* Constructs a new default-initialised velocity map +** Parameters: (int) velocity_maps_nr: Number of velocity maps to allocate +** Returns : (sfx_instrument_map *) an initialised instrument map +*/ + +void +sfx_instrument_map_free(sfx_instrument_map_t *map); +/* Deallocates an instrument map +** Parameters: (sfx_instrument_map *) map: The map to deallocate, or NULL for a no-op +*/ + +sfx_instrument_map_t * +sfx_instrument_map_load_sci(byte *data, size_t length); +/* Allocate and initialise an instrument map from SCI data +** Parameters: (byte *) Pointer to the data to initialise from +** (size_t) Number of bytes to expect within +** Returns : (sfx_instrument_map_t *) An initialised instrument map for these settings, or NULL +** if `data' is NULL or `data' and `length' do not permit a valid instrument map +** If `data' is null, the function will return NULL quietly. +*/ + +sfx_instrument_map_t * +sfx_instrument_map_mt32_to_gm(byte *data, size_t size); +/* Allocate and initialise an instrument map from MT-32 patch data +** Parameters: (byte *) Pointer to the MT-32 patch data to initialise from +** (size_t) Number of bytes to expect within +** Returns : (sfx_instrument_map_t *) An initialised instrument map for these settings +** If `data' is null or invalid, the function will return a default MT-32 to GM map. +*/ + +int +sfx_instrument_map_detect(byte *data, size_t size); +/* Detects the type of patch data +** Parameters: (byte *) Pointer to the patch data +** (size_t) Number of bytes to expect within +** Returns : (int) SFX_MAP_SCI1 for an SCI1 instrument map, SFX_MAP_SCI0_MT32 for SCI0 MT-32 patch data, +** or SFX_MAP_UNKNOWN for unknown. +*/ + +midi_writer_t * +sfx_mapped_writer(midi_writer_t *writer, sfx_instrument_map_t *map); +/* Wrap a midi_writer_t into an instrument map +** Parameters: (midi_writer_t *) writer: The writer to wrap +** (sfx_instrument_map_t *) map: The map to apply to all commands going into the writer, or NULL +** Returns : (midi_writer_t *) A MIDI writer that preprocesses all data by `map' and otherwise relies on `writer' +** Effects : If successful and neccessary, this operation will send initialisation messages to the writer, as needed. +** If `map' is NULL, this returns `writer'. Otherwise it sets up a Decorator that handles translation and automatically +** deallocates the instrument map when the writer is closed. +*/ + + +#endif /* !defined(SCI_INSTRUMENT_MAP_) */ diff --git a/engines/sci/sfx/seq/map-mt32-to-gm.c b/engines/sci/sfx/seq/map-mt32-to-gm.c new file mode 100644 index 0000000000..bcaf3556f1 --- /dev/null +++ b/engines/sci/sfx/seq/map-mt32-to-gm.c @@ -0,0 +1,813 @@ +/*************************************************************************** + map-mt32-to-gm.c (C) 1999,2001 Christoph Reichenbach, TU Darmstadt + (C) 1999-2000 Rickard Lind + (C) 2008 Walter van Niftrik + + 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 (CJR) [creichen@rbg.informatik.tu-darmstadt.de] + + Roland MT-32 to General MIDI conversion: + + Rickard Lind [rpl@dd.chalmers.se] + +***************************************************************************/ + +#include <sciresource.h> +#include <engine.h> +#include <stdarg.h> +#include "instrument-map.h" + +#define DEBUG_MT32_TO_GM + +static char +*GM_Instrument_Names[] = { + /*000*/ "Acoustic Grand Piano", + /*001*/ "Bright Acoustic Piano", + /*002*/ "Electric Grand Piano", + /*003*/ "Honky-tonk Piano", + /*004*/ "Electric Piano 1", + /*005*/ "Electric Piano 2", + /*006*/ "Harpsichord", + /*007*/ "Clavinet", + /*008*/ "Celesta", + /*009*/ "Glockenspiel", + /*010*/ "Music Box", + /*011*/ "Vibraphone", + /*012*/ "Marimba", + /*013*/ "Xylophone", + /*014*/ "Tubular Bells", + /*015*/ "Dulcimer", + /*016*/ "Drawbar Organ", + /*017*/ "Percussive Organ", + /*018*/ "Rock Organ", + /*019*/ "Church Organ", + /*020*/ "Reed Organ", + /*021*/ "Accordion", + /*022*/ "Harmonica", + /*023*/ "Tango Accordion", + /*024*/ "Acoustic Guitar (nylon)", + /*025*/ "Acoustic Guitar (steel)", + /*026*/ "Electric Guitar (jazz)", + /*027*/ "Electric Guitar (clean)", + /*028*/ "Electric Guitar (muted)", + /*029*/ "Overdriven Guitar", + /*030*/ "Distortion Guitar", + /*031*/ "Guitar Harmonics", + /*032*/ "Acoustic Bass", + /*033*/ "Electric Bass (finger)", + /*034*/ "Electric Bass (pick)", + /*035*/ "Fretless Bass", + /*036*/ "Slap Bass 1", + /*037*/ "Slap Bass 2", + /*038*/ "Synth Bass 1", + /*039*/ "Synth Bass 2", + /*040*/ "Violin", + /*041*/ "Viola", + /*042*/ "Cello", + /*043*/ "Contrabass", + /*044*/ "Tremolo Strings", + /*045*/ "Pizzicato Strings", + /*046*/ "Orchestral Harp", + /*047*/ "Timpani", + /*048*/ "String Ensemble 1", + /*049*/ "String Ensemble 2", + /*050*/ "SynthStrings 1", + /*051*/ "SynthStrings 2", + /*052*/ "Choir Aahs", + /*053*/ "Voice Oohs", + /*054*/ "Synth Voice", + /*055*/ "Orchestra Hit", + /*056*/ "Trumpet", + /*057*/ "Trombone", + /*058*/ "Tuba", + /*059*/ "Muted Trumpet", + /*060*/ "French Horn", + /*061*/ "Brass Section", + /*062*/ "SynthBrass 1", + /*063*/ "SynthBrass 2", + /*064*/ "Soprano Sax", + /*065*/ "Alto Sax", + /*066*/ "Tenor Sax", + /*067*/ "Baritone Sax", + /*068*/ "Oboe", + /*069*/ "English Horn", + /*070*/ "Bassoon", + /*071*/ "Clarinet", + /*072*/ "Piccolo", + /*073*/ "Flute", + /*074*/ "Recorder", + /*075*/ "Pan Flute", + /*076*/ "Blown Bottle", + /*077*/ "Shakuhachi", + /*078*/ "Whistle", + /*079*/ "Ocarina", + /*080*/ "Lead 1 (square)", + /*081*/ "Lead 2 (sawtooth)", + /*082*/ "Lead 3 (calliope)", + /*083*/ "Lead 4 (chiff)", + /*084*/ "Lead 5 (charang)", + /*085*/ "Lead 6 (voice)", + /*086*/ "Lead 7 (fifths)", + /*087*/ "Lead 8 (bass+lead)", + /*088*/ "Pad 1 (new age)", + /*089*/ "Pad 2 (warm)", + /*090*/ "Pad 3 (polysynth)", + /*091*/ "Pad 4 (choir)", + /*092*/ "Pad 5 (bowed)", + /*093*/ "Pad 6 (metallic)", + /*094*/ "Pad 7 (halo)", + /*095*/ "Pad 8 (sweep)", + /*096*/ "FX 1 (rain)", + /*097*/ "FX 2 (soundtrack)", + /*098*/ "FX 3 (crystal)", + /*099*/ "FX 4 (atmosphere)", + /*100*/ "FX 5 (brightness)", + /*101*/ "FX 6 (goblins)", + /*102*/ "FX 7 (echoes)", + /*103*/ "FX 8 (sci-fi)", + /*104*/ "Sitar", + /*105*/ "Banjo", + /*106*/ "Shamisen", + /*107*/ "Koto", + /*108*/ "Kalimba", + /*109*/ "Bag pipe", + /*110*/ "Fiddle", + /*111*/ "Shannai", + /*112*/ "Tinkle Bell", + /*113*/ "Agogo", + /*114*/ "Steel Drums", + /*115*/ "Woodblock", + /*116*/ "Taiko Drum", + /*117*/ "Melodic Tom", + /*118*/ "Synth Drum", + /*119*/ "Reverse Cymbal", + /*120*/ "Guitar Fret Noise", + /*121*/ "Breath Noise", + /*122*/ "Seashore", + /*123*/ "Bird Tweet", + /*124*/ "Telephone Ring", + /*125*/ "Helicopter", + /*126*/ "Applause", + /*127*/ "Gunshot" +}; + +/* The GM Percussion map is downwards compatible to the MT32 map, which is used in SCI */ +static char +*GM_Percussion_Names[] = { + /*00*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /*10*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /*20*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /*30*/ 0, 0, 0, 0, +/* The preceeding percussions are not covered by the GM standard */ + /*34*/ "Acoustic Bass Drum", + /*35*/ "Bass Drum 1", + /*36*/ "Side Stick", + /*37*/ "Acoustic Snare", + /*38*/ "Hand Clap", + /*39*/ "Electric Snare", + /*40*/ "Low Floor Tom", + /*41*/ "Closed Hi-Hat", + /*42*/ "High Floor Tom", + /*43*/ "Pedal Hi-Hat", + /*44*/ "Low Tom", + /*45*/ "Open Hi-Hat", + /*46*/ "Low-Mid Tom", + /*47*/ "Hi-Mid Tom", + /*48*/ "Crash Cymbal 1", + /*49*/ "High Tom", + /*50*/ "Ride Cymbal 1", + /*51*/ "Chinese Cymbal", + /*52*/ "Ride Bell", + /*53*/ "Tambourine", + /*54*/ "Splash Cymbal", + /*55*/ "Cowbell", + /*56*/ "Crash Cymbal 2", + /*57*/ "Vibraslap", + /*58*/ "Ride Cymbal 2", + /*59*/ "Hi Bongo", + /*60*/ "Low Bongo", + /*61*/ "Mute Hi Conga", + /*62*/ "Open Hi Conga", + /*63*/ "Low Conga", + /*64*/ "High Timbale", + /*65*/ "Low Timbale", + /*66*/ "High Agogo", + /*67*/ "Low Agogo", + /*68*/ "Cabasa", + /*69*/ "Maracas", + /*70*/ "Short Whistle", + /*71*/ "Long Whistle", + /*72*/ "Short Guiro", + /*73*/ "Long Guiro", + /*74*/ "Claves", + /*75*/ "Hi Wood Block", + /*76*/ "Low Wood Block", + /*77*/ "Mute Cuica", + /*78*/ "Open Cuica", + /*79*/ "Mute Triangle", + /*80*/ "Open Triangle" +}; + +/******************************************* + * Fancy instrument mappings begin here... * + *******************************************/ + + +static struct { + char *name; + gint8 gm_instr; + gint8 gm_rhythm_key; +} MT32_PresetTimbreMaps[] = { + /*000*/ {"AcouPiano1", 0, SFX_UNMAPPED}, + /*001*/ {"AcouPiano2", 1, SFX_UNMAPPED}, + /*002*/ {"AcouPiano3", 0, SFX_UNMAPPED}, + /*003*/ {"ElecPiano1", 4, SFX_UNMAPPED}, + /*004*/ {"ElecPiano2", 5, SFX_UNMAPPED}, + /*005*/ {"ElecPiano3", 4, SFX_UNMAPPED}, + /*006*/ {"ElecPiano4", 5, SFX_UNMAPPED}, + /*007*/ {"Honkytonk ", 3, SFX_UNMAPPED}, + /*008*/ {"Elec Org 1", 16, SFX_UNMAPPED}, + /*009*/ {"Elec Org 2", 17, SFX_UNMAPPED}, + /*010*/ {"Elec Org 3", 18, SFX_UNMAPPED}, + /*011*/ {"Elec Org 4", 18, SFX_UNMAPPED}, + /*012*/ {"Pipe Org 1", 19, SFX_UNMAPPED}, + /*013*/ {"Pipe Org 2", 19, SFX_UNMAPPED}, + /*014*/ {"Pipe Org 3", 20, SFX_UNMAPPED}, + /*015*/ {"Accordion ", 21, SFX_UNMAPPED}, + /*016*/ {"Harpsi 1 ", 6, SFX_UNMAPPED}, + /*017*/ {"Harpsi 2 ", 6, SFX_UNMAPPED}, + /*018*/ {"Harpsi 3 ", 6, SFX_UNMAPPED}, + /*019*/ {"Clavi 1 ", 7, SFX_UNMAPPED}, + /*020*/ {"Clavi 2 ", 7, SFX_UNMAPPED}, + /*021*/ {"Clavi 3 ", 7, SFX_UNMAPPED}, + /*022*/ {"Celesta 1 ", 8, SFX_UNMAPPED}, + /*023*/ {"Celesta 2 ", 8, SFX_UNMAPPED}, + /*024*/ {"Syn Brass1", 62, SFX_UNMAPPED}, + /*025*/ {"Syn Brass2", 63, SFX_UNMAPPED}, + /*026*/ {"Syn Brass3", 62, SFX_UNMAPPED}, + /*027*/ {"Syn Brass4", 63, SFX_UNMAPPED}, + /*028*/ {"Syn Bass 1", 38, SFX_UNMAPPED}, + /*029*/ {"Syn Bass 2", 39, SFX_UNMAPPED}, + /*030*/ {"Syn Bass 3", 38, SFX_UNMAPPED}, + /*031*/ {"Syn Bass 4", 39, SFX_UNMAPPED}, + /*032*/ {"Fantasy ", 88, SFX_UNMAPPED}, + /*033*/ {"Harmo Pan ", 89, SFX_UNMAPPED}, + /*034*/ {"Chorale ", 52, SFX_UNMAPPED}, + /*035*/ {"Glasses ", 98, SFX_UNMAPPED}, + /*036*/ {"Soundtrack", 97, SFX_UNMAPPED}, + /*037*/ {"Atmosphere", 99, SFX_UNMAPPED}, + /*038*/ {"Warm Bell ", 89, SFX_UNMAPPED}, + /*039*/ {"Funny Vox ", 85, SFX_UNMAPPED}, + /*040*/ {"Echo Bell ", 39, SFX_UNMAPPED}, + /*041*/ {"Ice Rain ", 101, SFX_UNMAPPED}, + /*042*/ {"Oboe 2001 ", 68, SFX_UNMAPPED}, + /*043*/ {"Echo Pan ", 87, SFX_UNMAPPED}, + /*044*/ {"DoctorSolo", 86, SFX_UNMAPPED}, + /*045*/ {"Schooldaze", 103, SFX_UNMAPPED}, + /*046*/ {"BellSinger", 88, SFX_UNMAPPED}, + /*047*/ {"SquareWave", 80, SFX_UNMAPPED}, + /*048*/ {"Str Sect 1", 48, SFX_UNMAPPED}, + /*049*/ {"Str Sect 2", 48, SFX_UNMAPPED}, + /*050*/ {"Str Sect 3", 49, SFX_UNMAPPED}, + /*051*/ {"Pizzicato ", 45, SFX_UNMAPPED}, + /*052*/ {"Violin 1 ", 40, SFX_UNMAPPED}, + /*053*/ {"Violin 2 ", 40, SFX_UNMAPPED}, + /*054*/ {"Cello 1 ", 42, SFX_UNMAPPED}, + /*055*/ {"Cello 2 ", 42, SFX_UNMAPPED}, + /*056*/ {"Contrabass", 43, SFX_UNMAPPED}, + /*057*/ {"Harp 1 ", 46, SFX_UNMAPPED}, + /*058*/ {"Harp 2 ", 46, SFX_UNMAPPED}, + /*059*/ {"Guitar 1 ", 24, SFX_UNMAPPED}, + /*060*/ {"Guitar 2 ", 25, SFX_UNMAPPED}, + /*061*/ {"Elec Gtr 1", 26, SFX_UNMAPPED}, + /*062*/ {"Elec Gtr 2", 27, SFX_UNMAPPED}, + /*063*/ {"Sitar ", 104, SFX_UNMAPPED}, + /*064*/ {"Acou Bass1", 32, SFX_UNMAPPED}, + /*065*/ {"Acou Bass2", 33, SFX_UNMAPPED}, + /*066*/ {"Elec Bass1", 34, SFX_UNMAPPED}, + /*067*/ {"Elec Bass2", 39, SFX_UNMAPPED}, + /*068*/ {"Slap Bass1", 36, SFX_UNMAPPED}, + /*069*/ {"Slap Bass2", 37, SFX_UNMAPPED}, + /*070*/ {"Fretless 1", 35, SFX_UNMAPPED}, + /*071*/ {"Fretless 2", 35, SFX_UNMAPPED}, + /*072*/ {"Flute 1 ", 73, SFX_UNMAPPED}, + /*073*/ {"Flute 2 ", 73, SFX_UNMAPPED}, + /*074*/ {"Piccolo 1 ", 72, SFX_UNMAPPED}, + /*075*/ {"Piccolo 2 ", 72, SFX_UNMAPPED}, + /*076*/ {"Recorder ", 74, SFX_UNMAPPED}, + /*077*/ {"Panpipes ", 75, SFX_UNMAPPED}, + /*078*/ {"Sax 1 ", 64, SFX_UNMAPPED}, + /*079*/ {"Sax 2 ", 65, SFX_UNMAPPED}, + /*080*/ {"Sax 3 ", 66, SFX_UNMAPPED}, + /*081*/ {"Sax 4 ", 67, SFX_UNMAPPED}, + /*082*/ {"Clarinet 1", 71, SFX_UNMAPPED}, + /*083*/ {"Clarinet 2", 71, SFX_UNMAPPED}, + /*084*/ {"Oboe ", 68, SFX_UNMAPPED}, + /*085*/ {"Engl Horn ", 69, SFX_UNMAPPED}, + /*086*/ {"Bassoon ", 70, SFX_UNMAPPED}, + /*087*/ {"Harmonica ", 22, SFX_UNMAPPED}, + /*088*/ {"Trumpet 1 ", 56, SFX_UNMAPPED}, + /*089*/ {"Trumpet 2 ", 56, SFX_UNMAPPED}, + /*090*/ {"Trombone 1", 57, SFX_UNMAPPED}, + /*091*/ {"Trombone 2", 57, SFX_UNMAPPED}, + /*092*/ {"Fr Horn 1 ", 60, SFX_UNMAPPED}, + /*093*/ {"Fr Horn 2 ", 60, SFX_UNMAPPED}, + /*094*/ {"Tuba ", 58, SFX_UNMAPPED}, + /*095*/ {"Brs Sect 1", 61, SFX_UNMAPPED}, + /*096*/ {"Brs Sect 2", 61, SFX_UNMAPPED}, + /*097*/ {"Vibe 1 ", 11, SFX_UNMAPPED}, + /*098*/ {"Vibe 2 ", 11, SFX_UNMAPPED}, + /*099*/ {"Syn Mallet", 15, SFX_UNMAPPED}, + /*100*/ {"Wind Bell ", 88, SFX_UNMAPPED}, + /*101*/ {"Glock ", 9, SFX_UNMAPPED}, + /*102*/ {"Tube Bell ", 14, SFX_UNMAPPED}, + /*103*/ {"Xylophone ", 13, SFX_UNMAPPED}, + /*104*/ {"Marimba ", 12, SFX_UNMAPPED}, + /*105*/ {"Koto ", 107, SFX_UNMAPPED}, + /*106*/ {"Sho ", 111, SFX_UNMAPPED}, + /*107*/ {"Shakuhachi", 77, SFX_UNMAPPED}, + /*108*/ {"Whistle 1 ", 78, SFX_UNMAPPED}, + /*109*/ {"Whistle 2 ", 78, SFX_UNMAPPED}, + /*110*/ {"BottleBlow", 76, SFX_UNMAPPED}, + /*111*/ {"BreathPipe", 121, SFX_UNMAPPED}, + /*112*/ {"Timpani ", 47, SFX_UNMAPPED}, + /*113*/ {"MelodicTom", 117, SFX_UNMAPPED}, + /*114*/ {"Deep Snare", SFX_MAPPED_TO_RHYTHM, 37}, + /*115*/ {"Elec Perc1", 115, SFX_UNMAPPED}, /* ? */ + /*116*/ {"Elec Perc2", 118, SFX_UNMAPPED}, /* ? */ + /*117*/ {"Taiko ", 116, SFX_UNMAPPED}, + /*118*/ {"Taiko Rim ", 118, SFX_UNMAPPED}, + /*119*/ {"Cymbal ", SFX_MAPPED_TO_RHYTHM, 50}, + /*120*/ {"Castanets ", SFX_MAPPED_TO_RHYTHM, SFX_UNMAPPED}, + /*121*/ {"Triangle ", 112, SFX_UNMAPPED}, + /*122*/ {"Orche Hit ", 55, SFX_UNMAPPED}, + /*123*/ {"Telephone ", 124, SFX_UNMAPPED}, + /*124*/ {"Bird Tweet", 123, SFX_UNMAPPED}, + /*125*/ {"OneNoteJam", SFX_UNMAPPED, SFX_UNMAPPED}, /* ? */ + /*126*/ {"WaterBells", 98, SFX_UNMAPPED}, + /*127*/ {"JungleTune", SFX_UNMAPPED, SFX_UNMAPPED} /* ? */ +}; + +static struct { + char *name; + gint8 gm_instr; + gint8 gm_rhythmkey; +} MT32_RhythmTimbreMaps[] = { + /*00*/ {"Acou BD ", SFX_MAPPED_TO_RHYTHM, 34}, + /*01*/ {"Acou SD ", SFX_MAPPED_TO_RHYTHM, 37}, + /*02*/ {"Acou HiTom", 117, 49}, + /*03*/ {"AcouMidTom", 117, 46}, + /*04*/ {"AcouLowTom", 117, 40}, + /*05*/ {"Elec SD ", SFX_MAPPED_TO_RHYTHM, 39}, + /*06*/ {"Clsd HiHat", SFX_MAPPED_TO_RHYTHM, 41}, + /*07*/ {"OpenHiHat1", SFX_MAPPED_TO_RHYTHM, 45}, + /*08*/ {"Crash Cym ", SFX_MAPPED_TO_RHYTHM, 48}, + /*09*/ {"Ride Cym ", SFX_MAPPED_TO_RHYTHM, 50}, + /*10*/ {"Rim Shot ", SFX_MAPPED_TO_RHYTHM, 36}, + /*11*/ {"Hand Clap ", SFX_MAPPED_TO_RHYTHM, 38}, + /*12*/ {"Cowbell ", SFX_MAPPED_TO_RHYTHM, 55}, + /*13*/ {"Mt HiConga", SFX_MAPPED_TO_RHYTHM, 61}, + /*14*/ {"High Conga", SFX_MAPPED_TO_RHYTHM, 62}, + /*15*/ {"Low Conga ", SFX_MAPPED_TO_RHYTHM, 63}, + /*16*/ {"Hi Timbale", SFX_MAPPED_TO_RHYTHM, 64}, + /*17*/ {"LowTimbale", SFX_MAPPED_TO_RHYTHM, 65}, + /*18*/ {"High Bongo", SFX_MAPPED_TO_RHYTHM, 59}, + /*19*/ {"Low Bongo ", SFX_MAPPED_TO_RHYTHM, 60}, + /*20*/ {"High Agogo", 113, 66}, + /*21*/ {"Low Agogo ", 113, 67}, + /*22*/ {"Tambourine", SFX_MAPPED_TO_RHYTHM, 53}, + /*23*/ {"Claves ", SFX_MAPPED_TO_RHYTHM, 74}, + /*24*/ {"Maracas ", SFX_MAPPED_TO_RHYTHM, 69}, + /*25*/ {"SmbaWhis L", 78, 71}, + /*26*/ {"SmbaWhis S", 78, 70}, + /*27*/ {"Cabasa ", SFX_MAPPED_TO_RHYTHM, 68}, + /*28*/ {"Quijada ", SFX_MAPPED_TO_RHYTHM, 72}, + /*29*/ {"OpenHiHat2", SFX_MAPPED_TO_RHYTHM, 43} +}; + +static gint8 +MT32_PresetRhythmKeymap[] = { + SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, + SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, + SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, + SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, 34, 34, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, + 50, SFX_UNMAPPED, SFX_UNMAPPED, 53, SFX_UNMAPPED, 55, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, 59, + 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, + 70, 71, 72, SFX_UNMAPPED, 74, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, + SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, + SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, + SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, + SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, + SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED, SFX_UNMAPPED +}; + +/* +++ - Don't change unless you've got a good reason + ++ - Looks good, sounds ok + + - Not too bad, but is it right? + ? - Where do I map this one? + ?? - Any good ideas? + ??? - I'm clueless? + R - Rhythm... */ +static struct { + char *name; + gint8 gm_instr; + gint8 gm_rhythm_key; +} MT32_MemoryTimbreMaps[] = { + {"AccPnoKA2 ", 1, SFX_UNMAPPED}, /* ++ (KQ1) */ + {"Acou BD ", SFX_MAPPED_TO_RHYTHM, 34}, /* R (PQ2) */ + {"Acou SD ", SFX_MAPPED_TO_RHYTHM, 37}, /* R (PQ2) */ + {"AcouPnoKA ", 0, SFX_UNMAPPED}, /* ++ (KQ1) */ + {"BASS ", 32, SFX_UNMAPPED}, /* + (LSL3) */ + {"BASSOONPCM", 70, SFX_UNMAPPED}, /* + (CB) */ + {"BEACH WAVE", 122, SFX_UNMAPPED}, /* + (LSL3) */ + {"BagPipes ", 109, SFX_UNMAPPED}, + {"BassPizzMS", 45, SFX_UNMAPPED}, /* ++ (HQ) */ + {"BassoonKA ", 70, SFX_UNMAPPED}, /* ++ (KQ1) */ + {"Bell MS", 112, SFX_UNMAPPED}, /* ++ (iceMan) */ + {"Bells MS", 112, SFX_UNMAPPED}, /* + (HQ) */ + {"Big Bell ", 14, SFX_UNMAPPED}, /* + (CB) */ + {"Bird Tweet", 123, SFX_UNMAPPED}, + {"BrsSect MS", 61, SFX_UNMAPPED}, /* +++ (iceMan) */ + {"CLAPPING ", 126, SFX_UNMAPPED}, /* ++ (LSL3) */ + {"Cabasa ", SFX_MAPPED_TO_RHYTHM, 68}, /* R (HBoG) */ + {"Calliope ", 82, SFX_UNMAPPED}, /* +++ (HQ) */ + {"CelticHarp", 46, SFX_UNMAPPED}, /* ++ (CoC) */ + {"Chicago MS", 1, SFX_UNMAPPED}, /* ++ (iceMan) */ + {"Chop ", 117, SFX_UNMAPPED}, + {"Chorale MS", 52, SFX_UNMAPPED}, /* + (CoC) */ + {"ClarinetMS", 71, SFX_UNMAPPED}, + {"Claves ", SFX_MAPPED_TO_RHYTHM, 74}, /* R (PQ2) */ + {"Claw MS", 118, SFX_UNMAPPED}, /* + (HQ) */ + {"ClockBell ", 14, SFX_UNMAPPED}, /* + (CB) */ + {"ConcertCym", SFX_MAPPED_TO_RHYTHM, 54}, /* R ? (KQ1) */ + {"Conga MS", SFX_MAPPED_TO_RHYTHM, 63}, /* R (HQ) */ + {"CoolPhone ", 124, SFX_UNMAPPED}, /* ++ (LSL3) */ + {"CracklesMS", 115, SFX_UNMAPPED}, /* ? (CoC, HQ) */ + {"CreakyD MS", SFX_UNMAPPED, SFX_UNMAPPED}, /* ??? (KQ1) */ + {"Cricket ", 120, SFX_UNMAPPED}, /* ? (CB) */ + {"CrshCymbMS", SFX_MAPPED_TO_RHYTHM, 56}, /* R +++ (iceMan) */ + {"CstlGateMS", SFX_UNMAPPED, SFX_UNMAPPED}, /* ? (HQ) */ + {"CymSwellMS", SFX_MAPPED_TO_RHYTHM, 54}, /* R ? (CoC, HQ) */ + {"CymbRollKA", SFX_MAPPED_TO_RHYTHM, 56}, /* R ? (KQ1) */ + {"Cymbal Lo ", SFX_UNMAPPED, SFX_UNMAPPED}, /* R ? (LSL3) */ + {"card ", SFX_UNMAPPED, SFX_UNMAPPED}, /* ? (HBoG) */ + {"DirtGtr MS", 30, SFX_UNMAPPED}, /* + (iceMan) */ + {"DirtGtr2MS", 29, SFX_UNMAPPED}, /* + (iceMan) */ + {"E Bass MS", 33, SFX_UNMAPPED}, /* + (SQ3) */ + {"ElecBassMS", 33, SFX_UNMAPPED}, + {"ElecGtr MS", 27, SFX_UNMAPPED}, /* ++ (iceMan) */ + {"EnglHornMS", 69, SFX_UNMAPPED}, + {"FantasiaKA", 88, SFX_UNMAPPED}, + {"Fantasy ", 99, SFX_UNMAPPED}, /* + (PQ2) */ + {"Fantasy2MS", 99, SFX_UNMAPPED}, /* ++ (CoC, HQ) */ + {"Filter MS", 95, SFX_UNMAPPED}, /* +++ (iceMan) */ + {"Filter2 MS", 95, SFX_UNMAPPED}, /* ++ (iceMan) */ + {"Flame2 MS", 121, SFX_UNMAPPED}, /* ? (HQ) */ + {"Flames MS", 121, SFX_UNMAPPED}, /* ? (HQ) */ + {"Flute MS", 73, SFX_UNMAPPED}, /* +++ (HQ) */ + {"FogHorn MS", 58, SFX_UNMAPPED}, + {"FrHorn1 MS", 60, SFX_UNMAPPED}, /* +++ (HQ) */ + {"FunnyTrmp ", 56, SFX_UNMAPPED}, /* ++ (CB) */ + {"GameSnd MS", 80, SFX_UNMAPPED}, + {"Glock MS", 9, SFX_UNMAPPED}, /* +++ (HQ) */ + {"Gunshot ", 127, SFX_UNMAPPED}, /* +++ (CB) */ + {"Hammer MS", SFX_UNMAPPED, SFX_UNMAPPED}, /* ? (HQ) */ + {"Harmonica2", 22, SFX_UNMAPPED}, /* +++ (CB) */ + {"Harpsi 1 ", 6, SFX_UNMAPPED}, /* + (HBoG) */ + {"Harpsi 2 ", 6, SFX_UNMAPPED}, /* +++ (CB) */ + {"Heart MS", 116, SFX_UNMAPPED}, /* ? (iceMan) */ + {"Horse1 MS", 115, SFX_UNMAPPED}, /* ? (CoC, HQ) */ + {"Horse2 MS", 115, SFX_UNMAPPED}, /* ? (CoC, HQ) */ + {"InHale MS", 121, SFX_UNMAPPED}, /* ++ (iceMan) */ + {"KNIFE ", 120, SFX_UNMAPPED}, /* ? (LSL3) */ + {"KenBanjo ", 105, SFX_UNMAPPED}, /* +++ (CB) */ + {"Kiss MS", 25, SFX_UNMAPPED}, /* ++ (HQ) */ + {"KongHit ", SFX_UNMAPPED, SFX_UNMAPPED}, /* ??? (KQ1) */ + {"Koto ", 107, SFX_UNMAPPED}, /* +++ (PQ2) */ + {"Laser MS", 81, SFX_UNMAPPED}, /* ?? (HQ) */ + {"Meeps MS", 62, SFX_UNMAPPED}, /* ? (HQ) */ + {"MTrak MS", 62, SFX_UNMAPPED}, /* ?? (iceMan) */ + {"MachGun MS", 127, SFX_UNMAPPED}, /* ? (iceMan) */ + {"OCEANSOUND", 122, SFX_UNMAPPED}, /* + (LSL3) */ + {"Oboe 2001 ", 68, SFX_UNMAPPED}, /* + (PQ2) */ + {"Ocean MS", 122, SFX_UNMAPPED}, /* + (iceMan) */ + {"PPG 2.3 MS", 75, SFX_UNMAPPED}, /* ? (iceMan) */ + {"PianoCrank", SFX_UNMAPPED, SFX_UNMAPPED}, /* ? (CB) */ + {"PicSnareMS", SFX_MAPPED_TO_RHYTHM, 39}, /* R ? (iceMan) */ + {"PiccoloKA ", 72, SFX_UNMAPPED}, /* +++ (KQ1) */ + {"PinkBassMS", 39, SFX_UNMAPPED}, + {"Pizz2 ", 45, SFX_UNMAPPED}, /* ++ (CB) */ + {"Portcullis", SFX_UNMAPPED, SFX_UNMAPPED}, /* ? (KQ1) */ + {"Raspbry MS", 81, SFX_UNMAPPED}, /* ? (HQ) */ + {"RatSqueek ", 72, SFX_UNMAPPED}, /* ? (CB, CoC) */ + {"Record78 ", SFX_UNMAPPED, SFX_UNMAPPED}, /* +++ (CB) */ + {"RecorderMS", 74, SFX_UNMAPPED}, /* +++ (CoC) */ + {"Red Baron ", 125, SFX_UNMAPPED}, /* ? (CB) */ + {"ReedPipMS ", 20, SFX_UNMAPPED}, /* +++ (Coc) */ + {"RevCymb MS", 119, SFX_UNMAPPED}, + {"RifleShot ", 127, SFX_UNMAPPED}, /* + (CB) */ + {"RimShot MS", SFX_MAPPED_TO_RHYTHM, 36}, /* R */ + {"SHOWER ", 52, SFX_UNMAPPED}, /* ? (LSL3) */ + {"SQ Bass MS", 32, SFX_UNMAPPED}, /* + (SQ3) */ + {"ShakuVibMS", 79, SFX_UNMAPPED}, /* + (iceMan) */ + {"SlapBassMS", 36, SFX_UNMAPPED}, /* +++ (iceMan) */ + {"Snare MS", SFX_MAPPED_TO_RHYTHM, 37}, /* R (HQ) */ + {"Some Birds", 123, SFX_UNMAPPED}, /* + (CB) */ + {"Sonar MS", 78, SFX_UNMAPPED}, /* ? (iceMan) */ + {"Soundtrk2 ", 97, SFX_UNMAPPED}, /* +++ (CB) */ + {"Soundtrack", 97, SFX_UNMAPPED}, /* ++ (CoC) */ + {"SqurWaveMS", 80, SFX_UNMAPPED}, + {"StabBassMS", 34, SFX_UNMAPPED}, /* + (iceMan) */ + {"SteelDrmMS", 114, SFX_UNMAPPED}, /* +++ (iceMan) */ + {"StrSect1MS", 48, SFX_UNMAPPED}, /* ++ (HQ) */ + {"String MS", 45, SFX_UNMAPPED}, /* + (CoC) */ + {"Syn-Choir ", 91, SFX_UNMAPPED}, + {"Syn Brass4", 63, SFX_UNMAPPED}, /* ++ (PQ2) */ + {"SynBass MS", 38, SFX_UNMAPPED}, + {"SwmpBackgr", 120, SFX_UNMAPPED}, /* ?? (CB,HQ) */ + {"T-Bone2 MS", 57, SFX_UNMAPPED}, /* +++ (HQ) */ + {"Taiko ", 116, 34}, /* +++ (Coc) */ + {"Taiko Rim ", 118, 36}, /* +++ (LSL3) */ + {"Timpani1 ", 47, SFX_UNMAPPED}, /* +++ (CB) */ + {"Tom MS", 117, 47}, /* +++ (iceMan) */ + {"Toms MS", 117, 47}, /* +++ (CoC, HQ) */ + {"Tpt1prtl ", 56, SFX_UNMAPPED}, /* +++ (KQ1) */ + {"TriangleMS", 112, 80}, /* R (CoC) */ + {"Trumpet 1 ", 56, SFX_UNMAPPED}, /* +++ (CoC) */ + {"Type MS", 114, SFX_UNMAPPED}, /* ? (iceMan) */ + {"WaterBells", 98, SFX_UNMAPPED}, /* + (PQ2) */ + {"WaterFallK", SFX_UNMAPPED, SFX_UNMAPPED}, /* ? (KQ1) */ + {"Whiporill ", 123, SFX_UNMAPPED}, /* + (CB) */ + {"Wind ", SFX_UNMAPPED, SFX_UNMAPPED}, /* ? (CB) */ + {"Wind MS", SFX_UNMAPPED, SFX_UNMAPPED}, /* ? (HQ, iceMan) */ + {"Wind2 MS", SFX_UNMAPPED, SFX_UNMAPPED}, /* ? (CoC) */ + {"Woodpecker", 115, SFX_UNMAPPED}, /* ? (CB) */ + {"WtrFall MS", SFX_UNMAPPED, SFX_UNMAPPED}, /* ? (CoC, HQ, iceMan) */ + {0, 0} +}; + +static gint8 +lookup_instrument(char *iname) +{ + int i = 0; + + while (MT32_MemoryTimbreMaps[i].name) { + if (strncasecmp(iname, MT32_MemoryTimbreMaps[i].name, 10) == 0) + return MT32_MemoryTimbreMaps[i].gm_instr; + i++; + } + return SFX_UNMAPPED; +} + +static gint8 +lookup_rhythm_key(char *iname) +{ + int i = 0; + + while (MT32_MemoryTimbreMaps[i].name) { + if (strncasecmp(iname, MT32_MemoryTimbreMaps[i].name, 10) == 0) + return MT32_MemoryTimbreMaps[i].gm_rhythm_key; + i++; + } + return SFX_UNMAPPED; +} + +static void +print_map(int sci, int ins, int rhythm, int mt32) +{ +#ifdef DEBUG_MT32_TO_GM + if (ins == SFX_UNMAPPED || (ins == SFX_MAPPED_TO_RHYTHM && rhythm == SFX_UNMAPPED)) { + sciprintf("[MT32-to-GM] No mapping available for [%i] `%s' (%i)\n", + sci, MT32_PresetTimbreMaps[mt32].name, mt32); + return; + } + + if (ins == SFX_MAPPED_TO_RHYTHM) { + sciprintf("[MT32-to-GM] Mapping [%i] `%s' (%i) to `%s' [R] (%i)\n", + sci, MT32_PresetTimbreMaps[mt32].name, mt32, + GM_Percussion_Names[rhythm], rhythm); + return; + } + + sciprintf("[MT32-to-GM] Mapping [%i] `%s' (%i) to `%s' (%i)\n", + sci, MT32_PresetTimbreMaps[mt32].name, mt32, + GM_Instrument_Names[ins], ins); +#endif +} + +static void +print_map_mem(int sci, int ins, int rhythm, char *mt32) +{ +#ifdef DEBUG_MT32_TO_GM + char name[11]; + + strncpy(name, mt32, 10); + name[10] = 0; + + if (ins == SFX_UNMAPPED || (ins == SFX_MAPPED_TO_RHYTHM && rhythm == SFX_UNMAPPED)) { + sciprintf("[MT32-to-GM] No mapping available for [%i] `%s'\n", + sci, name); + return; + } + + if (ins == SFX_MAPPED_TO_RHYTHM) { + sciprintf("[MT32-to-GM] Mapping [%i] `%s' to `%s' [R] (%i)\n", + sci, name, GM_Percussion_Names[rhythm], rhythm); + return; + } + + sciprintf("[MT32-to-GM] Mapping [%i] `%s' to `%s' (%i)\n", + sci, name, GM_Instrument_Names[ins], ins); +#endif +} + +static void +print_map_rhythm(int sci, int ins, int rhythm, int mt32) +{ +#ifdef DEBUG_MT32_TO_GM + if (ins == SFX_UNMAPPED || (ins == SFX_MAPPED_TO_RHYTHM && rhythm == SFX_UNMAPPED)) { + sciprintf("[MT32-to-GM] No mapping available for [%i] `%s' [R] (%i)\n", + sci, MT32_RhythmTimbreMaps[mt32].name, mt32); + return; + } + + if (ins == SFX_MAPPED_TO_RHYTHM) { + sciprintf("[MT32-to-GM] Mapping [%i] `%s' [R] (%i) to `%s' [R] (%i)\n", + sci, MT32_RhythmTimbreMaps[mt32].name, mt32, + GM_Percussion_Names[rhythm], rhythm); + return; + } + + sciprintf("[MT32-to-GM] Mapping [%i] `%s' [R] (%i) to `%s' (%i)\n", + sci, MT32_RhythmTimbreMaps[mt32].name, mt32, + GM_Instrument_Names[ins], ins); +#endif +} + +static void +print_map_rhythm_mem(int sci, int rhythm, char *mt32) +{ +#ifdef DEBUG_MT32_TO_GM + char name[11]; + + strncpy(name, mt32, 10); + name[10] = 0; + + if (rhythm == SFX_UNMAPPED) { + sciprintf("[MT32-to-GM] No mapping available for [%i] `%s'\n", + sci, name); + return; + } + + sciprintf("[MT32-to-GM] Mapping [%i] `%s' to `%s' (%i)\n", + sci, name, GM_Percussion_Names[rhythm], rhythm); +#endif +} + +sfx_instrument_map_t * +sfx_instrument_map_mt32_to_gm(byte *data, size_t size) +{ + int memtimbres, patches; + guint8 group, number, keyshift, finetune, bender_range; + guint8 *patchpointer; + guint32 pos; + sfx_instrument_map_t * map; + int i; + int type; + + map = sfx_instrument_map_new(0); + + for (i = 0; i < SFX_INSTRUMENTS_NR; i++) { + map->patch_map[i].patch = MT32_PresetTimbreMaps[i].gm_instr; + map->patch_key_shift[i] = 0; + map->patch_volume_adjust[i] = 0; + map->patch_bend_range[i] = 12; + map->velocity_map_index[i] = SFX_NO_VELOCITY_MAP; + } + + map->percussion_volume_adjust = 0; + map->percussion_velocity_map_index = SFX_NO_VELOCITY_MAP; + + for (i = 0; i < SFX_RHYTHM_NR; i++) { + map->percussion_map[i] = MT32_PresetRhythmKeymap[i]; + map->percussion_velocity_scale[i] = SFX_MAX_VELOCITY; + } + + if (!data) { + sciprintf("[MT32-to-GM] No MT-32 patch data supplied, using default mapping\n"); + return map; + } + + type = sfx_instrument_map_detect(data, size); + + if (type == SFX_MAP_UNKNOWN) { + sciprintf("[MT32-to-GM] Patch data format unknown, using default mapping\n"); + return map; + } + if (type == SFX_MAP_MT32_GM) { + sciprintf("[MT32-to-GM] Patch data format not supported, using default mapping\n"); + return map; + } + + memtimbres = *(data + 0x1EB); + pos = 0x1EC + memtimbres * 0xF6; + + if (size > pos && ((0x100 * *(data + pos) + *(data +pos + 1)) == 0xABCD)) { + patches = 96; + pos += 2 + 8 * 48; + } else + patches = 48; + + sciprintf("[MT32-to-GM] %d MT-32 Patches detected\n", patches); + sciprintf("[MT32-to-GM] %d MT-32 Memory Timbres\n", memtimbres); + + sciprintf("[MT32-to-GM] Mapping patches..\n"); + + for (i = 0; i < patches; i++) { + char *name; + + if (i < 48) + patchpointer = data + 0x6B + 8 * i; + else + patchpointer = data + 0x1EC + 8 * (i - 48) + memtimbres * 0xF6 + 2; + + group = *patchpointer; + number = *(patchpointer + 1); + keyshift = *(patchpointer + 2); + finetune = *(patchpointer + 3); + bender_range = *(patchpointer + 4); + + switch (group) { + case 0: + map->patch_map[i].patch = MT32_PresetTimbreMaps[number].gm_instr; + map->patch_map[i].rhythm = MT32_PresetTimbreMaps[number].gm_rhythm_key; + print_map(i, map->patch_map[i].patch, map->patch_map[i].rhythm, number); + break; + case 1: + map->patch_map[i].patch = MT32_PresetTimbreMaps[number + 64].gm_instr; + map->patch_map[i].rhythm = MT32_PresetTimbreMaps[number + 64].gm_rhythm_key; + print_map(i, map->patch_map[i].patch, map->patch_map[i].rhythm, number + 64); + break; + case 2: + name = (char *) data + 0x1EC + number * 0xF6; + map->patch_map[i].patch = lookup_instrument(name); + map->patch_map[i].rhythm = SFX_UNMAPPED; + print_map_mem(i, map->patch_map[i].patch, map->patch_map[i].rhythm, name); + break; + case 3: + map->patch_map[i].patch = MT32_RhythmTimbreMaps[number].gm_instr; + map->patch_map[i].rhythm = SFX_UNMAPPED; + print_map_rhythm(i, map->patch_map[i].patch, map->patch_map[i].rhythm, number); + break; + default: + break; + } + + /* map->patch_key_shift[i] = (int) (keyshift & 0x3F) - 24; */ + map->patch_bend_range[i] = bender_range & 0x1F; + } + + if (size > pos && ((0x100 * *(data + pos) + *(data + pos + 1)) == 0xDCBA)) { + sciprintf("[MT32-to-GM] Mapping percussion..\n"); + + for (i = 0; i < 64 ; i++) { + number = *(data + pos + 4 * i + 2); + + if (number < 64) { + char *name = (char *) data + 0x1EC + number * 0xF6; + map->percussion_map[i + 23] = lookup_rhythm_key(name); + print_map_rhythm_mem(i, map->percussion_map[i + 23], name); + } else { + if (number < 94) { + map->percussion_map[i + 23] = MT32_RhythmTimbreMaps[number - 64].gm_rhythmkey; + print_map_rhythm(i, SFX_MAPPED_TO_RHYTHM, map->percussion_map[i + 23], number - 64); + } else + map->percussion_map[i + 23] = SFX_UNMAPPED; + } + + map->percussion_velocity_scale[i + 23] = *(data + pos + 4 * i + 3) * SFX_MAX_VELOCITY / 100; + } + } + + return map; +} + diff --git a/engines/sci/sfx/seq/mt32.c b/engines/sci/sfx/seq/mt32.c new file mode 100644 index 0000000000..b71d474927 --- /dev/null +++ b/engines/sci/sfx/seq/mt32.c @@ -0,0 +1,480 @@ +/*************************************************************************** + midi_mt32.c Copyright (C) 2000,2001 Rickard Lind, Solomon Peachy + mt32.c Copyright (C) 2002..04 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. + +***************************************************************************/ + +#include <stdio.h> +#ifdef HAVE_UNISTD_H +# include <unistd.h> +#endif +#include "../sequencer.h" +#include "instrument-map.h" +#include <resource.h> + +#ifdef _WIN32 +# include <win32/sci_win32.h> +# include <windows.h> +#endif + +#ifdef __BEOS__ +# include <be/kernel/OS.h> +#endif + +static int delta = 0; /* Accumulated delta time */ +static midi_writer_t *midi_writer = NULL; + +static int midi_mt32_poke(guint32 address, guint8 *data, unsigned int n); +static int midi_mt32_poke_gather(guint32 address, guint8 *data1, unsigned int count1, + guint8 *data2, unsigned int count2); +static int midi_mt32_write_block(guint8 *data, unsigned int count); +static int midi_mt32_sysex_delay(void); +static int midi_mt32_volume(guint8 volume); +static int midi_mt32_reverb(int param); +static int midi_mt32_event(byte command, int argc, byte *argv); +static int midi_mt32_allstop(void); + +static int type; +static guint8 sysex_buffer[266] = {0xF0, 0x41, 0x10, 0x16, 0x12}; +static guint8 default_reverb; +static char shutdown_msg[20]; + +static long mt32_init_sec, mt32_init_usec; /* Time at initialisation */ +static int mt32_init_delay = 0; /* Used to count the number of ticks (1/60s of a second) we should + ** wait before initialisation has been completed */ + +/* timbre, volume, panpot, reverb. keys 24-87 (64 keys)*/ +static guint8 default_rhythm_keymap[256] = { /* MT-32 default */ + 0x7f,0x64,7,1, 0x7f,0x64,7,1, 0x7f,0x64,7,1, 0x7f,0x64,7,1, /* 24-27 */ + 0x7f,0x64,7,1, 0x7f,0x64,7,1, 0x7f,0x64,7,1, 0x7f,0x64,7,1, + 0x7f,0x64,7,1, 0x7f,0x64,7,1, 0x7f,0x64,7,1, 0x40,0x64,7,1, + 0x40,0x64,7,1, 0x4a,0x64,6,1, 0x41,0x64,7,1, 0x4b,0x64,8,1, + 0x45,0x64,6,1, 0x44,0x64,11,1, 0x46,0x64,6,1, 0x44,0x64,11,1, + 0x5d,0x64,6,1, 0x43,0x64,8,1, 0x47,0x64,6,1, 0x43,0x64,8,1, + 0x42,0x64,3,1, 0x48,0x64,6,1, 0x42,0x64,3,1, 0x49,0x64,8,1, + 0x7f,0x64,7,1, 0x7f,0x64,7,1, 0x56,0x64,9,1, 0x7f,0x64,7,1, + 0x4c,0x64,7,1, 0x7f,0x64,7,1, 0x7f,0x64,7,1, 0x7f,0x64,7,1, + 0x52,0x64,2,1, 0x53,0x64,4,1, 0x4d,0x64,8,1, 0x4e,0x64,9,1, + 0x4f,0x64,10,1, 0x50,0x64,7,1, 0x51,0x64,5,1, 0x54,0x64,2,1, + 0x55,0x64,2,1, 0x5b,0x64,9,1, 0x58,0x64,4,1, 0x5a,0x64,9,1, + 0x59,0x64,9,1, 0x5c,0x64,10,1, 0x7f,0x64,7,1, 0x57,0x64,12,1, + 0x7f,0x64,7,1, 0x7f,0x64,7,1, 0x7f,0x64,7,1, 0x7f,0x64,7,1, + 0x7f,0x64,7,1, 0x7f,0x64,7,1, 0x7f,0x64,7,1, 0x7f,0x64,7,1, + 0x7f,0x64,7,1, 0x7f,0x64,7,1, 0x7f,0x64,7,1, 0x7f,0x64,7,1 /* 84-87 */ +}; + +static guint8 default_partial_reserve[9] = { /* MT-32 DEFAULT */ + 3, 10, 6, 4, 3, 0, 0, 0, 6 }; + +static struct { + guint8 mode; + guint8 time; + guint8 level; +} mt32_reverb[11]; + + +static int +midiout_write_block(byte *buf, int len, int delta) +{ + if (delta) + midi_writer->delay(midi_writer, delta); + + return midi_writer->write(midi_writer, buf, len); +} + +/* The following is the result of some experimenting, trying to approach the MT32's processing speed */ +#define MAGIC_MIDIOUT_DELAY 40 + +static int +midiout_write_delayed_block(byte *buf, int len) + /* Only used for initial programming */ +{ + int rv = midiout_write_block(buf, len, 0); + int delay = 1 + (len / MAGIC_MIDIOUT_DELAY); + + midi_writer->delay(midi_writer, delay); + + mt32_init_delay += delay; /* Keep track of delay times */ + + return rv; +} + +/* send default rhythm map and reserve */ +int midi_mt32_defaults(guint8 volume, guint8 reverb) { + printf("MT-32: Writing Default Rhythm key map\n"); + midi_mt32_poke(0x030110, default_rhythm_keymap, 256); + + printf("MT-32: Writing Default Partial Reserve\n"); + midi_mt32_poke(0x100004, default_partial_reserve, 9); + + if (reverb) { + mt32_reverb[0].mode = 0; + mt32_reverb[0].time = 5; + mt32_reverb[0].level = 3; + default_reverb = 0; + + printf("MT-32: Setting up default reverb levels\n"); + midi_mt32_reverb(default_reverb); + } + + if (volume) { + printf("MT-32: Setting default volume (%d)\n", volume); + midi_mt32_volume(volume); + } + + return SFX_OK; +} + +int midi_mt32_open(int length, byte *data, int length2, byte *data2, void *dev) +{ + guint8 unknown_sysex[6] = {0x16, 0x16, 0x16, 0x16, 0x16, 0x16}; + guint8 i, memtimbres; + unsigned int block2, block3; + + if (!dev) { + fprintf(stderr, "Attempt to use MT-32 sequencer without device\n"); + return SFX_ERROR; + } + + sci_gettime(&mt32_init_sec, &mt32_init_usec); + + midi_writer = (midi_writer_t *) dev; + + midi_mt32_allstop(); + + if (!data) { + type = SFX_MAP_UNKNOWN; + sciprintf("MT-32: No patch.001 found, using defaults\n"); + } else { + type = sfx_instrument_map_detect(data, length); + if (type == SFX_MAP_UNKNOWN) + sciprintf("MT-32: Unknown patch.001 format, using defaults\n"); + else + sciprintf("MT-32: Programming Roland MT-32 with patch.001 (v%i) %d bytes\n", type, length); + } + + if (type == SFX_MAP_MT32) { + /* Display MT-32 initialization message */ + printf("MT-32: Displaying Text: \"%.20s\"\n", data + 20); + midi_mt32_poke(0x200000, data + 20, 20); + + /* Cache a copy of the shutdown message */ + memcpy(shutdown_msg, data + 40, 20); + + /* Write Patches (48 or 96) */ + memtimbres = data[491]; + block2 = (memtimbres * 246) + 492; + printf("MT-32: Writing Patches #01 - #32\n"); + midi_mt32_poke(0x050000, data + 107, 256); + if ((length > block2) && + data[block2] == 0xAB && + data[block2 + 1] == 0xCD) { + printf("MT-32: Writing Patches #33 - #64\n"); + midi_mt32_poke_gather(0x050200, data + 363, 128, data + block2 + 2, 128); + printf("MT-32: Writing Patches #65 - #96\n"); + midi_mt32_poke(0x050400, data + block2 + 130, 256); + block3 = block2 + 386; + } else { + printf("MT-32: Writing Patches #33 - #48\n"); + midi_mt32_poke(0x050200, data + 363, 128); + block3 = block2; + } + /* Write Memory Timbres */ + for (i = 0; i < memtimbres; i++) { + printf("MT-32: Writing Memory Timbre #%02d: \"%.10s\"\n", + i + 1, data + 492 + i * 246); + midi_mt32_poke(0x080000 + (i << 9), data + 492 + i * 246, 246); + } + /* Write Rhythm key map and Partial Reserve */ + if ((length > block3) && + data[block3] == 0xDC && + data[block3 + 1] == 0xBA) { + printf("MT-32: Writing Rhythm key map\n"); + midi_mt32_poke(0x030110, data + block3 + 2, 256); + printf("MT-32: Writing Partial Reserve\n"); + midi_mt32_poke(0x100004, data + block3 + 258, 9); + } else { + midi_mt32_defaults(0,0); /* send default keymap/reserve */ + } + /* Display MT-32 initialization done message */ + printf("MT-32: Displaying Text: \"%.20s\"\n", data); + midi_mt32_poke(0x200000, data, 20); + /* Write undocumented MT-32(?) SysEx */ + printf("MT-32: Writing {F0 41 10 16 12 52 00 0A 16 16 16 16 16 16 20 F7}\n"); + midi_mt32_poke(0x52000A, unknown_sysex, 6); + printf("MT-32: Setting up reverb levels\n"); + default_reverb = data[0x3e]; + memcpy(mt32_reverb,data+ 0x4a, 3 * 11); + midi_mt32_reverb(default_reverb); + printf("MT-32: Setting default volume (%d)\n", data[0x3c]); + midi_mt32_volume(data[0x3c]); + return 0; + } else if (type == SFX_MAP_MT32_GM) { + printf("MT-32: Loading SysEx bank\n"); + midi_mt32_write_block(data + 1155, (data[1154] << 8) + data[1153]); + return 0; + } else { + midi_mt32_poke(0x200000, (guint8 *)" FreeSCI Rocks! ", 20); + return midi_mt32_defaults(0x0c,1); /* send defaults in absence of patch data */ + } + return -1; +} + +int midi_mt32_close(void) +{ + midi_mt32_allstop(); + if (type == 0) { + printf("MT-32: Displaying Text: \"%.20s\"\n", shutdown_msg); + midi_mt32_poke(0x200000, (unsigned char *) shutdown_msg, 20); + } + midi_writer->close(midi_writer); + return SFX_OK; +} + +int midi_mt32_volume(guint8 volume) +{ + volume &= 0x7f; /* (make sure it's not over 127) */ + if (midi_mt32_poke(0x100016, &volume, 1) < 0) + return -1; + + return 0; +} + +int midi_mt32_allstop(void) +{ + byte buf[4]; + int i; + + buf[0] = 0x7b; + buf[1] = 0; + buf[2] = 0; + + for (i = 0; i < 16; i++) { + midi_mt32_event((guint8)(0xb0 | i), 2, buf); + } + + return 0; +} + +int midi_mt32_reverb(int param) +{ + guint8 buffer[3]; + + if (param == -1) + param = default_reverb; + + printf("MT-32: Sending reverb # %d (%d, %d, %d)\n",param, mt32_reverb[param].mode, + mt32_reverb[param].time, + mt32_reverb[param].level); + + buffer[0] = mt32_reverb[param].mode; + buffer[1] = mt32_reverb[param].time; + buffer[2] = mt32_reverb[param].level; + midi_mt32_poke(0x100001, buffer, 3); + + return 0; +} + + +static int +midi_mt32_poke(guint32 address, guint8 *data, unsigned int count) +{ + guint8 checksum = 0; + unsigned int i; + + if (count > 256) return -1; + + checksum -= (sysex_buffer[5] = (char)((address >> 16) & 0x7F)); + checksum -= (sysex_buffer[6] = (char)((address >> 8) & 0x7F)); + checksum -= (sysex_buffer[7] = (char)(address & 0x7F)); + + for (i = 0; i < count; i++) + checksum -= (sysex_buffer[i + 8] = data[i]); + + sysex_buffer[count + 8] = checksum & 0x7F; + sysex_buffer[count + 9] = 0xF7; + + midiout_write_delayed_block(sysex_buffer, count + 10); + if (midi_writer->flush) + midi_writer->flush(midi_writer); + midi_mt32_sysex_delay(); + + return count + 10; + +} + +static int +midi_mt32_poke_gather(guint32 address, guint8 *data1, unsigned int count1, + guint8 *data2, unsigned int count2) +{ + guint8 checksum = 0; + unsigned int i; + + if ((count1 + count2) > 256) return -1; + + checksum -= (sysex_buffer[5] = (char)((address >> 16) & 0x7F)); + checksum -= (sysex_buffer[6] = (char)((address >> 8) & 0x7F)); + checksum -= (sysex_buffer[7] = (char)(address & 0x7F)); + + for (i = 0; i < count1; i++) + checksum -= (sysex_buffer[i + 8] = data1[i]); + for (i = 0; i < count2; i++) + checksum -= (sysex_buffer[i + 8 + count1] = data2[i]); + + sysex_buffer[count1 + count2 + 8] = checksum & 0x7F; + sysex_buffer[count1 + count2 + 9] = 0xF7; + + midiout_write_delayed_block(sysex_buffer, count1 + count2 + 10); + if (midi_writer->flush) + midi_writer->flush(midi_writer); + midi_mt32_sysex_delay(); + return count1 + count2 + 10; +} + + +static int +midi_mt32_write_block(guint8 *data, unsigned int count) +{ + unsigned int block_start = 0; + unsigned int i = 0; + + while (i < count) { + if ((data[i] == 0xF0) && (i != block_start)) { + midiout_write_delayed_block(data + block_start, i - block_start); + block_start = i; + } + if (data[i] == 0xF7) { + midiout_write_delayed_block(data + block_start, i - block_start + 1); + midi_mt32_sysex_delay(); + block_start = i + 1; + } + i++; + } + if (count >= block_start) { + if (midiout_write_delayed_block(data + block_start, count - block_start + ) != (count - block_start)) { + fprintf(stderr, "midi_mt32_write_block(): midiout_write_block failed!\n"); + return 1; + } + } + + return 0; +} + +static int +midi_mt32_sysex_delay(void) +{ + /* Under Win32, we won't get any sound, in any case... */ +#ifdef HAVE_USLEEP + usleep(320 * 63); /* One MIDI byte is 320us, 320us * 63 > 20ms */ +#elif defined (_WIN32) + Sleep(((320 * 63) / 1000) + 1); +#elif defined (__BEOS__) + snooze(320 * 63); +#else + sleep(1); +#endif + return 0; +} + +static int +midi_mt32_event(byte command, int argc, byte *argv) +{ + byte buf[8]; + + buf[0] = command; + memcpy(buf + 1, argv, argc); + + midiout_write_block(buf, argc + 1, delta); + delta = 0; + + return SFX_OK; +} + + +static void +delay_init(void) +{/* Wait for MT-32 initialisation to complete */ + long endsec = mt32_init_sec, uendsec = mt32_init_usec; + long sec, usec; + int loopcount = 0; + + uendsec += (mt32_init_delay * 100000) / 6; /* mt32_init_delay is in ticks (1/60th seconds), uendsecs in microseconds */ + endsec += uendsec / 1000000; + uendsec %= 1000000; + + + do { + if (loopcount == 1) + sciprintf("Waiting for MT-32 programming to complete...\n"); + + sci_gettime(&sec, &usec); + sleep(1); /* Idle a bit */ + ++loopcount; + } while ((sec < endsec) || ((sec == endsec) && (usec < uendsec))); + +} + +static int +midi_mt32_reset_timer(GTimeVal ts) +{ + if (mt32_init_delay) { /* We might still have to wait for initialisation to complete */ + delay_init(); + mt32_init_delay = 0; + } + + + midi_writer->reset_timer(midi_writer); + return SFX_OK; +} + + +static int +midi_mt32_delay(int ticks) +{ + delta += ticks; /* Accumulate, write before next command */ + return SFX_OK; +} + +static int +midi_mt32_set_option(char *name, char *value) +{ + return SFX_ERROR; /* No options are supported at this time */ +} + +/* the driver struct */ + +sfx_sequencer_t sfx_sequencer_mt32 = { + "MT32", + "0.1", + SFX_DEVICE_MIDI, /* No device dependancy-- fixme, this might becomde ossseq */ + &midi_mt32_set_option, + &midi_mt32_open, + &midi_mt32_close, + &midi_mt32_event, + &midi_mt32_delay, + &midi_mt32_reset_timer, + &midi_mt32_allstop, + &midi_mt32_volume, + &midi_mt32_reverb, + 001, /* patch.001 */ + SFX_SEQ_PATCHFILE_NONE, + 0x01, /* playflag */ + 1, /* do play channel 9 */ + 32, /* Max polyphony */ + 0 /* Does not require any write-ahead by its own */ +}; diff --git a/engines/sci/sfx/seq/oss-adlib.c b/engines/sci/sfx/seq/oss-adlib.c new file mode 100644 index 0000000000..0406556f56 --- /dev/null +++ b/engines/sci/sfx/seq/oss-adlib.c @@ -0,0 +1,374 @@ +/*************************************************************************** + oss-adlib.c Copyright (C) 2001 Solomon Peachy, 03,04 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. + +***************************************************************************/ + +#include <stdio.h> +#ifdef HAVE_UNISTD_H +# include <unistd.h> +#endif +#include "../sequencer.h" + +#ifdef HAVE_SYS_SOUNDCARD_H + +#include "../adlib.h" + +#include <sys/ioctl.h> +#include <sys/time.h> +#include <sys/soundcard.h> +#include <sfx_iterator.h> /* for some MIDI information */ + +#if 1 +SEQ_DEFINEBUF(2048); +static int seqfd; +#else +extern unsigned char _seqbuf[2048]; +extern int _seqbuflen; +extern int _seqbufptr; +extern int seqfd; +#endif + +static guint8 instr[MIDI_CHANNELS]; +static int dev; +static int free_voices = ADLIB_VOICES; +static long note_time[ADLIB_VOICES]; +static unsigned char oper_note[ADLIB_VOICES]; +static unsigned char oper_chn[ADLIB_VOICES]; + +#if 1 +void seqbuf_dump(void) /* OSS upcall */ +{ + if (_seqbufptr) + if (write(seqfd, _seqbuf, _seqbufptr) == -1) { + perror("ADLIB write "); + exit(-1); + } + _seqbufptr = 0; +} +#endif + +/* initialise note/operator lists, etc. */ +void adlib_init_lists(void) +{ + int i; + for(i = 0 ; i < ADLIB_VOICES ; i++) { + oper_note[i] = 255; + oper_chn[i] = 255; + note_time[i] = 0; + } + free_voices = ADLIB_VOICES; +} + +int adlib_stop_note(int chn, int note, int velocity) +{ + int i, op=255; + + for (i=0;i<ADLIB_VOICES && op==255;i++) { + if (oper_chn[i] == chn) + if (oper_note[i] == note) + op=i; + } + + if (op==255) { + printf ("can't stop.. chn %d %d %d\n", chn, note, velocity); + return 255; /* not playing */ + } + + SEQ_STOP_NOTE(dev, op, note, velocity); + SEQ_DUMPBUF(); + + oper_chn[op] = 255; + oper_note[op] = 255; + note_time[op] = 0; + + free_voices++; + + return op; +} + +int adlib_kill_one_note(int chn) +{ + int oldest = 255, i = 255; + long time = 0; + + if (free_voices >= ADLIB_VOICES) { + printf("Free list empty but no notes playing\n"); + return 255; + } /* No notes playing */ + + for (i = 0; i < ADLIB_VOICES ; i++) { + if (oper_chn[i] != chn) + continue; + if (note_time[i] == 0) + continue; + if (time == 0) { + time = note_time[i]; + oldest = i; + continue; + } + if (note_time[i] < time) { + time = note_time[i]; + oldest = i; + } + } + + /* printf("Killing chn %d, oper %d\n", chn, oldest); */ + + if (oldest == 255) + return 255; /* Was already stopped. Why? */ + + SEQ_STOP_NOTE(dev, oldest, oper_note[oldest], 0); + SEQ_DUMPBUF(); + + oper_chn[oldest] = 255; + oper_note[oldest] = 255; + note_time[oldest] = 0; + free_voices++; + + return oldest; +} + +static void +adlib_start_note(int chn, int note, int velocity) +{ + int free; + struct timeval now; + + if (velocity == 0) { + adlib_stop_note(chn, note, velocity); + return; + } + + gettimeofday(&now, NULL); + + if (free_voices <= 0) + free = adlib_kill_one_note(chn); + else + for (free = 0; free < ADLIB_VOICES ; free++) + if (oper_chn[free] == 255) + break; + + /* printf("play operator %d/%d: %d %d %d\n", free, free_voices, chn, note, velocity); */ + + oper_chn[free] = chn; + oper_note[free] = note; + note_time[free] = now.tv_sec * 1000000 + now.tv_usec; + free_voices--; + + SEQ_SET_PATCH(dev, free, instr[chn]); + SEQ_START_NOTE(dev, free, note, velocity); + SEQ_DUMPBUF(); +} + +static int +midi_adlib_open(int data_length, byte *data_ptr, int data2_length, + byte *data2_ptr, void *seq) +{ + int nrdevs, i, n; + struct synth_info info; + struct sbi_instrument sbi; + + if (data_length < 1344) { + printf ("invalid patch.003"); + return -1; + } + + for (i = 0; i < 48; i++) + make_sbi((adlib_def *)(data_ptr+(28 * i)), adlib_sbi[i]); + + if (data_length > 1344) + for (i = 48; i < 96; i++) + make_sbi((adlib_def *)(data_ptr+2+(28 * i)), adlib_sbi[i]); + + memset(instr, 0, sizeof(instr)); + + if (!IS_VALID_FD(seqfd=open("/dev/sequencer", O_WRONLY, 0))) { + perror("/dev/sequencer"); + return(-1); + } + if (ioctl(seqfd, SNDCTL_SEQ_NRSYNTHS, &nrdevs) == -1) { + perror("/dev/sequencer"); + return(-1); + } + for (i=0;i<nrdevs && dev==-1;i++) { + info.device = i; + if (ioctl(seqfd, SNDCTL_SYNTH_INFO, &info)==-1) { + perror("info: /dev/sequencer"); + return(-1); + } + if (info.synth_type == SYNTH_TYPE_FM) + dev = i; + } + if (dev == -1) { + fprintf(stderr, "ADLIB: FM synthesizer not detected\n"); + return(-1); + } + + /* free_voices = info.nr_voices; */ + adlib_init_lists(); + + printf("ADLIB: Loading patches into synthesizer\n"); + sbi.device = dev; + sbi.key = FM_PATCH; + for (i = 0; i < 96; i++) { + for (n = 0; n < 32; n++) + memcpy(sbi.operators, &adlib_sbi[i], sizeof(sbi_instr_data)); + sbi.channel=i; + SEQ_WRPATCH(&sbi, sizeof(sbi)); + SEQ_DUMPBUF(); + } + SEQ_START_TIMER(); + SEQ_SET_TEMPO(60); + SEQ_DUMPBUF(); + return 0; +} + + +static int +midi_adlib_close(void) +{ + SEQ_DUMPBUF(); + return close(seqfd); +} + + +static int +midi_adlib_allstop(void) +{ + int i; + for (i = 0; i < ADLIB_VOICES ; i++) { + if (oper_chn[i] == 255) + continue; + adlib_stop_note(oper_chn[i], oper_note[i], 0); + } + adlib_init_lists(); + + return 0; +} + +static int +midi_adlib_reverb(int param) +{ + printf("reverb NYI %04x \n", param); + return 0; +} + +static inline int +midi_adlib_event1(guint8 command, guint8 note, guint8 velocity) +{ + guint8 channel, oper; + + channel = command & 0x0f; + oper = command & 0xf0; + + switch (oper) { + case 0x80: + adlib_stop_note(channel, note, velocity); + return 0; + case 0x90: + adlib_start_note(channel,note,velocity); + return 0; + case 0xe0: /* Pitch bend needs scaling? */ + SEQ_BENDER(dev, channel, ((note << 8) & velocity)); + SEQ_DUMPBUF(); + break; + case 0xb0: /* CC changes. we ignore. */ + /* XXXX we need to parse out 0x07 volume, at least. */ + return 0; + case 0xd0: /* aftertouch */ + SEQ_CHN_PRESSURE(dev, channel, note); + SEQ_DUMPBUF(); + return 0; + default: + printf("ADLIB: Unknown event %02x\n", command); + return 0; + } + + SEQ_DUMPBUF(); + return 0; +} + +static inline int +midi_adlib_event2(guint8 command, guint8 param) +{ + guint8 channel; + guint8 oper; + + channel = command & 0x0f; + oper = command & 0xf0; + switch (oper) { + case 0xc0: { /* change instrument */ + int inst = param; + instr[channel] = inst; /* XXXX offset? */ + // SEQ_SET_PATCH(dev, channel, inst); + // SEQ_DUMPBUF(); + return 0; + } + default: + printf("ADLIB: Unknown event %02x\n", command); + } + + SEQ_DUMPBUF(); + return 0; +} + +static int +midi_adlib_event(byte command, int argc, byte *argv) +{ + if (argc > 1) + return midi_adlib_event1(command, argv[0], argv[1]); + else + return midi_adlib_event2(command, argv[0]); +} + +static int +midi_adlib_delay(int ticks) +{ + SEQ_DELTA_TIME(ticks); + return SFX_OK; +} + +static int +midi_adlib_set_option(char *name, char *value) +{ + return SFX_ERROR; /* No options are supported at this time */ +} + +/* the driver struct */ + +sfx_sequencer_t sfx_sequencer_oss_adlib = { + "adlib", + "0.1", + SFX_DEVICE_NONE, /* No device dependancy-- fixme, this might become ossseq */ + &midi_adlib_set_option, + &midi_adlib_open, + &midi_adlib_close, + &midi_adlib_event, + &midi_adlib_delay, + NULL, + &midi_adlib_allstop, + NULL, + &midi_adlib_reverb, + 003, /* patch.003 */ + SFX_SEQ_PATCHFILE_NONE, + 0x04, /* playflag */ + 0, /* do not play channel 9 */ + ADLIB_VOICES, /* Max polyphony */ + 0 /* Does not require any write-ahead by its own */ +}; + +#endif /* HAVE_SYS_SOUNDCARD_H */ diff --git a/engines/sci/sfx/seq/sequencers.c b/engines/sci/sfx/seq/sequencers.c new file mode 100644 index 0000000000..cb43381fa9 --- /dev/null +++ b/engines/sci/sfx/seq/sequencers.c @@ -0,0 +1,68 @@ +/*************************************************************************** + sequencers.c Copyright (C) 2004 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> + +***************************************************************************/ + +#include "../sequencer.h" +#include <resource.h> + +#ifndef SCUMMVM +extern sfx_sequencer_t sfx_sequencer_gm; +extern sfx_sequencer_t sfx_sequencer_mt32; +#ifdef HAVE_SYS_SOUNDCARD_H +extern sfx_sequencer_t sfx_sequencer_oss_adlib; +#endif +#endif // SCUMMVM + +sfx_sequencer_t *sfx_sequencers[] = { +#ifndef SCUMMVM + &sfx_sequencer_gm, + &sfx_sequencer_mt32, +#ifdef HAVE_SYS_SOUNDCARD_H + &sfx_sequencer_oss_adlib, +#endif +#endif // SCUMMVM + NULL +}; + + +sfx_sequencer_t * +sfx_find_sequencer(char *name) +{ + if (!name) { + /* Implement default policy for your platform (if any) here, or in a function + ** called from here (if it's non-trivial). Try to use midi_devices[0], if + ** feasible. */ + + return sfx_sequencers[0]; /* default */ + } else { + int n = 0; + while (sfx_sequencers[n] + && strcasecmp(sfx_sequencers[n]->name, name)) + ++n; + + return sfx_sequencers[n]; + } +} diff --git a/engines/sci/sfx/sequencer.h b/engines/sci/sfx/sequencer.h new file mode 100644 index 0000000000..586e4b3e51 --- /dev/null +++ b/engines/sci/sfx/sequencer.h @@ -0,0 +1,142 @@ +/*************************************************************************** + sfx_sequencer.h, from + midi_device.h Copyright (C) 2001 Solomon Peachy + Copytight (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. + +***************************************************************************/ + + +#ifndef _SFX_SEQUENCER_H_ +#define _SFX_SEQUENCER_H_ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ +#include <sfx_core.h> +#include <stdio.h> +#include "device.h" +#include <scitypes.h> + +#define SFX_SEQ_PATCHFILE_NONE -1 + +typedef struct _sfx_sequencer { + const char *name; /* Sequencer name */ + const char *version; /* Sequencer version */ + + int device; /* Type of device the sequencer depends on, may be SFX_DEVICE_NONE. */ + + int + (*set_option)(char *name, char *value); + /* Sets an option for the sequencing mechanism + ** Parameters: (char *) name: The name describing what to set + ** (char *) value: The value to set + ** Returns : (int) SFX_OK, or SFX_ERROR if the name wasn't understood + */ + + int + (*open)(int patch_len, byte *patch, int patch2_len, byte *patch2, void *device); + /* Opens the sequencer for writing + ** Parameters: (int) patch_len, patch2_len: Length of the patch data + ** (byte *) patch, patch2: Bulk patch data + ** (void *) device: A device matching the 'device' property, or NULL + ** if the property is null. + ** Returns : (int) SFX_OK on success, SFX_ERROR otherwise + ** The device should be initialized to a tick frequency of 60 Hz. + ** 'patch' and 'patch_len' refer to the patch resource passed to open, + ** as specified by the 'patchfile' property. 'patch' may be NULL if the + ** resource wasn't found. + ** For more information regarding patch resources, please refer to the + ** FreeSCI documentation, particularly the part regarding 'patch.*' resource + ** data. + */ + + int (*close)(void); + /* Closes the sequencer + ** Returns : SFX_OK on success, SFX_ERROR otherwise + */ + + int (*event)(byte command, int argc, byte *argv); + /* Plays a MIDI event + ** Parameters: (byte) command: MIDI command to play + ** (int) argc: Number of arguments to the command + ** (byte *) argv: Pointer to additional arguments + ** Returns : SFX_OK on success, SFX_ERROR otherwise + ** argv is guaranteed to point to a sufficiently large number of + ** arguments, as indicated by 'command' and the MIDI standard. + ** No 'running status' will be passed, 'command' will always be + ** explicit. + */ + int (*delay)(int ticks); /* OPTIONAL -- may be NULL, but highly recommended */ + /* Inserts a delay (delta time) into the sequencer cue + ** Parameters: (int) ticks: Number of 60 Hz ticks to delay + ** Returns : SFX_OK on success, SFX_ERROR otherwise + */ + + int (*reset_timer)(GTimeVal ts); + /* OPTIONAL -- may be NULL, but highly recommended in combination with delay() */ + /* Resets the timer counter associated with the 'delay()' function + ** Parameters: (GTimeVal) ts: Timestamp of the base time + ** Returns : SFX_OK on success, SFX_ERROR otherwise + */ + + int (*allstop)(void); /* OPTIONAL -- may be NULL */ + /* Stops playing everything in the sequencer queue + ** Returns : SFX_OK on success, SFX_ERROR otherwise + */ + + int (*volume)(guint8 volume); /* OPTIONAL -- can be NULL */ + /* Sets the sequencer volume + ** Parameters; (byte) volume: The volume to set, with 0 being mute and 127 full volume + ** Returns : SFX_OK on success, SFX_ERROR otherwise + */ + + int (*reverb)(int param); /* OPTIONAL -- may be NULL */ + /* Sets the device reverb + ** Parameters; (int) param: The reverb to set + ** Returns : SFX_OK on success, SFX_ERROR otherwise + */ + + int patchfile, patchfile2; /* Patch resources to pass into the call to open(), + ** if present, or SFX_SEQ_PATCHFILE_NONE */ + guint8 playmask; /* SCI 'playflag' mask to determine which SCI song channels + ** this sequencer should play */ + /* 0x01 -- MT-32 + ** 0x02 -- Yamaha FB-01 + ** 0x04 -- CMS or Game Blaster + ** 0x08 -- Casio MT540 or CT460 + ** 0x10 -- Tandy 3-voice + ** 0x20 -- PC speaker + */ + guint8 play_rhythm; /* Plays the rhythm channel? */ + gint8 polyphony; /* Device polyphony (# of voices) */ + + int min_write_ahead_ms; /* Minimal write-ahead, in milliseconds */ + /* Note that write-ahead is tuned automatically; this enforces a lower limit */ + +} sfx_sequencer_t; + + +sfx_sequencer_t * +sfx_find_sequencer(char *name); +/* Finds a sequencer by name +** Parameters: (char *) name: Name of the sequencer to look up, or NULL for default +** Returns : (sfx_sequencer_t *) The sequencer of matching name, or NULL +** if not found +*/ + + +#endif /* _SFX_SEQUENCER_H_ */ diff --git a/engines/sci/sfx/softseq.h b/engines/sci/sfx/softseq.h new file mode 100644 index 0000000000..38efac6d59 --- /dev/null +++ b/engines/sci/sfx/softseq.h @@ -0,0 +1,134 @@ +/*************************************************************************** + sfx_softseq.h Copyright (C) 2004 Christoph Reichenbach + + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public Licence as + published by the Free Software Foundaton; either version 2 of the + Licence, or (at your option) any later version. + + It is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + merchantibility or fitness for a particular purpose. See the + GNU General Public Licence for more details. + + You should have received a copy of the GNU General Public Licence + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + + Please contact the maintainer for any program-related bug reports or + inquiries. + + Current Maintainer: + + Christoph Reichenbach (CR) <jameson@linuxgames.com> + +***************************************************************************/ + +#ifndef SFX_SOFTSEQ_H_ +#define SFX_SOFTSEQ_H_ + +#include <sfx_core.h> +#include <sfx_pcm.h> +#include "sequencer.h" +#include <resource.h> + + +/* Software sequencer */ +typedef struct sfx_softseq { + const char *name; + const char *version; + + int + (*set_option)(struct sfx_softseq *self, char *name, char *value); + /* Sets an option for the sequencer + ** Parameters: (sfx_softseq_t *) self: Self reference + ** (char *) name: Name of the option to set + ** (char *0 value: Value to set the option to + ** Returns : (int) GFX_OK on success, or GFX_ERROR if not supported + */ + + int + (*init)(struct sfx_softseq *self, byte *res_data, int res_size, + byte *res2_data, int res2_size); + /* Initialises the sequencer + ** Parameters: (sfx_softseq_t *) self: Self reference + ** (byte *) res_data: Resource data for 'patch_nr' (see below) + ** (int) res_size: Number of bytes in 'res_data' + ** (byte *) res2_data: Resource data for 'patch2_nr' (see below) + ** (int) res2_size: Number of bytes in 'res2_data' + ** Returns : (int) SFX_OK on success, SFX_ERROR otherwise + ** Note that 'res_data' is only a valid pointer for this call. If the + ** data is needed later during execution, it should be backed up internally. + ** If the requested resource is not available, res_data will be NULL + ** /even if/ patch_nr is set. + */ + + void + (*exit)(struct sfx_softseq *self); + /* Uninitialises the sequencer and frees all used resources + ** Parameters: (sfx_softseq_t *) self: Self reference + */ + + void + (*set_volume)(struct sfx_softseq *self, int new_volume); + /* Sets the sequencer volume + ** Parameters: (sfx_softseq_t *) self: Self reference + ** (int) new_volume: A volume, between 0 (quiet) and 127 (max) + */ + + void + (*handle_command)(struct sfx_softseq *self, byte cmd, int argc, byte *argv); + /* Handle a MIDI command + ** Parameters: (sfx_softseq_t *) self: Self reference + ** (byte) cmd: Basic MIDI command, always includes command and channel + ** (int) argc: Number of additional arguments to this command + ** (byte *) argv: Additional arguments to 'cmd' + */ + + void + (*poll)(struct sfx_softseq *self, byte *dest, int len); + /* Asks the software sequencer to fill in parts of a buffer + ** Parameters: (sfx_softseq_t *) self: Self reference + ** (int) len: Number of _frames_ to write + ** Returns : (byte) *dest: 'len' frames must be written to this buffer + */ + + void + (*allstop)(struct sfx_softseq *self); + /* Stops all sound generation + ** Parameters: (sfx_softseq_t *) self: Self reference + */ + + void *internal; /* Internal data, may be used by sfx_softseq_t inmplementors */ + + int patch_nr; /* Number of the first patch file associated with this sequencer, + ** or SFX_SEQ_PATCHFILE_NONE */ + int patch2_nr; /* Number of the second patch file associated with this sequencer, + ** or SFX_SEQ_PATCHFILE_NONE */ + int playmask; /* playflag identifying the device emulated */ + /* 0x01 -- MT-32 + ** 0x02 -- Yamaha FB-01 + ** 0x04 -- CMS or Game Blaster + ** 0x08 -- Casio MT540 or CT460 + ** 0x10 -- Tandy 3-voice + ** 0x20 -- PC speaker + */ + int play_rhythm; /* Whether the rhythm channel (9) should be played */ + int polyphony; /* Number of voices played */ + + sfx_pcm_config_t pcm_conf; /* Setup of the channel the sequencer writes to */ + +} sfx_softseq_t; + + +sfx_softseq_t * +sfx_find_softseq(char *name); +/* Finds a given or default software sequencer +** Parameters: (char *) name: Name of the sequencer to look up, or NULL for default +** Returns : (sfx_softseq_t *) The requested sequencer, or NULL if not found +*/ + +#endif /* !defined(SFX_SOFTSEQ_H_) */ diff --git a/engines/sci/sfx/softseq/Makefile.am b/engines/sci/sfx/softseq/Makefile.am new file mode 100644 index 0000000000..af7f8ca2c9 --- /dev/null +++ b/engines/sci/sfx/softseq/Makefile.am @@ -0,0 +1,5 @@ +noinst_LIBRARIES = libscisoftseq.a +INCLUDES = -I$(top_srcdir)/src/include @EXTRA_INCLUDES@ +libscisoftseq_a_SOURCES = softsequencers.c pcspeaker.c SN76496.c fmopl.c opl2.c amiga.c \ + fluidsynth.c +EXTRA_DIST = fmopl.h diff --git a/engines/sci/sfx/softseq/SN76496.c b/engines/sci/sfx/softseq/SN76496.c new file mode 100644 index 0000000000..cb32b89f0c --- /dev/null +++ b/engines/sci/sfx/softseq/SN76496.c @@ -0,0 +1,248 @@ +/*************************************************************************** + SN76496.c Copyright (C) 2004 Christoph Reichenbach + + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public Licence as + published by the Free Software Foundaton; either version 2 of the + Licence, or (at your option) any later version. + + It is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + merchantibility or fitness for a particular purpose. See the + GNU General Public Licence for more details. + + You should have received a copy of the GNU General Public Licence + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + + Please contact the maintainer for any program-related bug reports or + inquiries. + + Current Maintainer: + + Christoph Reichenbach (CR) <jameson@linuxgames.com> + +***************************************************************************/ +/* Tandy/PCJr sequencer for FreeSCI */ + +#include "../softseq.h" +#include <sci_midi.h> + +#define FREQUENCY 44100 +#define CHANNELS_NR 3 +#define VOLUME_SHIFT 3 + +static int global_volume = 100; /* Base volume */ +static int volumes[CHANNELS_NR] = { 100, 100, 100 }; +static int notes[CHANNELS_NR] = {0, 0, 0}; /* Current halftone, or 0 if off */ +static int freq_count[CHANNELS_NR] = {0, 0, 0}; +static int channel_assigner = 0; +static int channels_assigned = 0; +static int chan_nrs[CHANNELS_NR] = {-1, -1, -1}; + +extern sfx_softseq_t sfx_softseq_pcspeaker; +/* Forward-declare the sequencer we are defining here */ + + +static int +SN76496_set_option(sfx_softseq_t *self, char *name, char *value) +{ + return SFX_ERROR; +} + +static int +SN76496_init(sfx_softseq_t *self, byte *patch, int patch_len, byte *patch2, + int patch2_len) +{ + return SFX_OK; +} + +static void +SN76496_exit(sfx_softseq_t *self) +{ +} + +static void +SN76496_event(sfx_softseq_t *self, byte command, int argc, byte *argv) +{ + int i; + int chan = -1; +#if 0 + fprintf(stderr, "Note [%02x : %02x %02x]\n", command, argc?argv[0] : 0, (argc > 1)? argv[1] : 0); +#endif + if ((command & 0xe0) == 0x80) { + int chan_nr = command & 0xf; + + /* First, test for channel having been assigned already */ + if (channels_assigned & (1 << chan_nr)) { + /* Already assigned this channel number: */ + for (i = 0; i < CHANNELS_NR; i++) + if (chan_nrs[i] == chan_nr) { + chan = i; + break; + } + } else { + /* Assign new channel round-robin */ + + /* Mark channel as unused: */ + if (chan_nrs[channel_assigner] >= 0) + channels_assigned &= ~(1 << chan_nrs[channel_assigner]); + + /* Remember channel: */ + chan_nrs[channel_assigner] = chan_nr; + /* Mark channel as used */ + channels_assigned |= (1 << chan_nrs[channel_assigner]); + + /* Save channel for use later in this call: */ + chan = channel_assigner; + /* Round-ropin iterate channel assigner: */ + channel_assigner = (channel_assigner + 1) % CHANNELS_NR; + } + } +#if 0 + fprintf(stderr, " --> %d [%04x], {%d,%d,%d}@%d\n", chan, + channels_assigned, chan_nrs[0],chan_nrs[1],chan_nrs[2],channel_assigner); +#endif + + switch (command & 0xf0) { + + case 0x80: + if (argv[0] == notes[chan]) + notes[chan] = 0; + break; + + case 0x90: + if (!argv[1]) { + if (argv[chan] == notes[chan]) + notes[chan] = 0; + } else { + notes[chan] = argv[0]; + volumes[chan] = argv[1]; + } + break; + + case 0xb0: + if (argv[1] == SCI_MIDI_CHANNEL_NOTES_OFF) + notes[chan] = 0; + break; + + + default: +#if DEBUG + fprintf(stderr, "[SFX:PCM-PC] Unused MIDI command %02x %02x %02x\n", command, argc?argv[0] : 0, (argc > 1)? argv[1] : 0); +#endif + break; /* ignore */ + } +} + +#define BASE_NOTE 129 /* A10 */ +#define BASE_OCTAVE 10 /* A10, as I said */ + +static int +freq_table[12] = { /* A4 is 440Hz, halftone map is x |-> ** 2^(x/12) */ + 28160, /* A10 */ + 29834, + 31608, + 33488, + 35479, + 37589, + 39824, + 42192, + 44701, + 47359, + 50175, + 53159 +}; + +static inline int +get_freq(int note) +{ + int halftone_delta = note - BASE_NOTE; + int oct_diff = ((halftone_delta + BASE_OCTAVE * 12) / 12) - BASE_OCTAVE; + int halftone_index = (halftone_delta + (12*100)) % 12 ; + int freq = (!note)? 0 : freq_table[halftone_index] / (1 << (-oct_diff)); + + return freq; +} + + +void +SN76496_poll(sfx_softseq_t *self, byte *dest, int len) +{ + gint16 *buf = (gint16 *) dest; + int i; + int chan; + int freq[CHANNELS_NR]; + + for (chan = 0; chan < CHANNELS_NR; chan++) + freq[chan] = get_freq(notes[chan]); + + for (i = 0; i < len; i++) { + int result = 0; + + for (chan = 0; chan < CHANNELS_NR; chan++) + if (notes[chan]) { + int volume = (global_volume * volumes[chan]) + >> VOLUME_SHIFT; + + freq_count[chan] += freq[chan]; + while (freq_count[chan] >= (FREQUENCY << 1)) + freq_count[chan] -= (FREQUENCY << 1); + + if (freq_count[chan] - freq[chan] < 0) { + /* Unclean rising edge */ + int l = volume << 1; + result += -volume + (l*freq_count[chan])/freq[chan]; + } else if (freq_count[chan] >= FREQUENCY + && freq_count[chan] - freq[chan] < FREQUENCY) { + /* Unclean falling edge */ + int l = volume << 1; + result += volume - (l*(freq_count[chan] - FREQUENCY))/freq[chan]; + } else { + if (freq_count[chan] < FREQUENCY) + result += volume; + else + result += -volume; + } + } + buf[i] = result; + } + +} + +void +SN76496_allstop(sfx_softseq_t *self) +{ + int i; + for (i = 0; i < CHANNELS_NR; i++) + notes[i] = 0; +} + +void +SN76496_volume(sfx_softseq_t *self, int new_volume) +{ + global_volume = new_volume; +} + + +sfx_softseq_t sfx_softseq_SN76496 = { + "SN76496", + "0.1", + SN76496_set_option, + SN76496_init, + SN76496_exit, + SN76496_volume, + SN76496_event, + SN76496_poll, + SN76496_allstop, + NULL, + SFX_SEQ_PATCHFILE_NONE, + SFX_SEQ_PATCHFILE_NONE, + 0x10, /* Tandy/PCJr channels */ + 0, /* No rhythm channel */ + 3, /* # of voices */ + {FREQUENCY, SFX_PCM_MONO, SFX_PCM_FORMAT_S16_NATIVE} +}; diff --git a/engines/sci/sfx/softseq/amiga.c b/engines/sci/sfx/softseq/amiga.c new file mode 100644 index 0000000000..12240897d7 --- /dev/null +++ b/engines/sci/sfx/softseq/amiga.c @@ -0,0 +1,658 @@ +/*************************************************************************** + amiga.c Copyright (C) 2007 Walter van Niftrik + + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public Licence as + published by the Free Software Foundaton; either version 2 of the + Licence, or (at your option) any later version. + + It is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + merchantibility or fitness for a particular purpose. See the + GNU General Public Licence for more details. + + You should have received a copy of the GNU General Public Licence + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + + Please contact the maintainer for any program-related bug reports or + inquiries. + + Current Maintainer: + + Walter van Niftrik <w.f.b.w.v.niftrik@stud.tue.nl> + +***************************************************************************/ + +#include "resource.h" +#include "sci_memory.h" +#include "../softseq.h" + +#define FREQUENCY 44100 +#define CHANNELS_NR 10 +#define HW_CHANNELS_NR 16 + +/* Samplerate of the instrument bank */ +#define BASE_FREQ 20000 + +/* Instrument looping flag */ +#define MODE_LOOP 1 << 0 +/* Instrument pitch changes flag */ +#define MODE_PITCH 1 << 1 + +#define PAN_LEFT 91 +#define PAN_RIGHT 164 + +/* #define DEBUG */ + +typedef struct envelope { + /* Phase period length in samples */ + int length; + /* Velocity delta per period */ + int delta; + /* Target velocity */ + int target; +} envelope_t; + +/* Fast decay envelope */ +static envelope_t env_decay = {FREQUENCY / (32 * 64), 1, 0}; + +typedef struct instrument { + char name[30]; + int mode; + /* Size of non-looping part in bytes */ + int size; + /* Starting offset and size of loop in bytes */ + int loop_size; + /* Transpose value in semitones */ + int transpose; + /* Envelope */ + envelope_t envelope[4]; + sbyte *samples; + sbyte *loop; +} instrument_t; + +typedef struct bank { + char name[30]; + int size; + instrument_t *instruments[256]; +} bank_t; + +typedef struct channel { + int instrument; + int note; + int note_velocity; + int velocity; + int envelope; + /* Number of samples till next envelope event */ + int envelope_samples; + int decay; + int looping; + int hw_channel; + frac_t offset; + frac_t rate; +} channel_t; + +typedef struct hw_channel { + int instrument; + int volume; + int pan; +} hw_channel_t; + +/* Instrument bank */ +static bank_t bank; +/* Internal channels */ +static channel_t channels[CHANNELS_NR]; +/* External channels */ +static hw_channel_t hw_channels[HW_CHANNELS_NR]; +/* Overall volume */ +static int volume = 127; + +/* Frequencies for every note */ +static int freq_table[] = { + 58, 62, 65, 69, 73, 78, 82, 87, + 92, 98, 104, 110, 117, 124, 131, 139, + 147, 156, 165, 175, 185, 196, 208, 220, + 234, 248, 262, 278, 294, 312, 331, 350, + 371, 393, 417, 441, 468, 496, 525, 556, + 589, 625, 662, 701, 743, 787, 834, 883, + 936, 992, 1051, 1113, 1179, 1250, 1324, 1403, + 1486, 1574, 1668, 1767, 1872, 1984, 2102, 2227, + 2359, 2500, 2648, 2806, 2973, 3149, 3337, 3535, + 3745, 3968, 4204, 4454, 4719, 5000, 5297, 5612, + 5946, 6299, 6674, 7071, 7491, 7937, 8408, 8908, + 9438, 10000, 10594, 11224, 11892, 12599, 13348, 14142, + 14983, 15874, 16817, 17817, 18877, 20000, 21189, 22449, + 23784, 25198, 26696, 28284, 29966, 31748, 33635, 35635, + 37754, 40000, 42378, 44898, 47568, 50396, 53393, 56568, + 59932, 63496, 67271, 71271, 75509, 80000, 84757, 89796 +}; + +static void +set_envelope(channel_t *channel, envelope_t *envelope, int phase) +{ + channel->envelope = phase; + channel->envelope_samples = envelope[phase].length; + + if (phase == 0) + channel->velocity = channel->note_velocity / 2; + else + channel->velocity = envelope[phase - 1].target; +} + +static inline int +interpolate(sbyte *samples, frac_t offset) +{ + int x = frac_to_int(offset); + int diff = (samples[x + 1] - samples[x]) << 8; + + return (samples[x] << 8) + frac_to_int(diff * (offset & FRAC_LO_MASK)); +} + +static void +play_instrument(gint16 *dest, channel_t *channel, int count) +{ + int index = 0; + int vol = hw_channels[channel->hw_channel].volume; + instrument_t *instrument = bank.instruments[channel->instrument]; + + while (1) { + /* Available source samples until end of segment */ + frac_t lin_avail; + int seg_end, rem, i, amount; + sbyte *samples; + + if (channel->looping) { + samples = instrument->loop; + seg_end = instrument->loop_size; + } + else { + samples = instrument->samples; + seg_end = instrument->size; + } + + lin_avail = int_to_frac(seg_end) - channel->offset; + + rem = count - index; + + /* Amount of destination samples that we will compute this iteration */ + amount = lin_avail / channel->rate; + + if (lin_avail % channel->rate) + amount++; + + if (amount > rem) + amount = rem; + + /* Stop at next envelope event */ + if ((channel->envelope_samples != -1) && (amount > channel->envelope_samples)) + amount = channel->envelope_samples; + + for (i = 0; i < amount; i++) { + dest[index++] = interpolate(samples, channel->offset) * channel->velocity / 64 * channel->note_velocity * vol / (127 * 127); + channel->offset += channel->rate; + } + + if (channel->envelope_samples != -1) + channel->envelope_samples -= amount; + + if (channel->envelope_samples == 0) { + envelope_t *envelope; + int delta, target, velocity; + + if (channel->decay) + envelope = &env_decay; + else + envelope = &instrument->envelope[channel->envelope]; + + delta = envelope->delta; + target = envelope->target; + velocity = channel->velocity - envelope->delta; + + /* Check whether we have reached the velocity target for the current phase */ + if ((delta >= 0 && velocity <= target) || (delta < 0 && velocity >= target)) { + channel->velocity = target; + + /* Stop note after velocity has dropped to 0 */ + if (target == 0) { + channel->note = -1; + break; + } else + switch (channel->envelope) { + case 0: + case 2: + /* Go to next phase */ + set_envelope(channel, instrument->envelope, channel->envelope + 1); + break; + case 1: + case 3: + /* Stop envelope */ + channel->envelope_samples = -1; + break; + } + } else { + /* We haven't reached the target yet */ + channel->envelope_samples = envelope->length; + channel->velocity = velocity; + } + } + + if (index == count) + break; + + if (frac_to_int(channel->offset) >= seg_end) { + if (instrument->mode & MODE_LOOP) { + /* Loop the samples */ + channel->offset -= int_to_frac(seg_end); + channel->looping = 1; + } else { + /* All samples have been played */ + channel->note = -1; + break; + } + } + } +} + +static void +change_instrument(int channel, int instrument) +{ +#ifdef DEBUG + if (bank.instruments[instrument]) + sciprintf("[sfx:seq:amiga] Setting channel %i to \"%s\" (%i)\n", channel, bank.instruments[instrument]->name, instrument); + else + sciprintf("[sfx:seq:amiga] Warning: instrument %i does not exist (channel %i)\n", instrument, channel); +#endif + hw_channels[channel].instrument = instrument; +} + +static void +stop_channel(int ch) +{ + int i; + + /* Start decay phase for note on this hw channel, if any */ + for (i = 0; i < CHANNELS_NR; i++) + if (channels[i].note != -1 && channels[i].hw_channel == ch && !channels[i].decay) { + /* Trigger fast decay envelope */ + channels[i].decay = 1; + channels[i].envelope_samples = env_decay.length; + break; + } +} + +static void +stop_note(int ch, int note) +{ + int channel; + instrument_t *instrument; + + for (channel = 0; channel < CHANNELS_NR; channel++) + if (channels[channel].note == note && channels[channel].hw_channel == ch && !channels[channel].decay) + break; + + if (channel == CHANNELS_NR) { +#ifdef DEBUG + sciprintf("[sfx:seq:amiga] Warning: cannot stop note %i on channel %i\n", note, ch); +#endif + return; + } + + instrument = bank.instruments[channels[channel].instrument]; + + /* Start the envelope phases for note-off if looping is on and envelope is enabled */ + if ((instrument->mode & MODE_LOOP) && (instrument->envelope[0].length != 0)) + set_envelope(&channels[channel], instrument->envelope, 2); +} + +static void +start_note(int ch, int note, int velocity) +{ + instrument_t *instrument; + int channel; + + if (hw_channels[ch].instrument < 0 || hw_channels[ch].instrument > 255) { + sciprintf("[sfx:seq:amiga] Error: invalid instrument %i on channel %i\n", hw_channels[ch].instrument, ch); + return; + } + + instrument = bank.instruments[hw_channels[ch].instrument]; + + if (!instrument) { + sciprintf("[sfx:seq:amiga] Error: instrument %i does not exist\n", hw_channels[ch].instrument); + return; + } + + for (channel = 0; channel < CHANNELS_NR; channel++) + if (channels[channel].note == -1) + break; + + if (channel == CHANNELS_NR) { + sciprintf("[sfx:seq:amiga] Warning: could not find a free channel\n"); + return; + } + + stop_channel(ch); + + if (instrument->mode & MODE_PITCH) { + int fnote = note + instrument->transpose; + + if (fnote < 0 || fnote > 127) { + sciprintf("[sfx:seq:amiga] Error: illegal note %i\n", fnote); + return; + } + + /* Compute rate for note */ + channels[channel].rate = double_to_frac(freq_table[fnote] / (double) FREQUENCY); + } + else + channels[channel].rate = double_to_frac(BASE_FREQ / (double) FREQUENCY); + + channels[channel].instrument = hw_channels[ch].instrument; + channels[channel].note = note; + channels[channel].note_velocity = velocity; + + if ((instrument->mode & MODE_LOOP) && (instrument->envelope[0].length != 0)) + set_envelope(&channels[channel], instrument->envelope, 0); + else { + channels[channel].velocity = 64; + channels[channel].envelope_samples = -1; + } + + channels[channel].offset = 0; + channels[channel].hw_channel = ch; + channels[channel].decay = 0; + channels[channel].looping = 0; +} + +static gint16 read_int16(byte *data) +{ + return (data[0] << 8) | data[1]; +} + +static gint32 read_int32(byte *data) +{ + return (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3]; +} + +static instrument_t *read_instrument(FILE *file, int *id) +{ + instrument_t *instrument; + byte header[61]; + int size; + int seg_size[3]; + int loop_offset; + int i; + + if (fread(header, 1, 61, file) < 61) { + sciprintf("[sfx:seq:amiga] Error: failed to read instrument header\n"); + return NULL; + } + + instrument = (instrument_t *) sci_malloc(sizeof(instrument_t)); + + seg_size[0] = read_int16(header + 35) * 2; + seg_size[1] = read_int16(header + 41) * 2; + seg_size[2] = read_int16(header + 47) * 2; + + instrument->mode = header[33]; + instrument->transpose = (gint8) header[34]; + for (i = 0; i < 4; i++) { + int length = (gint8) header[49 + i]; + + if (length == 0 && i > 0) + length = 256; + + instrument->envelope[i].length = length * FREQUENCY / 60; + instrument->envelope[i].delta = (gint8) header[53 + i]; + instrument->envelope[i].target = header[57 + i]; + } + /* Final target must be 0 */ + instrument->envelope[3].target = 0; + + loop_offset = read_int32(header + 37) & ~1; + size = seg_size[0] + seg_size[1] + seg_size[2]; + + *id = read_int16(header); + + strncpy(instrument->name, (char *) header + 2, 29); + instrument->name[29] = 0; +#ifdef DEBUG + sciprintf("[sfx:seq:amiga] Reading instrument %i: \"%s\" (%i bytes)\n", + *id, instrument->name, size); + sciprintf(" Mode: %02x\n", instrument->mode); + sciprintf(" Looping: %s\n", instrument->mode & MODE_LOOP ? "on" : "off"); + sciprintf(" Pitch changes: %s\n", instrument->mode & MODE_PITCH ? "on" : "off"); + sciprintf(" Segment sizes: %i %i %i\n", seg_size[0], seg_size[1], seg_size[2]); + sciprintf(" Segment offsets: 0 %i %i\n", loop_offset, read_int32(header + 43)); +#endif + instrument->samples = (sbyte *) sci_malloc(size + 1); + if (fread(instrument->samples, 1, size, file) < size) { + sciprintf("[sfx:seq:amiga] Error: failed to read instrument samples\n"); + return NULL; + } + + if (instrument->mode & MODE_LOOP) { + if (loop_offset + seg_size[1] > size) { +#ifdef DEBUG + sciprintf("[sfx:seq:amiga] Warning: looping samples extend %i bytes past end of sample block\n", + loop_offset + seg_size[1] - size); +#endif + seg_size[1] = size - loop_offset; + } + + if (seg_size[1] < 0) { + sciprintf("[sfx:seq:amiga] Error: invalid looping point\n"); + return NULL; + } + + instrument->size = seg_size[0]; + instrument->loop_size = seg_size[1]; + + instrument->loop = (sbyte*)sci_malloc(instrument->loop_size + 1); + memcpy(instrument->loop, instrument->samples + loop_offset, instrument->loop_size); + + instrument->samples[instrument->size] = instrument->loop[0]; + instrument->loop[instrument->loop_size] = instrument->loop[0]; + } else { + instrument->size = size; + instrument->samples[instrument->size] = 0; + } + + return instrument; +} + +static int +ami_set_option(sfx_softseq_t *self, char *name, char *value) +{ + return SFX_ERROR; +} + +static int +ami_init(sfx_softseq_t *self, byte *patch, int patch_len, byte *patch2, int patch2_len) +{ + FILE *file; + byte header[40]; + int i; + + file = sci_fopen("bank.001", "rb"); + + if (!file) { + sciprintf("[sfx:seq:amiga] Error: file bank.001 not found\n"); + return SFX_ERROR; + } + + if (fread(header, 1, 40, file) < 40) { + sciprintf("[sfx:seq:amiga] Error: failed to read header of file bank.001\n"); + fclose(file); + return SFX_ERROR; + } + + for (i = 0; i < 256; i++) + bank.instruments[i] = NULL; + + for (i = 0; i < CHANNELS_NR; i++) { + channels[i].note = -1; + } + + for (i = 0; i < HW_CHANNELS_NR; i++) { + hw_channels[i].instrument = -1; + hw_channels[i].volume = 127; + hw_channels[i].pan = (i % 4 == 0 || i % 4 == 3 ? PAN_LEFT : PAN_RIGHT); + } + + bank.size = read_int16(header + 38); + strncpy(bank.name, (char *) header + 8, 29); + bank.name[29] = 0; +#ifdef DEBUG + sciprintf("[sfx:seq:amiga] Reading %i instruments from bank \"%s\"\n", bank.size, bank.name); +#endif + + for (i = 0; i < bank.size; i++) { + int id; + instrument_t *instrument = read_instrument(file, &id); + + if (!instrument) { + sciprintf("[sfx:seq:amiga] Error: failed to read bank.001\n"); + fclose(file); + return SFX_ERROR; + } + + if (id < 0 || id > 255) { + sciprintf("[sfx:seq:amiga] Error: instrument ID out of bounds\n"); + return SFX_ERROR; + } + + bank.instruments[id] = instrument; + } + + fclose(file); + + return SFX_OK; +} + +static void +ami_exit(sfx_softseq_t *self) +{ + int i; + + for (i = 0; i < bank.size; i++) { + if (bank.instruments[i]) { + sci_free(bank.instruments[i]->samples); + sci_free(bank.instruments[i]); + } + } +} + +static void +ami_event(sfx_softseq_t *self, byte command, int argc, byte *argv) +{ + int channel, oper; + + channel = command & 0x0f; + oper = command & 0xf0; + + if (channel >= HW_CHANNELS_NR) { +#ifdef DEBUG + sciprintf("[sfx:seq:amiga] Warning: received event for non-existing channel %i\n", channel); +#endif + return; + } + + switch(oper) { + case 0x90: + if (argv[1] > 0) + start_note(channel, argv[0], argv[1]); + else + stop_note(channel, argv[0]); + break; + case 0xb0: + switch (argv[0]) { + case 0x07: + hw_channels[channel].volume = argv[1]; + break; + case 0x0a: +#ifdef DEBUG + sciprintf("[sfx:seq:amiga] Warning: ignoring pan 0x%02x event for channel %i\n", argv[1], channel); +#endif + break; + case 0x7b: + stop_channel(channel); + break; + default: + sciprintf("[sfx:seq:amiga] Warning: unknown control event 0x%02x\n", argv[0]); + } + break; + case 0xc0: + change_instrument(channel, argv[0]); + break; + default: + sciprintf("[sfx:seq:amiga] Warning: unknown event %02x\n", command); + } +} + +void +ami_poll(sfx_softseq_t *self, byte *dest, int len) +{ + int i, j; + gint16 *buf = (gint16 *) dest; + gint16 *buffers = (gint16*)malloc(len * 2 * CHANNELS_NR); + + memset(buffers, 0, len * 2 * CHANNELS_NR); + memset(dest, 0, len * 4); + + /* Generate samples for all notes */ + for (i = 0; i < CHANNELS_NR; i++) + if (channels[i].note >= 0) + play_instrument(buffers + i * len, &channels[i], len); + + for (j = 0; j < len; j++) { + int mixedl = 0, mixedr = 0; + + /* Mix and pan */ + for (i = 0; i < CHANNELS_NR; i++) { + mixedl += buffers[i * len + j] * (256 - hw_channels[channels[i].hw_channel].pan); + mixedr += buffers[i * len + j] * hw_channels[channels[i].hw_channel].pan; + } + + /* Adjust volume */ + buf[2 * j] = mixedl * volume >> 16; + buf[2 * j + 1] = mixedr *volume >> 16; + } +} + +void +ami_volume(sfx_softseq_t *self, int new_volume) +{ + volume = new_volume; +} + +void +ami_allstop(sfx_softseq_t *self) +{ + int i; + for (i = 0; i < HW_CHANNELS_NR; i++) + stop_channel(i); +} + +sfx_softseq_t sfx_softseq_amiga = { + "amiga", + "0.1", + ami_set_option, + ami_init, + ami_exit, + ami_volume, + ami_event, + ami_poll, + ami_allstop, + NULL, + SFX_SEQ_PATCHFILE_NONE, + SFX_SEQ_PATCHFILE_NONE, + 0x40, + 0, /* No rhythm channel (9) */ + HW_CHANNELS_NR, /* # of voices */ + {FREQUENCY, SFX_PCM_STEREO_LR, SFX_PCM_FORMAT_S16_NATIVE} +}; diff --git a/engines/sci/sfx/softseq/fluidsynth.c b/engines/sci/sfx/softseq/fluidsynth.c new file mode 100644 index 0000000000..a44d75e5bd --- /dev/null +++ b/engines/sci/sfx/softseq/fluidsynth.c @@ -0,0 +1,262 @@ +/*************************************************************************** + fluidsynth.c Copyright (C) 2008 Walter van Niftrik + + 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: + + Walter van Niftrik <w.f.b.w.v.niftrik@stud.tue.nl> + +***************************************************************************/ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#ifdef HAVE_FLUIDSYNTH_H + +#include <fluidsynth.h> + +#include "../softseq.h" +#include "../sequencer.h" +#include "../device.h" +#include "resource.h" + +static sfx_sequencer_t *gmseq; +static fluid_settings_t* settings; +static fluid_synth_t* synth; +static guint8 status; +static char *soundfont = "/etc/midi/8MBGMSFX.SF2"; +static int rpn[16]; + +#define SAMPLE_RATE 44100 +#define CHANNELS SFX_PCM_STEREO_LR + +/* MIDI writer */ + +static int +fluidsynth_midi_init(struct _midi_writer *self) +{ + return SFX_OK; +} + +static int +fluidsynth_midi_set_option(struct _midi_writer *self, char *name, char *value) +{ + return SFX_ERROR; +} + +static int +fluidsynth_midi_write(struct _midi_writer *self, unsigned char *buf, int len) +{ + if (buf[0] == 0xf0) + sciprintf("FluidSynth: Skipping sysex message.\n"); + else if (len == 2) { + guint8 command, channel; + + command = buf[0] & 0xf0; + channel = buf[0] & 0x0f; + + switch(command) { + case 0xc0: + fluid_synth_program_change(synth, channel, buf[1]); + break; + default: + printf("FluidSynth: MIDI command [%02x %02x] not supported\n", buf[0], buf[1]); + } + } + else if (len == 3) { + guint8 command, channel; + + command = buf[0] & 0xf0; + channel = buf[0] & 0x0f; + + switch(command) { + case 0x80: + fluid_synth_noteoff(synth, channel, buf[1]); + break; + case 0x90: + fluid_synth_noteon(synth, channel, buf[1], buf[2]); + break; + case 0xb0: + switch (buf[1]) { + case 0x06: + /* Data Entry Slider - course */ + if (rpn[channel] == 0) + fluid_synth_pitch_wheel_sens(synth, channel, buf[2]); + else + sciprintf("FluidSynth: RPN %i not supported\n", rpn[channel]); + case 0x64: + /* Registered Parameter Number (RPN) - fine */ + rpn[channel] &= ~0x7f; + rpn[channel] |= buf[2] & 0x7f; + break; + case 0x65: + /* Registered Parameter Number (RPN) - course */ + rpn[channel] &= ~0x3f80; + rpn[channel] |= (buf[2] & 0x7f) << 7; + break; + default: + fluid_synth_cc(synth, channel, buf[1], buf[2]); + } + break; + case 0xe0: + fluid_synth_pitch_bend(synth, channel, (buf[2] << 7) | buf[1]); + break; + default: + sciprintf("FluidSynth: MIDI command [%02x %02x %02x] not supported\n", buf[0], buf[1], buf[2]); + } + } + else + sciprintf("FluidSynth: Skipping invalid message of %i bytes.\n", len); + + return SFX_OK; +} + +static void +fluidsynth_midi_delay(struct _midi_writer *self, int ticks) +{ +} + +static void +fluidsynth_midi_reset_timer(struct _midi_writer *self) +{ +} + +static void +fluidsynth_midi_close(struct _midi_writer *self) +{ +} + +static midi_writer_t midi_writer_fluidsynth = { + "fluidsynth", + fluidsynth_midi_init, + fluidsynth_midi_set_option, + fluidsynth_midi_write, + fluidsynth_midi_delay, + NULL, + fluidsynth_midi_reset_timer, + fluidsynth_midi_close +}; + +/* Software sequencer */ + +static void +fluidsynth_poll(sfx_softseq_t *self, byte *dest, int count) +{ + fluid_synth_write_s16(synth, count, dest, 0, 2, dest + 2, 0, 2); +} + +static int +fluidsynth_init(sfx_softseq_t *self, byte *data_ptr, int data_length, + byte *data2_ptr, int data2_length) +{ + int sfont_id; + double min, max; + + if (0) { + sciprintf("FluidSynth ERROR: Mono sound output not supported.\n"); + return SFX_ERROR; + } + + gmseq = sfx_find_sequencer("General MIDI"); + if (!gmseq) { + sciprintf("FluidSynth ERROR: Unable to find General MIDI sequencer.\n"); + return SFX_ERROR; + } + + settings = new_fluid_settings(); + + fluid_settings_getnum_range(settings, "synth.sample-rate", &min, &max); + if (SAMPLE_RATE < min || SAMPLE_RATE > max) { + sciprintf("FluidSynth ERROR: Sample rate '%i' not supported. Valid " + "range is (%i-%i).\n", SAMPLE_RATE, (int) min, (int) max); + delete_fluid_settings(settings); + return SFX_ERROR; + } + + fluid_settings_setnum(settings, "synth.sample-rate", SAMPLE_RATE); + fluid_settings_setnum(settings, "synth.gain", 0.5f); + + synth = new_fluid_synth(settings); + + if ((sfont_id = fluid_synth_sfload(synth, soundfont, 1)) < 0) { + delete_fluid_synth(synth); + delete_fluid_settings(settings); + return SFX_ERROR; + } + + gmseq->open(data_length, data_ptr, data2_length, data2_ptr, + &midi_writer_fluidsynth); + + return SFX_OK; +} + +static void +fluidsynth_exit(sfx_softseq_t *self) +{ + delete_fluid_synth(synth); + delete_fluid_settings(settings); +} + +static void +fluidsynth_allstop(sfx_softseq_t *self) +{ + if (gmseq->allstop) + gmseq->allstop(); +} + +static void +fluidsynth_volume(sfx_softseq_t *self, int volume) +{ + if (gmseq->volume) + gmseq->volume(volume); +} + +static int +fluidsynth_set_option(sfx_softseq_t *self, char *name, char *value) +{ + return SFX_ERROR; +} + +static void +fluidsynth_event(sfx_softseq_t *self, byte cmd, int argc, byte *argv) +{ + gmseq->event(cmd, argc, argv); +} + +sfx_softseq_t sfx_softseq_fluidsynth = { + "fluidsynth", + "0.1", + fluidsynth_set_option, + fluidsynth_init, + fluidsynth_exit, + fluidsynth_volume, + fluidsynth_event, + fluidsynth_poll, + fluidsynth_allstop, + NULL, + 004, /* patch.004 */ + 001, /* patch.001 */ + 0x01, /* playflag */ + 1, /* do play channel 9 */ + 32, /* Max polypgony */ + {SAMPLE_RATE, CHANNELS, SFX_PCM_FORMAT_S16_NATIVE} +}; + +#endif /* HAVE_FLUIDSYNTH_H */ diff --git a/engines/sci/sfx/softseq/fmopl.c b/engines/sci/sfx/softseq/fmopl.c new file mode 100644 index 0000000000..6ea777c667 --- /dev/null +++ b/engines/sci/sfx/softseq/fmopl.c @@ -0,0 +1,1144 @@ +/*************************************************************************** + fmopl.c Copyright (C) 1999-2000 Tatsuyuki Satoh + 2001-2004 The ScummVM project + 2002 Solomon Peachy + 2004 Walter van Niftrik + + 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. + + LGPL licensed version of MAMEs fmopl (V0.37a modified) by + Tatsuyuki Satoh. Included from LGPL'ed AdPlug. + +***************************************************************************/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif /* HAVE_CONFIG_H */ + +#ifndef INLINE +# ifdef _MSC_VER +# define INLINE __inline +# else +# define INLINE inline +# endif +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#include <math.h> + +#include "fmopl.h" + +#ifndef PI +#define PI 3.14159265358979323846 +#endif + +/* -------------------- preliminary define section --------------------- */ +/* attack/decay rate time rate */ +#define OPL_ARRATE 141280 /* RATE 4 = 2826.24ms @ 3.6MHz */ +#define OPL_DRRATE 1956000 /* RATE 4 = 39280.64ms @ 3.6MHz */ + +#define FREQ_BITS 24 /* frequency turn */ + +/* counter bits = 20 , octerve 7 */ +#define FREQ_RATE (1<<(FREQ_BITS-20)) +#define TL_BITS (FREQ_BITS+2) + +/* final output shift , limit minimum and maximum */ +#define OPL_OUTSB (TL_BITS+3-16) /* OPL output final shift 16bit */ +#define OPL_MAXOUT (0x7fff<<OPL_OUTSB) +#define OPL_MINOUT (-0x8000<<OPL_OUTSB) + +/* -------------------- quality selection --------------------- */ + +/* sinwave entries */ +/* used static memory = SIN_ENT * 4 (byte) */ +#define SIN_ENT 2048 + +/* output level entries (envelope,sinwave) */ +/* envelope counter lower bits */ +int ENV_BITS; +/* envelope output entries */ +int EG_ENT; + +/* used dynamic memory = EG_ENT*4*4(byte)or EG_ENT*6*4(byte) */ +/* used static memory = EG_ENT*4 (byte) */ +int EG_OFF; /* OFF */ +int EG_DED; +int EG_DST; /* DECAY START */ +int EG_AED; +#define EG_AST 0 /* ATTACK START */ + +#define EG_STEP (96.0/EG_ENT) /* OPL is 0.1875 dB step */ + +/* LFO table entries */ +#define VIB_ENT 512 +#define VIB_SHIFT (32-9) +#define AMS_ENT 512 +#define AMS_SHIFT (32-9) + +#define VIB_RATE 256 + +/* -------------------- local defines , macros --------------------- */ + +/* register number to channel number , slot offset */ +#define SLOT1 0 +#define SLOT2 1 + +/* envelope phase */ +#define ENV_MOD_RR 0x00 +#define ENV_MOD_DR 0x01 +#define ENV_MOD_AR 0x02 + +/* -------------------- tables --------------------- */ +static const int slot_array[32]= +{ + 0, 2, 4, 1, 3, 5,-1,-1, + 6, 8,10, 7, 9,11,-1,-1, + 12,14,16,13,15,17,-1,-1, +#ifdef TWELVE_VOICE + 18,20,22,19,21,23,-1,-1 +#else + -1,-1,-1,-1,-1,-1,-1,-1 +#endif +}; + +static guint32 KSL_TABLE[8 * 16]; + +static const double KSL_TABLE_SEED[8 * 16] = { + /* OCT 0 */ + 0.000, 0.000, 0.000, 0.000, + 0.000, 0.000, 0.000, 0.000, + 0.000, 0.000, 0.000, 0.000, + 0.000, 0.000, 0.000, 0.000, + /* OCT 1 */ + 0.000, 0.000, 0.000, 0.000, + 0.000, 0.000, 0.000, 0.000, + 0.000, 0.750, 1.125, 1.500, + 1.875, 2.250, 2.625, 3.000, + /* OCT 2 */ + 0.000, 0.000, 0.000, 0.000, + 0.000, 1.125, 1.875, 2.625, + 3.000, 3.750, 4.125, 4.500, + 4.875, 5.250, 5.625, 6.000, + /* OCT 3 */ + 0.000, 0.000, 0.000, 1.875, + 3.000, 4.125, 4.875, 5.625, + 6.000, 6.750, 7.125, 7.500, + 7.875, 8.250, 8.625, 9.000, + /* OCT 4 */ + 0.000, 0.000, 3.000, 4.875, + 6.000, 7.125, 7.875, 8.625, + 9.000, 9.750, 10.125, 10.500, + 10.875, 11.250, 11.625, 12.000, + /* OCT 5 */ + 0.000, 3.000, 6.000, 7.875, + 9.000, 10.125, 10.875, 11.625, + 12.000, 12.750, 13.125, 13.500, + 13.875, 14.250, 14.625, 15.000, + /* OCT 6 */ + 0.000, 6.000, 9.000, 10.875, + 12.000, 13.125, 13.875, 14.625, + 15.000, 15.750, 16.125, 16.500, + 16.875, 17.250, 17.625, 18.000, + /* OCT 7 */ + 0.000, 9.000, 12.000, 13.875, + 15.000, 16.125, 16.875, 17.625, + 18.000, 18.750, 19.125, 19.500, + 19.875, 20.250, 20.625, 21.000 +}; + +/* sustain lebel table (3db per step) */ +/* 0 - 15: 0, 3, 6, 9,12,15,18,21,24,27,30,33,36,39,42,93 (dB)*/ + +static int SL_TABLE[16]; + +static const guint32 SL_TABLE_SEED[16] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 31 +}; + +#define TL_MAX (EG_ENT * 2) /* limit(tl + ksr + envelope) + sinwave */ +/* TotalLevel : 48 24 12 6 3 1.5 0.75 (dB) */ +/* TL_TABLE[ 0 to TL_MAX ] : plus section */ +/* TL_TABLE[ TL_MAX to TL_MAX+TL_MAX-1 ] : minus section */ +static int *TL_TABLE; + +/* pointers to TL_TABLE with sinwave output offset */ +static int **SIN_TABLE; + +/* LFO table */ +static int *AMS_TABLE; +static int *VIB_TABLE; + +/* envelope output curve table */ +/* attack + decay + OFF */ +//static int ENV_CURVE[2*EG_ENT+1]; +static int ENV_CURVE[2 * 4096 + 1]; // to keep it static ... + +/* multiple table */ +#define ML(a) (int)(a * 2) +static const guint32 MUL_TABLE[16]= { +/* 1/2, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15 */ + ML(0.50), ML(1.00), ML(2.00), ML(3.00), ML(4.00), ML(5.00), ML(6.00), ML(7.00), + ML(8.00), ML(9.00), ML(10.00), ML(10.00),ML(12.00),ML(12.00),ML(15.00),ML(15.00) +}; +#undef ML + +/* dummy attack / decay rate ( when rate == 0 ) */ +static int RATE_0[16]= +{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; + +/* -------------------- static state --------------------- */ + +/* lock level of common table */ +static int num_lock = 0; + +/* work table */ +static void *cur_chip = NULL; /* current chip point */ +/* currenct chip state */ +/* static OPLSAMPLE *bufL,*bufR; */ +static OPL_CH *S_CH; +static OPL_CH *E_CH; +OPL_SLOT *SLOT7_1, *SLOT7_2, *SLOT8_1, *SLOT8_2; + +static int outd[1]; +static int ams; +static int vib; +int *ams_table; +int *vib_table; +static int amsIncr; +static int vibIncr; +static int feedback2; /* connect for SLOT 2 */ + +/* --------------------- rebuild tables ------------------- */ + +#define SC_KSL(mydb) ((guint32) (mydb / (EG_STEP / 2))) +#define SC_SL(db) (int)(db * ((3 / EG_STEP) * (1 << ENV_BITS))) + EG_DST + +void OPLBuildTables(int ENV_BITS_PARAM, int EG_ENT_PARAM) { + int i; + + ENV_BITS = ENV_BITS_PARAM; + EG_ENT = EG_ENT_PARAM; + EG_OFF = ((2 * EG_ENT)<<ENV_BITS); /* OFF */ + EG_DED = EG_OFF; + EG_DST = (EG_ENT << ENV_BITS); /* DECAY START */ + EG_AED = EG_DST; + //EG_STEP = (96.0/EG_ENT); + + for (i = 0; i < (int)(sizeof(KSL_TABLE_SEED) / sizeof(double)); i++) + KSL_TABLE[i] = SC_KSL(KSL_TABLE_SEED[i]); + + for (i = 0; i < (int)(sizeof(SL_TABLE_SEED) / sizeof(guint32)); i++) + SL_TABLE[i] = SC_SL(SL_TABLE_SEED[i]); +} + +#undef SC_KSL +#undef SC_SL + +/* --------------------- subroutines --------------------- */ + +INLINE int Limit(int val, int max, int min) { + if ( val > max ) + val = max; + else if ( val < min ) + val = min; + + return val; +} + +/* status set and IRQ handling */ +INLINE void OPL_STATUS_SET(FM_OPL *OPL, int flag) { + /* set status flag */ + OPL->status |= flag; + if(!(OPL->status & 0x80)) { + if(OPL->status & OPL->statusmask) { /* IRQ on */ + OPL->status |= 0x80; + /* callback user interrupt handler (IRQ is OFF to ON) */ + if(OPL->IRQHandler) + (OPL->IRQHandler)(OPL->IRQParam,1); + } + } +} + +/* status reset and IRQ handling */ +INLINE void OPL_STATUS_RESET(FM_OPL *OPL, int flag) { + /* reset status flag */ + OPL->status &= ~flag; + if((OPL->status & 0x80)) { + if (!(OPL->status & OPL->statusmask)) { + OPL->status &= 0x7f; + /* callback user interrupt handler (IRQ is ON to OFF) */ + if(OPL->IRQHandler) (OPL->IRQHandler)(OPL->IRQParam,0); + } + } +} + +/* IRQ mask set */ +INLINE void OPL_STATUSMASK_SET(FM_OPL *OPL, int flag) { + OPL->statusmask = flag; + /* IRQ handling check */ + OPL_STATUS_SET(OPL,0); + OPL_STATUS_RESET(OPL,0); +} + +/* ----- key on ----- */ +INLINE void OPL_KEYON(OPL_SLOT *SLOT) { + /* sin wave restart */ + SLOT->Cnt = 0; + /* set attack */ + SLOT->evm = ENV_MOD_AR; + SLOT->evs = SLOT->evsa; + SLOT->evc = EG_AST; + SLOT->eve = EG_AED; +} +/* ----- key off ----- */ +INLINE void OPL_KEYOFF(OPL_SLOT *SLOT) { + if( SLOT->evm > ENV_MOD_RR) { + /* set envelope counter from envleope output */ + SLOT->evm = ENV_MOD_RR; + if( !(SLOT->evc & EG_DST) ) + //SLOT->evc = (ENV_CURVE[SLOT->evc>>ENV_BITS]<<ENV_BITS) + EG_DST; + SLOT->evc = EG_DST; + SLOT->eve = EG_DED; + SLOT->evs = SLOT->evsr; + } +} + +/* ---------- calcrate Envelope Generator & Phase Generator ---------- */ +/* return : envelope output */ +INLINE guint32 OPL_CALC_SLOT(OPL_SLOT *SLOT) { + /* calcrate envelope generator */ + if((SLOT->evc += SLOT->evs) >= SLOT->eve) { + switch( SLOT->evm ){ + case ENV_MOD_AR: /* ATTACK -> DECAY1 */ + /* next DR */ + SLOT->evm = ENV_MOD_DR; + SLOT->evc = EG_DST; + SLOT->eve = SLOT->SL; + SLOT->evs = SLOT->evsd; + break; + case ENV_MOD_DR: /* DECAY -> SL or RR */ + SLOT->evc = SLOT->SL; + SLOT->eve = EG_DED; + if(SLOT->eg_typ) { + SLOT->evs = 0; + } else { + SLOT->evm = ENV_MOD_RR; + SLOT->evs = SLOT->evsr; + } + break; + case ENV_MOD_RR: /* RR -> OFF */ + SLOT->evc = EG_OFF; + SLOT->eve = EG_OFF + 1; + SLOT->evs = 0; + break; + } + } + /* calcrate envelope */ + return SLOT->TLL + ENV_CURVE[SLOT->evc>>ENV_BITS] + (SLOT->ams ? ams : 0); +} + +/* set algorythm connection */ +static void set_algorythm(OPL_CH *CH) { + int *carrier = &outd[0]; + CH->connect1 = CH->CON ? carrier : &feedback2; + CH->connect2 = carrier; +} + +/* ---------- frequency counter for operater update ---------- */ +INLINE void CALC_FCSLOT(OPL_CH *CH, OPL_SLOT *SLOT) { + int ksr; + + /* frequency step counter */ + SLOT->Incr = CH->fc * SLOT->mul; + ksr = CH->kcode >> SLOT->KSR; + + if( SLOT->ksr != ksr ) + { + SLOT->ksr = ksr; + /* attack , decay rate recalcration */ + SLOT->evsa = SLOT->AR[ksr]; + SLOT->evsd = SLOT->DR[ksr]; + SLOT->evsr = SLOT->RR[ksr]; + } + SLOT->TLL = SLOT->TL + (CH->ksl_base>>SLOT->ksl); +} + +/* set multi,am,vib,EG-TYP,KSR,mul */ +INLINE void set_mul(FM_OPL *OPL, int slot, int v) { + OPL_CH *CH = &OPL->P_CH[slot / 2]; + OPL_SLOT *SLOT = &CH->SLOT[slot & 1]; + + SLOT->mul = MUL_TABLE[v & 0x0f]; + SLOT->KSR = (v & 0x10) ? 0 : 2; + SLOT->eg_typ = (v & 0x20) >> 5; + SLOT->vib = (v & 0x40); + SLOT->ams = (v & 0x80); + CALC_FCSLOT(CH, SLOT); +} + +/* set ksl & tl */ +INLINE void set_ksl_tl(FM_OPL *OPL, int slot, int v) { + OPL_CH *CH = &OPL->P_CH[slot / 2]; + OPL_SLOT *SLOT = &CH->SLOT[slot & 1]; + int ksl = v >> 6; /* 0 / 1.5 / 3 / 6 db/OCT */ + + SLOT->ksl = ksl ? 3-ksl : 31; + SLOT->TL = (int)((v & 0x3f) * (0.75 / EG_STEP)); /* 0.75db step */ + + if(!(OPL->mode & 0x80)) { /* not CSM latch total level */ + SLOT->TLL = SLOT->TL + (CH->ksl_base >> SLOT->ksl); + } +} + +/* set attack rate & decay rate */ +INLINE void set_ar_dr(FM_OPL *OPL, int slot, int v) { + OPL_CH *CH = &OPL->P_CH[slot / 2]; + OPL_SLOT *SLOT = &CH->SLOT[slot & 1]; + int ar = v >> 4; + int dr = v & 0x0f; + + SLOT->AR = ar ? &OPL->AR_TABLE[ar << 2] : RATE_0; + SLOT->evsa = SLOT->AR[SLOT->ksr]; + if(SLOT->evm == ENV_MOD_AR) + SLOT->evs = SLOT->evsa; + + SLOT->DR = dr ? &OPL->DR_TABLE[dr<<2] : RATE_0; + SLOT->evsd = SLOT->DR[SLOT->ksr]; + if(SLOT->evm == ENV_MOD_DR) + SLOT->evs = SLOT->evsd; +} + +/* set sustain level & release rate */ +INLINE void set_sl_rr(FM_OPL *OPL, int slot, int v) { + OPL_CH *CH = &OPL->P_CH[slot / 2]; + OPL_SLOT *SLOT = &CH->SLOT[slot & 1]; + int sl = v >> 4; + int rr = v & 0x0f; + + SLOT->SL = SL_TABLE[sl]; + if(SLOT->evm == ENV_MOD_DR) + SLOT->eve = SLOT->SL; + SLOT->RR = &OPL->DR_TABLE[rr<<2]; + SLOT->evsr = SLOT->RR[SLOT->ksr]; + if(SLOT->evm == ENV_MOD_RR) + SLOT->evs = SLOT->evsr; +} + +/* operator output calcrator */ +#define OP_OUT(slot,env,con) slot->wavetable[((slot->Cnt + con) / (0x1000000 / SIN_ENT)) & (SIN_ENT-1)][env] +/* ---------- calcrate one of channel ---------- */ +INLINE void OPL_CALC_CH(OPL_CH *CH) { + guint32 env_out; + OPL_SLOT *SLOT; + + feedback2 = 0; + /* SLOT 1 */ + SLOT = &CH->SLOT[SLOT1]; + env_out=OPL_CALC_SLOT(SLOT); + if(env_out < (guint32)(EG_ENT - 1)) { + /* PG */ + if(SLOT->vib) + SLOT->Cnt += (SLOT->Incr * vib / VIB_RATE); + else + SLOT->Cnt += SLOT->Incr; + /* connectoion */ + if(CH->FB) { + int feedback1 = (CH->op1_out[0] + CH->op1_out[1]) >> CH->FB; + CH->op1_out[1] = CH->op1_out[0]; + *CH->connect1 += CH->op1_out[0] = OP_OUT(SLOT, env_out, feedback1); + } + else { + *CH->connect1 += OP_OUT(SLOT, env_out, 0); + } + }else { + CH->op1_out[1] = CH->op1_out[0]; + CH->op1_out[0] = 0; + } + /* SLOT 2 */ + SLOT = &CH->SLOT[SLOT2]; + env_out=OPL_CALC_SLOT(SLOT); + if(env_out < (guint32)(EG_ENT - 1)) { + /* PG */ + if(SLOT->vib) + SLOT->Cnt += (SLOT->Incr * vib / VIB_RATE); + else + SLOT->Cnt += SLOT->Incr; + /* connectoion */ + outd[0] += OP_OUT(SLOT, env_out, feedback2); + } +} + +/* ---------- calcrate rythm block ---------- */ +#define WHITE_NOISE_db 6.0 +INLINE void OPL_CALC_RH(OPL_CH *CH) { + guint32 env_tam, env_sd, env_top, env_hh; + int whitenoise = (int)((rand()&1) * (WHITE_NOISE_db / EG_STEP)); + int tone8; + + OPL_SLOT *SLOT; + int env_out; + + /* BD : same as FM serial mode and output level is large */ + feedback2 = 0; + /* SLOT 1 */ + SLOT = &CH[6].SLOT[SLOT1]; + env_out = OPL_CALC_SLOT(SLOT); + if(env_out < EG_ENT-1) { + /* PG */ + if(SLOT->vib) + SLOT->Cnt += (SLOT->Incr * vib / VIB_RATE); + else + SLOT->Cnt += SLOT->Incr; + /* connectoion */ + if(CH[6].FB) { + int feedback1 = (CH[6].op1_out[0] + CH[6].op1_out[1]) >> CH[6].FB; + CH[6].op1_out[1] = CH[6].op1_out[0]; + feedback2 = CH[6].op1_out[0] = OP_OUT(SLOT, env_out, feedback1); + } + else { + feedback2 = OP_OUT(SLOT, env_out, 0); + } + }else { + feedback2 = 0; + CH[6].op1_out[1] = CH[6].op1_out[0]; + CH[6].op1_out[0] = 0; + } + /* SLOT 2 */ + SLOT = &CH[6].SLOT[SLOT2]; + env_out = OPL_CALC_SLOT(SLOT); + if(env_out < EG_ENT-1) { + /* PG */ + if(SLOT->vib) + SLOT->Cnt += (SLOT->Incr * vib / VIB_RATE); + else + SLOT->Cnt += SLOT->Incr; + /* connectoion */ + outd[0] += OP_OUT(SLOT, env_out, feedback2) * 2; + } + + // SD (17) = mul14[fnum7] + white noise + // TAM (15) = mul15[fnum8] + // TOP (18) = fnum6(mul18[fnum8]+whitenoise) + // HH (14) = fnum7(mul18[fnum8]+whitenoise) + white noise + env_sd = OPL_CALC_SLOT(SLOT7_2) + whitenoise; + env_tam =OPL_CALC_SLOT(SLOT8_1); + env_top = OPL_CALC_SLOT(SLOT8_2); + env_hh = OPL_CALC_SLOT(SLOT7_1) + whitenoise; + + /* PG */ + if(SLOT7_1->vib) + SLOT7_1->Cnt += (2 * SLOT7_1->Incr * vib / VIB_RATE); + else + SLOT7_1->Cnt += 2 * SLOT7_1->Incr; + if(SLOT7_2->vib) + SLOT7_2->Cnt += ((CH[7].fc * 8) * vib / VIB_RATE); + else + SLOT7_2->Cnt += (CH[7].fc * 8); + if(SLOT8_1->vib) + SLOT8_1->Cnt += (SLOT8_1->Incr * vib / VIB_RATE); + else + SLOT8_1->Cnt += SLOT8_1->Incr; + if(SLOT8_2->vib) + SLOT8_2->Cnt += ((CH[8].fc * 48) * vib / VIB_RATE); + else + SLOT8_2->Cnt += (CH[8].fc * 48); + + tone8 = OP_OUT(SLOT8_2,whitenoise,0 ); + + /* SD */ + if(env_sd < (guint32)(EG_ENT - 1)) + outd[0] += OP_OUT(SLOT7_1, env_sd, 0) * 8; + /* TAM */ + if(env_tam < (guint32)(EG_ENT - 1)) + outd[0] += OP_OUT(SLOT8_1, env_tam, 0) * 2; + /* TOP-CY */ + if(env_top < (guint32)(EG_ENT - 1)) + outd[0] += OP_OUT(SLOT7_2, env_top, tone8) * 2; + /* HH */ + if(env_hh < (guint32)(EG_ENT-1)) + outd[0] += OP_OUT(SLOT7_2, env_hh, tone8) * 2; +} + +/* ----------- initialize time tabls ----------- */ +static void init_timetables(FM_OPL *OPL, int ARRATE, int DRRATE) { + int i; + double rate; + + /* make attack rate & decay rate tables */ + for (i = 0; i < 4; i++) + OPL->AR_TABLE[i] = OPL->DR_TABLE[i] = 0; + for (i = 4; i <= 60; i++){ + rate = OPL->freqbase; /* frequency rate */ + if(i < 60) + rate *= 1.0 + (i & 3) * 0.25; /* b0-1 : x1 , x1.25 , x1.5 , x1.75 */ + rate *= 1 << ((i >> 2) - 1); /* b2-5 : shift bit */ + rate *= (double)(EG_ENT << ENV_BITS); + OPL->AR_TABLE[i] = (int)(rate / ARRATE); + OPL->DR_TABLE[i] = (int)(rate / DRRATE); + } + for (i = 60; i < 75; i++) { + OPL->AR_TABLE[i] = EG_AED-1; + OPL->DR_TABLE[i] = OPL->DR_TABLE[60]; + } +} + +/* ---------- generic table initialize ---------- */ +static int OPLOpenTable(void) { + int s,t; + double rate; + int i,j; + double pom; + + /* allocate dynamic tables */ + if((TL_TABLE = (int *)malloc(TL_MAX * 2 * sizeof(int))) == NULL) + return 0; + if((SIN_TABLE = (int **)malloc(SIN_ENT * 4 * sizeof(int *))) == NULL) { + free(TL_TABLE); + return 0; + } + if((AMS_TABLE = (int *)malloc(AMS_ENT * 2 * sizeof(int))) == NULL) { + free(TL_TABLE); + free(SIN_TABLE); + return 0; + } + if((VIB_TABLE = (int *)malloc(VIB_ENT * 2 * sizeof(int))) == NULL) { + free(TL_TABLE); + free(SIN_TABLE); + free(AMS_TABLE); + return 0; + } + /* make total level table */ + for (t = 0; t < EG_ENT - 1 ; t++){ + rate = ((1 << TL_BITS) - 1) / pow(10.0, EG_STEP * t / 20); /* dB -> voltage */ + TL_TABLE[ t] = (int)rate; + TL_TABLE[TL_MAX + t] = -TL_TABLE[t]; + } + /* fill volume off area */ + for (t = EG_ENT - 1; t < TL_MAX; t++){ + TL_TABLE[t] = TL_TABLE[TL_MAX + t] = 0; + } + + /* make sinwave table (total level offet) */ + /* degree 0 = degree 180 = off */ + SIN_TABLE[0] = SIN_TABLE[SIN_ENT /2 ] = &TL_TABLE[EG_ENT - 1]; + for (s = 1;s <= SIN_ENT / 4; s++){ + pom = sin(2 * PI * s / SIN_ENT); /* sin */ + pom = 20 * log10(1 / pom); /* decibel */ + j = (int)(pom / EG_STEP); /* TL_TABLE steps */ + + /* degree 0 - 90 , degree 180 - 90 : plus section */ + SIN_TABLE[ s] = SIN_TABLE[SIN_ENT / 2 - s] = &TL_TABLE[j]; + /* degree 180 - 270 , degree 360 - 270 : minus section */ + SIN_TABLE[SIN_ENT / 2 + s] = SIN_TABLE[SIN_ENT - s] = &TL_TABLE[TL_MAX + j]; + } + for (s = 0;s < SIN_ENT; s++) { + SIN_TABLE[SIN_ENT * 1 + s] = s < (SIN_ENT / 2) ? SIN_TABLE[s] : &TL_TABLE[EG_ENT]; + SIN_TABLE[SIN_ENT * 2 + s] = SIN_TABLE[s % (SIN_ENT / 2)]; + SIN_TABLE[SIN_ENT * 3 + s] = (s / (SIN_ENT / 4)) & 1 ? &TL_TABLE[EG_ENT] : SIN_TABLE[SIN_ENT * 2 + s]; + } + + /* envelope counter -> envelope output table */ + for (i=0; i < EG_ENT; i++) { + /* ATTACK curve */ + pom = pow(((double)(EG_ENT - 1 - i) / EG_ENT), 8) * EG_ENT; + /* if( pom >= EG_ENT ) pom = EG_ENT-1; */ + ENV_CURVE[i] = (int)pom; + /* DECAY ,RELEASE curve */ + ENV_CURVE[(EG_DST >> ENV_BITS) + i]= i; + } + /* off */ + ENV_CURVE[EG_OFF >> ENV_BITS]= EG_ENT - 1; + /* make LFO ams table */ + for (i=0; i < AMS_ENT; i++) { + pom = (1.0 + sin(2 * PI * i / AMS_ENT)) / 2; /* sin */ + AMS_TABLE[i] = (int)((1.0 / EG_STEP) * pom); /* 1dB */ + AMS_TABLE[AMS_ENT + i] = (int)((4.8 / EG_STEP) * pom); /* 4.8dB */ + } + /* make LFO vibrate table */ + for (i=0; i < VIB_ENT; i++) { + /* 100cent = 1seminote = 6% ?? */ + pom = (double)VIB_RATE * 0.06 * sin(2 * PI * i / VIB_ENT); /* +-100sect step */ + VIB_TABLE[i] = (int)(VIB_RATE + (pom * 0.07)); /* +- 7cent */ + VIB_TABLE[VIB_ENT + i] = (int)(VIB_RATE + (pom * 0.14)); /* +-14cent */ + } + return 1; +} + +static void OPLCloseTable(void) { + free(TL_TABLE); + free(SIN_TABLE); + free(AMS_TABLE); + free(VIB_TABLE); +} + +/* CSM Key Controll */ +INLINE void CSMKeyControll(OPL_CH *CH) { + OPL_SLOT *slot1 = &CH->SLOT[SLOT1]; + OPL_SLOT *slot2 = &CH->SLOT[SLOT2]; + /* all key off */ + OPL_KEYOFF(slot1); + OPL_KEYOFF(slot2); + /* total level latch */ + slot1->TLL = slot1->TL + (CH->ksl_base>>slot1->ksl); + slot1->TLL = slot1->TL + (CH->ksl_base>>slot1->ksl); + /* key on */ + CH->op1_out[0] = CH->op1_out[1] = 0; + OPL_KEYON(slot1); + OPL_KEYON(slot2); +} + +/* ---------- opl initialize ---------- */ +static void OPL_initalize(FM_OPL *OPL) { + int fn; + + /* frequency base */ + OPL->freqbase = (OPL->rate) ? ((double)OPL->clock / OPL->rate) / 72 : 0; + /* Timer base time */ + OPL->TimerBase = 1.0/((double)OPL->clock / 72.0 ); + /* make time tables */ + init_timetables(OPL, OPL_ARRATE, OPL_DRRATE); + /* make fnumber -> increment counter table */ + for( fn=0; fn < 1024; fn++) { + OPL->FN_TABLE[fn] = (guint32)(OPL->freqbase * fn * FREQ_RATE * (1<<7) / 2); + } + /* LFO freq.table */ + OPL->amsIncr = (int)(OPL->rate ? (double)AMS_ENT * (1 << AMS_SHIFT) / OPL->rate * 3.7 * ((double)OPL->clock/3600000) : 0); + OPL->vibIncr = (int)(OPL->rate ? (double)VIB_ENT * (1 << VIB_SHIFT) / OPL->rate * 6.4 * ((double)OPL->clock/3600000) : 0); +} + +/* ---------- write a OPL registers ---------- */ +void OPLWriteReg(FM_OPL *OPL, int r, int v) { + OPL_CH *CH; + int slot; + guint32 block_fnum; + + switch(r & 0xe0) { + case 0x00: /* 00-1f:controll */ + switch(r & 0x1f) { + case 0x01: + /* wave selector enable */ + if(OPL->type&OPL_TYPE_WAVESEL) { + OPL->wavesel = v & 0x20; + if(!OPL->wavesel) { + /* preset compatible mode */ + int c; + for(c=0; c<OPL->max_ch; c++) { + OPL->P_CH[c].SLOT[SLOT1].wavetable = &SIN_TABLE[0]; + OPL->P_CH[c].SLOT[SLOT2].wavetable = &SIN_TABLE[0]; + } + } + } + return; + case 0x02: /* Timer 1 */ + OPL->T[0] = (256-v) * 4; + break; + case 0x03: /* Timer 2 */ + OPL->T[1] = (256-v) * 16; + return; + case 0x04: /* IRQ clear / mask and Timer enable */ + if(v & 0x80) { /* IRQ flag clear */ + OPL_STATUS_RESET(OPL, 0x7f); + } + else { /* set IRQ mask ,timer enable*/ + guint8 st1 = v & 1; + guint8 st2 = (v >> 1) & 1; + /* IRQRST,T1MSK,t2MSK,EOSMSK,BRMSK,x,ST2,ST1 */ + OPL_STATUS_RESET(OPL, v & 0x78); + OPL_STATUSMASK_SET(OPL,((~v) & 0x78) | 0x01); + /* timer 2 */ + if(OPL->st[1] != st2) { + double interval = st2 ? (double)OPL->T[1] * OPL->TimerBase : 0.0; + OPL->st[1] = st2; + if (OPL->TimerHandler) (OPL->TimerHandler)(OPL->TimerParam + 1, interval); + } + /* timer 1 */ + if(OPL->st[0] != st1) { + double interval = st1 ? (double)OPL->T[0] * OPL->TimerBase : 0.0; + OPL->st[0] = st1; + if (OPL->TimerHandler) (OPL->TimerHandler)(OPL->TimerParam + 0, interval); + } + } + return; + } + break; + case 0x20: /* am,vib,ksr,eg type,mul */ + slot = slot_array[r&0x1f]; + if(slot == -1) + return; + set_mul(OPL,slot,v); + return; + case 0x40: + slot = slot_array[r&0x1f]; + if(slot == -1) + return; + set_ksl_tl(OPL,slot,v); + return; + case 0x60: + slot = slot_array[r&0x1f]; + if(slot == -1) + return; + set_ar_dr(OPL,slot,v); + return; + case 0x80: + slot = slot_array[r&0x1f]; + if(slot == -1) + return; + set_sl_rr(OPL,slot,v); + return; + case 0xa0: + switch(r) { + case 0xbd: + /* amsep,vibdep,r,bd,sd,tom,tc,hh */ + { + guint8 rkey = OPL->rythm ^ v; + OPL->ams_table = &AMS_TABLE[v & 0x80 ? AMS_ENT : 0]; + OPL->vib_table = &VIB_TABLE[v & 0x40 ? VIB_ENT : 0]; + OPL->rythm = v & 0x3f; + if(OPL->rythm & 0x20) { + /* BD key on/off */ + if(rkey & 0x10) { + if(v & 0x10) { + OPL->P_CH[6].op1_out[0] = OPL->P_CH[6].op1_out[1] = 0; + OPL_KEYON(&OPL->P_CH[6].SLOT[SLOT1]); + OPL_KEYON(&OPL->P_CH[6].SLOT[SLOT2]); + } + else { + OPL_KEYOFF(&OPL->P_CH[6].SLOT[SLOT1]); + OPL_KEYOFF(&OPL->P_CH[6].SLOT[SLOT2]); + } + } + /* SD key on/off */ + if(rkey & 0x08) { + if(v & 0x08) + OPL_KEYON(&OPL->P_CH[7].SLOT[SLOT2]); + else + OPL_KEYOFF(&OPL->P_CH[7].SLOT[SLOT2]); + }/* TAM key on/off */ + if(rkey & 0x04) { + if(v & 0x04) + OPL_KEYON(&OPL->P_CH[8].SLOT[SLOT1]); + else + OPL_KEYOFF(&OPL->P_CH[8].SLOT[SLOT1]); + } + /* TOP-CY key on/off */ + if(rkey & 0x02) { + if(v & 0x02) + OPL_KEYON(&OPL->P_CH[8].SLOT[SLOT2]); + else + OPL_KEYOFF(&OPL->P_CH[8].SLOT[SLOT2]); + } + /* HH key on/off */ + if(rkey & 0x01) { + if(v & 0x01) + OPL_KEYON(&OPL->P_CH[7].SLOT[SLOT1]); + else + OPL_KEYOFF(&OPL->P_CH[7].SLOT[SLOT1]); + } + } + } + return; + } + /* keyon,block,fnum */ + if((r & 0x0f) >= ADLIB_VOICES) + return; + CH = &OPL->P_CH[r & 0x0f]; + if(!(r&0x10)) { /* a0-a8 */ + block_fnum = (CH->block_fnum & 0x1f00) | v; + } + else { /* b0-b8 */ + int keyon = (v >> 5) & 1; + block_fnum = ((v & 0x1f) << 8) | (CH->block_fnum & 0xff); + if(CH->keyon != keyon) { + if((CH->keyon=keyon)) { + CH->op1_out[0] = CH->op1_out[1] = 0; + OPL_KEYON(&CH->SLOT[SLOT1]); + OPL_KEYON(&CH->SLOT[SLOT2]); + } + else { + OPL_KEYOFF(&CH->SLOT[SLOT1]); + OPL_KEYOFF(&CH->SLOT[SLOT2]); + } + } + } + /* update */ + if(CH->block_fnum != block_fnum) { + int blockRv = 7 - (block_fnum >> 10); + int fnum = block_fnum & 0x3ff; + CH->block_fnum = block_fnum; + CH->ksl_base = KSL_TABLE[block_fnum >> 6]; + CH->fc = OPL->FN_TABLE[fnum] >> blockRv; + CH->kcode = CH->block_fnum >> 9; + if((OPL->mode & 0x40) && CH->block_fnum & 0x100) + CH->kcode |=1; + CALC_FCSLOT(CH,&CH->SLOT[SLOT1]); + CALC_FCSLOT(CH,&CH->SLOT[SLOT2]); + } + return; + case 0xc0: + /* FB,C */ + if((r & 0x0f) >= ADLIB_VOICES) + return; + CH = &OPL->P_CH[r&0x0f]; + { + int feedback = (v >> 1) & 7; + CH->FB = feedback ? (8 + 1) - feedback : 0; + CH->CON = v & 1; + set_algorythm(CH); + } + return; + case 0xe0: /* wave type */ + slot = slot_array[r & 0x1f]; + if(slot == -1) + return; + CH = &OPL->P_CH[slot / 2]; + if(OPL->wavesel) { + CH->SLOT[slot&1].wavetable = &SIN_TABLE[(v & 0x03) * SIN_ENT]; + } + return; + } +} + +/* lock/unlock for common table */ +static int OPL_LockTable(void) +{ + num_lock++; + if(num_lock>1) + return 0; + /* first time */ + cur_chip = NULL; + /* allocate total level table (128kb space) */ + if(!OPLOpenTable()) { + num_lock--; + return -1; + } + return 0; +} + +static void OPL_UnLockTable(void) { + if(num_lock) + num_lock--; + if(num_lock) + return; + /* last time */ + cur_chip = NULL; + OPLCloseTable(); +} + +/*******************************************************************************/ +/* YM3812 local section */ +/*******************************************************************************/ + +/* ---------- update one of chip ----------- */ +void YM3812UpdateOne(FM_OPL *OPL, gint16 *buffer, int length, int interleave) { + int i; + int data; + gint16 *buf = buffer; + guint32 amsCnt = OPL->amsCnt; + guint32 vibCnt = OPL->vibCnt; + guint8 rythm = OPL->rythm & 0x20; + OPL_CH *CH, *R_CH; + + if((void *)OPL != cur_chip){ + cur_chip = (void *)OPL; + /* channel pointers */ + S_CH = OPL->P_CH; + E_CH = &S_CH[9]; + /* rythm slot */ + SLOT7_1 = &S_CH[7].SLOT[SLOT1]; + SLOT7_2 = &S_CH[7].SLOT[SLOT2]; + SLOT8_1 = &S_CH[8].SLOT[SLOT1]; + SLOT8_2 = &S_CH[8].SLOT[SLOT2]; + /* LFO state */ + amsIncr = OPL->amsIncr; + vibIncr = OPL->vibIncr; + ams_table = OPL->ams_table; + vib_table = OPL->vib_table; + } + R_CH = rythm ? &S_CH[6] : E_CH; + for(i = 0; i < length; i++) { + /* channel A channel B channel C */ + /* LFO */ + ams = ams_table[(amsCnt += amsIncr) >> AMS_SHIFT]; + vib = vib_table[(vibCnt += vibIncr) >> VIB_SHIFT]; + outd[0] = 0; + /* FM part */ + for(CH=S_CH; CH < R_CH; CH++) + OPL_CALC_CH(CH); + /* Rythn part */ + if(rythm) + OPL_CALC_RH(S_CH); +#ifdef TWELVE_VOICE + for(CH=&S_CH[9]; CH < &S_CH[ADLIB_VOICES]; CH++) + OPL_CALC_CH(CH); +#endif + /* limit check */ + data = Limit(outd[0], OPL_MAXOUT, OPL_MINOUT); + /* store to sound buffer */ + buf[i << interleave] = data >> OPL_OUTSB; + } + + OPL->amsCnt = amsCnt; + OPL->vibCnt = vibCnt; +} + +/* ---------- reset a chip ---------- */ +void OPLResetChip(FM_OPL *OPL) { + int c,s; + int i; + + /* reset chip */ + OPL->mode = 0; /* normal mode */ + OPL_STATUS_RESET(OPL, 0x7f); + /* reset with register write */ + OPLWriteReg(OPL, 0x01,0); /* wabesel disable */ + OPLWriteReg(OPL, 0x02,0); /* Timer1 */ + OPLWriteReg(OPL, 0x03,0); /* Timer2 */ + OPLWriteReg(OPL, 0x04,0); /* IRQ mask clear */ + for(i = 0xff; i >= 0x20; i--) + OPLWriteReg(OPL,i,0); + /* reset OPerator parameter */ + for(c = 0; c < OPL->max_ch ;c++ ) { + OPL_CH *CH = &OPL->P_CH[c]; + /* OPL->P_CH[c].PAN = OPN_CENTER; */ + for(s = 0; s < 2; s++ ) { + /* wave table */ + CH->SLOT[s].wavetable = &SIN_TABLE[0]; + /* CH->SLOT[s].evm = ENV_MOD_RR; */ + CH->SLOT[s].evc = EG_OFF; + CH->SLOT[s].eve = EG_OFF + 1; + CH->SLOT[s].evs = 0; + } + } +} + +/* ---------- Create a virtual YM3812 ---------- */ +/* 'rate' is sampling rate and 'bufsiz' is the size of the */ +FM_OPL *OPLCreate(int type, int clock, int rate) { + char *ptr; + FM_OPL *OPL; + int state_size; +#ifdef TWELVE_VOICE + int max_ch = 12; +#else + int max_ch = 9; /* normaly 9 channels */ +#endif + + if( OPL_LockTable() == -1) + return NULL; + /* allocate OPL state space */ + state_size = sizeof(FM_OPL); + state_size += sizeof(OPL_CH) * max_ch; + + /* allocate memory block */ + ptr = (char *)calloc(state_size, 1); + if(ptr == NULL) + return NULL; + + /* clear */ + memset(ptr, 0, state_size); + OPL = (FM_OPL *)ptr; ptr += sizeof(FM_OPL); + OPL->P_CH = (OPL_CH *)ptr; ptr += sizeof(OPL_CH) * max_ch; + + /* set channel state pointer */ + OPL->type = type; + OPL->clock = clock; + OPL->rate = rate; + OPL->max_ch = max_ch; + + /* init grobal tables */ + OPL_initalize(OPL); + + /* reset chip */ + OPLResetChip(OPL); + return OPL; +} + +/* ---------- Destroy one of vietual YM3812 ---------- */ +void OPLDestroy(FM_OPL *OPL) { + OPL_UnLockTable(); + free(OPL); +} + +/* ---------- Option handlers ---------- */ + +void OPLSetTimerHandler(FM_OPL *OPL, OPL_TIMERHANDLER TimerHandler,int channelOffset) { + OPL->TimerHandler = TimerHandler; + OPL->TimerParam = channelOffset; +} + +void OPLSetIRQHandler(FM_OPL *OPL, OPL_IRQHANDLER IRQHandler, int param) { + OPL->IRQHandler = IRQHandler; + OPL->IRQParam = param; +} +void OPLSetUpdateHandler(FM_OPL *OPL, OPL_UPDATEHANDLER UpdateHandler,int param) { + OPL->UpdateHandler = UpdateHandler; + OPL->UpdateParam = param; +} + +/* ---------- YM3812 I/O interface ---------- */ +int OPLWrite(FM_OPL *OPL,int a,int v) { + if(!(a & 1)) { /* address port */ + OPL->address = v & 0xff; + } + else { /* data port */ + if(OPL->UpdateHandler) + OPL->UpdateHandler(OPL->UpdateParam,0); + OPLWriteReg(OPL, OPL->address,v); + } + return OPL->status >> 7; +} + +unsigned char OPLRead(FM_OPL *OPL,int a) { + if(!(a & 1)) { /* status port */ + return OPL->status & (OPL->statusmask | 0x80); + } + /* data port */ + switch(OPL->address) { + case 0x05: /* KeyBoard IN */ + fprintf(stderr, "OPL:read unmapped KEYBOARD port\n"); + return 0; + case 0x19: /* I/O DATA */ + fprintf(stderr, "OPL:read unmapped I/O port\n"); + return 0; + case 0x1a: /* PCM-DATA */ + return 0; + } + return 0; +} + +int OPLTimerOver(FM_OPL *OPL, int c) { + if(c) { /* Timer B */ + OPL_STATUS_SET(OPL, 0x20); + } + else { /* Timer A */ + OPL_STATUS_SET(OPL, 0x40); + /* CSM mode key,TL controll */ + if(OPL->mode & 0x80) { /* CSM mode total level latch and auto key on */ + int ch; + if(OPL->UpdateHandler) + OPL->UpdateHandler(OPL->UpdateParam,0); + for(ch = 0; ch < 9; ch++) + CSMKeyControll(&OPL->P_CH[ch]); + } + } + /* reload timer */ + if (OPL->TimerHandler) + (OPL->TimerHandler)(OPL->TimerParam + c, (double)OPL->T[c] * OPL->TimerBase); + return OPL->status >> 7; +} diff --git a/engines/sci/sfx/softseq/fmopl.h b/engines/sci/sfx/softseq/fmopl.h new file mode 100644 index 0000000000..5bb1cd80d7 --- /dev/null +++ b/engines/sci/sfx/softseq/fmopl.h @@ -0,0 +1,168 @@ +/*************************************************************************** + fmopl.h Copyright (C) 1999-2000 Tatsuyuki Satoh + 2001-2004 The ScummVM project + 2002 Solomon Peachy + 2004 Walter van Niftrik + + 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. + + LGPL licensed version of MAMEs fmopl (V0.37a modified) by + Tatsuyuki Satoh. Included from LGPL'ed AdPlug. + +***************************************************************************/ + +#ifndef FMOPL_H_ +#define FMOPL_H_ + +#include <scitypes.h> + +#define TWELVE_VOICE 1 +#ifdef TWELVE_VOICE +#define ADLIB_VOICES 12 +#else +#define ADLIB_VOICES 9 +#endif + +enum { + FMOPL_ENV_BITS_HQ = 16, + FMOPL_ENV_BITS_LQ = 8, + FMOPL_EG_ENT_HQ = 4096, + FMOPL_EG_ENT_LQ = 128 +}; + + +typedef void (*OPL_TIMERHANDLER)(int channel,double interval_Sec); +typedef void (*OPL_IRQHANDLER)(int param,int irq); +typedef void (*OPL_UPDATEHANDLER)(int param,int min_interval_us); + +#define OPL_TYPE_WAVESEL 0x01 /* waveform select */ + +/* Saving is necessary for member of the 'R' mark for suspend/resume */ +/* ---------- OPL one of slot ---------- */ +typedef struct fm_opl_slot { + int TL; /* total level :TL << 8 */ + int TLL; /* adjusted now TL */ + guint8 KSR; /* key scale rate :(shift down bit) */ + int *AR; /* attack rate :&AR_TABLE[AR<<2] */ + int *DR; /* decay rate :&DR_TABLE[DR<<2] */ + int SL; /* sustain level :SL_TABLE[SL] */ + int *RR; /* release rate :&DR_TABLE[RR<<2] */ + guint8 ksl; /* keyscale level :(shift down bits) */ + guint8 ksr; /* key scale rate :kcode>>KSR */ + guint32 mul; /* multiple :ML_TABLE[ML] */ + guint32 Cnt; /* frequency count */ + guint32 Incr; /* frequency step */ + + /* envelope generator state */ + guint8 eg_typ;/* envelope type flag */ + guint8 evm; /* envelope phase */ + int evc; /* envelope counter */ + int eve; /* envelope counter end point */ + int evs; /* envelope counter step */ + int evsa; /* envelope step for AR :AR[ksr] */ + int evsd; /* envelope step for DR :DR[ksr] */ + int evsr; /* envelope step for RR :RR[ksr] */ + + /* LFO */ + guint8 ams; /* ams flag */ + guint8 vib; /* vibrate flag */ + /* wave selector */ + int **wavetable; +} OPL_SLOT; + +/* ---------- OPL one of channel ---------- */ +typedef struct fm_opl_channel { + OPL_SLOT SLOT[2]; + guint8 CON; /* connection type */ + guint8 FB; /* feed back :(shift down bit)*/ + int *connect1; /* slot1 output pointer */ + int *connect2; /* slot2 output pointer */ + int op1_out[2]; /* slot1 output for selfeedback */ + + /* phase generator state */ + guint32 block_fnum; /* block+fnum */ + guint8 kcode; /* key code : KeyScaleCode */ + guint32 fc; /* Freq. Increment base */ + guint32 ksl_base; /* KeyScaleLevel Base step */ + guint8 keyon; /* key on/off flag */ +} OPL_CH; + +/* OPL state */ +typedef struct fm_opl_f { + guint8 type; /* chip type */ + int clock; /* master clock (Hz) */ + int rate; /* sampling rate (Hz) */ + double freqbase; /* frequency base */ + double TimerBase; /* Timer base time (==sampling time) */ + guint8 address; /* address register */ + guint8 status; /* status flag */ + guint8 statusmask; /* status mask */ + guint32 mode; /* Reg.08 : CSM , notesel,etc. */ + + /* Timer */ + int T[2]; /* timer counter */ + guint8 st[2]; /* timer enable */ + + /* FM channel slots */ + OPL_CH *P_CH; /* pointer of CH */ + int max_ch; /* maximum channel */ + + /* Rythm sention */ + guint8 rythm; /* Rythm mode , key flag */ + + /* time tables */ + int AR_TABLE[75]; /* atttack rate tables */ + int DR_TABLE[75]; /* decay rate tables */ + guint32 FN_TABLE[1024];/* fnumber -> increment counter */ + + /* LFO */ + int *ams_table; + int *vib_table; + int amsCnt; + int amsIncr; + int vibCnt; + int vibIncr; + + /* wave selector enable flag */ + guint8 wavesel; + + /* external event callback handler */ + OPL_TIMERHANDLER TimerHandler; /* TIMER handler */ + int TimerParam; /* TIMER parameter */ + OPL_IRQHANDLER IRQHandler; /* IRQ handler */ + int IRQParam; /* IRQ parameter */ + OPL_UPDATEHANDLER UpdateHandler; /* stream update handler */ + int UpdateParam; /* stream update parameter */ +} FM_OPL; + +/* ---------- Generic interface section ---------- */ +#define OPL_TYPE_YM3526 (0) +#define OPL_TYPE_YM3812 (OPL_TYPE_WAVESEL) + +void OPLBuildTables(int ENV_BITS_PARAM, int EG_ENT_PARAM); + +FM_OPL *OPLCreate(int type, int clock, int rate); +void OPLDestroy(FM_OPL *OPL); +void OPLSetTimerHandler(FM_OPL *OPL, OPL_TIMERHANDLER TimerHandler, int channelOffset); +void OPLSetIRQHandler(FM_OPL *OPL, OPL_IRQHANDLER IRQHandler, int param); +void OPLSetUpdateHandler(FM_OPL *OPL, OPL_UPDATEHANDLER UpdateHandler, int param); + +void OPLResetChip(FM_OPL *OPL); +int OPLWrite(FM_OPL *OPL, int a, int v); +unsigned char OPLRead(FM_OPL *OPL, int a); +int OPLTimerOver(FM_OPL *OPL, int c); +void OPLWriteReg(FM_OPL *OPL, int r, int v); +void YM3812UpdateOne(FM_OPL *OPL, gint16 *buffer, int length, int interleave); +#endif diff --git a/engines/sci/sfx/softseq/mt32.cpp b/engines/sci/sfx/softseq/mt32.cpp new file mode 100644 index 0000000000..6b37673c9e --- /dev/null +++ b/engines/sci/sfx/softseq/mt32.cpp @@ -0,0 +1,228 @@ +/*************************************************************************** + Copyright (C) 2008 Walter van Niftrik + + + 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 merchantability, + 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: + + Walter van Niftrik <w.f.b.w.v.niftrik@stud.tue.nl> + +***************************************************************************/ + +extern "C" { +#include "../softseq.h" +#include "../sequencer.h" +#include "../device.h" +#include "resource.h" +#include "sci_memory.h" +} + +#include "mt32/mt32emu.h" + +/* FIXME */ +#define FREESCI_GAMEDIR ".freesci" + +#define SAMPLE_RATE 32000 +#define CHANNELS SFX_PCM_STEREO_LR +#define STEREO 1 + +static MT32Emu::Synth *synth; +static sfx_sequencer_t *mt32seq; +static int initializing; + +/* MIDI writer */ + +static int +mt32_midi_init(struct _midi_writer *self) +{ + return SFX_OK; +} + +static int +mt32_midi_set_option(struct _midi_writer *self, char *name, char *value) +{ + return SFX_ERROR; +} + +static int +mt32_midi_write(struct _midi_writer *self, unsigned char *buf, int len) +{ + if (buf[0] == 0xf0) + synth->playSysex(buf, len); + else if (len < 4) { + MT32Emu::Bit32u msg; + msg = buf[0]; + if (len > 0) + msg |= buf[1] << 8; + if (len > 1) + msg |= buf[2] << 16; + synth->playMsg(msg); + } + else + sciprintf("MT32EMU: Skipping non-sysex message of more than 3 bytes.\n"); + + return SFX_OK; +} + +static void +mt32_midi_delay(struct _midi_writer *self, int ticks) +{ +} + +static void +mt32_midi_reset_timer(struct _midi_writer *self) +{ +} + +static void +mt32_midi_close(struct _midi_writer *self) +{ +} + +static midi_writer_t midi_writer_mt32 = { + "mt32", + mt32_midi_init, + mt32_midi_set_option, + mt32_midi_write, + mt32_midi_delay, + NULL, + mt32_midi_reset_timer, + mt32_midi_close +}; + +/* Software sequencer */ + +static void printDebug(void *userData, const char *fmt, va_list list) +{ + if (initializing) { + vprintf(fmt, list); + printf("\n"); + } +} + +static void +mt32_poll(sfx_softseq_t *self, byte *dest, int count) +{ + synth->render((MT32Emu::Bit16s *) dest, count); +} + +static int +mt32_init(sfx_softseq_t *self, byte *data_ptr, int data_length, byte *data2_ptr, + int data2_length) +{ + MT32Emu::SynthProperties prop; + char *home = sci_get_homedir(); + char *romdir; + + if (!home) { + sciprintf("MT32EMU: Couldn't determine home directory.\n"); + return SFX_ERROR; + } + + romdir = (char *) sci_malloc(sizeof(home) + 2 * sizeof(G_DIR_SEPARATOR_S) + + sizeof(FREESCI_GAMEDIR) + 1); + + strcpy(romdir, home); + strcat(romdir, G_DIR_SEPARATOR_S); + strcat(romdir, FREESCI_GAMEDIR); + strcat(romdir, G_DIR_SEPARATOR_S); + + mt32seq = sfx_find_sequencer("MT32"); + if (!mt32seq) { + sciprintf("MT32EMU: Unable to find MT32 sequencer.\n"); + return SFX_ERROR; + } + + prop.sampleRate = SAMPLE_RATE; + prop.useReverb = true; + prop.useDefaultReverb = true; + prop.baseDir = romdir; + prop.userData = NULL; + prop.report = NULL; + prop.printDebug = printDebug; + prop.openFile = NULL; + prop.closeFile = NULL; + + initializing = 1; + synth = new MT32Emu::Synth(); + if (!synth->open(prop)) + return SFX_ERROR; + initializing = 0; + + mt32seq->open(data_length, data_ptr, data2_length, data2_ptr, + &midi_writer_mt32); + + free(romdir); + + return SFX_OK; +} + +static void +mt32_exit(sfx_softseq_t *self) +{ + synth->close(); + delete synth; + + mt32seq->close(); +} + +static void +mt32_allstop(sfx_softseq_t *self) +{ + if (mt32seq->allstop) + mt32seq->allstop(); +} + +static void +mt32_volume(sfx_softseq_t *self, int volume) +{ + if (mt32seq->volume) + mt32seq->volume(volume / 2); /* High volume causes clipping. */ +} + +static int +mt32_set_option(sfx_softseq_t *self, char *name, char *value) +{ + return SFX_ERROR; +} + +static void +mt32_event(sfx_softseq_t *self, byte cmd, int argc, byte *argv) +{ + mt32seq->event(cmd, argc, argv); +} + +sfx_softseq_t sfx_softseq_mt32 = { + "mt32emu", + "0.1", + mt32_set_option, + mt32_init, + mt32_exit, + mt32_volume, + mt32_event, + mt32_poll, + mt32_allstop, + NULL, + 001, /* patch.001 */ + SFX_SEQ_PATCHFILE_NONE, + 0x01, /* playflag */ + 1, /* do play channel 9 */ + 32, /* Max polypgony */ + {SAMPLE_RATE, CHANNELS, SFX_PCM_FORMAT_S16_NATIVE} +}; diff --git a/engines/sci/sfx/softseq/mt32/Makefile.am b/engines/sci/sfx/softseq/mt32/Makefile.am new file mode 100644 index 0000000000..5d2ffa31c8 --- /dev/null +++ b/engines/sci/sfx/softseq/mt32/Makefile.am @@ -0,0 +1,5 @@ +noinst_HEADERS = freeverb.h mt32emu.h part.h partialManager.h synth.h \ +i386.h mt32_file.h partial.h structures.h tables.h +noinst_LIBRARIES = libmt32emu.a +libmt32emu_a_SOURCES = freeverb.cpp mt32_file.cpp partial.cpp synth.cpp \ +i386.cpp part.cpp partialManager.cpp tables.cpp diff --git a/engines/sci/sfx/softseq/mt32/freeverb.cpp b/engines/sci/sfx/softseq/mt32/freeverb.cpp new file mode 100644 index 0000000000..1c3aab0494 --- /dev/null +++ b/engines/sci/sfx/softseq/mt32/freeverb.cpp @@ -0,0 +1,310 @@ +/* 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$ + * + */ + +// Comb filter implementation +// +// Written by +// http://www.dreampoint.co.uk +// This code is public domain + +#include "freeverb.h" + +comb::comb() { + filterstore = 0; + bufidx = 0; +} + +void comb::setbuffer(float *buf, int size) { + buffer = buf; + bufsize = size; +} + +void comb::mute() { + for (int i = 0; i < bufsize; i++) + buffer[i] = 0; +} + +void comb::setdamp(float val) { + damp1 = val; + damp2 = 1 - val; +} + +float comb::getdamp() { + return damp1; +} + +void comb::setfeedback(float val) { + feedback = val; +} + +float comb::getfeedback() { + return feedback; +} + +// Allpass filter implementation + +allpass::allpass() { + bufidx = 0; +} + +void allpass::setbuffer(float *buf, int size) { + buffer = buf; + bufsize = size; +} + +void allpass::mute() { + for (int i = 0; i < bufsize; i++) + buffer[i] = 0; +} + +void allpass::setfeedback(float val) { + feedback = val; +} + +float allpass::getfeedback() { + return feedback; +} + +// Reverb model implementation + +revmodel::revmodel() { + // Tie the components to their buffers + combL[0].setbuffer(bufcombL1,combtuningL1); + combR[0].setbuffer(bufcombR1,combtuningR1); + combL[1].setbuffer(bufcombL2,combtuningL2); + combR[1].setbuffer(bufcombR2,combtuningR2); + combL[2].setbuffer(bufcombL3,combtuningL3); + combR[2].setbuffer(bufcombR3,combtuningR3); + combL[3].setbuffer(bufcombL4,combtuningL4); + combR[3].setbuffer(bufcombR4,combtuningR4); + combL[4].setbuffer(bufcombL5,combtuningL5); + combR[4].setbuffer(bufcombR5,combtuningR5); + combL[5].setbuffer(bufcombL6,combtuningL6); + combR[5].setbuffer(bufcombR6,combtuningR6); + combL[6].setbuffer(bufcombL7,combtuningL7); + combR[6].setbuffer(bufcombR7,combtuningR7); + combL[7].setbuffer(bufcombL8,combtuningL8); + combR[7].setbuffer(bufcombR8,combtuningR8); + allpassL[0].setbuffer(bufallpassL1,allpasstuningL1); + allpassR[0].setbuffer(bufallpassR1,allpasstuningR1); + allpassL[1].setbuffer(bufallpassL2,allpasstuningL2); + allpassR[1].setbuffer(bufallpassR2,allpasstuningR2); + allpassL[2].setbuffer(bufallpassL3,allpasstuningL3); + allpassR[2].setbuffer(bufallpassR3,allpasstuningR3); + allpassL[3].setbuffer(bufallpassL4,allpasstuningL4); + allpassR[3].setbuffer(bufallpassR4,allpasstuningR4); + + // Set default values + allpassL[0].setfeedback(0.5f); + allpassR[0].setfeedback(0.5f); + allpassL[1].setfeedback(0.5f); + allpassR[1].setfeedback(0.5f); + allpassL[2].setfeedback(0.5f); + allpassR[2].setfeedback(0.5f); + allpassL[3].setfeedback(0.5f); + allpassR[3].setfeedback(0.5f); + setwet(initialwet); + setroomsize(initialroom); + setdry(initialdry); + setdamp(initialdamp); + setwidth(initialwidth); + setmode(initialmode); + + // Buffer will be full of rubbish - so we MUST mute them + mute(); +} + +void revmodel::mute() { + int i; + + if (getmode() >= freezemode) + return; + + for (i = 0; i < numcombs; i++) { + combL[i].mute(); + combR[i].mute(); + } + + for (i = 0; i < numallpasses; i++) { + allpassL[i].mute(); + allpassR[i].mute(); + } +} + +void revmodel::processreplace(float *inputL, float *inputR, float *outputL, float *outputR, long numsamples, int skip) { + float outL, outR, input; + + while (numsamples-- > 0) { + int i; + + outL = outR = 0; + input = (*inputL + *inputR) * gain; + + // Accumulate comb filters in parallel + for (i = 0; i < numcombs; i++) { + outL += combL[i].process(input); + outR += combR[i].process(input); + } + + // Feed through allpasses in series + for (i = 0; i < numallpasses; i++) { + outL = allpassL[i].process(outL); + outR = allpassR[i].process(outR); + } + + // Calculate output REPLACING anything already there + *outputL = outL * wet1 + outR * wet2 + *inputL * dry; + *outputR = outR * wet1 + outL * wet2 + *inputR * dry; + + // Increment sample pointers, allowing for interleave (if any) + inputL += skip; + inputR += skip; + outputL += skip; + outputR += skip; + } +} + +void revmodel::processmix(float *inputL, float *inputR, float *outputL, float *outputR, long numsamples, int skip) { + float outL, outR, input; + + while (numsamples-- > 0) { + int i; + + outL = outR = 0; + input = (*inputL + *inputR) * gain; + + // Accumulate comb filters in parallel + for (i = 0; i < numcombs; i++) { + outL += combL[i].process(input); + outR += combR[i].process(input); + } + + // Feed through allpasses in series + for (i = 0; i < numallpasses; i++) { + outL = allpassL[i].process(outL); + outR = allpassR[i].process(outR); + } + + // Calculate output MIXING with anything already there + *outputL += outL * wet1 + outR * wet2 + *inputL * dry; + *outputR += outR * wet1 + outL * wet2 + *inputR * dry; + + // Increment sample pointers, allowing for interleave (if any) + inputL += skip; + inputR += skip; + outputL += skip; + outputR += skip; + } +} + +void revmodel::update() { + // Recalculate internal values after parameter change + + int i; + + wet1 = wet * (width / 2 + 0.5f); + wet2 = wet * ((1 - width) / 2); + + if (mode >= freezemode) { + roomsize1 = 1; + damp1 = 0; + gain = muted; + } else { + roomsize1 = roomsize; + damp1 = damp; + gain = fixedgain; + } + + for (i = 0; i < numcombs; i++) { + combL[i].setfeedback(roomsize1); + combR[i].setfeedback(roomsize1); + } + + for (i = 0; i < numcombs; i++) { + combL[i].setdamp(damp1); + combR[i].setdamp(damp1); + } +} + +// The following get/set functions are not inlined, because +// speed is never an issue when calling them, and also +// because as you develop the reverb model, you may +// wish to take dynamic action when they are called. + +void revmodel::setroomsize(float value) { + roomsize = (value * scaleroom) + offsetroom; + update(); +} + +float revmodel::getroomsize() { + return (roomsize - offsetroom) / scaleroom; +} + +void revmodel::setdamp(float value) { + damp = value * scaledamp; + update(); +} + +float revmodel::getdamp() { + return damp / scaledamp; +} + +void revmodel::setwet(float value) { + wet = value * scalewet; + update(); +} + +float revmodel::getwet() { + return wet / scalewet; +} + +void revmodel::setdry(float value) { + dry = value * scaledry; +} + +float revmodel::getdry() { + return dry / scaledry; +} + +void revmodel::setwidth(float value) { + width = value; + update(); +} + +float revmodel::getwidth() { + return width; +} + +void revmodel::setmode(float value) { + mode = value; + update(); +} + +float revmodel::getmode() { + if (mode >= freezemode) + return 1; + else + return 0; +} diff --git a/engines/sci/sfx/softseq/mt32/freeverb.h b/engines/sci/sfx/softseq/mt32/freeverb.h new file mode 100644 index 0000000000..53c5307c5a --- /dev/null +++ b/engines/sci/sfx/softseq/mt32/freeverb.h @@ -0,0 +1,239 @@ +/* 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$ + * + */ + +// Macro for killing denormalled numbers +// +// Written by Jezar at Dreampoint, June 2000 +// http://www.dreampoint.co.uk +// Based on IS_DENORMAL macro by Jon Watte +// This code is public domain + +#ifndef FREEVERB_H +#define FREEVERB_H + +#define undenormalise(sample) if (((*(unsigned int*)&sample) & 0x7f800000) == 0) sample = 0.0f + +// Comb filter class declaration + +class comb { +public: + comb(); + void setbuffer(float *buf, int size); + inline float process(float inp); + void mute(); + void setdamp(float val); + float getdamp(); + void setfeedback(float val); + float getfeedback(); +private: + float feedback; + float filterstore; + float damp1; + float damp2; + float *buffer; + int bufsize; + int bufidx; +}; + + +// Big to inline - but crucial for speed + +inline float comb::process(float input) { + float output; + + output = buffer[bufidx]; + undenormalise(output); + + filterstore = (output * damp2) + (filterstore * damp1); + undenormalise(filterstore); + + buffer[bufidx] = input + (filterstore * feedback); + + if (++bufidx >= bufsize) + bufidx = 0; + + return output; +} + +// Allpass filter declaration + +class allpass { +public: + allpass(); + void setbuffer(float *buf, int size); + inline float process(float inp); + void mute(); + void setfeedback(float val); + float getfeedback(); +private: + float feedback; + float *buffer; + int bufsize; + int bufidx; +}; + + +// Big to inline - but crucial for speed + +inline float allpass::process(float input) { + float output; + float bufout; + + bufout = buffer[bufidx]; + undenormalise(bufout); + + output = -input + bufout; + buffer[bufidx] = input + (bufout * feedback); + + if (++bufidx >= bufsize) + bufidx = 0; + + return output; +} + + +// Reverb model tuning values + +const int numcombs = 8; +const int numallpasses = 4; +const float muted = 0; +const float fixedgain = 0.015f; +const float scalewet = 3; +const float scaledry = 2; +const float scaledamp = 0.4f; +const float scaleroom = 0.28f; +const float offsetroom = 0.7f; +const float initialroom = 0.5f; +const float initialdamp = 0.5f; +const float initialwet = 1 / scalewet; +const float initialdry = 0; +const float initialwidth = 1; +const float initialmode = 0; +const float freezemode = 0.5f; +const int stereospread = 23; + +// These values assume 44.1KHz sample rate +// they will probably be OK for 48KHz sample rate +// but would need scaling for 96KHz (or other) sample rates. +// The values were obtained by listening tests. +const int combtuningL1 = 1116; +const int combtuningR1 = 1116 + stereospread; +const int combtuningL2 = 1188; +const int combtuningR2 = 1188 + stereospread; +const int combtuningL3 = 1277; +const int combtuningR3 = 1277 + stereospread; +const int combtuningL4 = 1356; +const int combtuningR4 = 1356 + stereospread; +const int combtuningL5 = 1422; +const int combtuningR5 = 1422 + stereospread; +const int combtuningL6 = 1491; +const int combtuningR6 = 1491 + stereospread; +const int combtuningL7 = 1557; +const int combtuningR7 = 1557 + stereospread; +const int combtuningL8 = 1617; +const int combtuningR8 = 1617 + stereospread; +const int allpasstuningL1 = 556; +const int allpasstuningR1 = 556 + stereospread; +const int allpasstuningL2 = 441; +const int allpasstuningR2 = 441 + stereospread; +const int allpasstuningL3 = 341; +const int allpasstuningR3 = 341 + stereospread; +const int allpasstuningL4 = 225; +const int allpasstuningR4 = 225 + stereospread; + + +// Reverb model declaration + +class revmodel { +public: + revmodel(); + void mute(); + void processmix(float *inputL, float *inputR, float *outputL, float *outputR, long numsamples, int skip); + void processreplace(float *inputL, float *inputR, float *outputL, float *outputR, long numsamples, int skip); + void setroomsize(float value); + float getroomsize(); + void setdamp(float value); + float getdamp(); + void setwet(float value); + float getwet(); + void setdry(float value); + float getdry(); + void setwidth(float value); + float getwidth(); + void setmode(float value); + float getmode(); +private: + void update(); + + float gain; + float roomsize, roomsize1; + float damp, damp1; + float wet, wet1, wet2; + float dry; + float width; + float mode; + + // The following are all declared inline + // to remove the need for dynamic allocation + // with its subsequent error-checking messiness + + // Comb filters + comb combL[numcombs]; + comb combR[numcombs]; + + // Allpass filters + allpass allpassL[numallpasses]; + allpass allpassR[numallpasses]; + + // Buffers for the combs + float bufcombL1[combtuningL1]; + float bufcombR1[combtuningR1]; + float bufcombL2[combtuningL2]; + float bufcombR2[combtuningR2]; + float bufcombL3[combtuningL3]; + float bufcombR3[combtuningR3]; + float bufcombL4[combtuningL4]; + float bufcombR4[combtuningR4]; + float bufcombL5[combtuningL5]; + float bufcombR5[combtuningR5]; + float bufcombL6[combtuningL6]; + float bufcombR6[combtuningR6]; + float bufcombL7[combtuningL7]; + float bufcombR7[combtuningR7]; + float bufcombL8[combtuningL8]; + float bufcombR8[combtuningR8]; + + // Buffers for the allpasses + float bufallpassL1[allpasstuningL1]; + float bufallpassR1[allpasstuningR1]; + float bufallpassL2[allpasstuningL2]; + float bufallpassR2[allpasstuningR2]; + float bufallpassL3[allpasstuningL3]; + float bufallpassR3[allpasstuningR3]; + float bufallpassL4[allpasstuningL4]; + float bufallpassR4[allpasstuningR4]; +}; + +#endif diff --git a/engines/sci/sfx/softseq/mt32/i386.cpp b/engines/sci/sfx/softseq/mt32/i386.cpp new file mode 100644 index 0000000000..f092189d76 --- /dev/null +++ b/engines/sci/sfx/softseq/mt32/i386.cpp @@ -0,0 +1,849 @@ +/* Copyright (c) 2003-2005 Various contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include "mt32emu.h" + +#ifdef MT32EMU_HAVE_X86 + +namespace MT32Emu { + +#ifndef _MSC_VER + +#define eflag(value) __asm__ __volatile__("pushfl \n popfl \n" : : "a"(value)) +#define cpuid_flag (1 << 21) + +static inline bool atti386_DetectCPUID() { + unsigned int result; + + // Is there a cpuid? + result = cpuid_flag; // set test + eflag(result); + if (!(result & cpuid_flag)) + return false; + + result = 0; // clear test + eflag(result); + if (result & cpuid_flag) + return false; + + return true; +} + +static inline bool atti386_DetectSIMD() { + unsigned int result; + + if (atti386_DetectCPUID() == false) + return false; + + /* check cpuid */ + __asm__ __volatile__( + "pushl %%ebx \n" \ + "movl $1, %%eax \n" \ + "cpuid \n" \ + "movl %%edx, %0 \n" \ + "popl %%ebx \n" \ + : "=r"(result) : : "eax", "ecx", "edx"); + + if (result & (1 << 25)) + return true; + + return false; +} + +static inline bool atti386_Detect3DNow() { + unsigned int result; + + if (atti386_DetectCPUID() == false) + return false; + + // get cpuid + __asm__ __volatile__( + "pushl %%ebx \n" \ + "movl $0x80000001, %%eax \n" \ + "cpuid \n" \ + "movl %%edx, %0 \n" \ + "popl %%ebx \n" \ + : "=r"(result) : : "eax", "ecx", "edx"); + + if (result & 0x80000000) + return true; + + return false; +} + + +static inline float atti386_iir_filter_sse(float *output, float *hist1_ptr, float *coef_ptr) { + __asm__ __volatile__ ( + "pushl %1 \n" \ + "pushl %2 \n" \ + "movss 0(%0), %%xmm1 \n" \ + "movups 0(%1), %%xmm2 \n" \ + "movlps 0(%2), %%xmm3 \n" \ + " \n" \ + "shufps $0x44, %%xmm3, %%xmm3 \n" \ + " \n" \ + "mulps %%xmm3, %%xmm2 \n" \ + " \n" \ + "subss %%xmm2, %%xmm1 \n" \ + "shufps $0x39, %%xmm2, %%xmm2 \n" \ + "subss %%xmm2, %%xmm1 \n" \ + " \n" \ + "movss %%xmm1, 0(%2) \n" \ + " \n" \ + "shufps $0x39, %%xmm2, %%xmm2 \n" \ + "addss %%xmm2, %%xmm1 \n" \ + " \n" \ + "shufps $0x39, %%xmm2, %%xmm2 \n" \ + "addss %%xmm2, %%xmm1 \n" \ + " \n" \ + "movss %%xmm3, 4(%2) \n" \ + " \n" \ + "addl $16, %1 \n" \ + "addl $8, %2 \n" \ + " \n" \ + "movups 0(%1), %%xmm2 \n" \ + " \n" \ + "movlps 0(%2), %%xmm3 \n" \ + "shufps $0x44, %%xmm3, %%xmm3 \n" \ + " \n" \ + "mulps %%xmm3, %%xmm2 \n" \ + " \n" \ + "subss %%xmm2, %%xmm1 \n" \ + "shufps $0x39, %%xmm2, %%xmm2 \n" \ + "subss %%xmm2, %%xmm1 \n" \ + " \n" \ + "movss %%xmm1, 0(%2) \n" \ + " \n" \ + "shufps $0x39, %%xmm2, %%xmm2 \n" \ + "addss %%xmm2, %%xmm1 \n" \ + " \n" \ + "shufps $0x39, %%xmm2, %%xmm2 \n" \ + "addss %%xmm2, %%xmm1 \n" \ + " \n" \ + "movss %%xmm3, 4(%2) \n" \ + "movss %%xmm1, 0(%0) \n" \ + "popl %2 \n" \ + "popl %1 \n" \ + : : "r"(output), "r"(coef_ptr), "r"(hist1_ptr) + : "memory" +#ifdef __SSE__ + , "xmm1", "xmm2", "xmm3" +#endif + ); + + return *output; +} + +static inline float atti386_iir_filter_3DNow(float output, float *hist1_ptr, float *coef_ptr) { + float tmp; + + __asm__ __volatile__ ( + "movq %0, %%mm1 \n" \ + " \n" \ + "movl %1, %%edi \n" \ + "movq 0(%%edi), %%mm2 \n" \ + " \n" \ + "movl %2, %%eax; \n" \ + "movq 0(%%eax), %%mm3 \n" \ + " \n" \ + "pfmul %%mm3, %%mm2 \n" \ + "pfsub %%mm2, %%mm1 \n" \ + " \n" \ + "psrlq $32, %%mm2 \n" \ + "pfsub %%mm2, %%mm1 \n" \ + " \n" \ + "movd %%mm1, %3 \n" \ + " \n" \ + "addl $8, %%edi \n" \ + "movq 0(%%edi), %%mm2 \n" \ + "movq 0(%%eax), %%mm3 \n" \ + " \n" \ + "pfmul %%mm3, %%mm2 \n" \ + "pfadd %%mm2, %%mm1 \n" \ + " \n" \ + "psrlq $32, %%mm2 \n" \ + "pfadd %%mm2, %%mm1 \n" \ + " \n" \ + "pushl %3 \n" \ + "popl 0(%%eax) \n" \ + " \n" \ + "movd %%mm3, 4(%%eax) \n" \ + " \n" \ + "addl $8, %%edi \n" \ + "addl $8, %%eax \n" \ + " \n" \ + "movq 0(%%edi), %%mm2 \n" \ + "movq 0(%%eax), %%mm3 \n" \ + " \n" \ + "pfmul %%mm3, %%mm2 \n" \ + "pfsub %%mm2, %%mm1 \n" \ + " \n" \ + "psrlq $32, %%mm2 \n" \ + "pfsub %%mm2, %%mm1 \n" \ + " \n" \ + "movd %%mm1, %3 \n" \ + " \n" \ + "addl $8, %%edi \n" \ + "movq 0(%%edi), %%mm2 \n" \ + "movq 0(%%eax), %%mm3 \n" \ + " \n" \ + "pfmul %%mm3, %%mm2 \n" \ + "pfadd %%mm2, %%mm1 \n" \ + " \n" \ + "psrlq $32, %%mm2 \n" \ + "pfadd %%mm2, %%mm1 \n" \ + " \n" \ + "pushl %3 \n" \ + "popl 0(%%eax) \n" \ + "movd %%mm3, 4(%%eax) \n" \ + " \n" \ + "movd %%mm1, %0 \n" \ + "femms \n" \ + : "=m"(output) : "g"(coef_ptr), "g"(hist1_ptr), "m"(tmp) + : "eax", "edi", "memory" +#ifdef __MMX__ + , "mm1", "mm2", "mm3" +#endif + ); + + return output; +} + +static inline void atti386_produceOutput1(int tmplen, Bit16s myvolume, Bit16s *useBuf, Bit16s *snd) { + __asm__ __volatile__( + "movl %0, %%ecx \n" \ + "movw %1, %%ax \n" \ + "shll $16, %%eax \n" \ + "movw %1, %%ax \n" \ + "movd %%eax, %%mm3 \n" \ + "movd %%eax, %%mm2 \n" \ + "psllq $32, %%mm3 \n" \ + "por %%mm2, %%mm3 \n" \ + "movl %2, %%esi \n" \ + "movl %3, %%edi \n" \ + "1: \n" \ + "movq 0(%%esi), %%mm1 \n" \ + "movq 0(%%edi), %%mm2 \n" \ + "pmulhw %%mm3, %%mm1 \n" \ + "paddw %%mm2, %%mm1 \n" \ + "movq %%mm1, 0(%%edi) \n" \ + " \n" \ + "addl $8, %%esi \n" \ + "addl $8, %%edi \n" \ + " \n" \ + "decl %%ecx \n" \ + "cmpl $0, %%ecx \n" \ + "jg 1b \n" \ + "emms \n" \ + : : "g"(tmplen), "g"(myvolume), "g"(useBuf), "g"(snd) + : "eax", "ecx", "edi", "esi", "memory" +#ifdef __MMX__ + , "mm1", "mm2", "mm3" +#endif + ); +} + +static inline void atti386_produceOutput2(Bit32u len, Bit16s *snd, float *sndbufl, float *sndbufr, float *multFactor) { + __asm__ __volatile__( + "movl %4, %%ecx \n" \ + "shrl $1, %%ecx \n" \ + "addl $4, %%ecx \n" \ + "pushl %%ecx \n" \ + " \n" \ + "movl %0, %%esi \n" \ + "movups 0(%%esi), %%xmm1 \n" \ + " \n" \ + "movl %1, %%esi \n" \ + "movl %2, %%edi \n" \ + "1: \n" \ + "xorl %%eax, %%eax \n" \ + "movw 0(%1), %%ax \n" \ + "cwde \n" \ + "incl %1 \n" \ + "incl %1 \n" \ + "movd %%eax, %%mm1 \n" \ + "psrlq $32, %%mm1 \n" \ + "movw 0(%1), %%ax \n" \ + "incl %1 \n" \ + "incl %1 \n" \ + "movd %%eax, %%mm2 \n" \ + "por %%mm2, %%mm1 \n" \ + " \n" \ + "decl %%ecx \n" \ + "jnz 1b \n" \ + " \n" \ + "popl %%ecx \n" \ + "movl %1, %%esi \n" \ + "movl %3, %%edi \n" \ + "incl %%esi \n" \ + "2: \n" \ + "decl %%ecx \n" \ + "jnz 2b \n" \ + : : "g"(multFactor), "r"(snd), "g"(sndbufl), "g"(sndbufr), "g"(len) + : "eax", "ecx", "edi", "esi", "mm1", "mm2", "xmm1", "memory"); +} + +static inline void atti386_mixBuffers(Bit16s * buf1, Bit16s *buf2, int len) { + __asm__ __volatile__( + "movl %0, %%ecx \n" \ + "movl %1, %%esi \n" \ + "movl %2, %%edi \n" \ + "1: \n" \ + "movq 0(%%edi), %%mm1 \n" \ + "movq 0(%%esi), %%mm2 \n" \ + "paddw %%mm2, %%mm1 \n" \ + "movq %%mm1, 0(%%esi) \n" \ + "addl $8, %%edi \n" \ + "addl $8, %%esi \n" \ + "decl %%ecx \n" \ + "cmpl $0, %%ecx \n" \ + "jg 1b \n" \ + "emms \n" \ + : : "g"(len), "g"(buf1), "g"(buf2) + : "ecx", "edi", "esi", "memory" +#ifdef __MMX__ + , "mm1", "mm2" +#endif + ); +} + +static inline void atti386_mixBuffersRingMix(Bit16s * buf1, Bit16s *buf2, int len) { + __asm__ __volatile__( + "movl %0, %%ecx \n" \ + "movl %1, %%esi \n" \ + "movl %2, %%edi \n" \ + "1: \n" \ + "movq 0(%%esi), %%mm1 \n" \ + "movq 0(%%edi), %%mm2 \n" \ + "movq %%mm1, %%mm3 \n" \ + "pmulhw %%mm2, %%mm1 \n" \ + "paddw %%mm3, %%mm1 \n" \ + "movq %%mm1, 0(%%esi) \n" \ + "addl $8, %%edi \n" \ + "addl $8, %%esi \n" \ + "decl %%ecx \n" \ + "cmpl $0, %%ecx \n" \ + "jg 1b \n" \ + "emms \n" \ + : : "g"(len), "g"(buf1), "g"(buf2) + : "ecx", "edi", "esi", "memory" +#ifdef __MMX__ + , "mm1", "mm2", "mm3" +#endif + ); +} + +static inline void atti386_mixBuffersRing(Bit16s * buf1, Bit16s *buf2, int len) { + __asm__ __volatile__( + "movl %0, %%ecx \n" \ + "movl %1, %%esi \n" \ + "movl %2, %%edi \n" \ + "1: \n" \ + "movq 0(%%esi), %%mm1 \n" \ + "movq 0(%%edi), %%mm2 \n" \ + "pmulhw %%mm2, %%mm1 \n" \ + "movq %%mm1, 0(%%esi) \n" \ + "addl $8, %%edi \n" \ + "addl $8, %%esi \n" \ + "decl %%ecx \n" \ + "cmpl $0, %%ecx \n" \ + "jg 1b \n" \ + "emms \n" \ + : : "g"(len), "g"(buf1), "g"(buf2) + : "ecx", "edi", "esi", "memory" +#ifdef __MMX__ + , "mm1", "mm2" +#endif + ); +} + +static inline void atti386_partialProductOutput(int quadlen, Bit16s leftvol, Bit16s rightvol, Bit16s *partialBuf, Bit16s *p1buf) { + __asm__ __volatile__( + "movl %0, %%ecx \n" \ + "movw %1, %%ax \n" \ + "shll $16, %%eax \n" \ + "movw %2, %%ax \n" \ + "movd %%eax, %%mm1 \n" \ + "movd %%eax, %%mm2 \n" \ + "psllq $32, %%mm1 \n" \ + "por %%mm2, %%mm1 \n" \ + "movl %3, %%edi \n" \ + "movl %4, %%esi \n" \ + "pushl %%ebx \n" \ + "1: \n" \ + "movw 0(%%esi), %%bx \n" \ + "addl $2, %%esi \n" \ + "movw 0(%%esi), %%dx \n" \ + "addl $2, %%esi \n" \ + "" \ + "movw %%dx, %%ax \n" \ + "shll $16, %%eax \n" \ + "movw %%dx, %%ax \n" \ + "movd %%eax, %%mm2 \n" \ + "psllq $32, %%mm2 \n" \ + "movw %%bx, %%ax \n" \ + "shll $16, %%eax \n" \ + "movw %%bx, %%ax \n" \ + "movd %%eax, %%mm3 \n" \ + "por %%mm3, %%mm2 \n" \ + "" \ + "pmulhw %%mm1, %%mm2 \n" \ + "movq %%mm2, 0(%%edi) \n" \ + "addl $8, %%edi \n" \ + "" \ + "decl %%ecx \n" \ + "cmpl $0, %%ecx \n" \ + "jg 1b \n" \ + "emms \n" \ + "popl %%ebx \n" \ + : : "g"(quadlen), "g"(leftvol), "g"(rightvol), "g"(partialBuf), "g"(p1buf) + : "eax", "ecx", "edx", "edi", "esi", "memory" +#ifdef __MMX__ + , "mm1", "mm2", "mm3" +#endif + ); +} + +#endif + +bool DetectSIMD() { +#ifdef _MSC_VER + bool found_simd; + __asm { + pushfd + pop eax // get EFLAGS into eax + mov ebx,eax // keep a copy + xor eax,0x200000 + // toggle CPUID bit + + push eax + popfd // set new EFLAGS + pushfd + pop eax // EFLAGS back into eax + + xor eax,ebx + // have we changed the ID bit? + + je NO_SIMD + // No, no CPUID instruction + + // we could toggle the + // ID bit so CPUID is present + mov eax,1 + + cpuid // get processor features + test edx,1<<25 // check the SIMD bit + jz NO_SIMD + mov found_simd,1 + jmp DONE + NO_SIMD: + mov found_simd,0 + DONE: + } + return found_simd; +#else + return atti386_DetectSIMD(); +#endif +} + +bool Detect3DNow() { +#ifdef _MSC_VER + bool found3D = false; + __asm { + pushfd + pop eax + mov edx, eax + xor eax, 00200000h + push eax + popfd + pushfd + pop eax + xor eax, edx + jz NO_3DNOW + + mov eax, 80000000h + cpuid + + cmp eax, 80000000h + jbe NO_3DNOW + + mov eax, 80000001h + cpuid + test edx, 80000000h + jz NO_3DNOW + mov found3D, 1 +NO_3DNOW: + + } + return found3D; +#else + return atti386_Detect3DNow(); +#endif +} + +float iir_filter_sse(float input,float *hist1_ptr, float *coef_ptr) { + float output; + + // 1st number of coefficients array is overall input scale factor, or filter gain + output = input * (*coef_ptr++); + +#ifdef _MSC_VER + __asm { + + movss xmm1, output + + mov eax, coef_ptr + movups xmm2, [eax] + + mov eax, hist1_ptr + movlps xmm3, [eax] + shufps xmm3, xmm3, 44h + // hist1_ptr+1, hist1_ptr, hist1_ptr+1, hist1_ptr + + mulps xmm2, xmm3 + + subss xmm1, xmm2 + // Rotate elements right + shufps xmm2, xmm2, 39h + subss xmm1, xmm2 + + // Store new_hist + movss DWORD PTR [eax], xmm1 + + // Rotate elements right + shufps xmm2, xmm2, 39h + addss xmm1, xmm2 + + // Rotate elements right + shufps xmm2, xmm2, 39h + addss xmm1, xmm2 + + // Store previous hist + movss DWORD PTR [eax+4], xmm3 + + add coef_ptr, 16 + add hist1_ptr, 8 + + mov eax, coef_ptr + movups xmm2, [eax] + + mov eax, hist1_ptr + movlps xmm3, [eax] + shufps xmm3, xmm3, 44h + // hist1_ptr+1, hist1_ptr, hist1_ptr+1, hist1_ptr + + mulps xmm2, xmm3 + + subss xmm1, xmm2 + // Rotate elements right + shufps xmm2, xmm2, 39h + subss xmm1, xmm2 + + // Store new_hist + movss DWORD PTR [eax], xmm1 + + // Rotate elements right + shufps xmm2, xmm2, 39h + addss xmm1, xmm2 + + // Rotate elements right + shufps xmm2, xmm2, 39h + addss xmm1, xmm2 + + // Store previous hist + movss DWORD PTR [eax+4], xmm3 + + movss output, xmm1 + } +#else + output = atti386_iir_filter_sse(&output, hist1_ptr, coef_ptr); +#endif + return output; +} + +float iir_filter_3dnow(float input,float *hist1_ptr, float *coef_ptr) { + float output; + + // 1st number of coefficients array is overall input scale factor, or filter gain + output = input * (*coef_ptr++); + + // I find it very sad that 3DNow requires twice as many instructions as Intel's SSE + // Intel does have the upper hand here. +#ifdef _MSC_VER + float tmp; + __asm { + movq mm1, output + mov ebx, coef_ptr + movq mm2, [ebx] + + mov eax, hist1_ptr; + movq mm3, [eax] + + pfmul mm2, mm3 + pfsub mm1, mm2 + + psrlq mm2, 32 + pfsub mm1, mm2 + + // Store new hist + movd tmp, mm1 + + add ebx, 8 + movq mm2, [ebx] + movq mm3, [eax] + + pfmul mm2, mm3 + pfadd mm1, mm2 + + psrlq mm2, 32 + pfadd mm1, mm2 + + push tmp + pop DWORD PTR [eax] + + movd DWORD PTR [eax+4], mm3 + + add ebx, 8 + add eax, 8 + + movq mm2, [ebx] + movq mm3, [eax] + + pfmul mm2, mm3 + pfsub mm1, mm2 + + psrlq mm2, 32 + pfsub mm1, mm2 + + // Store new hist + movd tmp, mm1 + + add ebx, 8 + movq mm2, [ebx] + movq mm3, [eax] + + pfmul mm2, mm3 + pfadd mm1, mm2 + + psrlq mm2, 32 + pfadd mm1, mm2 + + push tmp + pop DWORD PTR [eax] + movd DWORD PTR [eax+4], mm3 + + movd output, mm1 + + femms + } +#else + output = atti386_iir_filter_3DNow(output, hist1_ptr, coef_ptr); +#endif + return output; +} + +#if MT32EMU_USE_MMX > 0 + +int i386_partialProductOutput(int len, Bit16s leftvol, Bit16s rightvol, Bit16s *partialBuf, Bit16s *mixedBuf) { + int tmplen = len >> 1; + if (tmplen == 0) { + return 0; + } +#ifdef _MSC_VER + __asm { + mov ecx,tmplen + mov ax, leftvol + shl eax,16 + mov ax, rightvol + movd mm1, eax + movd mm2, eax + psllq mm1, 32 + por mm1, mm2 + mov edi, partialBuf + mov esi, mixedBuf +mmxloop1: + mov bx, [esi] + add esi,2 + mov dx, [esi] + add esi,2 + + mov ax, dx + shl eax, 16 + mov ax, dx + movd mm2,eax + psllq mm2, 32 + mov ax, bx + shl eax, 16 + mov ax, bx + movd mm3,eax + por mm2,mm3 + + pmulhw mm2, mm1 + movq [edi], mm2 + add edi, 8 + + dec ecx + cmp ecx,0 + jg mmxloop1 + emms + } +#else + atti386_partialProductOutput(tmplen, leftvol, rightvol, partialBuf, mixedBuf); +#endif + return tmplen << 1; +} + +int i386_mixBuffers(Bit16s * buf1, Bit16s *buf2, int len) { + int tmplen = len >> 2; + if (tmplen == 0) { + return 0; + } +#ifdef _MSC_VER + __asm { + mov ecx, tmplen + mov esi, buf1 + mov edi, buf2 + +mixloop1: + movq mm1, [edi] + movq mm2, [esi] + paddw mm1,mm2 + movq [esi],mm1 + add edi,8 + add esi,8 + + dec ecx + cmp ecx,0 + jg mixloop1 + emms + } +#else + atti386_mixBuffers(buf1, buf2, tmplen); +#endif + return tmplen << 2; +} + + +int i386_mixBuffersRingMix(Bit16s * buf1, Bit16s *buf2, int len) { + int tmplen = len >> 2; + if (tmplen == 0) { + return 0; + } +#ifdef _MSC_VER + __asm { + mov ecx, tmplen + mov esi, buf1 + mov edi, buf2 + +mixloop2: + movq mm1, [esi] + movq mm2, [edi] + movq mm3, mm1 + pmulhw mm1, mm2 + paddw mm1,mm3 + movq [esi],mm1 + add edi,8 + add esi,8 + + dec ecx + cmp ecx,0 + jg mixloop2 + emms + } +#else + atti386_mixBuffersRingMix(buf1, buf2, tmplen); +#endif + return tmplen << 2; +} + +int i386_mixBuffersRing(Bit16s * buf1, Bit16s *buf2, int len) { + int tmplen = len >> 2; + if (tmplen == 0) { + return 0; + } +#ifdef _MSC_VER + __asm { + mov ecx, tmplen + mov esi, buf1 + mov edi, buf2 + +mixloop3: + movq mm1, [esi] + movq mm2, [edi] + pmulhw mm1, mm2 + movq [esi],mm1 + add edi,8 + add esi,8 + + dec ecx + cmp ecx,0 + jg mixloop3 + emms + } +#else + atti386_mixBuffersRing(buf1, buf2, tmplen); +#endif + return tmplen << 2; +} + +int i386_produceOutput1(Bit16s *useBuf, Bit16s *stream, Bit32u len, Bit16s volume) { + int tmplen = (len >> 1); + if (tmplen == 0) { + return 0; + } +#ifdef _MSC_VER + __asm { + mov ecx, tmplen + mov ax,volume + shl eax,16 + mov ax,volume + movd mm3,eax + movd mm2,eax + psllq mm3, 32 + por mm3,mm2 + mov esi, useBuf + mov edi, stream +mixloop4: + movq mm1, [esi] + movq mm2, [edi] + pmulhw mm1, mm3 + paddw mm1,mm2 + movq [edi], mm1 + + add esi,8 + add edi,8 + + dec ecx + cmp ecx,0 + jg mixloop4 + emms + } +#else + atti386_produceOutput1(tmplen, volume, useBuf, stream); +#endif + return tmplen << 1; +} + +#endif + +} + +#endif diff --git a/engines/sci/sfx/softseq/mt32/i386.h b/engines/sci/sfx/softseq/mt32/i386.h new file mode 100644 index 0000000000..e8644411cd --- /dev/null +++ b/engines/sci/sfx/softseq/mt32/i386.h @@ -0,0 +1,49 @@ +/* Copyright (c) 2003-2005 Various contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef MT32EMU_I386_H +#define MT32EMU_I386_H + +namespace MT32Emu { +#ifdef MT32EMU_HAVE_X86 + +// Function that detects the availablity of SSE SIMD instructions +bool DetectSIMD(); +// Function that detects the availablity of 3DNow instructions +bool Detect3DNow(); + +float iir_filter_sse(float input,float *hist1_ptr, float *coef_ptr); +float iir_filter_3dnow(float input,float *hist1_ptr, float *coef_ptr); +float iir_filter_normal(float input,float *hist1_ptr, float *coef_ptr); + +#if MT32EMU_USE_MMX > 0 +int i386_partialProductOutput(int len, Bit16s leftvol, Bit16s rightvol, Bit16s *partialBuf, Bit16s *mixedBuf); +int i386_mixBuffers(Bit16s * buf1, Bit16s *buf2, int len); +int i386_mixBuffersRingMix(Bit16s * buf1, Bit16s *buf2, int len); +int i386_mixBuffersRing(Bit16s * buf1, Bit16s *buf2, int len); +int i386_produceOutput1(Bit16s *useBuf, Bit16s *stream, Bit32u len, Bit16s volume); +#endif + +#endif + +} + +#endif diff --git a/engines/sci/sfx/softseq/mt32/mt32_file.cpp b/engines/sci/sfx/softseq/mt32/mt32_file.cpp new file mode 100644 index 0000000000..86cb29fd49 --- /dev/null +++ b/engines/sci/sfx/softseq/mt32/mt32_file.cpp @@ -0,0 +1,112 @@ +/* Copyright (c) 2003-2005 Various contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include <stdio.h> + +#include "mt32emu.h" + +namespace MT32Emu { + + bool ANSIFile::open(const char *filename, OpenMode mode) { + const char *fmode; + if (mode == OpenMode_read) { + fmode = "rb"; + } else { + fmode = "wb"; + } + fp = fopen(filename, fmode); + return (fp != NULL); + } + + void ANSIFile::close() { + fclose(fp); + } + + size_t ANSIFile::read(void *in, size_t size) { + return fread(in, 1, size, fp); + } + + bool ANSIFile::readLine(char *in, size_t size) { + return fgets(in, (int)size, fp) != NULL; + } + + bool ANSIFile::readBit8u(Bit8u *in) { + int c = fgetc(fp); + if (c == EOF) + return false; + *in = (Bit8u)c; + return true; + } + + bool File::readBit16u(Bit16u *in) { + Bit8u b[2]; + if (read(&b[0], 2) != 2) + return false; + *in = ((b[0] << 8) | b[1]); + return true; + } + + bool File::readBit32u(Bit32u *in) { + Bit8u b[4]; + if (read(&b[0], 4) != 4) + return false; + *in = ((b[0] << 24) | (b[1] << 16) | (b[2] << 8) | b[3]); + return true; + } + + size_t ANSIFile::write(const void *out, size_t size) { + return fwrite(out, 1, size, fp); + } + + bool ANSIFile::writeBit8u(Bit8u out) { + return fputc(out, fp) != EOF; + } + + bool File::writeBit16u(Bit16u out) { + if (!writeBit8u((Bit8u)((out & 0xFF00) >> 8))) { + return false; + } + if (!writeBit8u((Bit8u)(out & 0x00FF))) { + return false; + } + return true; + } + + bool File::writeBit32u(Bit32u out) { + if (!writeBit8u((Bit8u)((out & 0xFF000000) >> 24))) { + return false; + } + if (!writeBit8u((Bit8u)((out & 0x00FF0000) >> 16))) { + return false; + } + if (!writeBit8u((Bit8u)((out & 0x0000FF00) >> 8))) { + return false; + } + if (!writeBit8u((Bit8u)(out & 0x000000FF))) { + return false; + } + return true; + } + + bool ANSIFile::isEOF() { + return feof(fp) != 0; + } +} diff --git a/engines/sci/sfx/softseq/mt32/mt32_file.h b/engines/sci/sfx/softseq/mt32/mt32_file.h new file mode 100644 index 0000000000..5f05c9e9ae --- /dev/null +++ b/engines/sci/sfx/softseq/mt32/mt32_file.h @@ -0,0 +1,67 @@ +/* Copyright (c) 2003-2005 Various contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef MT32EMU_FILE_H +#define MT32EMU_FILE_H + +#include <stdio.h> + +namespace MT32Emu { + +class File { +public: + enum OpenMode { + OpenMode_read = 0, + OpenMode_write = 1 + }; + virtual ~File() {} + virtual void close() = 0; + virtual size_t read(void *in, size_t size) = 0; + virtual bool readLine(char *in, size_t size) = 0; + virtual bool readBit8u(Bit8u *in) = 0; + virtual bool readBit16u(Bit16u *in); + virtual bool readBit32u(Bit32u *in); + virtual size_t write(const void *out, size_t size) = 0; + virtual bool writeBit8u(Bit8u out) = 0; + // Note: May write a single byte to the file before failing + virtual bool writeBit16u(Bit16u out); + // Note: May write some (<4) bytes to the file before failing + virtual bool writeBit32u(Bit32u out); + virtual bool isEOF() = 0; +}; + +class ANSIFile: public File { +private: + FILE *fp; +public: + bool open(const char *filename, OpenMode mode); + void close(); + size_t read(void *in, size_t size); + bool readLine(char *in, size_t size); + bool readBit8u(Bit8u *in); + size_t write(const void *out, size_t size); + bool writeBit8u(Bit8u out); + bool isEOF(); +}; + +} + +#endif diff --git a/engines/sci/sfx/softseq/mt32/mt32emu.h b/engines/sci/sfx/softseq/mt32/mt32emu.h new file mode 100644 index 0000000000..0aa4df7488 --- /dev/null +++ b/engines/sci/sfx/softseq/mt32/mt32emu.h @@ -0,0 +1,70 @@ +/* Copyright (c) 2003-2005 Various contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef MT32EMU_MT32EMU_H +#define MT32EMU_MT32EMU_H + +// Debugging +// Show the instruments played +#define MT32EMU_MONITOR_INSTRUMENTS 1 +// Shows number of partials MT-32 is playing, and on which parts +#define MT32EMU_MONITOR_PARTIALS 0 +// Determines how the waveform cache file is handled (must be regenerated after sampling rate change) +#define MT32EMU_WAVECACHEMODE 0 // Load existing cache if possible, otherwise generate and save cache +//#define MT32EMU_WAVECACHEMODE 1 // Load existing cache if possible, otherwise generage but don't save cache +//#define MT32EMU_WAVECACHEMODE 2 // Ignore existing cache, generate and save cache +//#define MT32EMU_WAVECACHEMODE 3 // Ignore existing cache, generate but don't save cache + +// Configuration +// The maximum number of partials playing simultaneously +#define MT32EMU_MAX_PARTIALS 32 +// The maximum number of notes playing simultaneously per part. +// No point making it more than MT32EMU_MAX_PARTIALS, since each note needs at least one partial. +#define MT32EMU_MAX_POLY 32 +// This calculates the exact frequencies of notes as they are played, instead of offsetting from pre-cached semitones. Potentially very slow. +#define MT32EMU_ACCURATENOTES 0 + +#if (defined (_MSC_VER) && defined(_M_IX86)) +#define MT32EMU_HAVE_X86 +#elif defined(__GNUC__) +#if __GNUC__ >= 3 && defined(__i386__) +#define MT32EMU_HAVE_X86 +#endif +#endif + +#ifdef MT32EMU_HAVE_X86 +#define MT32EMU_USE_MMX 1 +#else +#define MT32EMU_USE_MMX 0 +#endif + +#include "freeverb.h" + +#include "structures.h" +#include "i386.h" +#include "mt32_file.h" +#include "tables.h" +#include "partial.h" +#include "partialManager.h" +#include "part.h" +#include "synth.h" + +#endif diff --git a/engines/sci/sfx/softseq/mt32/part.cpp b/engines/sci/sfx/softseq/mt32/part.cpp new file mode 100644 index 0000000000..b3d71bccca --- /dev/null +++ b/engines/sci/sfx/softseq/mt32/part.cpp @@ -0,0 +1,632 @@ +/* Copyright (c) 2003-2005 Various contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include <string.h> +#include <math.h> + +#include "mt32emu.h" + +namespace MT32Emu { + +static const Bit8u PartialStruct[13] = { + 0, 0, 2, 2, 1, 3, + 3, 0, 3, 0, 2, 1, 3 }; + +static const Bit8u PartialMixStruct[13] = { + 0, 1, 0, 1, 1, 0, + 1, 3, 3, 2, 2, 2, 2 }; + +static const float floatKeyfollow[17] = { + -1.0f, -1.0f/2.0f, -1.0f/4.0f, 0.0f, + 1.0f/8.0f, 1.0f/4.0f, 3.0f/8.0f, 1.0f/2.0f, 5.0f/8.0f, 3.0f/4.0f, 7.0f/8.0f, 1.0f, + 5.0f/4.0f, 3.0f/2.0f, 2.0f, + 1.0009765625f, 1.0048828125f +}; + +//FIXME:KG: Put this dpoly stuff somewhere better +bool dpoly::isActive() const { + return partials[0] != NULL || partials[1] != NULL || partials[2] != NULL || partials[3] != NULL; +} + +Bit32u dpoly::getAge() const { + for (int i = 0; i < 4; i++) { + if (partials[i] != NULL) { + return partials[i]->age; + } + } + return 0; +} + +RhythmPart::RhythmPart(Synth *useSynth, unsigned int usePartNum): Part(useSynth, usePartNum) { + strcpy(name, "Rhythm"); + rhythmTemp = &synth->mt32ram.rhythmSettings[0]; + refresh(); +} + +Part::Part(Synth *useSynth, unsigned int usePartNum) { + this->synth = useSynth; + this->partNum = usePartNum; + patchCache[0].dirty = true; + holdpedal = false; + patchTemp = &synth->mt32ram.patchSettings[partNum]; + if (usePartNum == 8) { + // Nasty hack for rhythm + timbreTemp = NULL; + } else { + sprintf(name, "Part %d", partNum + 1); + timbreTemp = &synth->mt32ram.timbreSettings[partNum]; + } + currentInstr[0] = 0; + currentInstr[10] = 0; + expression = 127; + volumeMult = 0; + volumesetting.leftvol = 32767; + volumesetting.rightvol = 32767; + bend = 0.0f; + memset(polyTable,0,sizeof(polyTable)); + memset(patchCache, 0, sizeof(patchCache)); +} + +void Part::setHoldPedal(bool pedalval) { + if (holdpedal && !pedalval) { + holdpedal = false; + stopPedalHold(); + } else { + holdpedal = pedalval; + } +} + +void RhythmPart::setBend(unsigned int midiBend) { + synth->printDebug("%s: Setting bend (%d) not supported on rhythm", name, midiBend); + return; +} + +void Part::setBend(unsigned int midiBend) { + // FIXME:KG: Slightly unbalanced increments, but I wanted min -1.0, centre 0.0 and max 1.0 + if (midiBend <= 0x2000) { + bend = ((signed int)midiBend - 0x2000) / (float)0x2000; + } else { + bend = ((signed int)midiBend - 0x2000) / (float)0x1FFF; + } + // Loop through all partials to update their bend + for (int i = 0; i < MT32EMU_MAX_POLY; i++) { + for (int j = 0; j < 4; j++) { + if (polyTable[i].partials[j] != NULL) { + polyTable[i].partials[j]->setBend(bend); + } + } + } +} + +void RhythmPart::setModulation(unsigned int midiModulation) { + synth->printDebug("%s: Setting modulation (%d) not supported on rhythm", name, midiModulation); +} + +void Part::setModulation(unsigned int midiModulation) { + // Just a bloody guess, as always, before I get things figured out + for (int t = 0; t < 4; t++) { + if (patchCache[t].playPartial) { + int newrate = (patchCache[t].modsense * midiModulation) >> 7; + //patchCache[t].lfoperiod = lfotable[newrate]; + patchCache[t].lfodepth = newrate; + //FIXME:KG: timbreTemp->partial[t].lfo.depth = + } + } +} + +void RhythmPart::refresh() { + updateVolume(); + // (Re-)cache all the mapped timbres ahead of time + for (unsigned int drumNum = 0; drumNum < synth->controlROMMap->rhythmSettingsCount; drumNum++) { + int drumTimbreNum = rhythmTemp[drumNum].timbre; + if (drumTimbreNum >= 127) // 94 on MT-32 + continue; + Bit16s pan = rhythmTemp[drumNum].panpot; // They use R-L 0-14... + // FIXME:KG: Panning cache should be backed up to partials using it, too + if (pan < 7) { + drumPan[drumNum].leftvol = pan * 4681; + drumPan[drumNum].rightvol = 32767; + } else { + drumPan[drumNum].rightvol = (14 - pan) * 4681; + drumPan[drumNum].leftvol = 32767; + } + PatchCache *cache = drumCache[drumNum]; + backupCacheToPartials(cache); + for (int t = 0; t < 4; t++) { + // Common parameters, stored redundantly + cache[t].dirty = true; + cache[t].pitchShift = 0.0f; + cache[t].benderRange = 0.0f; + cache[t].pansetptr = &drumPan[drumNum]; + cache[t].reverb = rhythmTemp[drumNum].reverbSwitch > 0; + } + } +} + +void Part::refresh() { + updateVolume(); + backupCacheToPartials(patchCache); + for (int t = 0; t < 4; t++) { + // Common parameters, stored redundantly + patchCache[t].dirty = true; + patchCache[t].pitchShift = (patchTemp->patch.keyShift - 24) + (patchTemp->patch.fineTune - 50) / 100.0f; + patchCache[t].benderRange = patchTemp->patch.benderRange; + patchCache[t].pansetptr = &volumesetting; + patchCache[t].reverb = patchTemp->patch.reverbSwitch > 0; + } + memcpy(currentInstr, timbreTemp->common.name, 10); +} + +const char *Part::getCurrentInstr() const { + return ¤tInstr[0]; +} + +void RhythmPart::refreshTimbre(unsigned int absTimbreNum) { + for (int m = 0; m < 85; m++) { + if (rhythmTemp[m].timbre == absTimbreNum - 128) + drumCache[m][0].dirty = true; + } +} + +void Part::refreshTimbre(unsigned int absTimbreNum) { + if (getAbsTimbreNum() == absTimbreNum) { + memcpy(currentInstr, timbreTemp->common.name, 10); + patchCache[0].dirty = true; + } +} + +int Part::fixBiaslevel(int srcpnt, int *dir) { + int noteat = srcpnt & 0x3F; + int outnote; + if (srcpnt < 64) + *dir = 0; + else + *dir = 1; + outnote = 33 + noteat; + //synth->printDebug("Bias note %d, dir %d", outnote, *dir); + + return outnote; +} + +int Part::fixKeyfollow(int srckey) { + if (srckey>=0 && srckey<=16) { + int keyfix[17] = { -256*16, -128*16, -64*16, 0, 32*16, 64*16, 96*16, 128*16, (128+32)*16, 192*16, (192+32)*16, 256*16, (256+64)*16, (256+128)*16, (512)*16, 4100, 4116}; + return keyfix[srckey]; + } else { + //LOG(LOG_ERROR|LOG_MISC,"Missed key: %d", srckey); + return 256; + } +} + +void Part::abortPoly(dpoly *poly) { + if (!poly->isPlaying) { + return; + } + for (int i = 0; i < 4; i++) { + Partial *partial = poly->partials[i]; + if (partial != NULL) { + partial->deactivate(); + } + } + poly->isPlaying = false; +} + +void Part::setPatch(const PatchParam *patch) { + patchTemp->patch = *patch; +} + +void RhythmPart::setTimbre(TimbreParam * /*timbre*/) { + synth->printDebug("%s: Attempted to call setTimbre() - doesn't make sense for rhythm", name); +} + +void Part::setTimbre(TimbreParam *timbre) { + *timbreTemp = *timbre; +} + +unsigned int RhythmPart::getAbsTimbreNum() const { + synth->printDebug("%s: Attempted to call getAbsTimbreNum() - doesn't make sense for rhythm", name); + return 0; +} + +unsigned int Part::getAbsTimbreNum() const { + return (patchTemp->patch.timbreGroup * 64) + patchTemp->patch.timbreNum; +} + +void RhythmPart::setProgram(unsigned int patchNum) { + synth->printDebug("%s: Attempt to set program (%d) on rhythm is invalid", name, patchNum); +} + +void Part::setProgram(unsigned int patchNum) { + setPatch(&synth->mt32ram.patches[patchNum]); + setTimbre(&synth->mt32ram.timbres[getAbsTimbreNum()].timbre); + + refresh(); + + allSoundOff(); //FIXME:KG: Is this correct? +} + +void Part::backupCacheToPartials(PatchCache cache[4]) { + // check if any partials are still playing with the old patch cache + // if so then duplicate the cached data from the part to the partial so that + // we can change the part's cache without affecting the partial. + // We delay this until now to avoid a copy operation with every note played + for (int m = 0; m < MT32EMU_MAX_POLY; m++) { + for (int i = 0; i < 4; i++) { + Partial *partial = polyTable[m].partials[i]; + if (partial != NULL && partial->patchCache == &cache[i]) { + partial->cachebackup = cache[i]; + partial->patchCache = &partial->cachebackup; + } + } + } +} + +void Part::cacheTimbre(PatchCache cache[4], const TimbreParam *timbre) { + backupCacheToPartials(cache); + int partialCount = 0; + for (int t = 0; t < 4; t++) { + if (((timbre->common.pmute >> t) & 0x1) == 1) { + cache[t].playPartial = true; + partialCount++; + } else { + cache[t].playPartial = false; + continue; + } + + // Calculate and cache common parameters + + cache[t].pcm = timbre->partial[t].wg.pcmwave; + cache[t].useBender = (timbre->partial[t].wg.bender == 1); + + switch (t) { + case 0: + cache[t].PCMPartial = (PartialStruct[(int)timbre->common.pstruct12] & 0x2) ? true : false; + cache[t].structureMix = PartialMixStruct[(int)timbre->common.pstruct12]; + cache[t].structurePosition = 0; + cache[t].structurePair = 1; + break; + case 1: + cache[t].PCMPartial = (PartialStruct[(int)timbre->common.pstruct12] & 0x1) ? true : false; + cache[t].structureMix = PartialMixStruct[(int)timbre->common.pstruct12]; + cache[t].structurePosition = 1; + cache[t].structurePair = 0; + break; + case 2: + cache[t].PCMPartial = (PartialStruct[(int)timbre->common.pstruct34] & 0x2) ? true : false; + cache[t].structureMix = PartialMixStruct[(int)timbre->common.pstruct34]; + cache[t].structurePosition = 0; + cache[t].structurePair = 3; + break; + case 3: + cache[t].PCMPartial = (PartialStruct[(int)timbre->common.pstruct34] & 0x1) ? true : false; + cache[t].structureMix = PartialMixStruct[(int)timbre->common.pstruct34]; + cache[t].structurePosition = 1; + cache[t].structurePair = 2; + break; + default: + break; + } + + cache[t].waveform = timbre->partial[t].wg.waveform; + cache[t].pulsewidth = timbre->partial[t].wg.pulsewid; + cache[t].pwsens = timbre->partial[t].wg.pwvelo; + if (timbre->partial[t].wg.keyfollow > 16) { + synth->printDebug("Bad keyfollow value in timbre!"); + cache[t].pitchKeyfollow = 1.0f; + } else { + cache[t].pitchKeyfollow = floatKeyfollow[timbre->partial[t].wg.keyfollow]; + } + + cache[t].pitch = timbre->partial[t].wg.coarse + (timbre->partial[t].wg.fine - 50) / 100.0f + 24.0f; + cache[t].pitchEnv = timbre->partial[t].env; + cache[t].pitchEnv.sensitivity = (char)((float)cache[t].pitchEnv.sensitivity * 1.27f); + cache[t].pitchsustain = cache[t].pitchEnv.level[3]; + + // Calculate and cache TVA envelope stuff + cache[t].ampEnv = timbre->partial[t].tva; + cache[t].ampEnv.level = (char)((float)cache[t].ampEnv.level * 1.27f); + + cache[t].ampbias[0] = fixBiaslevel(cache[t].ampEnv.biaspoint1, &cache[t].ampdir[0]); + cache[t].ampblevel[0] = 12 - cache[t].ampEnv.biaslevel1; + cache[t].ampbias[1] = fixBiaslevel(cache[t].ampEnv.biaspoint2, &cache[t].ampdir[1]); + cache[t].ampblevel[1] = 12 - cache[t].ampEnv.biaslevel2; + cache[t].ampdepth = cache[t].ampEnv.envvkf * cache[t].ampEnv.envvkf; + + // Calculate and cache filter stuff + cache[t].filtEnv = timbre->partial[t].tvf; + cache[t].filtkeyfollow = fixKeyfollow(cache[t].filtEnv.keyfollow); + cache[t].filtEnv.envdepth = (char)((float)cache[t].filtEnv.envdepth * 1.27); + cache[t].tvfbias = fixBiaslevel(cache[t].filtEnv.biaspoint, &cache[t].tvfdir); + cache[t].tvfblevel = cache[t].filtEnv.biaslevel; + cache[t].filtsustain = cache[t].filtEnv.envlevel[3]; + + // Calculate and cache LFO stuff + cache[t].lfodepth = timbre->partial[t].lfo.depth; + cache[t].lfoperiod = synth->tables.lfoPeriod[(int)timbre->partial[t].lfo.rate]; + cache[t].lforate = timbre->partial[t].lfo.rate; + cache[t].modsense = timbre->partial[t].lfo.modsense; + } + for (int t = 0; t < 4; t++) { + // Common parameters, stored redundantly + cache[t].dirty = false; + cache[t].partialCount = partialCount; + cache[t].sustain = (timbre->common.nosustain == 0); + } + //synth->printDebug("Res 1: %d 2: %d 3: %d 4: %d", cache[0].waveform, cache[1].waveform, cache[2].waveform, cache[3].waveform); + +#if MT32EMU_MONITOR_INSTRUMENTS == 1 + synth->printDebug("%s (%s): Recached timbre", name, currentInstr); + for (int i = 0; i < 4; i++) { + synth->printDebug(" %d: play=%s, pcm=%s (%d), wave=%d", i, cache[i].playPartial ? "YES" : "NO", cache[i].PCMPartial ? "YES" : "NO", timbre->partial[i].wg.pcmwave, timbre->partial[i].wg.waveform); + } +#endif +} + +const char *Part::getName() const { + return name; +} + +void Part::updateVolume() { + volumeMult = synth->tables.volumeMult[patchTemp->outlevel * expression / 127]; +} + +int Part::getVolume() const { + // FIXME: Use the mappings for this in the control ROM + return patchTemp->outlevel * 127 / 100; +} + +void Part::setVolume(int midiVolume) { + // FIXME: Use the mappings for this in the control ROM + patchTemp->outlevel = (Bit8u)(midiVolume * 100 / 127); + updateVolume(); + synth->printDebug("%s (%s): Set volume to %d", name, currentInstr, midiVolume); +} + +void Part::setExpression(int midiExpression) { + expression = midiExpression; + updateVolume(); +} + +void RhythmPart::setPan(unsigned int midiPan) +{ + // FIXME:KG: This is unchangeable for drums (they always use drumPan), is that correct? + synth->printDebug("%s: Setting pan (%d) not supported on rhythm", name, midiPan); +} + +void Part::setPan(unsigned int midiPan) { + // FIXME:KG: Tweaked this a bit so that we have a left 100%, centre and right 100% + // (But this makes the range somewhat skewed) + // Check against the real thing + // NOTE: Panning is inverted compared to GM. + if (midiPan < 64) { + volumesetting.leftvol = (Bit16s)(midiPan * 512); + volumesetting.rightvol = 32767; + } else if (midiPan == 64) { + volumesetting.leftvol = 32767; + volumesetting.rightvol = 32767; + } else { + volumesetting.rightvol = (Bit16s)((127 - midiPan) * 520); + volumesetting.leftvol = 32767; + } + patchTemp->panpot = (Bit8u)(midiPan * 14 / 127); + //synth->printDebug("%s (%s): Set pan to %d", name, currentInstr, panpot); +} + +void RhythmPart::playNote(unsigned int key, int vel) { + if (key < 24 || key > 108)/*> 87 on MT-32)*/ { + synth->printDebug("%s: Attempted to play invalid key %d", name, key); + return; + } + int drumNum = key - 24; + int drumTimbreNum = rhythmTemp[drumNum].timbre; + if (drumTimbreNum >= 127) { // 94 on MT-32 + synth->printDebug("%s: Attempted to play unmapped key %d", name, key); + return; + } + int absTimbreNum = drumTimbreNum + 128; + TimbreParam *timbre = &synth->mt32ram.timbres[absTimbreNum].timbre; + memcpy(currentInstr, timbre->common.name, 10); +#if MT32EMU_MONITOR_INSTRUMENTS == 1 + synth->printDebug("%s (%s): starting poly (drum %d, timbre %d) - Vel %d Key %d", name, currentInstr, drumNum, absTimbreNum, vel, key); +#endif + if (drumCache[drumNum][0].dirty) { + cacheTimbre(drumCache[drumNum], timbre); + } + playPoly(drumCache[drumNum], key, MIDDLEC, vel); +} + +void Part::playNote(unsigned int key, int vel) { + int freqNum = key; + if (freqNum < 12) { + synth->printDebug("%s (%s): Attempted to play invalid key %d < 12; moving up by octave", name, currentInstr, key); + freqNum += 12; + } else if (freqNum > 108) { + synth->printDebug("%s (%s): Attempted to play invalid key %d > 108; moving down by octave", name, currentInstr, key); + while (freqNum > 108) { + freqNum -= 12; + } + } + // POLY1 mode, Single Assign + // Haven't found any software that uses any of the other poly modes + // FIXME:KG: Should this also apply to rhythm? + for (unsigned int i = 0; i < MT32EMU_MAX_POLY; i++) { + if (polyTable[i].isActive() && (polyTable[i].key == key)) { + //AbortPoly(&polyTable[i]); + stopNote(key); + break; + } + } +#if MT32EMU_MONITOR_INSTRUMENTS == 1 + synth->printDebug("%s (%s): starting poly - Vel %d Key %d", name, currentInstr, vel, key); +#endif + if (patchCache[0].dirty) { + cacheTimbre(patchCache, timbreTemp); + } + playPoly(patchCache, key, freqNum, vel); +} + +void Part::playPoly(const PatchCache cache[4], unsigned int key, int freqNum, int vel) { + unsigned int needPartials = cache[0].partialCount; + unsigned int freePartials = synth->partialManager->getFreePartialCount(); + + if (freePartials < needPartials) { + if (!synth->partialManager->freePartials(needPartials - freePartials, partNum)) { + synth->printDebug("%s (%s): Insufficient free partials to play key %d (vel=%d); needed=%d, free=%d", name, currentInstr, key, vel, needPartials, synth->partialManager->getFreePartialCount()); + return; + } + } + // Find free poly + int m; + for (m = 0; m < MT32EMU_MAX_POLY; m++) { + if (!polyTable[m].isActive()) { + break; + } + } + if (m == MT32EMU_MAX_POLY) { + synth->printDebug("%s (%s): No free poly to play key %d (vel %d)", name, currentInstr, key, vel); + return; + } + + dpoly *tpoly = &polyTable[m]; + + tpoly->isPlaying = true; + tpoly->key = key; + tpoly->isDecay = false; + tpoly->freqnum = freqNum; + tpoly->vel = vel; + tpoly->pedalhold = false; + + bool allnull = true; + for (int x = 0; x < 4; x++) { + if (cache[x].playPartial) { + tpoly->partials[x] = synth->partialManager->allocPartial(partNum); + allnull = false; + } else { + tpoly->partials[x] = NULL; + } + } + + if (allnull) + synth->printDebug("%s (%s): No partials to play for this instrument", name, this->currentInstr); + + tpoly->sustain = cache[0].sustain; + tpoly->volumeptr = &volumeMult; + + for (int x = 0; x < 4; x++) { + if (tpoly->partials[x] != NULL) { + tpoly->partials[x]->startPartial(tpoly, &cache[x], tpoly->partials[cache[x].structurePair]); + tpoly->partials[x]->setBend(bend); + } + } +} + +static void startDecayPoly(dpoly *tpoly) { + if (tpoly->isDecay) { + return; + } + tpoly->isDecay = true; + + for (int t = 0; t < 4; t++) { + Partial *partial = tpoly->partials[t]; + if (partial == NULL) + continue; + partial->startDecayAll(); + } + tpoly->isPlaying = false; +} + +void Part::allNotesOff() { + // Note: Unchecked on real MT-32, but the MIDI specification states that all notes off (0x7B) + // should treat the hold pedal as usual. + // All *sound* off (0x78) should stop notes immediately regardless of the hold pedal. + // The latter controller is not implemented on the MT-32 (according to the docs). + for (int q = 0; q < MT32EMU_MAX_POLY; q++) { + dpoly *tpoly = &polyTable[q]; + if (tpoly->isPlaying) { + if (holdpedal) + tpoly->pedalhold = true; + else if (tpoly->sustain) + startDecayPoly(tpoly); + } + } +} + +void Part::allSoundOff() { + for (int q = 0; q < MT32EMU_MAX_POLY; q++) { + dpoly *tpoly = &polyTable[q]; + if (tpoly->isPlaying) { + startDecayPoly(tpoly); + } + } +} + +void Part::stopPedalHold() { + for (int q = 0; q < MT32EMU_MAX_POLY; q++) { + dpoly *tpoly; + tpoly = &polyTable[q]; + if (tpoly->isActive() && tpoly->pedalhold) + stopNote(tpoly->key); + } +} + +void Part::stopNote(unsigned int key) { + // Non-sustaining instruments ignore stop commands. + // They die away eventually anyway + +#if MT32EMU_MONITOR_INSTRUMENTS == 1 + synth->printDebug("%s (%s): stopping key %d", name, currentInstr, key); +#endif + + if (key != 255) { + for (int q = 0; q < MT32EMU_MAX_POLY; q++) { + dpoly *tpoly = &polyTable[q]; + if (tpoly->isPlaying && tpoly->key == key) { + if (holdpedal) + tpoly->pedalhold = true; + else if (tpoly->sustain) + startDecayPoly(tpoly); + } + } + return; + } + + // Find oldest poly... yes, the MT-32 can be reconfigured to kill different poly first + // This is simplest + int oldest = -1; + Bit32u oldage = 0; + + for (int q = 0; q < MT32EMU_MAX_POLY; q++) { + dpoly *tpoly = &polyTable[q]; + + if (tpoly->isPlaying && !tpoly->isDecay) { + if (tpoly->getAge() >= oldage) { + oldage = tpoly->getAge(); + oldest = q; + } + } + } + + if (oldest != -1) { + startDecayPoly(&polyTable[oldest]); + } +} + +} diff --git a/engines/sci/sfx/softseq/mt32/part.h b/engines/sci/sfx/softseq/mt32/part.h new file mode 100644 index 0000000000..54c4999653 --- /dev/null +++ b/engines/sci/sfx/softseq/mt32/part.h @@ -0,0 +1,113 @@ +/* Copyright (c) 2003-2005 Various contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef MT32EMU_PART_H +#define MT32EMU_PART_H + +namespace MT32Emu { + +class PartialManager; +class Synth; + +class Part { +private: + // Pointers to the areas of the MT-32's memory dedicated to this part (for parts 1-8) + MemParams::PatchTemp *patchTemp; + TimbreParam *timbreTemp; + + // 0=Part 1, .. 7=Part 8, 8=Rhythm + unsigned int partNum; + + bool holdpedal; + + StereoVolume volumesetting; + + PatchCache patchCache[4]; + + float bend; // -1.0 .. +1.0 + + dpoly polyTable[MT32EMU_MAX_POLY]; + + void abortPoly(dpoly *poly); + + static int fixKeyfollow(int srckey); + static int fixBiaslevel(int srcpnt, int *dir); + + void setPatch(const PatchParam *patch); + +protected: + Synth *synth; + char name[8]; // "Part 1".."Part 8", "Rhythm" + char currentInstr[11]; + int expression; + Bit32u volumeMult; + + void updateVolume(); + void backupCacheToPartials(PatchCache cache[4]); + void cacheTimbre(PatchCache cache[4], const TimbreParam *timbre); + void playPoly(const PatchCache cache[4], unsigned int key, int freqNum, int vel); + const char *getName() const; + +public: + Part(Synth *synth, unsigned int usePartNum); + virtual ~Part() {} + virtual void playNote(unsigned int key, int vel); + void stopNote(unsigned int key); + void allNotesOff(); + void allSoundOff(); + int getVolume() const; + void setVolume(int midiVolume); + void setExpression(int midiExpression); + virtual void setPan(unsigned int midiPan); + virtual void setBend(unsigned int midiBend); + virtual void setModulation(unsigned int midiModulation); + virtual void setProgram(unsigned int midiProgram); + void setHoldPedal(bool pedalval); + void stopPedalHold(); + virtual void refresh(); + virtual void refreshTimbre(unsigned int absTimbreNum); + virtual void setTimbre(TimbreParam *timbre); + virtual unsigned int getAbsTimbreNum() const; + const char *getCurrentInstr() const; +}; + +class RhythmPart: public Part { + // Pointer to the area of the MT-32's memory dedicated to rhythm + const MemParams::RhythmTemp *rhythmTemp; + + // This caches the timbres/settings in use by the rhythm part + PatchCache drumCache[85][4]; + StereoVolume drumPan[85]; +public: + RhythmPart(Synth *synth, unsigned int usePartNum); + void refresh(); + void refreshTimbre(unsigned int timbreNum); + void setTimbre(TimbreParam *timbre); + void playNote(unsigned int key, int vel); + unsigned int getAbsTimbreNum() const; + void setPan(unsigned int midiPan); + void setBend(unsigned int midiBend); + void setModulation(unsigned int midiModulation); + void setProgram(unsigned int patchNum); +}; + +} +#endif diff --git a/engines/sci/sfx/softseq/mt32/partial.cpp b/engines/sci/sfx/softseq/mt32/partial.cpp new file mode 100644 index 0000000000..9d32282a82 --- /dev/null +++ b/engines/sci/sfx/softseq/mt32/partial.cpp @@ -0,0 +1,960 @@ +/* Copyright (c) 2003-2005 Various contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include <stdlib.h> +#include <math.h> +#include <string.h> + +#include "mt32emu.h" + +#ifdef MACOSX +// Older versions of Mac OS X didn't supply a powf function. To ensure +// binary compatibility, we force using pow instead of powf (the only +// potential drawback is that it might be a little bit slower). +#define powf pow +#endif + +#define FIXEDPOINT_UDIV(x, y, point) (((x) << (point)) / ((y))) +#define FIXEDPOINT_SDIV(x, y, point) (((x) * (1 << point)) / ((y))) +#define FIXEDPOINT_UMULT(x, y, point) (((x) * (y)) >> point) +#define FIXEDPOINT_SMULT(x, y, point) (((x) * (y)) / (1 << point)) + +using namespace MT32Emu; + +Partial::Partial(Synth *useSynth) { + this->synth = useSynth; + ownerPart = -1; + poly = NULL; + pair = NULL; +#if MT32EMU_ACCURATENOTES == 1 + for (int i = 0; i < 3; i++) { + noteLookupStorage.waveforms[i] = new Bit16s[65536]; + } + noteLookup = ¬eLookupStorage; +#endif +} + +Partial::~Partial() { +#if MT32EMU_ACCURATENOTES == 1 + for (int i = 0; i < 3; i++) { + delete[] noteLookupStorage.waveforms[i]; + } + delete[] noteLookupStorage.wavTable; +#endif +} + +int Partial::getOwnerPart() const { + return ownerPart; +} + +bool Partial::isActive() { + return ownerPart > -1; +} + +const dpoly *Partial::getDpoly() const { + return this->poly; +} + +void Partial::activate(int part) { + // This just marks the partial as being assigned to a part + ownerPart = part; +} + +void Partial::deactivate() { + ownerPart = -1; + if (poly != NULL) { + for (int i = 0; i < 4; i++) { + if (poly->partials[i] == this) { + poly->partials[i] = NULL; + break; + } + } + if (pair != NULL) { + pair->pair = NULL; + } + } +} + +void Partial::initKeyFollow(int key) { + // Setup partial keyfollow + // Note follow relative to middle C + + // Calculate keyfollow for pitch +#if 1 + float rel = key == -1 ? 0.0f : (key - MIDDLEC); + float newPitch = rel * patchCache->pitchKeyfollow + patchCache->pitch + patchCache->pitchShift; + //FIXME:KG: Does it truncate the keyfollowed pitch to a semitone (towards MIDDLEC)? + //int newKey = (int)(rel * patchCache->pitchKeyfollow); + //float newPitch = newKey + patchCache->pitch + patchCache->pitchShift; +#else + float rel = key == -1 ? 0.0f : (key + patchCache->pitchShift - MIDDLEC); + float newPitch = rel * patchCache->pitchKeyfollow + patchCache->pitch; +#endif +#if MT32EMU_ACCURATENOTES == 1 + noteVal = newPitch; + synth->printDebug("key=%d, pitch=%f, pitchKeyfollow=%f, pitchShift=%f, newPitch=%f", key, patchCache->pitch, patchCache->pitchKeyfollow, patchCache->pitchShift, newPitch); +#else + float newPitchInt; + float newPitchFract = modff(newPitch, &newPitchInt); + if (newPitchFract > 0.5f) { + newPitchInt += 1.0f; + newPitchFract -= 1.0f; + } + noteVal = (int)newPitchInt; + fineShift = (int)(powf(2.0f, newPitchFract / 12.0f) * 4096.0f); + synth->printDebug("key=%d, pitch=%f, pitchKeyfollow=%f, pitchShift=%f, newPitch=%f, noteVal=%d, fineShift=%d", key, patchCache->pitch, patchCache->pitchKeyfollow, patchCache->pitchShift, newPitch, noteVal, fineShift); +#endif + // FIXME:KG: Raise/lower by octaves until in the supported range. + while (noteVal > HIGHEST_NOTE) // FIXME:KG: see tables.cpp: >108? + noteVal -= 12; + while (noteVal < LOWEST_NOTE) // FIXME:KG: see tables.cpp: <12? + noteVal += 12; + // Calculate keyfollow for filter + int keyfollow = ((key - MIDDLEC) * patchCache->filtkeyfollow) / 4096; + if (keyfollow > 108) + keyfollow = 108; + else if (keyfollow < -108) + keyfollow = -108; + filtVal = synth->tables.tvfKeyfollowMult[keyfollow + 108]; + realVal = synth->tables.tvfKeyfollowMult[(noteVal - MIDDLEC) + 108]; +} + +int Partial::getKey() const { + if (poly == NULL) { + return -1; + } else { + return poly->key; + } +} + +void Partial::startPartial(dpoly *usePoly, const PatchCache *useCache, Partial *pairPartial) { + if (usePoly == NULL || useCache == NULL) { + synth->printDebug("*** Error: Starting partial for owner %d, usePoly=%s, useCache=%s", ownerPart, usePoly == NULL ? "*** NULL ***" : "OK", useCache == NULL ? "*** NULL ***" : "OK"); + return; + } + patchCache = useCache; + poly = usePoly; + mixType = patchCache->structureMix; + structurePosition = patchCache->structurePosition; + + play = true; + initKeyFollow(poly->freqnum); // Initialises noteVal, filtVal and realVal +#if MT32EMU_ACCURATENOTES == 0 + noteLookup = &synth->tables.noteLookups[noteVal - LOWEST_NOTE]; +#else + Tables::initNote(synth, ¬eLookupStorage, noteVal, (float)synth->myProp.sampleRate, synth->masterTune, synth->pcmWaves, NULL); +#endif + keyLookup = &synth->tables.keyLookups[poly->freqnum - 12]; + + if (patchCache->PCMPartial) { + pcmNum = patchCache->pcm; + if (synth->controlROMMap->pcmCount > 128) { + // CM-32L, etc. support two "banks" of PCMs, selectable by waveform type parameter. + if (patchCache->waveform > 1) { + pcmNum += 128; + } + } + pcmWave = &synth->pcmWaves[pcmNum]; + } else { + pcmWave = NULL; + } + + lfoPos = 0; + pulsewidth = patchCache->pulsewidth + synth->tables.pwVelfollowAdd[patchCache->pwsens][poly->vel]; + if (pulsewidth > 100) { + pulsewidth = 100; + } else if (pulsewidth < 0) { + pulsewidth = 0; + } + + for (int e = 0; e < 3; e++) { + envs[e].envpos = 0; + envs[e].envstat = -1; + envs[e].envbase = 0; + envs[e].envdist = 0; + envs[e].envsize = 0; + envs[e].sustaining = false; + envs[e].decaying = false; + envs[e].prevlevel = 0; + envs[e].counter = 0; + envs[e].count = 0; + } + ampEnvVal = 0; + pitchEnvVal = 0; + pitchSustain = false; + loopPos = 0; + partialOff.pcmoffset = partialOff.pcmplace = 0; + pair = pairPartial; + useNoisePair = pairPartial == NULL && (mixType == 1 || mixType == 2); + age = 0; + alreadyOutputed = false; + memset(history,0,sizeof(history)); +} + +Bit16s *Partial::generateSamples(long length) { + if (!isActive() || alreadyOutputed) { + return NULL; + } + if (poly == NULL) { + synth->printDebug("*** ERROR: poly is NULL at Partial::generateSamples()!"); + return NULL; + } + + alreadyOutputed = true; + + // Generate samples + + Bit16s *partialBuf = &myBuffer[0]; + Bit32u volume = *poly->volumeptr; + while (length--) { + Bit32s envval; + Bit32s sample = 0; + if (!envs[EnvelopeType_amp].sustaining) { + if (envs[EnvelopeType_amp].count <= 0) { + Bit32u ampval = getAmpEnvelope(); + if (!play) { + deactivate(); + break; + } + if (ampval > 100) { + ampval = 100; + } + + ampval = synth->tables.volumeMult[ampval]; + ampval = FIXEDPOINT_UMULT(ampval, synth->tables.tvaVelfollowMult[poly->vel][(int)patchCache->ampEnv.velosens], 8); + //if (envs[EnvelopeType_amp].sustaining) + ampEnvVal = ampval; + } + --envs[EnvelopeType_amp].count; + } + + unsigned int lfoShift = 0x1000; + if (pitchSustain) { + // Calculate LFO position + // LFO does not kick in completely until pitch envelope sustains + if (patchCache->lfodepth > 0) { + lfoPos++; + if (lfoPos >= patchCache->lfoperiod) + lfoPos = 0; + int lfoatm = FIXEDPOINT_UDIV(lfoPos, patchCache->lfoperiod, 16); + int lfoatr = synth->tables.sintable[lfoatm]; + lfoShift = synth->tables.lfoShift[patchCache->lfodepth][lfoatr]; + } + } else { + // Calculate Pitch envelope + envval = getPitchEnvelope(); + int pd = patchCache->pitchEnv.depth; + pitchEnvVal = synth->tables.pitchEnvVal[pd][envval]; + } + + int delta; + + // Wrap positions or end if necessary + if (patchCache->PCMPartial) { + // PCM partial + + delta = noteLookup->wavTable[pcmNum]; + int len = pcmWave->len; + if (partialOff.pcmplace >= len) { + if (pcmWave->loop) { + //partialOff.pcmplace = partialOff.pcmoffset = 0; + partialOff.pcmplace %= len; + } else { + play = false; + deactivate(); + break; + } + } + } else { + // Synthesis partial + delta = 0x10000; + partialOff.pcmplace %= (Bit16u)noteLookup->div2; + } + + // Build delta for position of next sample + // Fix delta code + Bit32u tdelta = delta; +#if MT32EMU_ACCURATENOTES == 0 + tdelta = FIXEDPOINT_UMULT(tdelta, fineShift, 12); +#endif + tdelta = FIXEDPOINT_UMULT(tdelta, pitchEnvVal, 12); + tdelta = FIXEDPOINT_UMULT(tdelta, lfoShift, 12); + tdelta = FIXEDPOINT_UMULT(tdelta, bendShift, 12); + delta = (int)tdelta; + + // Get waveform - either PCM or synthesized sawtooth or square + if (ampEnvVal > 0) { + if (patchCache->PCMPartial) { + // Render PCM sample + int ra, rb, dist; + Bit32u taddr; + Bit32u pcmAddr = pcmWave->addr; + if (delta < 0x10000) { + // Linear sound interpolation + taddr = pcmAddr + partialOff.pcmplace; + ra = synth->pcmROMData[taddr]; + taddr++; + if (taddr == pcmAddr + pcmWave->len) { + // Past end of PCM + if (pcmWave->loop) { + rb = synth->pcmROMData[pcmAddr]; + } else { + rb = 0; + } + } else { + rb = synth->pcmROMData[taddr]; + } + dist = rb - ra; + sample = (ra + ((dist * (Bit32s)(partialOff.pcmoffset >> 8)) >> 8)); + } else { + // Sound decimation + // The right way to do it is to use a lowpass filter on the waveform before selecting + // a point. This is too slow. The following approximates this as fast as possible + int idelta = delta >> 16; + taddr = pcmAddr + partialOff.pcmplace; + ra = synth->pcmROMData[taddr++]; + for (int ix = 0; ix < idelta - 1; ix++) { + if (taddr == pcmAddr + pcmWave->len) { + // Past end of PCM + if (pcmWave->loop) { + taddr = pcmAddr; + } else { + // Behave as if all subsequent samples were 0 + break; + } + } + ra += synth->pcmROMData[taddr++]; + } + sample = ra / idelta; + } + } else { + // Render synthesised sample + int toff = partialOff.pcmplace; + int minorplace = partialOff.pcmoffset >> 14; + Bit32s filterInput; + Bit32s filtval = getFiltEnvelope(); + + //synth->printDebug("Filtval: %d", filtval); + + if ((patchCache->waveform & 1) == 0) { + // Square waveform. Made by combining two pregenerated bandlimited + // sawtooth waveforms + Bit32u ofsA = ((toff << 2) + minorplace) % noteLookup->waveformSize[0]; + int width = FIXEDPOINT_UMULT(noteLookup->div2, synth->tables.pwFactor[pulsewidth], 7); + Bit32u ofsB = (ofsA + width) % noteLookup->waveformSize[0]; + Bit16s pa = noteLookup->waveforms[0][ofsA]; + Bit16s pb = noteLookup->waveforms[0][ofsB]; + filterInput = pa - pb; + // Non-bandlimited squarewave + /* + ofs = FIXEDPOINT_UMULT(noteLookup->div2, synth->tables.pwFactor[patchCache->pulsewidth], 8); + if (toff < ofs) + sample = 1 * WGAMP; + else + sample = -1 * WGAMP; + */ + } else { + // Sawtooth. Made by combining the full cosine and half cosine according + // to how it looks on the MT-32. What it really does it takes the + // square wave and multiplies it by a full cosine + int waveoff = (toff << 2) + minorplace; + if (toff < noteLookup->sawTable[pulsewidth]) + filterInput = noteLookup->waveforms[1][waveoff % noteLookup->waveformSize[1]]; + else + filterInput = noteLookup->waveforms[2][waveoff % noteLookup->waveformSize[2]]; + // This is the correct way + // Seems slow to me (though bandlimited) -- doesn't seem to + // sound any better though + /* + //int pw = (patchCache->pulsewidth * pulsemod[filtval]) >> 8; + + Bit32u ofs = toff % (noteLookup->div2 >> 1); + + Bit32u ofs3 = toff + FIXEDPOINT_UMULT(noteLookup->div2, synth->tables.pwFactor[patchCache->pulsewidth], 9); + ofs3 = ofs3 % (noteLookup->div2 >> 1); + + pa = noteLookup->waveforms[0][ofs]; + pb = noteLookup->waveforms[0][ofs3]; + sample = ((pa - pb) * noteLookup->waveforms[2][toff]) / 2; + */ + } + + //Very exact filter + if (filtval > ((FILTERGRAN * 15) / 16)) + filtval = ((FILTERGRAN * 15) / 16); + sample = (Bit32s)(floorf((synth->iirFilter)((float)filterInput, &history[0], synth->tables.filtCoeff[filtval][(int)patchCache->filtEnv.resonance])) / synth->tables.resonanceFactor[patchCache->filtEnv.resonance]); + if (sample < -32768) { + synth->printDebug("Overdriven amplitude for %d: %d:=%d < -32768", patchCache->waveform, filterInput, sample); + sample = -32768; + } + else if (sample > 32767) { + synth->printDebug("Overdriven amplitude for %d: %d:=%d > 32767", patchCache->waveform, filterInput, sample); + sample = 32767; + } + } + } + + // Add calculated delta to our waveform offset + Bit32u absOff = ((partialOff.pcmplace << 16) | partialOff.pcmoffset); + absOff += delta; + partialOff.pcmplace = (Bit16u)((absOff & 0xFFFF0000) >> 16); + partialOff.pcmoffset = (Bit16u)(absOff & 0xFFFF); + + // Put volume envelope over generated sample + sample = FIXEDPOINT_SMULT(sample, ampEnvVal, 9); + sample = FIXEDPOINT_SMULT(sample, volume, 7); + envs[EnvelopeType_amp].envpos++; + envs[EnvelopeType_pitch].envpos++; + envs[EnvelopeType_filt].envpos++; + + *partialBuf++ = (Bit16s)sample; + } + // We may have deactivated and broken out of the loop before the end of the buffer, + // if so then fill the remainder with 0s. + if (++length > 0) + memset(partialBuf, 0, length * 2); + return &myBuffer[0]; +} + +void Partial::setBend(float factor) { + if (!patchCache->useBender || factor == 0.0f) { + bendShift = 4096; + return; + } + // NOTE:KG: We can't do this smoothly with lookup tables, unless we use several MB. + // FIXME:KG: Bend should be influenced by pitch key-follow too, according to docs. + float bendSemitones = factor * patchCache->benderRange; // -24 .. 24 + float mult = powf(2.0f, bendSemitones / 12.0f); + synth->printDebug("setBend(): factor=%f, benderRange=%f, semitones=%f, mult=%f\n", factor, patchCache->benderRange, bendSemitones, mult); + bendShift = (int)(mult * 4096.0f); +} + +Bit16s *Partial::mixBuffers(Bit16s * buf1, Bit16s *buf2, int len) { + if (buf1 == NULL) + return buf2; + if (buf2 == NULL) + return buf1; + + Bit16s *outBuf = buf1; +#if MT32EMU_USE_MMX >= 1 + // KG: This seems to be fine + int donelen = i386_mixBuffers(buf1, buf2, len); + len -= donelen; + buf1 += donelen; + buf2 += donelen; +#endif + while (len--) { + *buf1 = *buf1 + *buf2; + buf1++, buf2++; + } + return outBuf; +} + +Bit16s *Partial::mixBuffersRingMix(Bit16s * buf1, Bit16s *buf2, int len) { + if (buf1 == NULL) + return NULL; + if (buf2 == NULL) { + Bit16s *outBuf = buf1; + while (len--) { + if (*buf1 < -8192) + *buf1 = -8192; + else if (*buf1 > 8192) + *buf1 = 8192; + buf1++; + } + return outBuf; + } + + Bit16s *outBuf = buf1; +#if MT32EMU_USE_MMX >= 1 + // KG: This seems to be fine + int donelen = i386_mixBuffersRingMix(buf1, buf2, len); + len -= donelen; + buf1 += donelen; + buf2 += donelen; +#endif + while (len--) { + float a, b; + a = ((float)*buf1) / 8192.0f; + b = ((float)*buf2) / 8192.0f; + a = (a * b) + a; + if (a>1.0) + a = 1.0; + if (a<-1.0) + a = -1.0; + *buf1 = (Bit16s)(a * 8192.0f); + buf1++; + buf2++; + //buf1[i] = (Bit16s)(((Bit32s)buf1[i] * (Bit32s)buf2[i]) >> 10) + buf1[i]; + } + return outBuf; +} + +Bit16s *Partial::mixBuffersRing(Bit16s * buf1, Bit16s *buf2, int len) { + if (buf1 == NULL) { + return NULL; + } + if (buf2 == NULL) { + return NULL; + } + + Bit16s *outBuf = buf1; +#if MT32EMU_USE_MMX >= 1 + // FIXME:KG: Not really checked as working + int donelen = i386_mixBuffersRing(buf1, buf2, len); + len -= donelen; + buf1 += donelen; + buf2 += donelen; +#endif + while (len--) { + float a, b; + a = ((float)*buf1) / 8192.0f; + b = ((float)*buf2) / 8192.0f; + a *= b; + if (a>1.0) + a = 1.0; + if (a<-1.0) + a = -1.0; + *buf1 = (Bit16s)(a * 8192.0f); + buf1++; + buf2++; + } + return outBuf; +} + +void Partial::mixBuffersStereo(Bit16s *buf1, Bit16s *buf2, Bit16s *outBuf, int len) { + if (buf2 == NULL) { + while (len--) { + *outBuf++ = *buf1++; + *outBuf++ = 0; + } + } else if (buf1 == NULL) { + while (len--) { + *outBuf++ = 0; + *outBuf++ = *buf2++; + } + } else { + while (len--) { + *outBuf++ = *buf1++; + *outBuf++ = *buf2++; + } + } +} + +bool Partial::produceOutput(Bit16s *partialBuf, long length) { + if (!isActive() || alreadyOutputed) + return false; + if (poly == NULL) { + synth->printDebug("*** ERROR: poly is NULL at Partial::produceOutput()!"); + return false; + } + + Bit16s *pairBuf = NULL; + // Check for dependant partial + if (pair != NULL) { + if (!pair->alreadyOutputed) { + // Note: pair may have become NULL after this + pairBuf = pair->generateSamples(length); + } + } else if (useNoisePair) { + // Generate noise for pairless ring mix + pairBuf = synth->tables.noiseBuf; + } + + Bit16s *myBuf = generateSamples(length); + + if (myBuf == NULL && pairBuf == NULL) + return false; + + Bit16s *p1buf, *p2buf; + + if (structurePosition == 0 || pairBuf == NULL) { + p1buf = myBuf; + p2buf = pairBuf; + } else { + p2buf = myBuf; + p1buf = pairBuf; + } + + //synth->printDebug("mixType: %d", mixType); + + Bit16s *mixedBuf; + switch (mixType) { + case 0: + // Standard sound mix + mixedBuf = mixBuffers(p1buf, p2buf, length); + break; + + case 1: + // Ring modulation with sound mix + mixedBuf = mixBuffersRingMix(p1buf, p2buf, length); + break; + + case 2: + // Ring modulation alone + mixedBuf = mixBuffersRing(p1buf, p2buf, length); + break; + + case 3: + // Stereo mixing. One partial to one speaker channel, one to another. + // FIXME:KG: Surely we should be multiplying by the left/right volumes here? + mixBuffersStereo(p1buf, p2buf, partialBuf, length); + return true; + + default: + mixedBuf = mixBuffers(p1buf, p2buf, length); + break; + } + + if (mixedBuf == NULL) + return false; + + Bit16s leftvol, rightvol; + leftvol = patchCache->pansetptr->leftvol; + rightvol = patchCache->pansetptr->rightvol; + +#if MT32EMU_USE_MMX >= 2 + // FIXME:KG: This appears to introduce crackle + int donelen = i386_partialProductOutput(length, leftvol, rightvol, partialBuf, mixedBuf); + length -= donelen; + mixedBuf += donelen; + partialBuf += donelen * 2; +#endif + while (length--) { + *partialBuf++ = (Bit16s)(((Bit32s)*mixedBuf * (Bit32s)leftvol) >> 15); + *partialBuf++ = (Bit16s)(((Bit32s)*mixedBuf * (Bit32s)rightvol) >> 15); + mixedBuf++; + } + return true; +} + +Bit32s Partial::getFiltEnvelope() { + int reshigh; + + int cutoff, depth; + + EnvelopeStatus *tStat = &envs[EnvelopeType_filt]; + + if (tStat->decaying) { + reshigh = tStat->envbase; + reshigh = (reshigh + ((tStat->envdist * tStat->envpos) / tStat->envsize)); + if (tStat->envpos >= tStat->envsize) + reshigh = 0; + } else { + if (tStat->envstat==4) { + reshigh = patchCache->filtsustain; + if (!poly->sustain) { + startDecay(EnvelopeType_filt, reshigh); + } + } else { + if ((tStat->envstat==-1) || (tStat->envpos >= tStat->envsize)) { + if (tStat->envstat==-1) + tStat->envbase = 0; + else + tStat->envbase = patchCache->filtEnv.envlevel[tStat->envstat]; + tStat->envstat++; + tStat->envpos = 0; + if (tStat->envstat == 3) { + tStat->envsize = synth->tables.envTime[(int)patchCache->filtEnv.envtime[tStat->envstat]]; + } else { + Bit32u envTime = (int)patchCache->filtEnv.envtime[tStat->envstat]; + if (tStat->envstat > 1) { + int envDiff = abs(patchCache->filtEnv.envlevel[tStat->envstat] - patchCache->filtEnv.envlevel[tStat->envstat - 1]); + if (envTime > synth->tables.envDeltaMaxTime[envDiff]) { + envTime = synth->tables.envDeltaMaxTime[envDiff]; + } + } + + tStat->envsize = (synth->tables.envTime[envTime] * keyLookup->envTimeMult[(int)patchCache->filtEnv.envtkf]) >> 8; + } + + tStat->envsize++; + tStat->envdist = patchCache->filtEnv.envlevel[tStat->envstat] - tStat->envbase; + } + + reshigh = tStat->envbase; + reshigh = (reshigh + ((tStat->envdist * tStat->envpos) / tStat->envsize)); + + } + tStat->prevlevel = reshigh; + } + + cutoff = patchCache->filtEnv.cutoff; + + //if (patchCache->waveform==1) reshigh = (reshigh * 3) >> 2; + + depth = patchCache->filtEnv.envdepth; + + //int sensedep = (depth * 127-patchCache->filtEnv.envsense) >> 7; + depth = FIXEDPOINT_UMULT(depth, synth->tables.tvfVelfollowMult[poly->vel][(int)patchCache->filtEnv.envsense], 8); + + int bias = patchCache->tvfbias; + int dist; + + if (bias != 0) { + //FIXME:KG: Is this really based on pitch (as now), or key pressed? + //synth->printDebug("Cutoff before %d", cutoff); + if (patchCache->tvfdir == 0) { + if (noteVal < bias) { + dist = bias - noteVal; + cutoff = FIXEDPOINT_UMULT(cutoff, synth->tables.tvfBiasMult[patchCache->tvfblevel][dist], 8); + } + } else { + // > Bias + if (noteVal > bias) { + dist = noteVal - bias; + cutoff = FIXEDPOINT_UMULT(cutoff, synth->tables.tvfBiasMult[patchCache->tvfblevel][dist], 8); + } + + } + //synth->printDebug("Cutoff after %d", cutoff); + } + + depth = (depth * keyLookup->envDepthMult[patchCache->filtEnv.envdkf]) >> 8; + reshigh = (reshigh * depth) >> 7; + + Bit32s tmp; + + cutoff *= filtVal; + cutoff /= realVal; //FIXME:KG: With filter keyfollow 0, this makes no sense. What's correct? + + reshigh *= filtVal; + reshigh /= realVal; //FIXME:KG: As above for cutoff + + if (patchCache->waveform == 1) { + reshigh = (reshigh * 65) / 100; + } + + if (cutoff > 100) + cutoff = 100; + else if (cutoff < 0) + cutoff = 0; + if (reshigh > 100) + reshigh = 100; + else if (reshigh < 0) + reshigh = 0; + tmp = noteLookup->nfiltTable[cutoff][reshigh]; + //tmp *= keyfollow; + //tmp /= realfollow; + + //synth->printDebug("Cutoff %d, tmp %d, freq %d", cutoff, tmp, tmp * 256); + return tmp; +} + +bool Partial::shouldReverb() { + if (!isActive()) + return false; + return patchCache->reverb; +} + +Bit32u Partial::getAmpEnvelope() { + Bit32s tc; + + EnvelopeStatus *tStat = &envs[EnvelopeType_amp]; + + if (!play) + return 0; + + if (tStat->decaying) { + tc = tStat->envbase; + tc += (tStat->envdist * tStat->envpos) / tStat->envsize; + if (tc < 0) + tc = 0; + if ((tStat->envpos >= tStat->envsize) || (tc == 0)) { + play = false; + // Don't have to worry about prevlevel storage or anything, this partial's about to die + return 0; + } + } else { + if ((tStat->envstat == -1) || (tStat->envpos >= tStat->envsize)) { + if (tStat->envstat == -1) + tStat->envbase = 0; + else + tStat->envbase = patchCache->ampEnv.envlevel[tStat->envstat]; + tStat->envstat++; + tStat->envpos = 0; + if (tStat->envstat == 4) { + //synth->printDebug("Envstat %d, size %d", tStat->envstat, tStat->envsize); + tc = patchCache->ampEnv.envlevel[3]; + if (!poly->sustain) + startDecay(EnvelopeType_amp, tc); + else + tStat->sustaining = true; + goto PastCalc; + } + Bit8u targetLevel = patchCache->ampEnv.envlevel[tStat->envstat]; + tStat->envdist = targetLevel - tStat->envbase; + Bit32u envTime = patchCache->ampEnv.envtime[tStat->envstat]; + if (targetLevel == 0) { + tStat->envsize = synth->tables.envDecayTime[envTime]; + } else { + int envLevelDelta = abs(tStat->envdist); + if (envTime > synth->tables.envDeltaMaxTime[envLevelDelta]) { + envTime = synth->tables.envDeltaMaxTime[envLevelDelta]; + } + tStat->envsize = synth->tables.envTime[envTime]; + } + + // Time keyfollow is used by all sections of the envelope (confirmed on CM-32L) + tStat->envsize = FIXEDPOINT_UMULT(tStat->envsize, keyLookup->envTimeMult[(int)patchCache->ampEnv.envtkf], 8); + + switch (tStat->envstat) { + case 0: + //Spot for velocity time follow + //Only used for first attack + tStat->envsize = FIXEDPOINT_UMULT(tStat->envsize, synth->tables.envTimeVelfollowMult[(int)patchCache->ampEnv.envvkf][poly->vel], 8); + //synth->printDebug("Envstat %d, size %d", tStat->envstat, tStat->envsize); + break; + case 1: + case 2: + case 3: + //synth->printDebug("Envstat %d, size %d", tStat->envstat, tStat->envsize); + break; + default: + synth->printDebug("Invalid TVA envelope number %d hit!", tStat->envstat); + break; + } + + tStat->envsize++; + + if (tStat->envdist != 0) { + tStat->counter = abs(tStat->envsize / tStat->envdist); + //synth->printDebug("Pos %d, envsize %d envdist %d", tStat->envstat, tStat->envsize, tStat->envdist); + } else { + tStat->counter = 0; + //synth->printDebug("Pos %d, envsize %d envdist %d", tStat->envstat, tStat->envsize, tStat->envdist); + } + } + tc = tStat->envbase; + tc = (tc + ((tStat->envdist * tStat->envpos) / tStat->envsize)); + tStat->count = tStat->counter; +PastCalc: + tc = (tc * (Bit32s)patchCache->ampEnv.level) / 100; + } + + // Prevlevel storage is bottle neck + tStat->prevlevel = tc; + + //Bias level crap stuff now + + for (int i = 0; i < 2; i++) { + if (patchCache->ampblevel[i]!=0) { + int bias = patchCache->ampbias[i]; + if (patchCache->ampdir[i]==0) { + // < Bias + if (noteVal < bias) { + int dist = bias - noteVal; + tc = FIXEDPOINT_UMULT(tc, synth->tables.tvaBiasMult[patchCache->ampblevel[i]][dist], 8); + } + } else { + // > Bias + if (noteVal > bias) { + int dist = noteVal - bias; + tc = FIXEDPOINT_UMULT(tc, synth->tables.tvaBiasMult[patchCache->ampblevel[i]][dist], 8); + } + } + } + } + if (tc < 0) { + synth->printDebug("*** ERROR: tc < 0 (%d) at getAmpEnvelope()", tc); + tc = 0; + } + return (Bit32u)tc; +} + +Bit32s Partial::getPitchEnvelope() { + EnvelopeStatus *tStat = &envs[EnvelopeType_pitch]; + + Bit32s tc; + pitchSustain = false; + if (tStat->decaying) { + if (tStat->envpos >= tStat->envsize) + tc = patchCache->pitchEnv.level[4]; + else { + tc = tStat->envbase; + tc = (tc + ((tStat->envdist * tStat->envpos) / tStat->envsize)); + } + } else { + if (tStat->envstat==3) { + tc = patchCache->pitchsustain; + if (poly->sustain) + pitchSustain = true; + else + startDecay(EnvelopeType_pitch, tc); + } else { + if ((tStat->envstat==-1) || (tStat->envpos >= tStat->envsize)) { + tStat->envstat++; + + tStat->envbase = patchCache->pitchEnv.level[tStat->envstat]; + + Bit32u envTime = patchCache->pitchEnv.time[tStat->envstat]; + int envDiff = abs(patchCache->pitchEnv.level[tStat->envstat] - patchCache->pitchEnv.level[tStat->envstat + 1]); + if (envTime > synth->tables.envDeltaMaxTime[envDiff]) { + envTime = synth->tables.envDeltaMaxTime[envDiff]; + } + + tStat->envsize = (synth->tables.envTime[envTime] * keyLookup->envTimeMult[(int)patchCache->pitchEnv.timekeyfollow]) >> 8; + + tStat->envpos = 0; + tStat->envsize++; + tStat->envdist = patchCache->pitchEnv.level[tStat->envstat + 1] - tStat->envbase; + } + tc = tStat->envbase; + tc = (tc + ((tStat->envdist * tStat->envpos) / tStat->envsize)); + } + tStat->prevlevel = tc; + } + return tc; +} + +void Partial::startDecayAll() { + startDecay(EnvelopeType_amp, envs[EnvelopeType_amp].prevlevel); + startDecay(EnvelopeType_filt, envs[EnvelopeType_filt].prevlevel); + startDecay(EnvelopeType_pitch, envs[EnvelopeType_pitch].prevlevel); + pitchSustain = false; +} + +void Partial::startDecay(EnvelopeType envnum, Bit32s startval) { + EnvelopeStatus *tStat = &envs[envnum]; + + tStat->sustaining = false; + tStat->decaying = true; + tStat->envpos = 0; + tStat->envbase = startval; + + switch (envnum) { + case EnvelopeType_amp: + tStat->envsize = FIXEDPOINT_UMULT(synth->tables.envDecayTime[(int)patchCache->ampEnv.envtime[4]], keyLookup->envTimeMult[(int)patchCache->ampEnv.envtkf], 8); + tStat->envdist = -startval; + break; + case EnvelopeType_filt: + tStat->envsize = FIXEDPOINT_UMULT(synth->tables.envDecayTime[(int)patchCache->filtEnv.envtime[4]], keyLookup->envTimeMult[(int)patchCache->filtEnv.envtkf], 8); + tStat->envdist = -startval; + break; + case EnvelopeType_pitch: + tStat->envsize = FIXEDPOINT_UMULT(synth->tables.envDecayTime[(int)patchCache->pitchEnv.time[3]], keyLookup->envTimeMult[(int)patchCache->pitchEnv.timekeyfollow], 8); + tStat->envdist = patchCache->pitchEnv.level[4] - startval; + break; + default: + break; + } + tStat->envsize++; +} diff --git a/engines/sci/sfx/softseq/mt32/partial.h b/engines/sci/sfx/softseq/mt32/partial.h new file mode 100644 index 0000000000..93d8bcd985 --- /dev/null +++ b/engines/sci/sfx/softseq/mt32/partial.h @@ -0,0 +1,148 @@ +/* Copyright (c) 2003-2005 Various contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef MT32EMU_PARTIAL_H +#define MT32EMU_PARTIAL_H + +namespace MT32Emu { + +class Synth; +struct NoteLookup; + +enum EnvelopeType { + EnvelopeType_amp = 0, + EnvelopeType_filt = 1, + EnvelopeType_pitch = 2 +}; + +struct EnvelopeStatus { + Bit32s envpos; + Bit32s envstat; + Bit32s envbase; + Bit32s envdist; + Bit32s envsize; + + bool sustaining; + bool decaying; + Bit32s prevlevel; + + Bit32s counter; + Bit32s count; +}; + +// Class definition of MT-32 partials. 32 in all. +class Partial { +private: + Synth *synth; + + int ownerPart; // -1 if unassigned + int mixType; + int structurePosition; // 0 or 1 of a structure pair + bool useNoisePair; + + Bit16s myBuffer[MAX_SAMPLE_OUTPUT]; + + // Keyfollowed note value +#if MT32EMU_ACCURATENOTES == 1 + NoteLookup noteLookupStorage; + float noteVal; +#else + int noteVal; + int fineShift; +#endif + const NoteLookup *noteLookup; // LUTs for this noteVal + const KeyLookup *keyLookup; // LUTs for the clamped (12..108) key + + // Keyfollowed filter values + int realVal; + int filtVal; + + // Only used for PCM partials + int pcmNum; + PCMWaveEntry *pcmWave; + + int pulsewidth; + + Bit32u lfoPos; + soundaddr partialOff; + + Bit32u ampEnvVal; + Bit32u pitchEnvVal; + + float history[32]; + + bool pitchSustain; + + int loopPos; + + dpoly *poly; + + int bendShift; + + Bit16s *mixBuffers(Bit16s *buf1, Bit16s *buf2, int len); + Bit16s *mixBuffersRingMix(Bit16s *buf1, Bit16s *buf2, int len); + Bit16s *mixBuffersRing(Bit16s *buf1, Bit16s *buf2, int len); + void mixBuffersStereo(Bit16s *buf1, Bit16s *buf2, Bit16s *outBuf, int len); + + Bit32s getFiltEnvelope(); + Bit32u getAmpEnvelope(); + Bit32s getPitchEnvelope(); + + void initKeyFollow(int freqNum); + +public: + const PatchCache *patchCache; + EnvelopeStatus envs[3]; + bool play; + + PatchCache cachebackup; + + Partial *pair; + bool alreadyOutputed; + Bit32u age; + + Partial(Synth *synth); + ~Partial(); + + int getOwnerPart() const; + int getKey() const; + const dpoly *getDpoly() const; + bool isActive(); + void activate(int part); + void deactivate(void); + void startPartial(dpoly *usePoly, const PatchCache *useCache, Partial *pairPartial); + void startDecay(EnvelopeType envnum, Bit32s startval); + void startDecayAll(); + void setBend(float factor); + bool shouldReverb(); + + // Returns true only if data written to buffer + // This function (unlike the one below it) returns processed stereo samples + // made from combining this single partial with its pair, if it has one. + bool produceOutput(Bit16s * partialBuf, long length); + + // This function produces mono sample output using the partial's private internal buffer + Bit16s *generateSamples(long length); +}; + +} + +#endif diff --git a/engines/sci/sfx/softseq/mt32/partialManager.cpp b/engines/sci/sfx/softseq/mt32/partialManager.cpp new file mode 100644 index 0000000000..3d3b6302db --- /dev/null +++ b/engines/sci/sfx/softseq/mt32/partialManager.cpp @@ -0,0 +1,272 @@ +/* Copyright (c) 2003-2005 Various contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include <string.h> + +#include "mt32emu.h" + +using namespace MT32Emu; + +PartialManager::PartialManager(Synth *useSynth) { + this->synth = useSynth; + for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) + partialTable[i] = new Partial(synth); +} + +PartialManager::~PartialManager(void) { + for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) + delete partialTable[i]; +} + +void PartialManager::getPerPartPartialUsage(int usage[9]) { + memset(usage, 0, 9 * sizeof (int)); + for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { + if (partialTable[i]->isActive()) + usage[partialTable[i]->getOwnerPart()]++; + } +} + +void PartialManager::clearAlreadyOutputed() { + for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) + partialTable[i]->alreadyOutputed = false; +} + +void PartialManager::ageAll() { + for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) + partialTable[i]->age++; +} + +bool PartialManager::shouldReverb(int i) { + return partialTable[i]->shouldReverb(); +} + +bool PartialManager::produceOutput(int i, Bit16s *buffer, Bit32u bufferLength) { + return partialTable[i]->produceOutput(buffer, bufferLength); +} + +void PartialManager::deactivateAll() { + for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { + partialTable[i]->deactivate(); + } +} + +unsigned int PartialManager::setReserve(Bit8u *rset) { + unsigned int pr = 0; + for (int x = 0; x < 9; x++) { + for (int y = 0; y < rset[x]; y++) { + partialReserveTable[pr] = x; + pr++; + } + } + return pr; +} + +Partial *PartialManager::allocPartial(int partNum) { + Partial *outPartial = NULL; + + // Use the first inactive partial reserved for the specified part (if there are any) + // Otherwise, use the last inactive partial, if any + for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { + if (!partialTable[i]->isActive()) { + outPartial = partialTable[i]; + if (partialReserveTable[i] == partNum) + break; + } + } + if (outPartial != NULL) { + outPartial->activate(partNum); + outPartial->age = 0; + } + return outPartial; +} + +unsigned int PartialManager::getFreePartialCount(void) { + int count = 0; + memset(partialPart, 0, sizeof(partialPart)); + for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { + if (!partialTable[i]->isActive()) + count++; + else + partialPart[partialTable[i]->getOwnerPart()]++; + } + return count; +} + +/* +bool PartialManager::freePartials(unsigned int needed, int partNum) { + int i; + int myPartPrior = (int)mt32ram.system.reserveSettings[partNum]; + if (myPartPrior<partialPart[partNum]) { + //This can have more parts, must kill off those with less priority + int most, mostPart; + while (needed > 0) { + int selectPart = -1; + //Find the worst offender with more partials than allocated and kill them + most = -1; + mostPart = -1; + int diff; + + for (i=0;i<9;i++) { + diff = partialPart[i] - (int)mt32ram.system.reserveSettings[i]; + + if (diff>0) { + if (diff>most) { + most = diff; + mostPart = i; + } + } + } + selectPart = mostPart; + if (selectPart == -1) { + // All parts are within the allocated limits, you suck + // Look for first partial not of this part that's decaying perhaps? + return false; + } + bool found; + int oldest; + int oldnum; + while (partialPart[selectPart] > (int)mt32ram.system.reserveSettings[selectPart]) { + oldest = -1; + oldnum = -1; + found = false; + for (i = 0; i < MT32EMU_MAX_PARTIALS; i++) { + if (partialTable[i]->isActive) { + if (partialTable[i]->ownerPart == selectPart) { + found = true; + if (partialTable[i]->age > oldest) { + oldest = partialTable[i]->age; + oldnum = i; + } + } + } + } + if (!found) break; + partialTable[oldnum]->deactivate(); + --partialPart[selectPart]; + --needed; + } + + } + return true; + + } else { + //This part has reached its max, must kill off its own + bool found; + int oldest; + int oldnum; + while (needed > 0) { + oldest = -1; + oldnum = -1; + found = false; + for (i = 0; i < MT32EMU_MAX_PARTIALS; i++) { + if (partialTable[i]->isActive) { + if (partialTable[i]->ownerPart == partNum) { + found = true; + if (partialTable[i]->age > oldest) { + oldest = partialTable[i]->age; + oldnum = i; + } + } + } + } + if (!found) break; + partialTable[oldnum]->deactivate(); + --needed; + } + // Couldn't free enough partials, sorry + if (needed>0) return false; + return true; + } + +} +*/ +bool PartialManager::freePartials(unsigned int needed, int partNum) { + if (needed == 0) { + return true; + } + // Reclaim partials reserved for this part + // Kill those that are already decaying first + /* + for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { + if (partialReserveTable[i] == partNum) { + if (partialTable[i]->ownerPart != partNum) { + if (partialTable[i]->partCache->envs[AMPENV].decaying) { + partialTable[i]->isActive = false; + --needed; + if (needed == 0) + return true; + } + } + } + }*/ + // Then kill those with the lowest part priority -- oldest at the moment + while (needed > 0) { + Bit32u prior = 0; + int priornum = -1; + + for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { + if (partialReserveTable[i] == partNum && partialTable[i]->isActive() && partialTable[i]->getOwnerPart() != partNum) { + /* + if (mt32ram.system.reserveSettings[partialTable[i]->ownerPart] < prior) { + prior = mt32ram.system.reserveSettings[partialTable[i]->ownerPart]; + priornum = i; + }*/ + if (partialTable[i]->age >= prior) { + prior = partialTable[i]->age; + priornum = i; + } + } + } + if (priornum != -1) { + partialTable[priornum]->deactivate(); + --needed; + } else { + break; + } + } + + // Kill off the oldest partials within this part + while (needed > 0) { + Bit32u oldest = 0; + int oldlist = -1; + for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { + if (partialTable[i]->getOwnerPart() == partNum && partialTable[i]->isActive()) { + if (partialTable[i]->age >= oldest) { + oldest = partialTable[i]->age; + oldlist = i; + } + } + } + if (oldlist != -1) { + partialTable[oldlist]->deactivate(); + --needed; + } else { + break; + } + } + return needed == 0; +} + +const Partial *PartialManager::getPartial(unsigned int partialNum) const { + if (partialNum > MT32EMU_MAX_PARTIALS - 1) + return NULL; + return partialTable[partialNum]; +} diff --git a/engines/sci/sfx/softseq/mt32/partialManager.h b/engines/sci/sfx/softseq/mt32/partialManager.h new file mode 100644 index 0000000000..b10f93ff02 --- /dev/null +++ b/engines/sci/sfx/softseq/mt32/partialManager.h @@ -0,0 +1,56 @@ +/* Copyright (c) 2003-2005 Various contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef MT32EMU_PARTIALMANAGER_H +#define MT32EMU_PARTIALMANAGER_H + +namespace MT32Emu { + +class Synth; + +class PartialManager { +private: + Synth *synth; // Only used for sending debug output + + Partial *partialTable[MT32EMU_MAX_PARTIALS]; + Bit32s partialReserveTable[MT32EMU_MAX_PARTIALS]; + Bit32s partialPart[9]; // The count of partials played per part + +public: + + PartialManager(Synth *synth); + ~PartialManager(); + Partial *allocPartial(int partNum); + unsigned int getFreePartialCount(void); + bool freePartials(unsigned int needed, int partNum); + unsigned int setReserve(Bit8u *rset); + void deactivateAll(); + void ageAll(); + bool produceOutput(int i, Bit16s *buffer, Bit32u bufferLength); + bool shouldReverb(int i); + void clearAlreadyOutputed(); + void getPerPartPartialUsage(int usage[9]); + const Partial *getPartial(unsigned int partialNum) const; +}; + +} + +#endif diff --git a/engines/sci/sfx/softseq/mt32/structures.h b/engines/sci/sfx/softseq/mt32/structures.h new file mode 100644 index 0000000000..ef58c1d20f --- /dev/null +++ b/engines/sci/sfx/softseq/mt32/structures.h @@ -0,0 +1,284 @@ +/* Copyright (c) 2003-2005 Various contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef MT32EMU_STRUCTURES_H +#define MT32EMU_STRUCTURES_H + +namespace MT32Emu { + +const unsigned int MAX_SAMPLE_OUTPUT = 4096; + +// MT32EMU_MEMADDR() converts from sysex-padded, MT32EMU_SYSEXMEMADDR converts to it +// Roland provides documentation using the sysex-padded addresses, so we tend to use that in code and output +#define MT32EMU_MEMADDR(x) ((((x) & 0x7f0000) >> 2) | (((x) & 0x7f00) >> 1) | ((x) & 0x7f)) +#define MT32EMU_SYSEXMEMADDR(x) ((((x) & 0x1FC000) << 2) | (((x) & 0x3F80) << 1) | ((x) & 0x7f)) + +#ifdef _MSC_VER +#define MT32EMU_ALIGN_PACKED __declspec(align(1)) +typedef unsigned __int64 Bit64u; +typedef signed __int64 Bit64s; +#else +#define MT32EMU_ALIGN_PACKED __attribute__((packed)) +typedef unsigned long long Bit64u; +typedef signed long long Bit64s; +#endif + +typedef unsigned int Bit32u; +typedef signed int Bit32s; +typedef unsigned short int Bit16u; +typedef signed short int Bit16s; +typedef unsigned char Bit8u; +typedef signed char Bit8s; + +// The following structures represent the MT-32's memory +// Since sysex allows this memory to be written to in blocks of bytes, +// we keep this packed so that we can copy data into the various +// banks directly +#if defined(_MSC_VER) || defined (__MINGW32__) +#pragma pack(push, 1) +#else +#pragma pack(1) +#endif + +struct TimbreParam { + struct commonParam { + char name[10]; + Bit8u pstruct12; // 1&2 0-12 (1-13) + Bit8u pstruct34; // #3&4 0-12 (1-13) + Bit8u pmute; // 0-15 (0000-1111) + Bit8u nosustain; // 0-1(Normal, No sustain) + } MT32EMU_ALIGN_PACKED common; + + struct partialParam { + struct wgParam { + Bit8u coarse; // 0-96 (C1,C#1-C9) + Bit8u fine; // 0-100 (-50 to +50 (cents?)) + Bit8u keyfollow; // 0-16 (-1,-1/2,0,1,1/8,1/4,3/8,1/2,5/8,3/4,7/8,1,5/4,3/2,2.s1,s2) + Bit8u bender; // 0,1 (ON/OFF) + Bit8u waveform; // MT-32: 0-1 (SQU/SAW); LAPC-I: WG WAVEFORM/PCM BANK 0 - 3 (SQU/1, SAW/1, SQU/2, SAW/2) + Bit8u pcmwave; // 0-127 (1-128) + Bit8u pulsewid; // 0-100 + Bit8u pwvelo; // 0-14 (-7 - +7) + } MT32EMU_ALIGN_PACKED wg; + + struct envParam { + Bit8u depth; // 0-10 + Bit8u sensitivity; // 1-100 + Bit8u timekeyfollow; // 0-4 + Bit8u time[4]; // 1-100 + Bit8u level[5]; // 1-100 (-50 - +50) + } MT32EMU_ALIGN_PACKED env; + + struct lfoParam { + Bit8u rate; // 0-100 + Bit8u depth; // 0-100 + Bit8u modsense; // 0-100 + } MT32EMU_ALIGN_PACKED lfo; + + struct tvfParam { + Bit8u cutoff; // 0-100 + Bit8u resonance; // 0-30 + Bit8u keyfollow; // 0-16 (-1,-1/2,1/4,0,1,1/8,1/4,3/8,1/2,5/8,3/2,7/8,1,5/4,3/2,2,s1,s2) + Bit8u biaspoint; // 0-127 (<1A-<7C >1A-7C) + Bit8u biaslevel; // 0-14 (-7 - +7) + Bit8u envdepth; // 0-100 + Bit8u envsense; // 0-100 + Bit8u envdkf; // DEPTH KEY FOLL0W 0-4 + Bit8u envtkf; // TIME KEY FOLLOW 0-4 + Bit8u envtime[5]; // 1-100 + Bit8u envlevel[4]; // 1-100 + } MT32EMU_ALIGN_PACKED tvf; + + struct tvaParam { + Bit8u level; // 0-100 + Bit8u velosens; // 0-100 + Bit8u biaspoint1; // 0-127 (<1A-<7C >1A-7C) + Bit8u biaslevel1; // 0-12 (-12 - 0) + Bit8u biaspoint2; // 0-127 (<1A-<7C >1A-7C) + Bit8u biaslevel2; // 0-12 (-12 - 0) + Bit8u envtkf; // TIME KEY FOLLOW 0-4 + Bit8u envvkf; // VELOS KEY FOLL0W 0-4 + Bit8u envtime[5]; // 1-100 + Bit8u envlevel[4]; // 1-100 + } MT32EMU_ALIGN_PACKED tva; + } MT32EMU_ALIGN_PACKED partial[4]; +} MT32EMU_ALIGN_PACKED; + +struct PatchParam { + Bit8u timbreGroup; // TIMBRE GROUP 0-3 (group A, group B, Memory, Rhythm) + Bit8u timbreNum; // TIMBRE NUMBER 0-63 + Bit8u keyShift; // KEY SHIFT 0-48 (-24 - +24 semitones) + Bit8u fineTune; // FINE TUNE 0-100 (-50 - +50 cents) + Bit8u benderRange; // BENDER RANGE 0-24 + Bit8u assignMode; // ASSIGN MODE 0-3 (POLY1, POLY2, POLY3, POLY4) + Bit8u reverbSwitch; // REVERB SWITCH 0-1 (OFF,ON) + Bit8u dummy; // (DUMMY) +} MT32EMU_ALIGN_PACKED; + +struct MemParams { + // NOTE: The MT-32 documentation only specifies PatchTemp areas for parts 1-8. + // The LAPC-I documentation specified an additional area for rhythm at the end, + // where all parameters but fine tune, assign mode and output level are ignored + struct PatchTemp { + PatchParam patch; + Bit8u outlevel; // OUTPUT LEVEL 0-100 + Bit8u panpot; // PANPOT 0-14 (R-L) + Bit8u dummyv[6]; + } MT32EMU_ALIGN_PACKED; + + PatchTemp patchSettings[9]; + + struct RhythmTemp { + Bit8u timbre; // TIMBRE 0-94 (M1-M64,R1-30,OFF) + Bit8u outlevel; // OUTPUT LEVEL 0-100 + Bit8u panpot; // PANPOT 0-14 (R-L) + Bit8u reverbSwitch; // REVERB SWITCH 0-1 (OFF,ON) + } MT32EMU_ALIGN_PACKED; + + RhythmTemp rhythmSettings[85]; + + TimbreParam timbreSettings[8]; + + PatchParam patches[128]; + + // NOTE: There are only 30 timbres in the "rhythm" bank for MT-32; the additional 34 are for LAPC-I and above + struct PaddedTimbre { + TimbreParam timbre; + Bit8u padding[10]; + } MT32EMU_ALIGN_PACKED; + + PaddedTimbre timbres[64 + 64 + 64 + 64]; // Group A, Group B, Memory, Rhythm + + struct SystemArea { + Bit8u masterTune; // MASTER TUNE 0-127 432.1-457.6Hz + Bit8u reverbMode; // REVERB MODE 0-3 (room, hall, plate, tap delay) + Bit8u reverbTime; // REVERB TIME 0-7 (1-8) + Bit8u reverbLevel; // REVERB LEVEL 0-7 (1-8) + Bit8u reserveSettings[9]; // PARTIAL RESERVE (PART 1) 0-32 + Bit8u chanAssign[9]; // MIDI CHANNEL (PART1) 0-16 (1-16,OFF) + Bit8u masterVol; // MASTER VOLUME 0-100 + } MT32EMU_ALIGN_PACKED; + + SystemArea system; +}; + +#if defined(_MSC_VER) || defined (__MINGW32__) +#pragma pack(pop) +#else +#pragma pack() +#endif + +struct PCMWaveEntry { + Bit32u addr; + Bit32u len; + double tune; + bool loop; +}; + +struct soundaddr { + Bit16u pcmplace; + Bit16u pcmoffset; +}; + +struct StereoVolume { + Bit16s leftvol; + Bit16s rightvol; +}; + +// This is basically a per-partial, pre-processed combination of timbre and patch/rhythm settings +struct PatchCache { + bool playPartial; + bool PCMPartial; + int pcm; + char waveform; + int pulsewidth; + int pwsens; + + float pitch; + + int lfodepth; + int lforate; + Bit32u lfoperiod; + int modsense; + + float pitchKeyfollow; + + int filtkeyfollow; + + int tvfbias; + int tvfblevel; + int tvfdir; + + int ampbias[2]; + int ampblevel[2]; + int ampdir[2]; + + int ampdepth; + int amplevel; + + bool useBender; + float benderRange; // 0.0, 1.0, .., 24.0 (semitones) + + TimbreParam::partialParam::envParam pitchEnv; + TimbreParam::partialParam::tvaParam ampEnv; + TimbreParam::partialParam::tvfParam filtEnv; + + Bit32s pitchsustain; + Bit32s filtsustain; + + Bit32u structureMix; + int structurePosition; + int structurePair; + + // The following fields are actually common to all partials in the timbre + bool dirty; + Bit32u partialCount; + bool sustain; + float pitchShift; + bool reverb; + const StereoVolume *pansetptr; +}; + +class Partial; // Forward reference for class defined in partial.h + +struct dpoly { + bool isPlaying; + + unsigned int key; + int freqnum; + int vel; + + bool isDecay; + + const Bit32u *volumeptr; + + Partial *partials[4]; + + bool pedalhold; // This marks keys that have been released on the keyboard, but are being held by the pedal + bool sustain; + + bool isActive() const; + Bit32u getAge() const; +}; + +} + +#endif diff --git a/engines/sci/sfx/softseq/mt32/synth.cpp b/engines/sci/sfx/softseq/mt32/synth.cpp new file mode 100644 index 0000000000..5ae196dc63 --- /dev/null +++ b/engines/sci/sfx/softseq/mt32/synth.cpp @@ -0,0 +1,1199 @@ +/* Copyright (c) 2003-2005 Various contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include <math.h> +#include <string.h> +#include <stdlib.h> + +#include "mt32emu.h" + +#ifdef MACOSX +// Older versions of Mac OS X didn't supply a powf function. To ensure +// binary compatibility, we force using pow instead of powf (the only +// potential drawback is that it might be a little bit slower). +#define powf pow +#endif + +namespace MT32Emu { + +const int MAX_SYSEX_SIZE = 512; + +const ControlROMMap ControlROMMaps[5] = { + // ID IDc IDbytes PCMmap PCMc tmbrA tmbrAO, tmbrB tmbrBO, tmbrR trC rhythm rhyC rsrv panpot prog + {0x4014, 22, "\000 ver1.04 14 July 87 ", 0x3000, 128, 0x8000, 0x0000, 0xC000, 0x4000, 0x3200, 30, 0x73A6, 85, 0x57C7, 0x57D0, 0x57E2}, // MT-32 revision 0 + {0x4014, 22, "\000 ver1.06 31 Aug, 87 ", 0x3000, 128, 0x8000, 0x0000, 0xC000, 0x4000, 0x3200, 30, 0x7414, 85, 0x57D9, 0x57E2, 0x57F4}, // MT-32 revision 0 + {0x4010, 22, "\000 ver1.07 10 Oct, 87 ", 0x3000, 128, 0x8000, 0x0000, 0xC000, 0x4000, 0x3200, 30, 0x73fe, 85, 0x57B1, 0x57BA, 0x57CC}, // MT-32 revision 1 + {0x4010, 22, "\000verX.XX 30 Sep, 88 ", 0x3000, 128, 0x8000, 0x0000, 0xC000, 0x4000, 0x3200, 30, 0x741C, 85, 0x57E5, 0x57EE, 0x5800}, // MT-32 Blue Ridge mod + {0x2205, 22, "\000CM32/LAPC1.02 891205", 0x8100, 256, 0x8000, 0x8000, 0x8080, 0x8000, 0x8500, 64, 0x8580, 85, 0x4F93, 0x4F9C, 0x4FAE} // CM-32L + // (Note that all but CM-32L ROM actually have 86 entries for rhythmTemp) +}; + +float iir_filter_normal(float input, float *hist1_ptr, float *coef_ptr) { + float *hist2_ptr; + float output,new_hist; + + hist2_ptr = hist1_ptr + 1; // next history + + // 1st number of coefficients array is overall input scale factor, or filter gain + output = input * (*coef_ptr++); + + output = output - *hist1_ptr * (*coef_ptr++); + new_hist = output - *hist2_ptr * (*coef_ptr++); // poles + + output = new_hist + *hist1_ptr * (*coef_ptr++); + output = output + *hist2_ptr * (*coef_ptr++); // zeros + + *hist2_ptr++ = *hist1_ptr; + *hist1_ptr++ = new_hist; + hist1_ptr++; + hist2_ptr++; + + // i = 1 + output = output - *hist1_ptr * (*coef_ptr++); + new_hist = output - *hist2_ptr * (*coef_ptr++); // poles + + output = new_hist + *hist1_ptr * (*coef_ptr++); + output = output + *hist2_ptr * (*coef_ptr++); // zeros + + *hist2_ptr++ = *hist1_ptr; + *hist1_ptr++ = new_hist; + + return(output); +} + +Bit8u Synth::calcSysexChecksum(const Bit8u *data, Bit32u len, Bit8u checksum) { + for (unsigned int i = 0; i < len; i++) { + checksum = checksum + data[i]; + } + checksum = checksum & 0x7f; + if (checksum) + checksum = 0x80 - checksum; + return checksum; +} + +Synth::Synth() { + isOpen = false; + reverbModel = NULL; + partialManager = NULL; + memset(parts, 0, sizeof(parts)); +} + +Synth::~Synth() { + close(); // Make sure we're closed and everything is freed +} + +int Synth::report(ReportType type, const void *data) { + if (myProp.report != NULL) { + return myProp.report(myProp.userData, type, data); + } + return 0; +} + +void Synth::printDebug(const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + if (myProp.printDebug != NULL) { + myProp.printDebug(myProp.userData, fmt, ap); + } else { + vprintf(fmt, ap); + printf("\n"); + } + va_end(ap); +} + +void Synth::initReverb(Bit8u newRevMode, Bit8u newRevTime, Bit8u newRevLevel) { + // FIXME:KG: I don't think it's necessary to recreate the reverbModel... Just set the parameters + if (reverbModel != NULL) + delete reverbModel; + reverbModel = new revmodel(); + + switch (newRevMode) { + case 0: + reverbModel->setroomsize(.1f); + reverbModel->setdamp(.75f); + break; + case 1: + reverbModel->setroomsize(.5f); + reverbModel->setdamp(.5f); + break; + case 2: + reverbModel->setroomsize(.5f); + reverbModel->setdamp(.1f); + break; + case 3: + reverbModel->setroomsize(1.0f); + reverbModel->setdamp(.75f); + break; + default: + reverbModel->setroomsize(.1f); + reverbModel->setdamp(.5f); + break; + } + reverbModel->setdry(1); + reverbModel->setwet((float)newRevLevel / 8.0f); + reverbModel->setwidth((float)newRevTime / 8.0f); +} + +File *Synth::openFile(const char *filename, File::OpenMode mode) { + if (myProp.openFile != NULL) { + return myProp.openFile(myProp.userData, filename, mode); + } + char pathBuf[2048]; + if (myProp.baseDir != NULL) { + strcpy(&pathBuf[0], myProp.baseDir); + strcat(&pathBuf[0], filename); + filename = pathBuf; + } + ANSIFile *file = new ANSIFile(); + if (!file->open(filename, mode)) { + delete file; + return NULL; + } + return file; +} + +void Synth::closeFile(File *file) { + if (myProp.closeFile != NULL) { + myProp.closeFile(myProp.userData, file); + } else { + file->close(); + delete file; + } +} + +bool Synth::loadPreset(File *file) { + bool inSys = false; + Bit8u sysexBuf[MAX_SYSEX_SIZE]; + Bit16u syslen = 0; + bool rc = true; + for (;;) { + Bit8u c; + if (!file->readBit8u(&c)) { + if (!file->isEOF()) { + rc = false; + } + break; + } + sysexBuf[syslen] = c; + if (inSys) { + syslen++; + if (c == 0xF7) { + playSysex(&sysexBuf[0], syslen); + inSys = false; + syslen = 0; + } else if (syslen == MAX_SYSEX_SIZE) { + printDebug("MAX_SYSEX_SIZE (%d) exceeded while processing preset, ignoring message", MAX_SYSEX_SIZE); + inSys = false; + syslen = 0; + } + } else if (c == 0xF0) { + syslen++; + inSys = true; + } + } + return rc; +} + +bool Synth::loadControlROM(const char *filename) { + File *file = openFile(filename, File::OpenMode_read); // ROM File + if (file == NULL) { + return false; + } + bool rc = (file->read(controlROMData, CONTROL_ROM_SIZE) == CONTROL_ROM_SIZE); + + closeFile(file); + if (!rc) + return rc; + + // Control ROM successfully loaded, now check whether it's a known type + controlROMMap = NULL; + for (unsigned int i = 0; i < sizeof (ControlROMMaps) / sizeof (ControlROMMaps[0]); i++) { + if (memcmp(&controlROMData[ControlROMMaps[i].idPos], ControlROMMaps[i].idBytes, ControlROMMaps[i].idLen) == 0) { + controlROMMap = &ControlROMMaps[i]; + return true; + } + } + return false; +} + +bool Synth::loadPCMROM(const char *filename) { + File *file = openFile(filename, File::OpenMode_read); // ROM File + if (file == NULL) { + return false; + } + bool rc = true; + int i; + for (i = 0; i < pcmROMSize; i++) { + Bit8u s; + if (!file->readBit8u(&s)) { + if (!file->isEOF()) { + rc = false; + } + break; + } + Bit8u c; + if (!file->readBit8u(&c)) { + if (!file->isEOF()) { + rc = false; + } else { + printDebug("PCM ROM file has an odd number of bytes! Ignoring last"); + } + break; + } + + short e; + int bit; + int u; + int order[16] = {0, 9, 1 ,2, 3, 4, 5, 6, 7, 10, 11, 12, 13, 14, 15, 8}; + + e = 0; + for (u = 0; u < 15; u++) { + if (order[u] < 8) + bit = (s >> (7 - order[u])) & 0x1; + else + bit = (c >> (7 - (order[u] - 8))) & 0x1; + e = e | (short)(bit << (15 - u)); + } + + /* + //Bit16s e = ( ((s & 0x7f) << 4) | ((c & 0x40) << 6) | ((s & 0x80) << 6) | ((c & 0x3f))) << 2; + if (e<0) + e = -32767 - e; + int ut = abs(e); + int dif = 0x7fff - ut; + x = exp(((float)((float)0x8000-(float)dif) / (float)0x1000)); + e = (int)((float)e * (x/3200)); + */ + + // File is companded (dB?), convert to linear PCM + // MINDB = -96 + // MAXDB = -15 + float testval; + testval = (float)((~e) & 0x7fff); + testval = -(testval / 400.00f); + //testval = -(testval / 341.32291666666666666666666666667); + float vol = powf(8, testval / 20) * 32767.0f; + + if (e > 0) + vol = -vol; + + pcmROMData[i] = (Bit16s)vol; + } + if (i != pcmROMSize) { + printDebug("PCM ROM file is too short (expected %d, got %d)", pcmROMSize, i); + rc = false; + } + closeFile(file); + return rc; +} + +bool Synth::initPCMList(Bit16u mapAddress, Bit16u count) { + ControlROMPCMStruct *tps = (ControlROMPCMStruct *)&controlROMData[mapAddress]; + for (int i = 0; i < count; i++) { + int rAddr = tps[i].pos * 0x800; + int rLenExp = (tps[i].len & 0x70) >> 4; + int rLen = 0x800 << rLenExp; + bool rLoop = (tps[i].len & 0x80) != 0; + //Bit8u rFlag = tps[i].len & 0x0F; + Bit16u rTuneOffset = (tps[i].pitchMSB << 8) | tps[i].pitchLSB; + // The number below is confirmed to a reasonable degree of accuracy on CM-32L + double STANDARDFREQ = 442.0; + float rTune = (float)(STANDARDFREQ * pow(2.0, (0x5000 - rTuneOffset) / 4056.0 - 9.0 / 12.0)); + //printDebug("%f,%d,%d", pTune, tps[i].pitchCoarse, tps[i].pitchFine); + if (rAddr + rLen > pcmROMSize) { + printDebug("Control ROM error: Wave map entry %d points to invalid PCM address 0x%04X, length 0x%04X", i, rAddr, rLen); + return false; + } + pcmWaves[i].addr = rAddr; + pcmWaves[i].len = rLen; + pcmWaves[i].loop = rLoop; + pcmWaves[i].tune = rTune; + } + return false; +} + +bool Synth::initRhythmTimbre(int timbreNum, const Bit8u *mem, unsigned int memLen) { + if (memLen < sizeof(TimbreParam::commonParam)) { + return false; + } + TimbreParam *timbre = &mt32ram.timbres[timbreNum].timbre; + memcpy(&timbre->common, mem, 14); + unsigned int memPos = 14; + char drumname[11]; + strncpy(drumname, timbre->common.name, 10); + drumname[10] = 0; + for (int t = 0; t < 4; t++) { + if (((timbre->common.pmute >> t) & 0x1) == 0x1) { + if (memPos + 58 >= memLen) { + return false; + } + memcpy(&timbre->partial[t], mem + memPos, 58); + memPos += 58; + } + } + return true; +} + +bool Synth::initRhythmTimbres(Bit16u mapAddress, Bit16u count) { + const Bit8u *drumMap = &controlROMData[mapAddress]; + int timbreNum = 192; + for (Bit16u i = 0; i < count * 2; i += 2) { + Bit16u address = (drumMap[i + 1] << 8) | drumMap[i]; + /* + // This check is nonsensical when the control ROM is the full 64KB addressable by 16-bit absolute pointers (which it is) + if (address >= CONTROL_ROM_SIZE) { + printDebug("Control ROM error: Timbre map entry 0x%04x points to invalid timbre address 0x%04x", i, address); + return false; + } + */ + if (!initRhythmTimbre(timbreNum++, &controlROMData[address], CONTROL_ROM_SIZE - address)) { + printDebug("Control ROM error: Timbre map entry 0x%04x points to invalid timbre 0x%04x", i, address); + return false; + } + } + return true; +} + +bool Synth::initTimbres(Bit16u mapAddress, Bit16u offset, int startTimbre) { + for (Bit16u i = mapAddress; i < mapAddress + 0x80; i += 2) { + Bit16u address = (controlROMData[i + 1] << 8) | controlROMData[i]; + if (address + sizeof(TimbreParam) > CONTROL_ROM_SIZE) { + printDebug("Control ROM error: Timbre map entry 0x%04x points to invalid timbre address 0x%04x", i, address); + return false; + } + address = address + offset; + TimbreParam *timbre = &mt32ram.timbres[startTimbre++].timbre; + memcpy(timbre, &controlROMData[address], sizeof(TimbreParam)); + } + return true; +} + +bool Synth::open(SynthProperties &useProp) { + if (isOpen) + return false; + + myProp = useProp; + if (useProp.baseDir != NULL) { + myProp.baseDir = new char[strlen(useProp.baseDir) + 1]; + strcpy(myProp.baseDir, useProp.baseDir); + } + + // This is to help detect bugs + memset(&mt32ram, '?', sizeof(mt32ram)); + + printDebug("Loading Control ROM"); + if (!loadControlROM("CM32L_CONTROL.ROM")) { + if (!loadControlROM("MT32_CONTROL.ROM")) { + printDebug("Init Error - Missing or invalid MT32_CONTROL.ROM"); + report(ReportType_errorControlROM, NULL); + return false; + } + } + + // 512KB PCM ROM for MT-32, etc. + // 1MB PCM ROM for CM-32L, LAPC-I, CM-64, CM-500 + // Note that the size below is given in samples (16-bit), not bytes + pcmROMSize = controlROMMap->pcmCount == 256 ? 512 * 1024 : 256 * 1024; + pcmROMData = new Bit16s[pcmROMSize]; + + printDebug("Loading PCM ROM"); + if (!loadPCMROM("CM32L_PCM.ROM")) { + if (!loadPCMROM("MT32_PCM.ROM")) { + printDebug("Init Error - Missing MT32_PCM.ROM"); + report(ReportType_errorPCMROM, NULL); + return false; + } + } + + printDebug("Initialising Timbre Bank A"); + if (!initTimbres(controlROMMap->timbreAMap, controlROMMap->timbreAOffset, 0)) { + return false; + } + + printDebug("Initialising Timbre Bank B"); + if (!initTimbres(controlROMMap->timbreBMap, controlROMMap->timbreBOffset, 64)) { + return false; + } + + printDebug("Initialising Timbre Bank R"); + if (!initRhythmTimbres(controlROMMap->timbreRMap, controlROMMap->timbreRCount)) { + return false; + } + + printDebug("Initialising Timbre Bank M"); + // CM-64 seems to initialise all bytes in this bank to 0. + memset(&mt32ram.timbres[128], 0, sizeof (mt32ram.timbres[128]) * 64); + + partialManager = new PartialManager(this); + + pcmWaves = new PCMWaveEntry[controlROMMap->pcmCount]; + + printDebug("Initialising PCM List"); + initPCMList(controlROMMap->pcmTable, controlROMMap->pcmCount); + + printDebug("Initialising Rhythm Temp"); + memcpy(mt32ram.rhythmSettings, &controlROMData[controlROMMap->rhythmSettings], controlROMMap->rhythmSettingsCount * 4); + + printDebug("Initialising Patches"); + for (Bit8u i = 0; i < 128; i++) { + PatchParam *patch = &mt32ram.patches[i]; + patch->timbreGroup = i / 64; + patch->timbreNum = i % 64; + patch->keyShift = 24; + patch->fineTune = 50; + patch->benderRange = 12; + patch->assignMode = 0; + patch->reverbSwitch = 1; + patch->dummy = 0; + } + + printDebug("Initialising System"); + // The MT-32 manual claims that "Standard pitch" is 442Hz. + mt32ram.system.masterTune = 0x4A; // Confirmed on CM-64 + mt32ram.system.reverbMode = 0; // Confirmed + mt32ram.system.reverbTime = 5; // Confirmed + mt32ram.system.reverbLevel = 3; // Confirmed + memcpy(mt32ram.system.reserveSettings, &controlROMData[controlROMMap->reserveSettings], 9); // Confirmed + for (Bit8u i = 0; i < 9; i++) { + // This is the default: {1, 2, 3, 4, 5, 6, 7, 8, 9} + // An alternative configuration can be selected by holding "Master Volume" + // and pressing "PART button 1" on the real MT-32's frontpanel. + // The channel assignment is then {0, 1, 2, 3, 4, 5, 6, 7, 9} + mt32ram.system.chanAssign[i] = i + 1; + } + mt32ram.system.masterVol = 100; // Confirmed + if (!refreshSystem()) + return false; + + for (int i = 0; i < 8; i++) { + mt32ram.patchSettings[i].outlevel = 80; + mt32ram.patchSettings[i].panpot = controlROMData[controlROMMap->panSettings + i]; + memset(mt32ram.patchSettings[i].dummyv, 0, sizeof(mt32ram.patchSettings[i].dummyv)); + parts[i] = new Part(this, i); + parts[i]->setProgram(controlROMData[controlROMMap->programSettings + i]); + } + parts[8] = new RhythmPart(this, 8); + + // For resetting mt32 mid-execution + mt32default = mt32ram; + + iirFilter = &iir_filter_normal; + +#ifdef MT32EMU_HAVE_X86 + bool availableSSE = DetectSIMD(); + bool available3DNow = Detect3DNow(); + + if (availableSSE) + report(ReportType_availableSSE, NULL); + if (available3DNow) + report(ReportType_available3DNow, NULL); + + if (available3DNow) { + printDebug("Detected and using SIMD (AMD 3DNow) extensions"); + iirFilter = &iir_filter_3dnow; + report(ReportType_using3DNow, NULL); + } else if (availableSSE) { + printDebug("Detected and using SIMD (Intel SSE) extensions"); + iirFilter = &iir_filter_sse; + report(ReportType_usingSSE, NULL); + } +#endif + + isOpen = true; + isEnabled = false; + + printDebug("*** Initialisation complete ***"); + return true; +} + +void Synth::close(void) { + if (!isOpen) + return; + + tables.freeNotes(); + if (partialManager != NULL) { + delete partialManager; + partialManager = NULL; + } + + if (reverbModel != NULL) { + delete reverbModel; + reverbModel = NULL; + } + + for (int i = 0; i < 9; i++) { + if (parts[i] != NULL) { + delete parts[i]; + parts[i] = NULL; + } + } + if (myProp.baseDir != NULL) { + delete myProp.baseDir; + myProp.baseDir = NULL; + } + + delete[] pcmWaves; + delete[] pcmROMData; + isOpen = false; +} + +void Synth::playMsg(Bit32u msg) { + // FIXME: Implement active sensing + unsigned char code = (unsigned char)((msg & 0x0000F0) >> 4); + unsigned char chan = (unsigned char) (msg & 0x00000F); + unsigned char note = (unsigned char)((msg & 0x00FF00) >> 8); + unsigned char velocity = (unsigned char)((msg & 0xFF0000) >> 16); + isEnabled = true; + + //printDebug("Playing chan %d, code 0x%01x note: 0x%02x", chan, code, note); + + char part = chantable[chan]; + if (part < 0 || part > 8) { + printDebug("Play msg on unreg chan %d (%d): code=0x%01x, vel=%d", chan, part, code, velocity); + return; + } + playMsgOnPart(part, code, note, velocity); +} + +void Synth::playMsgOnPart(unsigned char part, unsigned char code, unsigned char note, unsigned char velocity) { + Bit32u bend; + + //printDebug("Synth::playMsg(0x%02x)",msg); + switch (code) { + case 0x8: + //printDebug("Note OFF - Part %d", part); + // The MT-32 ignores velocity for note off + parts[part]->stopNote(note); + break; + case 0x9: + //printDebug("Note ON - Part %d, Note %d Vel %d", part, note, velocity); + if (velocity == 0) { + // MIDI defines note-on with velocity 0 as being the same as note-off with velocity 40 + parts[part]->stopNote(note); + } else { + parts[part]->playNote(note, velocity); + } + break; + case 0xB: // Control change + switch (note) { + case 0x01: // Modulation + //printDebug("Modulation: %d", velocity); + parts[part]->setModulation(velocity); + break; + case 0x07: // Set volume + //printDebug("Volume set: %d", velocity); + parts[part]->setVolume(velocity); + break; + case 0x0A: // Pan + //printDebug("Pan set: %d", velocity); + parts[part]->setPan(velocity); + break; + case 0x0B: + //printDebug("Expression set: %d", velocity); + parts[part]->setExpression(velocity); + break; + case 0x40: // Hold (sustain) pedal + //printDebug("Hold pedal set: %d", velocity); + parts[part]->setHoldPedal(velocity>=64); + break; + + case 0x79: // Reset all controllers + //printDebug("Reset all controllers"); + //FIXME: Check for accuracy against real thing + parts[part]->setVolume(100); + parts[part]->setExpression(127); + parts[part]->setPan(64); + parts[part]->setBend(0x2000); + parts[part]->setHoldPedal(false); + break; + + case 0x7B: // All notes off + //printDebug("All notes off"); + parts[part]->allNotesOff(); + break; + + default: + printDebug("Unknown MIDI Control code: 0x%02x - vel 0x%02x", note, velocity); + break; + } + + break; + case 0xC: // Program change + //printDebug("Program change %01x", note); + parts[part]->setProgram(note); + break; + case 0xE: // Pitch bender + bend = (velocity << 7) | (note); + //printDebug("Pitch bender %02x", bend); + parts[part]->setBend(bend); + break; + default: + printDebug("Unknown Midi code: 0x%01x - %02x - %02x", code, note, velocity); + break; + } + + //midiOutShortMsg(m_out, msg); +} + +void Synth::playSysex(const Bit8u *sysex, Bit32u len) { + if (len < 2) { + printDebug("playSysex: Message is too short for sysex (%d bytes)", len); + } + if (sysex[0] != 0xF0) { + printDebug("playSysex: Message lacks start-of-sysex (0xF0)"); + return; + } + // Due to some programs (e.g. Java) sending buffers with junk at the end, we have to go through and find the end marker rather than relying on len. + Bit32u endPos; + for (endPos = 1; endPos < len; endPos++) + { + if (sysex[endPos] == 0xF7) + break; + } + if (endPos == len) { + printDebug("playSysex: Message lacks end-of-sysex (0xf7)"); + return; + } + playSysexWithoutFraming(sysex + 1, endPos - 1); +} + +void Synth::playSysexWithoutFraming(const Bit8u *sysex, Bit32u len) { + if (len < 4) { + printDebug("playSysexWithoutFraming: Message is too short (%d bytes)!", len); + return; + } + if (sysex[0] != SYSEX_MANUFACTURER_ROLAND) { + printDebug("playSysexWithoutFraming: Header not intended for this device manufacturer: %02x %02x %02x %02x", (int)sysex[0], (int)sysex[1], (int)sysex[2], (int)sysex[3]); + return; + } + if (sysex[2] == SYSEX_MDL_D50) { + printDebug("playSysexWithoutFraming: Header is intended for model D-50 (not yet supported): %02x %02x %02x %02x", (int)sysex[0], (int)sysex[1], (int)sysex[2], (int)sysex[3]); + return; + } + else if (sysex[2] != SYSEX_MDL_MT32) { + printDebug("playSysexWithoutFraming: Header not intended for model MT-32: %02x %02x %02x %02x", (int)sysex[0], (int)sysex[1], (int)sysex[2], (int)sysex[3]); + return; + } + playSysexWithoutHeader(sysex[1], sysex[3], sysex + 4, len - 4); +} + +void Synth::playSysexWithoutHeader(unsigned char device, unsigned char command, const Bit8u *sysex, Bit32u len) { + if (device > 0x10) { + // We have device ID 0x10 (default, but changeable, on real MT-32), < 0x10 is for channels + printDebug("playSysexWithoutHeader: Message is not intended for this device ID (provided: %02x, expected: 0x10 or channel)", (int)device); + return; + } + if (len < 4) { + printDebug("playSysexWithoutHeader: Message is too short (%d bytes)!", len); + return; + } + unsigned char checksum = calcSysexChecksum(sysex, len - 1, 0); + if (checksum != sysex[len - 1]) { + printDebug("playSysexWithoutHeader: Message checksum is incorrect (provided: %02x, expected: %02x)!", sysex[len - 1], checksum); + return; + } + len -= 1; // Exclude checksum + switch (command) { + case SYSEX_CMD_DT1: + writeSysex(device, sysex, len); + break; + case SYSEX_CMD_RQ1: + readSysex(device, sysex, len); + break; + default: + printDebug("playSysexWithoutFraming: Unsupported command %02x", command); + return; + } +} + +void Synth::readSysex(unsigned char /*device*/, const Bit8u * /*sysex*/, Bit32u /*len*/) { +} + +const MemoryRegion memoryRegions[8] = { + {MR_PatchTemp, MT32EMU_MEMADDR(0x030000), sizeof(MemParams::PatchTemp), 9}, + {MR_RhythmTemp, MT32EMU_MEMADDR(0x030110), sizeof(MemParams::RhythmTemp), 85}, + {MR_TimbreTemp, MT32EMU_MEMADDR(0x040000), sizeof(TimbreParam), 8}, + {MR_Patches, MT32EMU_MEMADDR(0x050000), sizeof(PatchParam), 128}, + {MR_Timbres, MT32EMU_MEMADDR(0x080000), sizeof(MemParams::PaddedTimbre), 64 + 64 + 64 + 64}, + {MR_System, MT32EMU_MEMADDR(0x100000), sizeof(MemParams::SystemArea), 1}, + {MR_Display, MT32EMU_MEMADDR(0x200000), MAX_SYSEX_SIZE - 1, 1}, + {MR_Reset, MT32EMU_MEMADDR(0x7F0000), 0x3FFF, 1} +}; + +const int NUM_REGIONS = sizeof(memoryRegions) / sizeof(MemoryRegion); + +void Synth::writeSysex(unsigned char device, const Bit8u *sysex, Bit32u len) { + Bit32u addr = (sysex[0] << 16) | (sysex[1] << 8) | (sysex[2]); + addr = MT32EMU_MEMADDR(addr); + sysex += 3; + len -= 3; + //printDebug("Sysex addr: 0x%06x", MT32EMU_SYSEXMEMADDR(addr)); + // NOTE: Please keep both lower and upper bounds in each check, for ease of reading + + // Process channel-specific sysex by converting it to device-global + if (device < 0x10) { + printDebug("WRITE-CHANNEL: Channel %d temp area 0x%06x", device, MT32EMU_SYSEXMEMADDR(addr)); + if (/*addr >= MT32EMU_MEMADDR(0x000000) && */addr < MT32EMU_MEMADDR(0x010000)) { + int offset; + if (chantable[device] == -1) { + printDebug(" (Channel not mapped to a partial... 0 offset)"); + offset = 0; + } else if (chantable[device] == 8) { + printDebug(" (Channel mapped to rhythm... 0 offset)"); + offset = 0; + } else { + offset = chantable[device] * sizeof(MemParams::PatchTemp); + printDebug(" (Setting extra offset to %d)", offset); + } + addr += MT32EMU_MEMADDR(0x030000) + offset; + } else if (/*addr >= 0x010000 && */ addr < MT32EMU_MEMADDR(0x020000)) { + addr += MT32EMU_MEMADDR(0x030110) - MT32EMU_MEMADDR(0x010000); + } else if (/*addr >= 0x020000 && */ addr < MT32EMU_MEMADDR(0x030000)) { + int offset; + if (chantable[device] == -1) { + printDebug(" (Channel not mapped to a partial... 0 offset)"); + offset = 0; + } else if (chantable[device] == 8) { + printDebug(" (Channel mapped to rhythm... 0 offset)"); + offset = 0; + } else { + offset = chantable[device] * sizeof(TimbreParam); + printDebug(" (Setting extra offset to %d)", offset); + } + addr += MT32EMU_MEMADDR(0x040000) - MT32EMU_MEMADDR(0x020000) + offset; + } else { + printDebug("PlaySysexWithoutHeader: Invalid channel %d address 0x%06x", device, MT32EMU_SYSEXMEMADDR(addr)); + return; + } + } + + // Process device-global sysex (possibly converted from channel-specific sysex above) + for (;;) { + // Find the appropriate memory region + int regionNum; + const MemoryRegion *region = NULL; // Initialised to please compiler + for (regionNum = 0; regionNum < NUM_REGIONS; regionNum++) { + region = &memoryRegions[regionNum]; + if (region->contains(addr)) { + writeMemoryRegion(region, addr, region->getClampedLen(addr, len), sysex); + break; + } + } + if (regionNum == NUM_REGIONS) { + printDebug("Sysex write to unrecognised address %06x, len %d", MT32EMU_SYSEXMEMADDR(addr), len); + break; + } + Bit32u next = region->next(addr, len); + if (next == 0) { + break; + } + addr += next; + sysex += next; + len -= next; + } +} + +void Synth::readMemory(Bit32u addr, Bit32u len, Bit8u *data) { + int regionNum; + const MemoryRegion *region = NULL; + for (regionNum = 0; regionNum < NUM_REGIONS; regionNum++) { + region = &memoryRegions[regionNum]; + if (region->contains(addr)) { + readMemoryRegion(region, addr, len, data); + break; + } + } +} + +void Synth::readMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u len, Bit8u *data) { + unsigned int first = region->firstTouched(addr); + //unsigned int last = region->lastTouched(addr, len); + unsigned int off = region->firstTouchedOffset(addr); + len = region->getClampedLen(addr, len); + + unsigned int m; + + switch(region->type) { + case MR_PatchTemp: + for (m = 0; m < len; m++) + data[m] = ((Bit8u *)&mt32ram.patchSettings[first])[off + m]; + break; + case MR_RhythmTemp: + for (m = 0; m < len; m++) + data[m] = ((Bit8u *)&mt32ram.rhythmSettings[first])[off + m]; + break; + case MR_TimbreTemp: + for (m = 0; m < len; m++) + data[m] = ((Bit8u *)&mt32ram.timbreSettings[first])[off + m]; + break; + case MR_Patches: + for (m = 0; m < len; m++) + data[m] = ((Bit8u *)&mt32ram.patches[first])[off + m]; + break; + case MR_Timbres: + for (m = 0; m < len; m++) + data[m] = ((Bit8u *)&mt32ram.timbres[first])[off + m]; + break; + case MR_System: + for (m = 0; m < len; m++) + data[m] = ((Bit8u *)&mt32ram.system)[m + off]; + break; + default: + for (m = 0; m < len; m += 2) { + data[m] = 0xff; + if (m + 1 < len) { + data[m+1] = (Bit8u)region->type; + } + } + // TODO: Don't care about the others ATM + break; + } + +} + +void Synth::writeMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u len, const Bit8u *data) { + unsigned int first = region->firstTouched(addr); + unsigned int last = region->lastTouched(addr, len); + unsigned int off = region->firstTouchedOffset(addr); + switch (region->type) { + case MR_PatchTemp: + for (unsigned int m = 0; m < len; m++) { + ((Bit8u *)&mt32ram.patchSettings[first])[off + m] = data[m]; + } + //printDebug("Patch temp: Patch %d, offset %x, len %d", off/16, off % 16, len); + + for (unsigned int i = first; i <= last; i++) { + int absTimbreNum = mt32ram.patchSettings[i].patch.timbreGroup * 64 + mt32ram.patchSettings[i].patch.timbreNum; + char timbreName[11]; + memcpy(timbreName, mt32ram.timbres[absTimbreNum].timbre.common.name, 10); + timbreName[10] = 0; + printDebug("WRITE-PARTPATCH (%d-%d@%d..%d): %d; timbre=%d (%s), outlevel=%d", first, last, off, off + len, i, absTimbreNum, timbreName, mt32ram.patchSettings[i].outlevel); + if (parts[i] != NULL) { + if (i != 8) { + // Note: Confirmed on CM-64 that we definitely *should* update the timbre here, + // but only in the case that the sysex actually writes to those values + if (i == first && off > 2) { + printDebug(" (Not updating timbre, since those values weren't touched)"); + } else { + parts[i]->setTimbre(&mt32ram.timbres[parts[i]->getAbsTimbreNum()].timbre); + } + } + parts[i]->refresh(); + } + } + break; + case MR_RhythmTemp: + for (unsigned int m = 0; m < len; m++) + ((Bit8u *)&mt32ram.rhythmSettings[first])[off + m] = data[m]; + for (unsigned int i = first; i <= last; i++) { + int timbreNum = mt32ram.rhythmSettings[i].timbre; + char timbreName[11]; + if (timbreNum < 94) { + memcpy(timbreName, mt32ram.timbres[128 + timbreNum].timbre.common.name, 10); + timbreName[10] = 0; + } else { + strcpy(timbreName, "[None]"); + } + printDebug("WRITE-RHYTHM (%d-%d@%d..%d): %d; level=%02x, panpot=%02x, reverb=%02x, timbre=%d (%s)", first, last, off, off + len, i, mt32ram.rhythmSettings[i].outlevel, mt32ram.rhythmSettings[i].panpot, mt32ram.rhythmSettings[i].reverbSwitch, mt32ram.rhythmSettings[i].timbre, timbreName); + } + if (parts[8] != NULL) { + parts[8]->refresh(); + } + break; + case MR_TimbreTemp: + for (unsigned int m = 0; m < len; m++) + ((Bit8u *)&mt32ram.timbreSettings[first])[off + m] = data[m]; + for (unsigned int i = first; i <= last; i++) { + char instrumentName[11]; + memcpy(instrumentName, mt32ram.timbreSettings[i].common.name, 10); + instrumentName[10] = 0; + printDebug("WRITE-PARTTIMBRE (%d-%d@%d..%d): timbre=%d (%s)", first, last, off, off + len, i, instrumentName); + if (parts[i] != NULL) { + parts[i]->refresh(); + } + } + break; + case MR_Patches: + for (unsigned int m = 0; m < len; m++) + ((Bit8u *)&mt32ram.patches[first])[off + m] = data[m]; + for (unsigned int i = first; i <= last; i++) { + PatchParam *patch = &mt32ram.patches[i]; + int patchAbsTimbreNum = patch->timbreGroup * 64 + patch->timbreNum; + char instrumentName[11]; + memcpy(instrumentName, mt32ram.timbres[patchAbsTimbreNum].timbre.common.name, 10); + instrumentName[10] = 0; + Bit8u *n = (Bit8u *)patch; + printDebug("WRITE-PATCH (%d-%d@%d..%d): %d; timbre=%d (%s) %02X%02X%02X%02X%02X%02X%02X%02X", first, last, off, off + len, i, patchAbsTimbreNum, instrumentName, n[0], n[1], n[2], n[3], n[4], n[5], n[6], n[7]); + // FIXME:KG: The below is definitely dodgy. We just guess that this is the patch that the part was using + // based on a timbre match (but many patches could have the same timbre!) + // If this refresh is really correct, we should store the patch number in use by each part. + /* + for (int part = 0; part < 8; part++) { + if (parts[part] != NULL) { + int partPatchAbsTimbreNum = mt32ram.patchSettings[part].patch.timbreGroup * 64 + mt32ram.patchSettings[part].patch.timbreNum; + if (parts[part]->getAbsTimbreNum() == patchAbsTimbreNum) { + parts[part]->setPatch(patch); + parts[part]->RefreshPatch(); + } + } + } + */ + } + break; + case MR_Timbres: + // Timbres + first += 128; + last += 128; + for (unsigned int m = 0; m < len; m++) + ((Bit8u *)&mt32ram.timbres[first])[off + m] = data[m]; + for (unsigned int i = first; i <= last; i++) { + char instrumentName[11]; + memcpy(instrumentName, mt32ram.timbres[i].timbre.common.name, 10); + instrumentName[10] = 0; + printDebug("WRITE-TIMBRE (%d-%d@%d..%d): %d; name=\"%s\"", first, last, off, off + len, i, instrumentName); + // FIXME:KG: Not sure if the stuff below should be done (for rhythm and/or parts)... + // Does the real MT-32 automatically do this? + for (unsigned int part = 0; part < 9; part++) { + if (parts[part] != NULL) { + parts[part]->refreshTimbre(i); + } + } + } + break; + case MR_System: + for (unsigned int m = 0; m < len; m++) + ((Bit8u *)&mt32ram.system)[m + off] = data[m]; + + report(ReportType_devReconfig, NULL); + + printDebug("WRITE-SYSTEM:"); + refreshSystem(); + break; + case MR_Display: + char buf[MAX_SYSEX_SIZE]; + memcpy(&buf, &data[0], len); + buf[len] = 0; + printDebug("WRITE-LCD: %s", buf); + report(ReportType_lcdMessage, buf); + break; + case MR_Reset: + printDebug("RESET"); + report(ReportType_devReset, NULL); + partialManager->deactivateAll(); + mt32ram = mt32default; + for (int i = 0; i < 9; i++) { + parts[i]->refresh(); + } + isEnabled = false; + break; + } +} + +bool Synth::refreshSystem() { + memset(chantable, -1, sizeof(chantable)); + + for (unsigned int i = 0; i < 9; i++) { + //LOG(LOG_MISC|LOG_ERROR,"Part %d set to MIDI channel %d",i,mt32ram.system.chanAssign[i]); + if (mt32ram.system.chanAssign[i] == 16 && parts[i] != NULL) { + parts[i]->allSoundOff(); + } else { + chantable[(int)mt32ram.system.chanAssign[i]] = (char)i; + } + } + //FIXME:KG: This is just an educated guess. + // The LAPC-I documentation claims a range of 427.5Hz-452.6Hz (similar to what we have here) + // The MT-32 documentation claims a range of 432.1Hz-457.6Hz + masterTune = 440.0f * powf(2.0f, (mt32ram.system.masterTune - 64.0f) / (128.0f * 12.0f)); + printDebug(" Master Tune: %f", masterTune); + printDebug(" Reverb: mode=%d, time=%d, level=%d", mt32ram.system.reverbMode, mt32ram.system.reverbTime, mt32ram.system.reverbLevel); + report(ReportType_newReverbMode, &mt32ram.system.reverbMode); + report(ReportType_newReverbTime, &mt32ram.system.reverbTime); + report(ReportType_newReverbLevel, &mt32ram.system.reverbLevel); + + if (myProp.useDefaultReverb) { + initReverb(mt32ram.system.reverbMode, mt32ram.system.reverbTime, mt32ram.system.reverbLevel); + } else { + initReverb(myProp.reverbType, myProp.reverbTime, mt32ram.system.reverbLevel); + } + + Bit8u *rset = mt32ram.system.reserveSettings; + printDebug(" Partial reserve: 1=%02d 2=%02d 3=%02d 4=%02d 5=%02d 6=%02d 7=%02d 8=%02d Rhythm=%02d", rset[0], rset[1], rset[2], rset[3], rset[4], rset[5], rset[6], rset[7], rset[8]); + int pr = partialManager->setReserve(rset); + if (pr != 32) + printDebug(" (Partial Reserve Table with less than 32 partials reserved!)"); + rset = mt32ram.system.chanAssign; + printDebug(" Part assign: 1=%02d 2=%02d 3=%02d 4=%02d 5=%02d 6=%02d 7=%02d 8=%02d Rhythm=%02d", rset[0], rset[1], rset[2], rset[3], rset[4], rset[5], rset[6], rset[7], rset[8]); + printDebug(" Master volume: %d", mt32ram.system.masterVol); + masterVolume = (Bit16u)(mt32ram.system.masterVol * 32767 / 100); + if (!tables.init(this, pcmWaves, (float)myProp.sampleRate, masterTune)) { + report(ReportType_errorSampleRate, NULL); + return false; + } + return true; +} + +bool Synth::dumpTimbre(File *file, const TimbreParam *timbre, Bit32u address) { + // Sysex header + if (!file->writeBit8u(0xF0)) + return false; + if (!file->writeBit8u(0x41)) + return false; + if (!file->writeBit8u(0x10)) + return false; + if (!file->writeBit8u(0x16)) + return false; + if (!file->writeBit8u(0x12)) + return false; + + char lsb = (char)(address & 0x7f); + char isb = (char)((address >> 7) & 0x7f); + char msb = (char)(((address >> 14) & 0x7f) | 0x08); + + //Address + if (!file->writeBit8u(msb)) + return false; + if (!file->writeBit8u(isb)) + return false; + if (!file->writeBit8u(lsb)) + return false; + + //Data + if (file->write(timbre, 246) != 246) + return false; + + //Checksum + unsigned char checksum = calcSysexChecksum((const Bit8u *)timbre, 246, msb + isb + lsb); + if (!file->writeBit8u(checksum)) + return false; + + //End of sysex + if (!file->writeBit8u(0xF7)) + return false; + return true; +} + +int Synth::dumpTimbres(const char *filename, int start, int len) { + File *file = openFile(filename, File::OpenMode_write); + if (file == NULL) + return -1; + + for (int timbreNum = start; timbreNum < start + len; timbreNum++) { + int useaddr = (timbreNum - start) * 256; + TimbreParam *timbre = &mt32ram.timbres[timbreNum].timbre; + if (!dumpTimbre(file, timbre, useaddr)) + break; + } + closeFile(file); + return 0; +} + +void ProduceOutput1(Bit16s *useBuf, Bit16s *stream, Bit32u len, Bit16s volume) { +#if MT32EMU_USE_MMX > 2 + //FIXME:KG: This appears to introduce crackle + int donelen = i386_produceOutput1(useBuf, stream, len, volume); + len -= donelen; + stream += donelen * 2; + useBuf += donelen * 2; +#endif + int end = len * 2; + while (end--) { + *stream = *stream + (Bit16s)(((Bit32s)*useBuf++ * (Bit32s)volume)>>15); + stream++; + } +} + +void Synth::render(Bit16s *stream, Bit32u len) { + memset(stream, 0, len * sizeof (Bit16s) * 2); + if (!isEnabled) + return; + while (len > 0) { + Bit32u thisLen = len > MAX_SAMPLE_OUTPUT ? MAX_SAMPLE_OUTPUT : len; + doRender(stream, thisLen); + len -= thisLen; + stream += 2 * thisLen; + } +} + +void Synth::doRender(Bit16s *stream, Bit32u len) { + partialManager->ageAll(); + + if (myProp.useReverb) { + for (unsigned int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { + if (partialManager->shouldReverb(i)) { + if (partialManager->produceOutput(i, &tmpBuffer[0], len)) { + ProduceOutput1(&tmpBuffer[0], stream, len, masterVolume); + } + } + } + Bit32u m = 0; + for (unsigned int i = 0; i < len; i++) { + sndbufl[i] = (float)stream[m] / 32767.0f; + m++; + sndbufr[i] = (float)stream[m] / 32767.0f; + m++; + } + reverbModel->processreplace(sndbufl, sndbufr, outbufl, outbufr, len, 1); + m=0; + for (unsigned int i = 0; i < len; i++) { + stream[m] = (Bit16s)(outbufl[i] * 32767.0f); + m++; + stream[m] = (Bit16s)(outbufr[i] * 32767.0f); + m++; + } + for (unsigned int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { + if (!partialManager->shouldReverb(i)) { + if (partialManager->produceOutput(i, &tmpBuffer[0], len)) { + ProduceOutput1(&tmpBuffer[0], stream, len, masterVolume); + } + } + } + } else { + for (unsigned int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { + if (partialManager->produceOutput(i, &tmpBuffer[0], len)) + ProduceOutput1(&tmpBuffer[0], stream, len, masterVolume); + } + } + + partialManager->clearAlreadyOutputed(); + +#if MT32EMU_MONITOR_PARTIALS == 1 + samplepos += len; + if (samplepos > myProp.SampleRate * 5) { + samplepos = 0; + int partialUsage[9]; + partialManager->GetPerPartPartialUsage(partialUsage); + printDebug("1:%02d 2:%02d 3:%02d 4:%02d 5:%02d 6:%02d 7:%02d 8:%02d", partialUsage[0], partialUsage[1], partialUsage[2], partialUsage[3], partialUsage[4], partialUsage[5], partialUsage[6], partialUsage[7]); + printDebug("Rhythm: %02d TOTAL: %02d", partialUsage[8], MT32EMU_MAX_PARTIALS - partialManager->GetFreePartialCount()); + } +#endif +} + +const Partial *Synth::getPartial(unsigned int partialNum) const { + return partialManager->getPartial(partialNum); +} + +const Part *Synth::getPart(unsigned int partNum) const { + if (partNum > 8) + return NULL; + return parts[partNum]; +} + +} diff --git a/engines/sci/sfx/softseq/mt32/synth.h b/engines/sci/sfx/softseq/mt32/synth.h new file mode 100644 index 0000000000..9d57c8d3cd --- /dev/null +++ b/engines/sci/sfx/softseq/mt32/synth.h @@ -0,0 +1,300 @@ +/* Copyright (c) 2003-2005 Various contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef MT32EMU_SYNTH_H +#define MT32EMU_SYNTH_H + +#include <stdarg.h> + +class revmodel; + +namespace MT32Emu { + +class File; +class TableInitialiser; +class Partial; +class PartialManager; +class Part; + +enum ReportType { + // Errors + ReportType_errorControlROM = 1, + ReportType_errorPCMROM, + ReportType_errorSampleRate, + + // Progress + ReportType_progressInit, + + // HW spec + ReportType_availableSSE, + ReportType_available3DNow, + ReportType_usingSSE, + ReportType_using3DNow, + + // General info + ReportType_lcdMessage, + ReportType_devReset, + ReportType_devReconfig, + ReportType_newReverbMode, + ReportType_newReverbTime, + ReportType_newReverbLevel +}; + +struct SynthProperties { + // Sample rate to use in mixing + int sampleRate; + + // Flag to activate reverb. True = use reverb, False = no reverb + bool useReverb; + // True to use software set reverb settings, False to set reverb settings in + // following parameters + bool useDefaultReverb; + // When not using the default settings, this specifies one of the 4 reverb types + // 1 = Room 2 = Hall 3 = Plate 4 = Tap + unsigned char reverbType; + // This specifies the delay time, from 0-7 (not sure of the actual MT-32's measurement) + unsigned char reverbTime; + // This specifies the reverb level, from 0-7 (not sure of the actual MT-32's measurement) + unsigned char reverbLevel; + // The name of the directory in which the ROM and data files are stored (with trailing slash/backslash) + // Not used if "openFile" is set. May be NULL in any case. + char *baseDir; + // This is used as the first argument to all callbacks + void *userData; + // Callback for reporting various errors and information. May be NULL + int (*report)(void *userData, ReportType type, const void *reportData); + // Callback for debug messages, in vprintf() format + void (*printDebug)(void *userData, const char *fmt, va_list list); + // Callback for providing an implementation of File, opened and ready for use + // May be NULL, in which case a default implementation will be used. + File *(*openFile)(void *userData, const char *filename, File::OpenMode mode); + // Callback for closing a File. May be NULL, in which case the File will automatically be close()d/deleted. + void (*closeFile)(void *userData, File *file); +}; + +// This is the specification of the Callback routine used when calling the RecalcWaveforms +// function +typedef void (*recalcStatusCallback)(int percDone); + +// This external function recreates the base waveform file (waveforms.raw) using a specifed +// sampling rate. The callback routine provides interactivity to let the user know what +// percentage is complete in regenerating the waveforms. When a NULL pointer is used as the +// callback routine, no status is reported. +bool RecalcWaveforms(char * baseDir, int sampRate, recalcStatusCallback callBack); + +typedef float (*iir_filter_type)(float input,float *hist1_ptr, float *coef_ptr); + +const Bit8u SYSEX_MANUFACTURER_ROLAND = 0x41; + +const Bit8u SYSEX_MDL_MT32 = 0x16; +const Bit8u SYSEX_MDL_D50 = 0x14; + +const Bit8u SYSEX_CMD_RQ1 = 0x11; // Request data #1 +const Bit8u SYSEX_CMD_DT1 = 0x12; // Data set 1 +const Bit8u SYSEX_CMD_WSD = 0x40; // Want to send data +const Bit8u SYSEX_CMD_RQD = 0x41; // Request data +const Bit8u SYSEX_CMD_DAT = 0x42; // Data set +const Bit8u SYSEX_CMD_ACK = 0x43; // Acknowledge +const Bit8u SYSEX_CMD_EOD = 0x45; // End of data +const Bit8u SYSEX_CMD_ERR = 0x4E; // Communications error +const Bit8u SYSEX_CMD_RJC = 0x4F; // Rejection + +const unsigned int CONTROL_ROM_SIZE = 64 * 1024; + +struct ControlROMPCMStruct +{ + Bit8u pos; + Bit8u len; + Bit8u pitchLSB; + Bit8u pitchMSB; +}; + +struct ControlROMMap { + Bit16u idPos; + Bit16u idLen; + const char *idBytes; + Bit16u pcmTable; + Bit16u pcmCount; + Bit16u timbreAMap; + Bit16u timbreAOffset; + Bit16u timbreBMap; + Bit16u timbreBOffset; + Bit16u timbreRMap; + Bit16u timbreRCount; + Bit16u rhythmSettings; + Bit16u rhythmSettingsCount; + Bit16u reserveSettings; + Bit16u panSettings; + Bit16u programSettings; +}; + +enum MemoryRegionType { + MR_PatchTemp, MR_RhythmTemp, MR_TimbreTemp, MR_Patches, MR_Timbres, MR_System, MR_Display, MR_Reset +}; + +class MemoryRegion { +public: + MemoryRegionType type; + Bit32u startAddr, entrySize, entries; + + int lastTouched(Bit32u addr, Bit32u len) const { + return (offset(addr) + len - 1) / entrySize; + } + int firstTouchedOffset(Bit32u addr) const { + return offset(addr) % entrySize; + } + int firstTouched(Bit32u addr) const { + return offset(addr) / entrySize; + } + Bit32u regionEnd() const { + return startAddr + entrySize * entries; + } + bool contains(Bit32u addr) const { + return addr >= startAddr && addr < regionEnd(); + } + int offset(Bit32u addr) const { + return addr - startAddr; + } + Bit32u getClampedLen(Bit32u addr, Bit32u len) const { + if (addr + len > regionEnd()) + return regionEnd() - addr; + return len; + } + Bit32u next(Bit32u addr, Bit32u len) const { + if (addr + len > regionEnd()) { + return regionEnd() - addr; + } + return 0; + } +}; + + +class Synth { +friend class Part; +friend class RhythmPart; +friend class Partial; +friend class Tables; +private: + bool isEnabled; + + iir_filter_type iirFilter; + + PCMWaveEntry *pcmWaves; // Array + + const ControlROMMap *controlROMMap; + Bit8u controlROMData[CONTROL_ROM_SIZE]; + Bit16s *pcmROMData; + int pcmROMSize; // This is in 16-bit samples, therefore half the number of bytes in the ROM + + Bit8s chantable[32]; + + #if MT32EMU_MONITOR_PARTIALS == 1 + static Bit32s samplepos = 0; + #endif + + Tables tables; + + MemParams mt32ram, mt32default; + + revmodel *reverbModel; + + float masterTune; + Bit16u masterVolume; + + bool isOpen; + + PartialManager *partialManager; + Part *parts[9]; + + Bit16s tmpBuffer[MAX_SAMPLE_OUTPUT * 2]; + float sndbufl[MAX_SAMPLE_OUTPUT]; + float sndbufr[MAX_SAMPLE_OUTPUT]; + float outbufl[MAX_SAMPLE_OUTPUT]; + float outbufr[MAX_SAMPLE_OUTPUT]; + + SynthProperties myProp; + + bool loadPreset(File *file); + void initReverb(Bit8u newRevMode, Bit8u newRevTime, Bit8u newRevLevel); + void doRender(Bit16s * stream, Bit32u len); + + void playAddressedSysex(unsigned char channel, const Bit8u *sysex, Bit32u len); + void readSysex(unsigned char channel, const Bit8u *sysex, Bit32u len); + void writeMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u len, const Bit8u *data); + void readMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u len, Bit8u *data); + + bool loadControlROM(const char *filename); + bool loadPCMROM(const char *filename); + bool dumpTimbre(File *file, const TimbreParam *timbre, Bit32u addr); + int dumpTimbres(const char *filename, int start, int len); + + bool initPCMList(Bit16u mapAddress, Bit16u count); + bool initRhythmTimbres(Bit16u mapAddress, Bit16u count); + bool initTimbres(Bit16u mapAddress, Bit16u offset, int startTimbre); + bool initRhythmTimbre(int drumNum, const Bit8u *mem, unsigned int memLen); + bool refreshSystem(); + +protected: + int report(ReportType type, const void *reportData); + File *openFile(const char *filename, File::OpenMode mode); + void closeFile(File *file); + void printDebug(const char *fmt, ...); + +public: + static Bit8u calcSysexChecksum(const Bit8u *data, Bit32u len, Bit8u checksum); + + Synth(); + ~Synth(); + + // Used to initialise the MT-32. Must be called before any other function. + // Returns true if initialization was sucessful, otherwise returns false. + bool open(SynthProperties &useProp); + + // Closes the MT-32 and deallocates any memory used by the synthesizer + void close(void); + + // Sends a 4-byte MIDI message to the MT-32 for immediate playback + void playMsg(Bit32u msg); + void playMsgOnPart(unsigned char part, unsigned char code, unsigned char note, unsigned char velocity); + + // Sends a string of Sysex commands to the MT-32 for immediate interpretation + // The length is in bytes + void playSysex(const Bit8u *sysex, Bit32u len); + void playSysexWithoutFraming(const Bit8u *sysex, Bit32u len); + void playSysexWithoutHeader(unsigned char device, unsigned char command, const Bit8u *sysex, Bit32u len); + void writeSysex(unsigned char channel, const Bit8u *sysex, Bit32u len); + + // This callback routine is used to have the MT-32 generate samples to the specified + // output stream. The length is in whole samples, not bytes. (I.E. in 16-bit stereo, + // one sample is 4 bytes) + void render(Bit16s * stream, Bit32u len); + + const Partial *getPartial(unsigned int partialNum) const; + + void readMemory(Bit32u addr, Bit32u len, Bit8u *data); + + // partNum should be 0..7 for Part 1..8, or 8 for Rhythm + const Part *getPart(unsigned int partNum) const; +}; + +} + +#endif diff --git a/engines/sci/sfx/softseq/mt32/tables.cpp b/engines/sci/sfx/softseq/mt32/tables.cpp new file mode 100644 index 0000000000..4591ea22e3 --- /dev/null +++ b/engines/sci/sfx/softseq/mt32/tables.cpp @@ -0,0 +1,749 @@ +/* Copyright (c) 2003-2005 Various contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include <stdlib.h> +#include <string.h> +#include <math.h> + +#include "mt32emu.h" + +#ifdef MACOSX +// Older versions of Mac OS X didn't supply a powf function. To ensure +// binary compatibility, we force using pow instead of powf (the only +// potential drawback is that it might be a little bit slower). +#define powf pow +#endif + +#define FIXEDPOINT_MAKE(x, point) ((Bit32u)((1 << point) * x)) + +namespace MT32Emu { + +//Amplitude time velocity follow exponential coefficients +static const double tvcatconst[5] = {0.0, 0.002791309, 0.005942882, 0.012652792, 0.026938637}; +static const double tvcatmult[5] = {1.0, 1.072662811, 1.169129367, 1.288579123, 1.229630539}; + +// These are division constants for the TVF depth key follow +static const Bit32u depexp[5] = {3000, 950, 485, 255, 138}; + +//Envelope time keyfollow exponential coefficients +static const double tkcatconst[5] = {0.0, 0.005853144, 0.011148054, 0.019086143, 0.043333215}; +static const double tkcatmult[5] = {1.0, 1.058245688, 1.048488989, 1.016049301, 1.097538067}; + +// Begin filter stuff + +// Pre-warp the coefficients of a numerator or denominator. +// Note that a0 is assumed to be 1, so there is no wrapping +// of it. +static void prewarp(double *a1, double *a2, double fc, double fs) { + double wp; + + wp = 2.0 * fs * tan(DOUBLE_PI * fc / fs); + + *a2 = *a2 / (wp * wp); + *a1 = *a1 / wp; +} + +// Transform the numerator and denominator coefficients +// of s-domain biquad section into corresponding +// z-domain coefficients. +// +// Store the 4 IIR coefficients in array pointed by coef +// in following order: +// beta1, beta2 (denominator) +// alpha1, alpha2 (numerator) +// +// Arguments: +// a0-a2 - s-domain numerator coefficients +// b0-b2 - s-domain denominator coefficients +// k - filter gain factor. initially set to 1 +// and modified by each biquad section in such +// a way, as to make it the coefficient by +// which to multiply the overall filter gain +// in order to achieve a desired overall filter gain, +// specified in initial value of k. +// fs - sampling rate (Hz) +// coef - array of z-domain coefficients to be filled in. +// +// Return: +// On return, set coef z-domain coefficients +static void bilinear(double a0, double a1, double a2, double b0, double b1, double b2, double *k, double fs, float *coef) { + double ad, bd; + + // alpha (Numerator in s-domain) + ad = 4. * a2 * fs * fs + 2. * a1 * fs + a0; + // beta (Denominator in s-domain) + bd = 4. * b2 * fs * fs + 2. * b1* fs + b0; + + // update gain constant for this section + *k *= ad/bd; + + // Denominator + *coef++ = (float)((2. * b0 - 8. * b2 * fs * fs) / bd); // beta1 + *coef++ = (float)((4. * b2 * fs * fs - 2. * b1 * fs + b0) / bd); // beta2 + + // Nominator + *coef++ = (float)((2. * a0 - 8. * a2 * fs * fs) / ad); // alpha1 + *coef = (float)((4. * a2 * fs * fs - 2. * a1 * fs + a0) / ad); // alpha2 +} + +// a0-a2: numerator coefficients +// b0-b2: denominator coefficients +// fc: Filter cutoff frequency +// fs: sampling rate +// k: overall gain factor +// coef: pointer to 4 iir coefficients +static void szxform(double *a0, double *a1, double *a2, double *b0, double *b1, double *b2, double fc, double fs, double *k, float *coef) { + // Calculate a1 and a2 and overwrite the original values + prewarp(a1, a2, fc, fs); + prewarp(b1, b2, fc, fs); + bilinear(*a0, *a1, *a2, *b0, *b1, *b2, k, fs, coef); +} + +static void initFilter(float fs, float fc, float *icoeff, float Q) { + float *coef; + double a0, a1, a2, b0, b1, b2; + + double k = 1.5; // Set overall filter gain factor + coef = icoeff + 1; // Skip k, or gain + + // Section 1 + a0 = 1.0; + a1 = 0; + a2 = 0; + b0 = 1.0; + b1 = 0.765367 / Q; // Divide by resonance or Q + b2 = 1.0; + szxform(&a0, &a1, &a2, &b0, &b1, &b2, fc, fs, &k, coef); + coef += 4; // Point to next filter section + + // Section 2 + a0 = 1.0; + a1 = 0; + a2 = 0; + b0 = 1.0; + b1 = 1.847759 / Q; + b2 = 1.0; + szxform(&a0, &a1, &a2, &b0, &b1, &b2, fc, fs, &k, coef); + + icoeff[0] = (float)k; +} + +void Tables::initFiltCoeff(float samplerate) { + for (int j = 0; j < FILTERGRAN; j++) { + for (int res = 0; res < 31; res++) { + float tres = resonanceFactor[res]; + initFilter((float)samplerate, (((float)(j+1.0)/FILTERGRAN)) * ((float)samplerate/2), filtCoeff[j][res], tres); + } + } +} + +void Tables::initEnvelopes(float samplerate) { + for (int lf = 0; lf <= 100; lf++) { + float elf = (float)lf; + + // General envelope + // This formula fits observation of the CM-32L by +/- 0.03s or so for the second time value in the filter, + // when all other times were 0 and all levels were 100. Note that variations occur depending on the level + // delta of the section, which we're not fully emulating. + float seconds = powf(2.0f, (elf / 8.0f) + 7.0f) / 32768.0f; + int samples = (int)(seconds * samplerate); + envTime[lf] = samples; + + // Cap on envelope times depending on the level delta + if (elf == 0) { + envDeltaMaxTime[lf] = 63; + } else { + float cap = 11.0f * (float)log(elf) + 64; + if (cap > 100.0f) { + cap = 100.0f; + } + envDeltaMaxTime[lf] = (int)cap; + } + + + // This (approximately) represents the time durations when the target level is 0. + // Not sure why this is a special case, but it's seen to be from the real thing. + seconds = powf(2, (elf / 8.0f) + 6) / 32768.0f; + envDecayTime[lf] = (int)(seconds * samplerate); + + // I am certain of this: Verified by hand LFO log + lfoPeriod[lf] = (Bit32u)(((float)samplerate) / (powf(1.088883372f, (float)lf) * 0.021236044f)); + } +} + +void Tables::initMT32ConstantTables(Synth *synth) { + int lf; + synth->printDebug("Initialising Pitch Tables"); + for (lf = -108; lf <= 108; lf++) { + tvfKeyfollowMult[lf + 108] = (int)(256 * powf(2.0f, (float)(lf / 24.0f))); + //synth->printDebug("KT %d = %d", f, keytable[f+108]); + } + + for (int res = 0; res < 31; res++) { + resonanceFactor[res] = powf((float)res / 30.0f, 5.0f) + 1.0f; + } + + int period = 65536; + + for (int ang = 0; ang < period; ang++) { + int halfang = (period / 2); + int angval = ang % halfang; + float tval = (((float)angval / (float)halfang) - 0.5f) * 2; + if (ang >= halfang) + tval = -tval; + sintable[ang] = (Bit16s)(tval * 50.0f) + 50; + } + + int velt, dep; + float tempdep; + for (velt = 0; velt < 128; velt++) { + for (dep = 0; dep < 5; dep++) { + if (dep > 0) { + float ff = (float)(exp(3.5f * tvcatconst[dep] * (59.0f - (float)velt)) * tvcatmult[dep]); + tempdep = 256.0f * ff; + envTimeVelfollowMult[dep][velt] = (int)tempdep; + //if ((velt % 16) == 0) { + // synth->printDebug("Key %d, depth %d, factor %d", velt, dep, (int)tempdep); + //} + } else + envTimeVelfollowMult[dep][velt] = 256; + } + + for (dep = -7; dep < 8; dep++) { + float fldep = (float)abs(dep) / 7.0f; + fldep = powf(fldep,2.5f); + if (dep < 0) + fldep = fldep * -1.0f; + pwVelfollowAdd[dep+7][velt] = Bit32s((fldep * (float)velt * 100) / 128.0); + } + } + + for (dep = 0; dep <= 100; dep++) { + for (velt = 0; velt < 128; velt++) { + float fdep = (float)dep * 0.000347013f; // Another MT-32 constant + float fv = ((float)velt - 64.0f)/7.26f; + float flogdep = powf(10, fdep * fv); + float fbase; + + if (velt > 64) + synth->tables.tvfVelfollowMult[velt][dep] = (int)(flogdep * 256.0); + else { + //lff = 1 - (pow(((128.0 - (float)lf) / 64.0),.25) * ((float)velt / 96)); + fbase = 1 - (powf(((float)dep / 100.0f),.25f) * ((float)(64-velt) / 96.0f)); + synth->tables.tvfVelfollowMult[velt][dep] = (int)(fbase * 256.0); + } + //synth->printDebug("Filvel dep %d velt %d = %x", dep, velt, filveltable[velt][dep]); + } + } + + for (lf = 0; lf < 128; lf++) { + float veloFract = lf / 127.0f; + for (int velsens = 0; velsens <= 100; velsens++) { + float sensFract = (velsens - 50) / 50.0f; + if (velsens < 50) { + tvaVelfollowMult[lf][velsens] = FIXEDPOINT_MAKE(1.0f / powf(2.0f, veloFract * -sensFract * 127.0f / 20.0f), 8); + } else { + tvaVelfollowMult[lf][velsens] = FIXEDPOINT_MAKE(1.0f / powf(2.0f, (1.0f - veloFract) * sensFract * 127.0f / 20.0f), 8); + } + } + } + + for (lf = 0; lf <= 100; lf++) { + // Converts the 0-100 range used by the MT-32 to volume multiplier + volumeMult[lf] = FIXEDPOINT_MAKE(powf((float)lf / 100.0f, FLOAT_LN), 7); + } + + for (lf = 0; lf <= 100; lf++) { + float mv = lf / 100.0f; + float pt = mv - 0.5f; + if (pt < 0) + pt = 0; + + // Original (CC version) + //pwFactor[lf] = (int)(pt * 210.04f) + 128; + + // Approximation from sample comparison + pwFactor[lf] = (int)(pt * 179.0f) + 128; + } + + for (unsigned int i = 0; i < MAX_SAMPLE_OUTPUT; i++) { + int myRand; + myRand = rand(); + //myRand = ((myRand - 16383) * 7168) >> 16; + // This one is slower but works with all values of RAND_MAX + myRand = (int)((myRand - RAND_MAX / 2) / (float)RAND_MAX * (7168 / 2)); + //FIXME:KG: Original ultimately set the lowest two bits to 0, for no obvious reason + noiseBuf[i] = (Bit16s)myRand; + } + + float tdist; + float padjtable[51]; + for (lf = 0; lf <= 50; lf++) { + if (lf == 0) + padjtable[lf] = 7; + else if (lf == 1) + padjtable[lf] = 6; + else if (lf == 2) + padjtable[lf] = 5; + else if (lf == 3) + padjtable[lf] = 4; + else if (lf == 4) + padjtable[lf] = 4 - (0.333333f); + else if (lf == 5) + padjtable[lf] = 4 - (0.333333f * 2); + else if (lf == 6) + padjtable[lf] = 3; + else if ((lf > 6) && (lf <= 12)) { + tdist = (lf-6.0f) / 6.0f; + padjtable[lf] = 3.0f - tdist; + } else if ((lf > 12) && (lf <= 25)) { + tdist = (lf - 12.0f) / 13.0f; + padjtable[lf] = 2.0f - tdist; + } else { + tdist = (lf - 25.0f) / 25.0f; + padjtable[lf] = 1.0f - tdist; + } + //synth->printDebug("lf %d = padj %f", lf, padjtable[lf]); + } + + float lfp, depf, finalval, tlf; + int depat, pval, depti; + for (lf = 0; lf <= 10; lf++) { + // I believe the depth is cubed or something + + for (depat = 0; depat <= 100; depat++) { + if (lf > 0) { + depti = abs(depat - 50); + tlf = (float)lf - padjtable[depti]; + if (tlf < 0) + tlf = 0; + lfp = (float)exp(0.713619942f * tlf) / 407.4945111f; + + if (depat < 50) + finalval = 4096.0f * powf(2, -lfp); + else + finalval = 4096.0f * powf(2, lfp); + pval = (int)finalval; + + pitchEnvVal[lf][depat] = pval; + //synth->printDebug("lf %d depat %d pval %d tlf %f lfp %f", lf,depat,pval, tlf, lfp); + } else { + pitchEnvVal[lf][depat] = 4096; + //synth->printDebug("lf %d depat %d pval 4096", lf, depat); + } + } + } + for (lf = 0; lf <= 100; lf++) { + // It's linear - verified on MT-32 - one of the few things linear + lfp = ((float)lf * 0.1904f) / 310.55f; + + for (depat = 0; depat <= 100; depat++) { + depf = ((float)depat - 50.0f) / 50.0f; + //finalval = pow(2, lfp * depf * .5); + finalval = 4096.0f + (4096.0f * lfp * depf); + + pval = (int)finalval; + + lfoShift[lf][depat] = pval; + + //synth->printDebug("lf %d depat %d pval %x", lf,depat,pval); + } + } + + for (lf = 0; lf <= 12; lf++) { + for (int distval = 0; distval < 128; distval++) { + float amplog, dval; + if (lf == 0) { + amplog = 0; + dval = 1; + tvaBiasMult[lf][distval] = 256; + } else { + /* + amplog = powf(1.431817011f, (float)lf) / FLOAT_PI; + dval = ((128.0f - (float)distval) / 128.0f); + amplog = exp(amplog); + dval = powf(amplog, dval) / amplog; + tvaBiasMult[lf][distval] = (int)(dval * 256.0); + */ + // Lets assume for a second it's linear + + // Distance of full volume reduction + amplog = (float)(12.0f / (float)lf) * 24.0f; + if (distval > amplog) { + tvaBiasMult[lf][distval] = 0; + } else { + dval = (amplog - (float)distval) / amplog; + tvaBiasMult[lf][distval] = (int)(dval * 256.0f); + } + } + //synth->printDebug("Ampbias lf %d distval %d = %f (%x) %f", lf, distval, dval, tvaBiasMult[lf][distval],amplog); + } + } + + for (lf = 0; lf <= 14; lf++) { + for (int distval = 0; distval < 128; distval++) { + float filval = fabsf((float)((lf - 7) * 12) / 7.0f); + float amplog, dval; + if (lf == 7) { + amplog = 0; + dval = 1; + tvfBiasMult[lf][distval] = 256; + } else { + //amplog = pow(1.431817011, filval) / FLOAT_PI; + amplog = powf(1.531817011f, filval) / FLOAT_PI; + dval = (128.0f - (float)distval) / 128.0f; + amplog = (float)exp(amplog); + dval = powf(amplog,dval)/amplog; + if (lf < 8) { + tvfBiasMult[lf][distval] = (int)(dval * 256.0f); + } else { + dval = powf(dval, 0.3333333f); + if (dval < 0.01f) + dval = 0.01f; + dval = 1 / dval; + tvfBiasMult[lf][distval] = (int)(dval * 256.0f); + } + } + //synth->printDebug("Fbias lf %d distval %d = %f (%x) %f", lf, distval, dval, tvfBiasMult[lf][distval],amplog); + } + } +} + +// Per-note table initialisation follows + +static void initSaw(NoteLookup *noteLookup, Bit32s div2) { + int tmpdiv = div2 << 16; + for (int rsaw = 0; rsaw <= 100; rsaw++) { + float fsaw; + if (rsaw < 50) + fsaw = 50.0f; + else + fsaw = (float)rsaw; + + //(66 - (((A8 - 50) / 50) ^ 0.63) * 50) / 132 + float sawfact = (66.0f - (powf((fsaw - 50.0f) / 50.0f, 0.63f) * 50.0f)) / 132.0f; + noteLookup->sawTable[rsaw] = (int)(sawfact * (float)tmpdiv) >> 16; + //synth->printDebug("F %d divtable %d saw %d sawtable %d", f, div, rsaw, sawtable[f][rsaw]); + } +} + +static void initDep(KeyLookup *keyLookup, float f) { + for (int dep = 0; dep < 5; dep++) { + if (dep == 0) { + keyLookup->envDepthMult[dep] = 256; + keyLookup->envTimeMult[dep] = 256; + } else { + float depfac = 3000.0f; + float ff, tempdep; + depfac = (float)depexp[dep]; + + ff = (f - (float)MIDDLEC) / depfac; + tempdep = powf(2, ff) * 256.0f; + keyLookup->envDepthMult[dep] = (int)tempdep; + + ff = (float)(exp(tkcatconst[dep] * ((float)MIDDLEC - f)) * tkcatmult[dep]); + keyLookup->envTimeMult[dep] = (int)(ff * 256.0f); + } + } + //synth->printDebug("F %f d1 %x d2 %x d3 %x d4 %x d5 %x", f, noteLookup->fildepTable[0], noteLookup->fildepTable[1], noteLookup->fildepTable[2], noteLookup->fildepTable[3], noteLookup->fildepTable[4]); +} + +Bit16s Tables::clampWF(Synth *synth, const char *n, float ampVal, double input) { + Bit32s x = (Bit32s)(input * ampVal); + if (x < -ampVal - 1) { + synth->printDebug("%s==%d<-WGAMP-1!", n, x); + x = (Bit32s)(-ampVal - 1); + } else if (x > ampVal) { + synth->printDebug("%s==%d>WGAMP!", n, x); + x = (Bit32s)ampVal; + } + return (Bit16s)x; +} + +File *Tables::initWave(Synth *synth, NoteLookup *noteLookup, float ampVal, float div2, File *file) { + int iDiv2 = (int)div2; + noteLookup->waveformSize[0] = iDiv2 << 1; + noteLookup->waveformSize[1] = iDiv2 << 1; + noteLookup->waveformSize[2] = iDiv2 << 2; + for (int i = 0; i < 3; i++) { + if (noteLookup->waveforms[i] == NULL) { + noteLookup->waveforms[i] = new Bit16s[noteLookup->waveformSize[i]]; + } + } + if (file != NULL) { + for (int i = 0; i < 3 && file != NULL; i++) { + size_t len = noteLookup->waveformSize[i]; + for (unsigned int j = 0; j < len; j++) { + if (!file->readBit16u((Bit16u *)¬eLookup->waveforms[i][j])) { + synth->printDebug("Error reading wave file cache!"); + file->close(); + file = NULL; + break; + } + } + } + } + if (file == NULL) { + double sd = DOUBLE_PI / div2; + + for (int fa = 0; fa < (iDiv2 << 1); fa++) { + // sa ranges from 0 to 2PI + double sa = fa * sd; + + // Calculate a sample for the bandlimited sawtooth wave + double saw = 0.0; + int sincs = iDiv2 >> 1; + double sinus = 1.0; + for (int sincNum = 1; sincNum <= sincs; sincNum++) { + saw += sin(sinus * sa) / sinus; + sinus++; + } + + // This works pretty well + // Multiplied by 0.84 so that the spikes caused by bandlimiting don't overdrive the amplitude + noteLookup->waveforms[0][fa] = clampWF(synth, "saw", ampVal, -saw / (0.5 * DOUBLE_PI) * 0.84); + noteLookup->waveforms[1][fa] = clampWF(synth, "cos", ampVal, -cos(sa / 2.0)); + noteLookup->waveforms[2][fa * 2] = clampWF(synth, "cosoff_0", ampVal, -cos(sa - DOUBLE_PI)); + noteLookup->waveforms[2][fa * 2 + 1] = clampWF(synth, "cosoff_1", ampVal, -cos((sa + (sd / 2)) - DOUBLE_PI)); + } + } + return file; +} + +static void initFiltTable(NoteLookup *noteLookup, float freq, float rate) { + for (int tr = 0; tr <= 200; tr++) { + float ftr = (float)tr; + + // Verified exact on MT-32 + if (tr > 100) + ftr = 100.0f + (powf((ftr - 100.0f) / 100.0f, 3.0f) * 100.0f); + + // I think this is the one + float brsq = powf(10.0f, (tr / 50.0f) - 1.0f); + noteLookup->filtTable[0][tr] = (int)((freq * brsq) / (rate / 2) * FILTERGRAN); + if (noteLookup->filtTable[0][tr]>=((FILTERGRAN*15)/16)) + noteLookup->filtTable[0][tr] = ((FILTERGRAN*15)/16); + + float brsa = powf(10.0f, ((tr / 55.0f) - 1.0f)) / 2.0f; + noteLookup->filtTable[1][tr] = (int)((freq * brsa) / (rate / 2) * FILTERGRAN); + if (noteLookup->filtTable[1][tr]>=((FILTERGRAN*15)/16)) + noteLookup->filtTable[1][tr] = ((FILTERGRAN*15)/16); + } +} + +static void initNFiltTable(NoteLookup *noteLookup, float freq, float rate) { + for (int cf = 0; cf <= 100; cf++) { + float cfmult = (float)cf; + + for (int tf = 0;tf <= 100; tf++) { + float tfadd = (float)tf; + + //float freqsum = exp((cfmult + tfadd) / 30.0f) / 4.0f; + //float freqsum = 0.15f * exp(0.45f * ((cfmult + tfadd) / 10.0f)); + + float freqsum = powf(2.0f, ((cfmult + tfadd) - 40.0f) / 16.0f); + + noteLookup->nfiltTable[cf][tf] = (int)((freq * freqsum) / (rate / 2) * FILTERGRAN); + if (noteLookup->nfiltTable[cf][tf] >= ((FILTERGRAN * 15) / 16)) + noteLookup->nfiltTable[cf][tf] = ((FILTERGRAN * 15) / 16); + } + } +} + +File *Tables::initNote(Synth *synth, NoteLookup *noteLookup, float note, float rate, float masterTune, PCMWaveEntry *pcmWaves, File *file) { + float freq = (float)(masterTune * pow(2.0, ((double)note - MIDDLEA) / 12.0)); + float div2 = rate * 2.0f / freq; + noteLookup->div2 = (int)div2; + + if (noteLookup->div2 == 0) + noteLookup->div2 = 1; + + initSaw(noteLookup, noteLookup->div2); + + //synth->printDebug("Note %f; freq=%f, div=%f", note, freq, rate / freq); + file = initWave(synth, noteLookup, (const float)WGAMP, div2, file); + + // Create the pitch tables + if (noteLookup->wavTable == NULL) + noteLookup->wavTable = new Bit32u[synth->controlROMMap->pcmCount]; + double rateMult = 32000.0 / rate; + double tuner = freq * 65536.0f; + for (int pc = 0; pc < synth->controlROMMap->pcmCount; pc++) { + noteLookup->wavTable[pc] = (int)(tuner / pcmWaves[pc].tune * rateMult); + } + + initFiltTable(noteLookup, freq, rate); + initNFiltTable(noteLookup, freq, rate); + return file; +} + +bool Tables::initNotes(Synth *synth, PCMWaveEntry *pcmWaves, float rate, float masterTune) { + const char *NoteNames[12] = { + "C ", "C#", "D ", "D#", "E ", "F ", "F#", "G ", "G#", "A ", "A#", "B " + }; + char filename[64]; + int intRate = (int)rate; + char version[4] = {0, 0, 0, 5}; + sprintf(filename, "waveformcache-%d-%.2f.raw", intRate, masterTune); + + File *file = NULL; + char header[20]; + strncpy(header, "MT32WAVE", 8); + int pos = 8; + // Version... + for (int i = 0; i < 4; i++) + header[pos++] = version[i]; + header[pos++] = (char)((intRate >> 24) & 0xFF); + header[pos++] = (char)((intRate >> 16) & 0xFF); + header[pos++] = (char)((intRate >> 8) & 0xFF); + header[pos++] = (char)(intRate & 0xFF); + int intTuning = (int)masterTune; + header[pos++] = (char)((intTuning >> 8) & 0xFF); + header[pos++] = (char)(intTuning & 0xFF); + header[pos++] = 0; + header[pos] = (char)((masterTune - intTuning) * 10); +#if MT32EMU_WAVECACHEMODE < 2 + bool reading = false; + file = synth->openFile(filename, File::OpenMode_read); + if (file != NULL) { + char fileHeader[20]; + if (file->read(fileHeader, 20) == 20) { + if (memcmp(fileHeader, header, 20) == 0) { + Bit16u endianCheck; + if (file->readBit16u(&endianCheck)) { + if (endianCheck == 1) { + reading = true; + } else { + synth->printDebug("Endian check in %s does not match expected", filename); + } + } else { + synth->printDebug("Unable to read endian check in %s", filename); + } + } else { + synth->printDebug("Header of %s does not match expected", filename); + } + } else { + synth->printDebug("Error reading 16 bytes of %s", filename); + } + if (!reading) { + file->close(); + file = NULL; + } + } else { + synth->printDebug("Unable to open %s for reading", filename); + } +#endif + + float progress = 0.0f; + bool abort = false; + synth->report(ReportType_progressInit, &progress); + for (int f = LOWEST_NOTE; f <= HIGHEST_NOTE; f++) { + synth->printDebug("Initialising note %s%d", NoteNames[f % 12], (f / 12) - 2); + NoteLookup *noteLookup = ¬eLookups[f - LOWEST_NOTE]; + file = initNote(synth, noteLookup, (float)f, rate, masterTune, pcmWaves, file); + progress = (f - LOWEST_NOTE + 1) / (float)NUM_NOTES; + abort = synth->report(ReportType_progressInit, &progress) != 0; + if (abort) + break; + } + +#if MT32EMU_WAVECACHEMODE == 0 || MT32EMU_WAVECACHEMODE == 2 + if (file == NULL) { + file = synth->openFile(filename, File::OpenMode_write); + if (file != NULL) { + if (file->write(header, 20) == 20 && file->writeBit16u(1)) { + for (int f = 0; f < NUM_NOTES; f++) { + for (int i = 0; i < 3 && file != NULL; i++) { + int len = noteLookups[f].waveformSize[i]; + for (int j = 0; j < len; j++) { + if (!file->writeBit16u(noteLookups[f].waveforms[i][j])) { + synth->printDebug("Error writing waveform cache file"); + file->close(); + file = NULL; + break; + } + } + } + } + } else { + synth->printDebug("Error writing 16-byte header to %s - won't continue saving", filename); + } + } else { + synth->printDebug("Unable to open %s for writing - won't be created", filename); + } + } +#endif + + if (file != NULL) + synth->closeFile(file); + return !abort; +} + +void Tables::freeNotes() { + for (int t = 0; t < 3; t++) { + for (int m = 0; m < NUM_NOTES; m++) { + if (noteLookups[m].waveforms[t] != NULL) { + delete[] noteLookups[m].waveforms[t]; + noteLookups[m].waveforms[t] = NULL; + noteLookups[m].waveformSize[t] = 0; + } + if (noteLookups[m].wavTable != NULL) { + delete[] noteLookups[m].wavTable; + noteLookups[m].wavTable = NULL; + } + } + } + initialisedMasterTune = 0.0f; +} + +Tables::Tables() { + initialisedSampleRate = 0.0f; + initialisedMasterTune = 0.0f; + memset(¬eLookups, 0, sizeof(noteLookups)); +} + +bool Tables::init(Synth *synth, PCMWaveEntry *pcmWaves, float sampleRate, float masterTune) { + if (sampleRate <= 0.0f) { + synth->printDebug("Bad sampleRate (%d <= 0.0f)", sampleRate); + return false; + } + if (initialisedSampleRate == 0.0f) { + initMT32ConstantTables(synth); + } + if (initialisedSampleRate != sampleRate) { + initFiltCoeff(sampleRate); + initEnvelopes(sampleRate); + for (int key = 12; key <= 108; key++) { + initDep(&keyLookups[key - 12], (float)key); + } + } + if (initialisedSampleRate != sampleRate || initialisedMasterTune != masterTune) { + freeNotes(); + if (!initNotes(synth, pcmWaves, sampleRate, masterTune)) { + return false; + } + initialisedSampleRate = sampleRate; + initialisedMasterTune = masterTune; + } + return true; +} + +} diff --git a/engines/sci/sfx/softseq/mt32/tables.h b/engines/sci/sfx/softseq/mt32/tables.h new file mode 100644 index 0000000000..d9af5114b2 --- /dev/null +++ b/engines/sci/sfx/softseq/mt32/tables.h @@ -0,0 +1,116 @@ +/* Copyright (c) 2003-2005 Various contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef MT32EMU_TABLES_H +#define MT32EMU_TABLES_H + +namespace MT32Emu { + +// Mathematical constants +const double DOUBLE_PI = 3.1415926535897932384626433832795; +const double DOUBLE_LN = 2.3025850929940456840179914546844; +const float FLOAT_PI = 3.1415926535897932384626433832795f; +const float FLOAT_LN = 2.3025850929940456840179914546844f; + +// Filter settings +const int FILTERGRAN = 512; + +// Amplitude of waveform generator +// FIXME: This value is the amplitude possible whilst avoiding +// overdriven values immediately after filtering when playing +// back SQ3MT.MID. Needs to be checked. +const int WGAMP = 12382; + +const int MIDDLEC = 60; +const int MIDDLEA = 69; // By this I mean "A above middle C" + +// FIXME:KG: may only need to do 12 to 108 +// 12..108 is the range allowed by note on commands, but the key can be modified by pitch keyfollow +// and adjustment for timbre pitch, so the results can be outside that range. +// Should we move it (by octave) into the 12..108 range, or keep it in 0..127 range, +// or something else altogether? +const int LOWEST_NOTE = 12; +const int HIGHEST_NOTE = 127; +const int NUM_NOTES = HIGHEST_NOTE - LOWEST_NOTE + 1; // Number of slots for note LUT + +class Synth; + +struct NoteLookup { + Bit32u div2; + Bit32u *wavTable; + Bit32s sawTable[101]; + int filtTable[2][201]; + int nfiltTable[101][101]; + Bit16s *waveforms[3]; + Bit32u waveformSize[3]; +}; + +struct KeyLookup { + Bit32s envTimeMult[5]; // For envelope time adjustment for key pressed + Bit32s envDepthMult[5]; +}; + +class Tables { + float initialisedSampleRate; + float initialisedMasterTune; + void initMT32ConstantTables(Synth *synth); + static Bit16s clampWF(Synth *synth, const char *n, float ampVal, double input); + static File *initWave(Synth *synth, NoteLookup *noteLookup, float ampsize, float div2, File *file); + bool initNotes(Synth *synth, PCMWaveEntry *pcmWaves, float rate, float tuning); + void initEnvelopes(float sampleRate); + void initFiltCoeff(float samplerate); +public: + // Constant LUTs + Bit32s tvfKeyfollowMult[217]; + Bit32s tvfVelfollowMult[128][101]; + Bit32s tvfBiasMult[15][128]; + Bit32u tvaVelfollowMult[128][101]; + Bit32s tvaBiasMult[13][128]; + Bit16s noiseBuf[MAX_SAMPLE_OUTPUT]; + Bit16s sintable[65536]; + Bit32s pitchEnvVal[16][101]; + Bit32s envTimeVelfollowMult[5][128]; + Bit32s pwVelfollowAdd[15][128]; + float resonanceFactor[31]; + Bit32u lfoShift[101][101]; + Bit32s pwFactor[101]; + Bit32s volumeMult[101]; + + // LUTs varying with sample rate + Bit32u envTime[101]; + Bit32u envDeltaMaxTime[101]; + Bit32u envDecayTime[101]; + Bit32u lfoPeriod[101]; + float filtCoeff[FILTERGRAN][31][8]; + + // Various LUTs for each note and key + NoteLookup noteLookups[NUM_NOTES]; + KeyLookup keyLookups[97]; + + Tables(); + bool init(Synth *synth, PCMWaveEntry *pcmWaves, float sampleRate, float masterTune); + File *initNote(Synth *synth, NoteLookup *noteLookup, float note, float rate, float tuning, PCMWaveEntry *pcmWaves, File *file); + void freeNotes(); +}; + +} + +#endif diff --git a/engines/sci/sfx/softseq/opl2.c b/engines/sci/sfx/softseq/opl2.c new file mode 100644 index 0000000000..ed2f3001c8 --- /dev/null +++ b/engines/sci/sfx/softseq/opl2.c @@ -0,0 +1,718 @@ +/*************************************************************************** + opl2.c Copyright (C) 2002/04 Solomon Peachy, 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. + +***************************************************************************/ + +#include <resource.h> +#include <sfx_iterator.h> +#include "../softseq.h" +#include "../adlib.h" +#include <math.h> + +#include "fmopl.h" + +#ifdef _DREAMCAST +#define SAMPLE_RATE 22050 +#define CHANNELS SFX_PCM_MONO +#define STEREO 0 +#else +#define SAMPLE_RATE 44100 +#define CHANNELS SFX_PCM_STEREO_LR +#define STEREO 1 +#endif + + +/* local function declarations */ + +static void opl2_allstop(sfx_softseq_t *self); + +//#define DEBUG_ADLIB + +/* portions shamelessly lifted from claudio's XMP */ +/* other portions lifted from sound/opl3.c in the Linux kernel */ + +#define ADLIB_LEFT 0 +#define ADLIB_RIGHT 1 + +/* #define OPL_INTERNAL_FREQ 3600000 */ +#define OPL_INTERNAL_FREQ 3579545 + +static int ready = 0; +static int pcmout_stereo = STEREO; + +static int register_base[11] = { + 0x20, 0x23, 0x40, 0x43, + 0x60, 0x63, 0x80, 0x83, + 0xe0, 0xe3, 0xc0 +}; + +static int register_offset[12] = { + /* Channel 1 2 3 4 5 6 7 8 9 */ + /* Operator 1 */ 0x00, 0x01, 0x02, 0x08, 0x09, 0x0A, 0x10, 0x11, 0x12, 0x18, 0x19, 0x1A + +}; + +static int ym3812_note[13] = { + 0x157, 0x16b, 0x181, 0x198, 0x1b0, 0x1ca, + 0x1e5, 0x202, 0x220, 0x241, 0x263, 0x287, + 0x2ae +}; + +static guint8 sci_adlib_vol_base[16] = { + 0x00, 0x11, 0x15, 0x19, 0x1D, 0x22, 0x26, 0x2A, + 0x2E, 0x23, 0x37, 0x3B, 0x3F, 0x3F, 0x3F, 0x3F +}; +static guint8 sci_adlib_vol_tables[16][64]; + +/* ripped out of the linux kernel, of all places. */ +static gint8 fm_volume_table[128] = { + -64, -48, -40, -35, -32, -29, -27, -26, + -24, -23, -21, -20, -19, -18, -18, -17, + -16, -15, -15, -14, -13, -13, -12, -12, + -11, -11, -10, -10, -10, -9, -9, -8, + -8, -8, -7, -7, -7, -6, -6, -6, + -5, -5, -5, -5, -4, -4, -4, -4, + -3, -3, -3, -3, -2, -2, -2, -2, + -2, -1, -1, -1, -1, 0, 0, 0, + 0, 0, 0, 1, 1, 1, 1, 1, + 1, 2, 2, 2, 2, 2, 2, 2, + 3, 3, 3, 3, 3, 3, 3, 4, + 4, 4, 4, 4, 4, 4, 4, 5, + 5, 5, 5, 5, 5, 5, 5, 5, + 6, 6, 6, 6, 6, 6, 6, 6, + 6, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 8, 8, 8, 8, 8 +}; + +/* back to your regularly scheduled definitions */ + +static guint8 instr[MIDI_CHANNELS]; +static guint16 pitch[MIDI_CHANNELS]; +static guint8 vol[MIDI_CHANNELS]; +static guint8 pan[MIDI_CHANNELS]; +static int free_voices = ADLIB_VOICES; +static guint8 oper_note[ADLIB_VOICES]; +static guint8 oper_chn[ADLIB_VOICES]; + +static FM_OPL *ym3812_L = NULL; +static FM_OPL *ym3812_R = NULL; + +static guint8 adlib_reg_L[256]; +static guint8 adlib_reg_R[256]; +static guint8 adlib_master; + + +/* initialise note/operator lists, etc. */ +void adlibemu_init_lists(void) +{ + int i; + + int j; + + for (i = 0 ; i < 16 ; i++) { + for (j = 0; j < 64 ; j++) { + sci_adlib_vol_tables[i][j] = ((guint16)sci_adlib_vol_base[i]) * j / 63; + } + } + + for (i = 0; i < MIDI_CHANNELS ; i++) { + pitch[i] = 8192; /* center the pitch wheel */ + } + + free_voices = ADLIB_VOICES; + + memset(instr, 0, sizeof(instr)); + memset(vol, 0x7f, sizeof(vol)); + memset(pan, 0x3f, sizeof(pan)); + memset(adlib_reg_L, 0, sizeof(adlib_reg_L)); + memset(adlib_reg_R, 0, sizeof(adlib_reg_R)); + memset(oper_chn, 0xff, sizeof(oper_chn)); + memset(oper_note, 0xff, sizeof(oper_note)); + adlib_master=12; +} + +/* more shamelessly lifted from xmp and adplug. And altered. :) */ + +static inline int opl_write_L (int a, int v) +{ + adlib_reg_L[a] = v; + OPLWrite (ym3812_L, 0x388, a); + return OPLWrite (ym3812_L, 0x389, v); +} + +static inline int opl_write_R (int a, int v) +{ + adlib_reg_R[a] = v; + OPLWrite (ym3812_R, 0x388, a); + return OPLWrite (ym3812_R, 0x389, v); +} + +static inline int opl_write(int a, int v) +{ + opl_write_L(a,v); + return opl_write_R(a,v); +} + +/* +static inline guint8 opl_read (int a) +{ + OPLWrite (ym3812_L, 0x388, a); + return OPLRead (ym3812_L, 0x389); +} +*/ + +void synth_setpatch (int voice, guint8 *data) +{ + int i; + + opl_write(0xBD, 0); + + for (i = 0; i < 10; i++) + opl_write(register_base[i] + register_offset[voice], data[i]); + + opl_write(register_base[10] + voice, data[10]); + + /* mute voice after patch change */ + opl_write_L(0xb0 + voice, adlib_reg_L[0xb0+voice] & 0xdf); + opl_write_R(0xb0 + voice, adlib_reg_R[0xb0+voice] & 0xdf); + +#ifdef DEBUG_ADLIB + for (i = 0; i < 10; i++) + printf("%02x ", adlib_reg_L[register_base[i]+register_offset[voice]]); + printf("%02x ", adlib_reg_L[register_base[10]+voice]); +#endif + +} + +void synth_setvolume_L (int voice, int volume) +{ + gint8 level1, level2; + + level1 = ~adlib_reg_L[register_base[2]+register_offset[voice]] & 0x3f; + level2 = ~adlib_reg_L[register_base[3]+register_offset[voice]] & 0x3f; + + if (level1) { + level1 += sci_adlib_vol_tables[adlib_master][volume>>1]; + } + + if (level2) { + level2 += sci_adlib_vol_tables[adlib_master][volume>>1]; + } + + if (level1 > 0x3f) + level1 = 0x3f; + if (level1 < 0) + level1 = 0; + + if (level2 > 0x3f) + level2 = 0x3f; + if (level2 < 0) + level2 = 0; + + /* algorithm-dependent; we may need to set both operators. */ + if (adlib_reg_L[register_base[10]+voice] & 1) + opl_write_L(register_base[2]+register_offset[voice], + (guint8)((~level1 &0x3f) | + (adlib_reg_L[register_base[2]+register_offset[voice]]&0xc0))); + + opl_write_L(register_base[3]+register_offset[voice], + (guint8)((~level2 &0x3f) | + (adlib_reg_L[register_base[3]+register_offset[voice]]&0xc0))); + +} + +void synth_setvolume_R (int voice, int volume) +{ + gint8 level1, level2; + + level1 = ~adlib_reg_R[register_base[2]+register_offset[voice]] & 0x3f; + level2 = ~adlib_reg_R[register_base[3]+register_offset[voice]] & 0x3f; + + if (level1) { + level1 += sci_adlib_vol_tables[adlib_master][volume>>1]; + } + + if (level2) { + level2 += sci_adlib_vol_tables[adlib_master][volume>>1]; + } + + if (level1 > 0x3f) + level1 = 0x3f; + if (level1 < 0) + level1 = 0; + + if (level2 > 0x3f) + level2 = 0x3f; + if (level2 < 0) + level2 = 0; + + /* now for the other side. */ + if (adlib_reg_R[register_base[10]+voice] & 1) + opl_write_R(register_base[2]+register_offset[voice], + (guint8)((~level1 &0x3f) | + (adlib_reg_R[register_base[2]+register_offset[voice]]&0xc0))); + + opl_write_R(register_base[3]+register_offset[voice], + (guint8)((~level2 &0x3f) | + (adlib_reg_R[register_base[3]+register_offset[voice]]&0xc0))); + +} + +void synth_setnote (int voice, int note, int bend) +{ + int n, fre, oct; + float delta; + + delta = 0; + + n = note % 12; + + if (bend < 8192) + bend = 8192-bend; + delta = pow(2, (float) (bend%8192)/8192.0); + + if (bend > 8192) + fre = ym3812_note[n]*delta; else + fre = ym3812_note[n]/delta; + + oct = note / 12 - 1; + + if (oct < 0) + oct = 0; + + opl_write(0xa0 + voice, fre & 0xff); + opl_write(0xb0 + voice, + 0x20 | ((oct << 2) & 0x1c) | ((fre >> 8) & 0x03)); +#ifdef DEBUG_ADLIB + printf("-- %02x %02x\n", adlib_reg_L[0xa0+voice], adlib_reg_L[0xb0+voice]); +#endif + +} + + +/* back to your regularly scheduled driver */ + +int adlibemu_stop_note(int chn, int note, int velocity) +{ + int i, op=255; + + // sciprintf("Note off %d %d %d\n", chn, note, velocity); + + for (i=0;i<ADLIB_VOICES && op==255;i++) { + if (oper_chn[i] == chn) + if (oper_note[i] == note) + op=i; + } + + if (op==255) { +#ifdef DEBUG_ADLIB + printf ("ADLIB: can't stop note: C%02x N%02x V%02x\n", chn, note, velocity); + printf ("C "); + for (i = 0; i < ADLIB_VOICES ; i++ ) { + printf ("%02x ", oper_chn[i]); + } + printf ("\n"); + printf ("N "); + for (i = 0; i < ADLIB_VOICES ; i++ ) { + printf ("%02x ", oper_note[i]); + } + printf ("\n"); +#endif + return -1; /* that note isn't playing.. */ + } + + opl_write_L(0xb0+op,(adlib_reg_L[0xb0+op] & 0xdf)); + opl_write_R(0xb0+op,(adlib_reg_R[0xb0+op] & 0xdf)); + + oper_chn[op] = 255; + oper_note[op] = 255; + + free_voices++; + +#ifdef DEBUG_ADLIB + printf("stop voice %d (%d rem): C%02x N%02x V%02x\n", op, free_voices, chn, note, velocity); +#endif + + return 0; +} + +int adlibemu_start_note(int chn, int note, int velocity) +{ + int op, volume_L, volume_R, inst = 0; + + // sciprintf("Note on %d %d %d\n", chn, note, velocity); + + if (velocity == 0) { + return adlibemu_stop_note(chn, note, velocity); + } + + if (free_voices <= 0) { + printf("ADLIB: All voices full\n"); /* XXX implement overflow code */ + return -1; + } + + for (op = 0; op < ADLIB_VOICES ; op++) + if (oper_chn[op] == 255) + break; + + if (op == ADLIB_VOICES) { + printf("ADLIB: WTF? We couldn't find a voice yet it we have %d left.\n", free_voices); + return -1; + } + + /* Scale channel volume */ + volume_L = velocity * vol[chn] / 127; + volume_R = velocity * vol[chn] / 127; + + /* Apply a pan */ + if (pcmout_stereo) { + if (pan[chn] > 0x3f) /* pan right; so we scale the left down. */ + volume_L = volume_L / 0x3f * (0x3f - (pan[chn] - 0x3f)); + else if (pan[chn] < 0x3f) /* pan left; so we scale the right down.*/ + volume_R = volume_R / 0x3f * (0x3f - (0x3f-pan[chn])); + } + inst = instr[chn]; + + synth_setpatch(op, adlib_sbi[inst]); + synth_setvolume_L(op, volume_L); + synth_setvolume_R(op, volume_R); + synth_setnote(op, note, pitch[chn]); + + oper_chn[op] = chn; + oper_note[op] = note; + free_voices--; + +#ifdef DEBUG_ADLIB + printf("play voice %d (%d rem): C%02x N%02x V%02x/%02x-%02x P%02x (%02x/%02x)\n", op, free_voices, chn, note, velocity, volume_L, volume_R, inst, + adlib_reg_L[register_base[2]+register_offset[op]] & 0x3f, + adlib_reg_L[register_base[3]+register_offset[op]] & 0x3f); +#endif + + return 0; +} + +static +void adlibemu_update_pitch(int chn, int note, int newpitch) +{ + int i; + int matched = 0; + + pitch[chn] = newpitch; + + for (i=0;i<ADLIB_VOICES;i++) { + if (oper_chn[i] == chn) + { + matched++; + synth_setnote(i, oper_note[i], newpitch); + } + } + +// printf("Matched %d notes on channel %d.\n", matched, chn); +} + +void test_adlib (void) { + + int voice = 0; +#if 0 + guint8 data[] = { 0x25, 0x21, 0x48, 0x48, 0xf0, 0xf2, 0xf0, 0xa5, 0x00, 0x00, 0x06 }; +#else + guint8 *data = adlib_sbi[0x0a]; +#endif + +#if 1 + opl_write(register_base[0]+register_offset[voice], data[0]); + opl_write(register_base[1]+register_offset[voice], data[1]); + opl_write(register_base[2]+register_offset[voice], data[2]); + opl_write(register_base[3]+register_offset[voice], data[3]); + opl_write(register_base[4]+register_offset[voice], data[4]); + opl_write(register_base[5]+register_offset[voice], data[5]); + opl_write(register_base[6]+register_offset[voice], data[6]); + opl_write(register_base[7]+register_offset[voice], data[7]); + opl_write(register_base[8]+register_offset[voice], data[8]); + opl_write(register_base[9]+register_offset[voice], data[9]); + opl_write(register_base[10]+register_offset[voice], data[10]); +#else + synth_setpatch(voice, data); +#endif + +#if 0 + opl_write(0xA0 + voice, 0x57); + opl_write(0xB0 + voice, 0x2d); +#else + synth_setvolume_L(voice, 0x50); + synth_setvolume_R(voice, 0x50); + synth_setnote(voice, 0x30, 0); +#endif + + /* + instr[0x0e] = 0x0a; + instr[0x03] = 0x26; + + adlibemu_start_note(0x0e, 0x30, 0x40); + sleep(1); + adlibemu_start_note(0x03, 0x48, 0x40); + sleep(1); + */ +} + + +/* count is # of FRAMES, not bytes. + We assume 16-bit stereo frames (ie 4 bytes) +*/ +static void +opl2_poll (sfx_softseq_t *self, byte *dest, int count) +{ + gint16 *buffer = (gint16 *) dest; + gint16 *ptr = buffer; + + if (!ready) { + fprintf(stderr, "synth_mixer(): !ready \n"); + exit(1); + } + + if (!buffer) { + fprintf(stderr, "synth_mixer(): !buffer \n"); + exit(1); + } + +#if 0 + { + static unsigned long remaining_delta = 0; + int samples; + int remaining = count; + + while (remaining > 0) { + samples = remaining_delta * pcmout_sample_rate / 1000000; + samples = sci_min(samples, remaining); + if (samples) { + YM3812UpdateOne(ADLIB_LEFT, ptr, samples, 1); + YM3812UpdateOne(ADLIB_RIGHT, ptr+1, samples, 1); + } + if (remaining > samples) { + remaining_delta = (remaining - samples) * 1000000 / pcmout_sample_rate; + } else { + song->play_next_note(); + remaining_delta = song->get_next_delta(); + song->advance(); + } + remaining -= samples; + } + } +#endif + + if (pcmout_stereo) { + YM3812UpdateOne (ym3812_L, ptr, count, 1); + YM3812UpdateOne (ym3812_R, ptr+1, count, 1); + } else { + YM3812UpdateOne (ym3812_L, ptr, count, 0); + } +} + +static int +opl2_init(sfx_softseq_t *self, byte *data_ptr, int data_length, byte *data2_ptr, + int data2_length) +{ + int i; + + /* load up the patch.003 file, parse out the instruments */ + if (data_length < 1344) { + sciprintf ("[sfx:seq:opl2] Invalid patch.003: Expected %d, got %d\n", 1344, data_length); + return -1; + } + + for (i = 0; i < 48; i++) + make_sbi((adlib_def *)(data_ptr+(28 * i)), adlib_sbi[i]); + + if (data_length > 1344) + for (i = 48; i < 96; i++) + make_sbi((adlib_def *)(data_ptr+2+(28 * i)), adlib_sbi[i]); + + OPLBuildTables(FMOPL_ENV_BITS_HQ, FMOPL_EG_ENT_HQ); + + if (!(ym3812_L = OPLCreate (OPL_TYPE_YM3812, OPL_INTERNAL_FREQ, SAMPLE_RATE)) || + !(ym3812_R = OPLCreate (OPL_TYPE_YM3812, OPL_INTERNAL_FREQ, SAMPLE_RATE))) { + sciprintf ("[sfx:seq:opl2] Failure: Emulator init failed!\n"); + return SFX_ERROR; + } + + ready = 1; + + opl2_allstop(self); + return SFX_OK; +} + + +static void +opl2_exit(sfx_softseq_t *self) +{ + FM_OPL *opl = ym3812_L; + ym3812_L = NULL; + OPLDestroy(opl); + opl = ym3812_R; + ym3812_R = NULL; + OPLDestroy(opl); + + // XXX deregister with pcm layer. +} + +static void +opl2_allstop(sfx_softseq_t *self) +{ + // printf("AdlibEmu: Reset\n"); + if (! ready) + return; + + adlibemu_init_lists(); + + OPLResetChip (ym3812_L); + OPLResetChip (ym3812_R); + + opl_write(0x01, 0x20); + opl_write(0xBD, 0xc0); + +#ifdef DEBUG_ADLIB + printf("ADLIB: reset complete\n"); +#endif + // test_adlib(); +} + +int midi_adlibemu_reverb(short param) +{ + printf("ADLIB: reverb NYI %04x \n", param); + return 0; +} + +int midi_adlibemu_event(guint8 command, guint8 note, guint8 velocity, guint32 delta) +{ + guint8 channel, oper; + + channel = command & 0x0f; + oper = command & 0xf0; + + switch (oper) { + case 0x80: + return adlibemu_stop_note(channel, note, velocity); + case 0x90: /* noteon and noteoff */ + return adlibemu_start_note(channel, note, velocity); + case 0xe0: /* Pitch bend */ + { + int bend = (note & 0x7f) | ((velocity & 0x7f) << 7); +// printf("Bend to %d\n", bend); + adlibemu_update_pitch(channel, note, bend); + } + case 0xb0: /* CC changes. */ + switch (note) { + case 0x07: + vol[channel] = velocity; + break; + case 0x0a: + pan[channel] = velocity; + break; + case 0x4b: + break; + case 0x7b: { /* all notes off */ + int i = 0; + for (i=0;i<ADLIB_VOICES;i++) + if (oper_chn[i] == channel) + adlibemu_stop_note(channel, oper_note[i], 0); + break; + } + default: + ; /* XXXX ignore everything else for now */ + } + return 0; + case 0xd0: /* aftertouch */ + /* XXX Aftertouch in the OPL thing? */ + return 0; + default: + printf("ADLIB: Unknown event %02x\n", command); + return 0; + } + + return 0; +} + +int midi_adlibemu_event2(guint8 command, guint8 param, guint32 delta) +{ + guint8 channel; + guint8 oper; + + channel = command & 0x0f; + oper = command & 0xf0; + switch (oper) { + case 0xc0: /* change instrument */ +#ifdef DEBUG_ADLIB + printf("ADLIB: Selecting instrument %d on channel %d\n", param, channel); +#endif + instr[channel] = param; + return 0; + default: + printf("ADLIB: Unknown event %02x\n", command); + } + + return 0; +} + +static void +opl2_volume(sfx_softseq_t *self, int volume) +{ + guint8 i; + + i = (guint8)volume * 15 / 100; + + adlib_master=i; + +#ifdef DEBUG_ADLIB + printf("ADLIB: master volume set to %d\n", adlib_master); +#endif +} + +int +opl2_set_option(sfx_softseq_t *self, char *name, char *value) +{ + return SFX_ERROR; +} + +void +opl2_event(sfx_softseq_t *self, byte cmd, int argc, byte *argv) +{ + if (argc == 1) + midi_adlibemu_event2(cmd, argv[0], 0); + else if (argc == 2) + midi_adlibemu_event(cmd, argv[0], argv[1], 0); +} + +/* the driver struct */ + +sfx_softseq_t sfx_softseq_opl2 = { + "adlibemu", + "0.1", + opl2_set_option, + opl2_init, + opl2_exit, + opl2_volume, + opl2_event, + opl2_poll, + opl2_allstop, + NULL, + 3, /* use patch.003 */ + SFX_SEQ_PATCHFILE_NONE, + 0x4, /* Play flags */ + 0, /* No rhythm channel (9) */ + ADLIB_VOICES, /* # of voices */ + {SAMPLE_RATE, CHANNELS, SFX_PCM_FORMAT_S16_NATIVE} +}; diff --git a/engines/sci/sfx/softseq/pcspeaker.c b/engines/sci/sfx/softseq/pcspeaker.c new file mode 100644 index 0000000000..771c8f0da5 --- /dev/null +++ b/engines/sci/sfx/softseq/pcspeaker.c @@ -0,0 +1,184 @@ +/*************************************************************************** + soft-pcspeaker.c Copyright (C) 2004 Christoph Reichenbach + + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public Licence as + published by the Free Software Foundaton; either version 2 of the + Licence, or (at your option) any later version. + + It is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + merchantibility or fitness for a particular purpose. See the + GNU General Public Licence for more details. + + You should have received a copy of the GNU General Public Licence + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + + Please contact the maintainer for any program-related bug reports or + inquiries. + + Current Maintainer: + + Christoph Reichenbach (CR) <jameson@linuxgames.com> + +***************************************************************************/ +/* PC speaker software sequencer for FreeSCI */ + +#include "../softseq.h" +#include <sci_midi.h> + +#define FREQUENCY 94020 + +static int volume = 0x0600; +static int note = 0; /* Current halftone, or 0 if off */ +static int freq_count = 0; + +extern sfx_softseq_t sfx_softseq_pcspeaker; +/* Forward-declare the sequencer we are defining here */ + + +static int +sps_set_option(sfx_softseq_t *self, char *name, char *value) +{ + return SFX_ERROR; +} + +static int +sps_init(sfx_softseq_t *self, byte *patch, int patch_len, byte *patch2, + int patch2_len) +{ + return SFX_OK; +} + +static void +sps_exit(sfx_softseq_t *self) +{ +} + +static void +sps_event(sfx_softseq_t *self, byte command, int argc, byte *argv) +{ +#if 0 + fprintf(stderr, "Note [%02x : %02x %02x]\n", command, argc?argv[0] : 0, (argc > 1)? argv[1] : 0); +#endif + + switch (command & 0xf0) { + + case 0x80: + if (argv[0] == note) + note = 0; + break; + + case 0x90: + if (!argv[1]) { + if (argv[0] == note) + note = 0; + } else + note = argv[0]; + /* Ignore velocity otherwise; we just use the global one */ + break; + + case 0xb0: + if (argv[1] == SCI_MIDI_CHANNEL_NOTES_OFF) + note = 0; + break; + + default: +#if DEBUG + fprintf(stderr, "[SFX:PCM-PC] Unused MIDI command %02x %02x %02x\n", command, argc?argv[0] : 0, (argc > 1)? argv[1] : 0); +#endif + break; /* ignore */ + } +} + +#define BASE_NOTE 129 /* A10 */ +#define BASE_OCTAVE 10 /* A10, as I said */ + +static int +freq_table[12] = { /* A4 is 440Hz, halftone map is x |-> ** 2^(x/12) */ + 28160, /* A10 */ + 29834, + 31608, + 33488, + 35479, + 37589, + 39824, + 42192, + 44701, + 47359, + 50175, + 53159 +}; + + +void +sps_poll(sfx_softseq_t *self, byte *dest, int len) +{ + int halftone_delta = note - BASE_NOTE; + int oct_diff = ((halftone_delta + BASE_OCTAVE * 12) / 12) - BASE_OCTAVE; + int halftone_index = (halftone_delta + (12*100)) % 12 ; + int freq = (!note)? 0 : freq_table[halftone_index] / (1 << (-oct_diff)); + gint16 *buf = (gint16 *) dest; + + int i; + for (i = 0; i < len; i++) { + if (note) { + freq_count += freq; + while (freq_count >= (FREQUENCY << 1)) + freq_count -= (FREQUENCY << 1); + + if (freq_count - freq < 0) { + /* Unclean rising edge */ + int l = volume << 1; + buf[i] = -volume + (l*freq_count)/freq; + } else if (freq_count >= FREQUENCY + && freq_count - freq < FREQUENCY) { + /* Unclean falling edge */ + int l = volume << 1; + buf[i] = volume - (l*(freq_count - FREQUENCY))/freq; + } else { + if (freq_count < FREQUENCY) + buf[i] = volume; + else + buf[i] = -volume; + } + } else + buf[i] = 0; + } + +} + +void +sps_volume(sfx_softseq_t *self, int new_volume) +{ + volume = new_volume << 4; +} + +void +sps_allstop(sfx_softseq_t *self) +{ + note = 0; +} + +sfx_softseq_t sfx_softseq_pcspeaker = { + "pc-speaker", + "0.3", + sps_set_option, + sps_init, + sps_exit, + sps_volume, + sps_event, + sps_poll, + sps_allstop, + NULL, + SFX_SEQ_PATCHFILE_NONE, + SFX_SEQ_PATCHFILE_NONE, + 0x20, /* PC speaker channel only */ + 0, /* No rhythm channel */ + 1, /* # of voices */ + {FREQUENCY, SFX_PCM_MONO, SFX_PCM_FORMAT_S16_NATIVE} +}; diff --git a/engines/sci/sfx/softseq/softsequencers.c b/engines/sci/sfx/softseq/softsequencers.c new file mode 100644 index 0000000000..d0f544190e --- /dev/null +++ b/engines/sci/sfx/softseq/softsequencers.c @@ -0,0 +1,71 @@ +/*************************************************************************** + softsequencers.c Copyright (C) 2004 Christoph Reichenbach + + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public Licence as + published by the Free Software Foundaton; either version 2 of the + Licence, or (at your option) any later version. + + It is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + merchantibility or fitness for a particular purpose. See the + GNU General Public Licence for more details. + + You should have received a copy of the GNU General Public Licence + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + + Please contact the maintainer for any program-related bug reports or + inquiries. + + Current Maintainer: + + Christoph Reichenbach (CR) <jameson@linuxgames.com> + +***************************************************************************/ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include "../softseq.h" + +extern sfx_softseq_t sfx_softseq_opl2; +extern sfx_softseq_t sfx_softseq_SN76496; +extern sfx_softseq_t sfx_softseq_pcspeaker; +extern sfx_softseq_t sfx_softseq_amiga; +extern sfx_softseq_t sfx_softseq_mt32; +extern sfx_softseq_t sfx_softseq_fluidsynth; + +static sfx_softseq_t *sw_sequencers[] = { + &sfx_softseq_opl2, +/* &sfx_softseq_mt32, */ + &sfx_softseq_SN76496, + &sfx_softseq_pcspeaker, + &sfx_softseq_amiga, +#ifdef HAVE_FLUIDSYNTH_H + &sfx_softseq_fluidsynth, +#endif + NULL +}; + + +sfx_softseq_t * +sfx_find_softseq(char *name) +{ + if (!name) + return sw_sequencers[0]; + else { + int i = 0; + while (sw_sequencers[i]) + if (!strcmp(sw_sequencers[i]->name, name)) + return sw_sequencers[i]; + else + ++i; + + return NULL; /* Not found */ + } +} diff --git a/engines/sci/sfx/songlib.c b/engines/sci/sfx/songlib.c new file mode 100644 index 0000000000..36287845f0 --- /dev/null +++ b/engines/sci/sfx/songlib.c @@ -0,0 +1,282 @@ +/*************************************************************************** + songlib.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 (CJR) <reichenb@colorado.edu> + +***************************************************************************/ + +#include <stdio.h> +#include <sfx_engine.h> +#include <sci_memory.h> + +#define debug_stream stderr + +GTimeVal +song_sleep_time(GTimeVal *lastslept, long ticks) +{ + GTimeVal tv; + long timetosleep; + long timeslept; /* Time already slept */ + timetosleep = ticks * SOUND_TICK; /* Time to sleep in us */ + + sci_get_current_time(&tv); + timeslept = 1000000 * (tv.tv_sec - lastslept->tv_sec) + + tv.tv_usec - lastslept->tv_usec; + + timetosleep -= timeslept; + + if (timetosleep < 0) + timetosleep = 0; + + tv.tv_sec = timetosleep / 1000000; + tv.tv_usec = timetosleep % 1000000; + + return tv; +} + + +GTimeVal +song_next_wakeup_time(GTimeVal *lastslept, long ticks) +{ + GTimeVal retval; + + retval.tv_sec = lastslept->tv_sec + (ticks / 60); + retval.tv_usec = lastslept->tv_usec + ((ticks % 60) * SOUND_TICK); + + if (retval.tv_usec >= 1000000) { + retval.tv_usec -= 1000000; + ++retval.tv_sec; + } + + return retval; +} + + +song_t * +song_new(song_handle_t handle, song_iterator_t *it, int priority) +{ + song_t *retval; + retval = (song_t*) sci_malloc(sizeof(song_t)); + +#ifdef SATISFY_PURIFY + memset(retval, 0, sizeof(song_t)); +#endif + + retval->handle = handle; + retval->priority = priority; + retval->next = NULL; + retval->delay = 0; + retval->it = it; + retval->status = SOUND_STATUS_STOPPED; + retval->next_playing = NULL; + retval->next_stopping = NULL; + retval->restore_behavior = RESTORE_BEHAVIOR_CONTINUE; + retval->restore_time = 0; + + return retval; +} + + +void +song_lib_add(songlib_t songlib, song_t *song) +{ + song_t **seeker = NULL; + int pri = song->priority; + + if (NULL == song) { + sciprintf("song_lib_add(): NULL passed for song\n"); + return; + } + + if (*(songlib.lib) == NULL) { + *(songlib.lib) = song; + song->next = NULL; + + return; + } + + seeker = (songlib.lib); + while (*seeker && ((*seeker)->priority > pri)) + seeker = &((*seeker)->next); + + song->next = *seeker; + *seeker = song; +} + +static void /* Recursively free a chain of songs */ +_songfree_chain(song_t *song) +{ + if (song) { + _songfree_chain(song->next); + songit_free(song->it); + song->it = NULL; + free(song); + } +} + + +void +song_lib_init(songlib_t *songlib) +{ + songlib->lib = &(songlib->_s); + songlib->_s = NULL; +} + +void +song_lib_free(songlib_t songlib) +{ + _songfree_chain(*(songlib.lib)); + *(songlib.lib) = NULL; +} + + +song_t * +song_lib_find(songlib_t songlib, song_handle_t handle) +{ + song_t *seeker = *(songlib.lib); + + while (seeker) { + if (seeker->handle == handle) + break; + seeker = seeker->next; + } + + return seeker; +} + + +song_t * +song_lib_find_next_active(songlib_t songlib, song_t *other) +{ + song_t *seeker = other? other->next : *(songlib.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_t * +song_lib_find_active(songlib_t songlib) +{ + return song_lib_find_next_active(songlib, NULL); +} + +int +song_lib_remove(songlib_t songlib, song_handle_t handle) +{ + int retval; + song_t *goner = *(songlib.lib); + + if (!goner) + return -1; + + if (goner->handle == handle) + *(songlib.lib) = goner->next; + + else { + while ((goner->next) && (goner->next->handle != handle)) + goner = goner->next; + + if (goner->next) { /* Found him? */ + song_t *oldnext = goner->next; + + goner->next = goner->next->next; + goner = oldnext; + } else return -1; /* No. */ + } + + retval = goner->status; + + songit_free(goner->it); + free(goner); + + return retval; +} + +void +song_lib_resort(songlib_t songlib, song_t *song) +{ + if (*(songlib.lib) == song) + *(songlib.lib) = song->next; + else { + song_t *seeker = *(songlib.lib); + + while (seeker->next && (seeker->next != song)) + seeker = seeker->next; + + if (seeker->next) + seeker->next = seeker->next->next; + } + + song_lib_add(songlib, song); +} + +int +song_lib_count(songlib_t songlib) +{ + song_t *seeker = *(songlib.lib); + int retval = 0; + + while (seeker) { + retval++; + seeker = seeker->next; + } + + return retval; +} + +void +song_lib_set_restore_behavior(songlib_t songlib, song_handle_t handle, RESTORE_BEHAVIOR action) +{ + song_t *seeker = song_lib_find(songlib, handle); + + seeker->restore_behavior = action; +} + +void +song_lib_dump(songlib_t songlib, int line) +{ + song_t *seeker = *(songlib.lib); + + fprintf(debug_stream,"L%d:", line); + do { + fprintf(debug_stream," %p", (void *)seeker); + + if (seeker) { + fprintf(debug_stream,"[%04lx,p=%d,s=%d]->", seeker->handle, seeker->priority,seeker->status); + seeker = seeker->next; + } + fprintf(debug_stream,"\n"); + } while (seeker); + fprintf(debug_stream,"\n"); + +} + diff --git a/engines/sci/sfx/test-iterator.c b/engines/sci/sfx/test-iterator.c new file mode 100644 index 0000000000..1ecce673f9 --- /dev/null +++ b/engines/sci/sfx/test-iterator.c @@ -0,0 +1,450 @@ +/*************************************************************************** + Copyright (C) 2008 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 merchantability, + 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> + +***************************************************************************/ + +#include <sfx_iterator.h> +#include "sfx_iterator_internal.h" +#include <stdarg.h> +#include <stdio.h> + +#define ASSERT_S(x) if (!(x)) { error("Failed assertion in L%d: " #x "\n", __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_it_struct { + INHERITS_SONG_ITERATOR; + int lifetime_remaining; + char *cues; + int cue_counter; + int cue_progress; + int cues_nr; +} simple_iterator; + +int +simple_it_next(song_iterator_t *_self, unsigned char *buf, int *result) +{ + struct simple_it_struct *self = (struct simple_it_struct *) _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; + } +} + +sfx_pcm_feed_t * +simple_it_pcm_feed(song_iterator_t *_self) +{ + error("No PCM feed!\n"); + return NULL; +} + +void +simple_it_init(song_iterator_t *_self) +{ +} + +song_iterator_t * +simple_it_handle_message(song_iterator_t *_self, song_iterator_message_t msg) +{ + return NULL; +} + +void +simple_it_cleanup(song_iterator_t *_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. */ +song_iterator_t * +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 (song_iterator_t *) &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() +{ + song_iterator_t *it; + song_iterator_t *simple_it = (song_iterator_t *) &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; +// fprintf(stderr, "XXX => %d\n", 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() +{ + song_iterator_t *it; + song_iterator_t *simple_it = (song_iterator_t *) &simple_iterator; + song_iterator_t *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() +{ + song_iterator_t *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() +{ + song_iterator_t *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() +{ + song_iterator_t *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) + fprintf(stderr, "[ERROR] %d errors total.\n", errors); + return (errors != 0); +} diff --git a/engines/sci/sfx/tests/stubs.c b/engines/sci/sfx/tests/stubs.c new file mode 100644 index 0000000000..06da6f9419 --- /dev/null +++ b/engines/sci/sfx/tests/stubs.c @@ -0,0 +1,9 @@ +int sciprintf(char *fmt,...) +{ + return 0; +} + +int sfx_pcm_available() +{ + return 1; +}
\ No newline at end of file diff --git a/engines/sci/sfx/tests/tests.cpp b/engines/sci/sfx/tests/tests.cpp new file mode 100644 index 0000000000..10eacc0de2 --- /dev/null +++ b/engines/sci/sfx/tests/tests.cpp @@ -0,0 +1,172 @@ +#include <stdio.h> +#include <sfx_iterator.h> + +#define TESTEQUAL(x, y) if(x != y) printf("test failure: expected %04x, got %04x @ file %s line %d \n", x, y, __FILE__, __LINE__); + +static char calledDeathListenerCallback; + +static unsigned char song[] = { +// PCM not present +0, +// channel defs +0, 0x20, 0, 0x21, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, +// note on, wait, note off +0xAA, +0x90, 0xAA, 0xAA, +0xAA, +0x80, 0xAA, 0xAA, +0xAA, +0x91, 0xAA, 0xAA, +0xAA, +0x81, 0xAA, 0xAA, +0xAA, +// end track +0xFC}; + +#define SONG_CMD_COUNT 10 + +#define TEST_SETUP() \ + unsigned char cmds[4] = {0}; \ + int result = 0; \ + int message; \ + int i; \ +\ + song_iterator_t *it = songit_new(song, sizeof(song), 0, 0); \ + it->init(it); \ + SIMSG_SEND(it, SIMSG_SET_PLAYMASK(0x20));\ + calledDeathListenerCallback = 0; + +#define TEST_TEARDOWN() \ + songit_free(it); + + +void testFinishSong() +{ + TEST_SETUP(); + message = songit_next(&it, &cmds, &result, IT_READER_MASK_ALL); + TESTEQUAL(0xAA, message); + + message = songit_next(&it, &cmds, &result, IT_READER_MASK_ALL); + TESTEQUAL(0, message); + TESTEQUAL(3, result); + + for (i=0; i < SONG_CMD_COUNT - 2; i++) + { + message = songit_next(&it, &cmds, &result, IT_READER_MASK_ALL); + } + + TESTEQUAL(SI_FINISHED, message); + + TEST_TEARDOWN(); +} + + +void DeathListenerCallback(void *v1, void *v2) +{ + calledDeathListenerCallback++; + return; +} + +void testDeathListener() +{ + TEST_SETUP(); + + song_iterator_add_death_listener( + it, + it, + (void (*)(void *, void*))DeathListenerCallback); + + for (i=0; i < SONG_CMD_COUNT; i++) + { + message = songit_next(&it, &cmds, &result, IT_READER_MASK_ALL); + } + + TESTEQUAL(SI_FINISHED, message); + + TEST_TEARDOWN(); + + TESTEQUAL(1, calledDeathListenerCallback); +} + +void testMultipleDeathListeners() +{ + TEST_SETUP(); + + song_iterator_add_death_listener( + it, + it, + (void (*)(void *, void*))DeathListenerCallback); + + song_iterator_add_death_listener( + it, + it, + (void (*)(void *, void*))DeathListenerCallback); + + for (i=0; i < SONG_CMD_COUNT; i++) + { + message = songit_next(&it, &cmds, &result, IT_READER_MASK_ALL); + } + + TESTEQUAL(SI_FINISHED, message); + + TEST_TEARDOWN(); + + TESTEQUAL(2, calledDeathListenerCallback); +} + +void testStopSong() +{ + TEST_SETUP(); + SIMSG_SEND(it, SIMSG_STOP); + + message = songit_next(&it, &cmds, &result, IT_READER_MASK_ALL); + TESTEQUAL(SI_FINISHED, message); + + TEST_TEARDOWN(); +} + +void testStopLoopedSong() +{ + TEST_SETUP(); + + SIMSG_SEND(it, SIMSG_SET_LOOPS(3)); + message = songit_next(&it, &cmds, &result, IT_READER_MASK_ALL); + TESTEQUAL(0xAA, message); + + SIMSG_SEND(it, SIMSG_STOP); + message = songit_next(&it, &cmds, &result, IT_READER_MASK_ALL); + TESTEQUAL(SI_FINISHED, message); + + TEST_TEARDOWN(); +} + +void testChangeSongMask() +{ + TEST_SETUP(); + + message = songit_next(&it, &cmds, &result, IT_READER_MASK_ALL); + TESTEQUAL(0xAA, message); + + SIMSG_SEND(it, SIMSG_SET_PLAYMASK(0x40)); + message = songit_next(&it, &cmds, &result, IT_READER_MASK_ALL); + TESTEQUAL(0, message); + TESTEQUAL(0, result); + + TEST_TEARDOWN(); +} + + +int main(int argc, char* argv[]) +{ + testFinishSong(); + testDeathListener(); + testMultipleDeathListeners(); + testStopSong(); + testStopLoopedSong(); + testChangeSongMask(); + return 0; +} + diff --git a/engines/sci/sfx/tests/tests.vcproj b/engines/sci/sfx/tests/tests.vcproj new file mode 100644 index 0000000000..a50c794bce --- /dev/null +++ b/engines/sci/sfx/tests/tests.vcproj @@ -0,0 +1,158 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="7.10" + Name="tests" + ProjectGUID="{3C6F6B16-D092-495C-B611-03FFDBFF27FC}" + Keyword="Win32Proj"> + <Platforms> + <Platform + Name="Win32"/> + </Platforms> + <Configurations> + <Configuration + Name="Debug|Win32" + OutputDirectory="Debug" + IntermediateDirectory="Debug" + ConfigurationType="1" + CharacterSet="2"> + <Tool + Name="VCCLCompilerTool" + Optimization="0" + AdditionalIncludeDirectories="..\..\include;..\..\include\win32" + PreprocessorDefinitions="PACKAGE=\"freesci\";HAVE_GETOPT_H;HAVE_LIBPNG;VERSION=__TIMESTAMP__;_DEBUG;WIN32;_CONSOLE;HAVE_STRING_H;HAVE_MEMCHR" + MinimalRebuild="TRUE" + BasicRuntimeChecks="3" + RuntimeLibrary="5" + UsePrecompiledHeader="0" + WarningLevel="3" + Detect64BitPortabilityProblems="TRUE" + DebugInformationFormat="4" + CompileAs="1"/> + <Tool + Name="VCCustomBuildTool"/> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="winmm.lib" + OutputFile="$(OutDir)/tests.exe" + LinkIncremental="2" + GenerateDebugInformation="TRUE" + ProgramDatabaseFile="$(OutDir)/tests.pdb" + SubSystem="1" + TargetMachine="1"/> + <Tool + Name="VCMIDLTool"/> + <Tool + Name="VCPostBuildEventTool"/> + <Tool + Name="VCPreBuildEventTool"/> + <Tool + Name="VCPreLinkEventTool"/> + <Tool + Name="VCResourceCompilerTool"/> + <Tool + Name="VCWebServiceProxyGeneratorTool"/> + <Tool + Name="VCXMLDataGeneratorTool"/> + <Tool + Name="VCWebDeploymentTool"/> + <Tool + Name="VCManagedWrapperGeneratorTool"/> + <Tool + Name="VCAuxiliaryManagedWrapperGeneratorTool"/> + </Configuration> + <Configuration + Name="Release|Win32" + OutputDirectory="Release" + IntermediateDirectory="Release" + ConfigurationType="1" + CharacterSet="2"> + <Tool + Name="VCCLCompilerTool" + AdditionalIncludeDirectories="..\..\include" + PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE" + RuntimeLibrary="4" + UsePrecompiledHeader="0" + WarningLevel="3" + Detect64BitPortabilityProblems="TRUE" + DebugInformationFormat="3"/> + <Tool + Name="VCCustomBuildTool"/> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="winmm.lib" + OutputFile="$(OutDir)/tests.exe" + LinkIncremental="1" + GenerateDebugInformation="TRUE" + SubSystem="1" + OptimizeReferences="2" + EnableCOMDATFolding="2" + TargetMachine="1"/> + <Tool + Name="VCMIDLTool"/> + <Tool + Name="VCPostBuildEventTool"/> + <Tool + Name="VCPreBuildEventTool"/> + <Tool + Name="VCPreLinkEventTool"/> + <Tool + Name="VCResourceCompilerTool"/> + <Tool + Name="VCWebServiceProxyGeneratorTool"/> + <Tool + Name="VCXMLDataGeneratorTool"/> + <Tool + Name="VCWebDeploymentTool"/> + <Tool + Name="VCManagedWrapperGeneratorTool"/> + <Tool + Name="VCAuxiliaryManagedWrapperGeneratorTool"/> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <Filter + Name="Source Files" + Filter="cpp;c;cxx;def;odl;idl;hpj;bat;asm;asmx" + UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"> + <File + RelativePath="..\..\sfx\iterator.c"> + </File> + <File + RelativePath="..\..\sfx\pcm-iterator.c"> + </File> + <File + RelativePath="..\..\scicore\sci_memory.c"> + </File> + <File + RelativePath=".\stubs.c"> + </File> + <File + RelativePath=".\tests.cpp"> + </File> + <File + RelativePath="..\..\scicore\tools.c"> + </File> + </Filter> + <Filter + Name="Header Files" + Filter="h;hpp;hxx;hm;inl;inc;xsd" + UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"> + <File + RelativePath="..\..\include\win32\sci_win32.h"> + </File> + </Filter> + <Filter + Name="Resource Files" + Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx" + UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}"> + </Filter> + <File + RelativePath=".\ReadMe.txt"> + </File> + </Files> + <Globals> + </Globals> +</VisualStudioProject> diff --git a/engines/sci/sfx/time.c b/engines/sci/sfx/time.c new file mode 100644 index 0000000000..553eb406f8 --- /dev/null +++ b/engines/sci/sfx/time.c @@ -0,0 +1,131 @@ +/*************************************************************************** + time.c Copyright (C) 2003 Christoph Reichenbach + + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public Licence as + published by the Free Software Foundaton; either version 2 of the + Licence, or (at your option) any later version. + + It is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + merchantibility or fitness for a particular purpose. See the + GNU General Public Licence for more details. + + You should have received a copy of the GNU General Public Licence + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + + Please contact the maintainer for any program-related bug reports or + inquiries. + + Current Maintainer: + + Christoph Reichenbach (CR) <jameson@linuxgames.com> + +***************************************************************************/ + +#include <sfx_time.h> +#include <resource.h> + +sfx_timestamp_t +sfx_new_timestamp(long secs, long usecs, int frame_rate) +{ + sfx_timestamp_t r; + r.secs = secs; + r.usecs = usecs; + r.frame_rate = frame_rate; + r.frame_offset = 0; + + return r; +} + + +sfx_timestamp_t +sfx_timestamp_add(sfx_timestamp_t timestamp, int frames) +{ + timestamp.frame_offset += frames; + + if (timestamp.frame_offset < 0) { + int secsub = 1 + (-timestamp.frame_offset / timestamp.frame_rate); + + timestamp.frame_offset += timestamp.frame_rate * secsub; + timestamp.secs -= secsub; + } + + timestamp.secs += (timestamp.frame_offset / timestamp.frame_rate); + timestamp.frame_offset %= timestamp.frame_rate; + + return timestamp; +} + +int +sfx_timestamp_frame_diff(sfx_timestamp_t a, sfx_timestamp_t b) +{ + long usecdelta = 0; + + if (a.frame_rate != b.frame_rate) { + fprintf(stderr, "Fatal: The semantics of subtracting two timestamps with a different base from each other is not defined!\n"); + BREAKPOINT(); + } + + if (a.usecs != b.usecs) { +#if (SIZEOF_LONG >= 8) + usecdelta = (a.usecs * a.frame_rate) / 1000000 + - (b.usecs * b.frame_rate) / 1000000; +#else + usecdelta = ((a.usecs/1000) * a.frame_rate) / 1000 + - ((b.usecs/1000) * b.frame_rate) / 1000; +#endif + } + + return usecdelta + + (a.secs - b.secs) * a.frame_rate + + a.frame_offset - b.frame_offset; +} + +long +sfx_timestamp_usecs_diff(sfx_timestamp_t t1, sfx_timestamp_t t2) +{ + long secs1, secs2; + long usecs1, usecs2; + + sfx_timestamp_gettime(&t1, &secs1, &usecs1); + sfx_timestamp_gettime(&t2, &secs2, &usecs2); + + return (usecs1 - usecs2) + ((secs1 - secs2) * 1000000); +} + +sfx_timestamp_t +sfx_timestamp_renormalise(sfx_timestamp_t timestamp, int new_freq) +{ + sfx_timestamp_t r; + sfx_timestamp_gettime(×tamp, &r.secs, &r.usecs); + r.frame_rate = new_freq; + r.frame_offset = 0; + + return r; +} + +void +sfx_timestamp_gettime(sfx_timestamp_t *timestamp, long *secs, long *usecs) +{ + long ust = timestamp->usecs; + /* On 64 bit machines, we can do an accurate computation */ +#if (SIZEOF_LONG >= 8) + ust += (timestamp->frame_offset * 1000000l) / (timestamp->frame_rate); +#else + ust += (timestamp->frame_offset * 1000l) / (timestamp->frame_rate / 1000l); +#endif + + if (ust > 1000000) { + ust -= 1000000; + *secs = timestamp->secs + 1; + } else + *secs = timestamp->secs; + + *usecs = ust; +} + diff --git a/engines/sci/sfx/timer/Makefile.am b/engines/sci/sfx/timer/Makefile.am new file mode 100644 index 0000000000..74f3bee426 --- /dev/null +++ b/engines/sci/sfx/timer/Makefile.am @@ -0,0 +1,4 @@ +EXTRA_DIST = pthread.c +noinst_LIBRARIES = libscitimer.a +INCLUDES = -I$(top_srcdir)/src/include @EXTRA_INCLUDES@ +libscitimer_a_SOURCES = timers.c sigalrm.c diff --git a/engines/sci/sfx/timer/pthread.c b/engines/sci/sfx/timer/pthread.c new file mode 100644 index 0000000000..074adbe08f --- /dev/null +++ b/engines/sci/sfx/timer/pthread.c @@ -0,0 +1,104 @@ +/*************************************************************************** + pthread.c Copyright (C) 2005 Walter van Niftrik + + + 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: + + Walter van Niftrik <w.f.b.w.v.niftrik@stud.tue.nl> + +***************************************************************************/ + +#include <sfx_timer.h> +#include <pthread.h> +#include <unistd.h> + +/* Timer frequency in hertz */ +#define FREQ 60 + +/* Delay in ms */ +#define DELAY (1000 / FREQ) + +static void (*callback)(void *) = NULL; +static void *callback_data = NULL; +pthread_t thread; +volatile static int thread_run; + +static void * +timer_thread(void *arg) +{ + while (thread_run) { + if (callback) + callback(callback_data); + + usleep(DELAY * 1000); + } + + return NULL; +} + +static int +set_option(char *name, char *value) +{ + return SFX_ERROR; +} + +static int +init(void (*func)(void *), void *data) +{ + if (callback) { + fprintf(stderr, "Error: Attempt to initialize pthread timer more than once\n"); + return SFX_ERROR; + } + + if (!func) { + fprintf(stderr, "Error: Attempt to initialize pthread timer w/o callback\n"); + return SFX_ERROR; + } + + callback = func; + callback_data = data; + + thread_run = 1; + if (pthread_create(&thread, NULL, timer_thread, NULL)) { + fprintf(stderr, "Error: Could not create thread.\n"); + return SFX_ERROR; + } + + return SFX_OK; +} + +static int +stop(void) +{ + thread_run = 0; + pthread_join(thread, NULL); + + return SFX_OK; +} + +sfx_timer_t sfx_timer_pthread = { + "pthread", + "0.1", + DELAY, + 0, + &set_option, + &init, + &stop +}; diff --git a/engines/sci/sfx/timer/scummvm.cpp b/engines/sci/sfx/timer/scummvm.cpp new file mode 100644 index 0000000000..f4e08d441a --- /dev/null +++ b/engines/sci/sfx/timer/scummvm.cpp @@ -0,0 +1,52 @@ +#include "common/timer.h" +#include "engines/engine.h" +#include "sfx_timer.h" + + +#define FREQ 60 +#define DELAY (1000000 / FREQ) + +typedef void (*scummvm_timer_callback_t)(void *); +static scummvm_timer_callback_t scummvm_timer_callback = NULL; +static void *scummvm_timer_callback_data = NULL; +extern ::Engine *g_engine; + +void scummvm_timer_update_internal(void *ptr) { + if (scummvm_timer_callback) + scummvm_timer_callback(scummvm_timer_callback_data); +} + +int scummvm_timer_start(void (*func)(void *), void *data) { + if (scummvm_timer_callback) { + fprintf(stderr, + "Error: Attempt to initialize gametick timer more than once\n"); + return SFX_ERROR; + } + + if (!func) { + fprintf(stderr, + "Error: Attempt to initialize gametick timer w/o callback\n"); + return SFX_ERROR; + } + + scummvm_timer_callback = func; + scummvm_timer_callback_data = data; + + ::g_engine->_timer->installTimerProc(&scummvm_timer_update_internal, DELAY, NULL); + return SFX_OK; +} + +int scummvm_timer_stop() { + scummvm_timer_callback = NULL; + return SFX_OK; +} + + +sfx_timer_t sfx_timer_scummvm = { + "ScummVM", + "0.1", + DELAY/1000, 0, + NULL, + &scummvm_timer_start, + &scummvm_timer_stop + }; diff --git a/engines/sci/sfx/timer/sigalrm.c b/engines/sci/sfx/timer/sigalrm.c new file mode 100644 index 0000000000..40cc2872e1 --- /dev/null +++ b/engines/sci/sfx/timer/sigalrm.c @@ -0,0 +1,157 @@ +/*************************************************************************** + sigalrm.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> + +***************************************************************************/ + +#include <sfx_timer.h> + +#ifdef HAVE_SETITIMER + +#include <sys/time.h> +#include <signal.h> +#include <stdio.h> + +#ifdef HAVE_PTHREAD +#include <pthread.h> +#define sigprocmask pthread_sigmask +#endif + +static void (*sig_callback)(void *) = NULL; +static void *sig_callback_data = NULL; +static sigset_t current_sigset; + +static void +timer_handler(int i) +{ + if (sig_callback) + sig_callback(sig_callback_data); +} + +static int +sigalrm_set_option(char *name, char *value) +{ + return SFX_ERROR; +} + + +static int +sigalrm_start(void) +{ + struct itimerval itimer; + + itimer.it_value.tv_sec = 0; + itimer.it_value.tv_usec = 1000000/60; + itimer.it_interval = itimer.it_value; + + signal(SIGALRM, timer_handler); /* Re-instate timer handler, to make sure */ + setitimer(ITIMER_REAL, &itimer, NULL); + + return SFX_OK; +} + + +static int +sigalrm_init(void (*callback)(void *), void *data) +{ + if (sig_callback) { + fprintf(stderr, "Error: Attempt to initialize sigalrm timer more than once\n"); + return SFX_ERROR; + } + + if (!callback) { + fprintf(stderr, "Error: Attempt to initialize sigalrm timer w/o callback\n"); + return SFX_ERROR; + } + + sig_callback = callback; + sig_callback_data = data; + + sigalrm_start(); + + sigemptyset(¤t_sigset); + sigaddset(¤t_sigset, SIGALRM); + + return SFX_OK; +} + + +static int +sigalrm_stop(void) +{ + struct itimerval itimer; + + if (!sig_callback) { + fprintf(stderr, "Error: Attempt to stop sigalrm timer when not running\n"); + return SFX_ERROR; + } + + itimer.it_value.tv_sec = 0; + itimer.it_value.tv_usec = 0; + itimer.it_interval = itimer.it_value; + + setitimer(ITIMER_REAL, &itimer, NULL); /* Stop timer */ + signal(SIGALRM, SIG_DFL); + + return SFX_OK; +} + + +static int +sigalrm_block(void) +{ + if (sigprocmask(SIG_BLOCK, ¤t_sigset, NULL) != 0) { + fprintf(stderr, "Error: Failed to block sigalrm\n"); + return SFX_ERROR; + } + + return SFX_OK; +} + + +static int +sigalrm_unblock(void) +{ + if (sigprocmask(SIG_UNBLOCK, ¤t_sigset, NULL) != 0) { + fprintf(stderr, "Error: Failed to unblock sigalrm\n"); + return SFX_ERROR; + } + + return SFX_OK; +} + + +sfx_timer_t sfx_timer_sigalrm = { + "sigalrm", + "0.1", + 17, /* 1000 / 60 */ + 0, + &sigalrm_set_option, + &sigalrm_init, + &sigalrm_stop, + &sigalrm_block, + &sigalrm_unblock +}; + +#endif /* HAVE_SETITIMER */ diff --git a/engines/sci/sfx/timer/timers.c b/engines/sci/sfx/timer/timers.c new file mode 100644 index 0000000000..b2a51d6062 --- /dev/null +++ b/engines/sci/sfx/timer/timers.c @@ -0,0 +1,73 @@ +/*************************************************************************** + timers.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> + +***************************************************************************/ + +#include <sfx_timer.h> +#include <resource.h> + +#ifdef SCUMMVM +extern sfx_timer_t sfx_timer_scummvm; +#else // SCUMMVM + +#ifdef HAVE_SETITIMER +extern sfx_timer_t sfx_timer_sigalrm; +#endif + +#ifdef _DREAMCAST +extern sfx_timer_t sfx_timer_pthread; +#endif +#endif // SCUMMVM + +sfx_timer_t *sfx_timers[] = { +#ifdef SCUMMVM + &sfx_timer_scummvm, +#else // SCUMMVM +#ifdef HAVE_SETITIMER + &sfx_timer_sigalrm, +#endif +#ifdef _DREAMCAST + &sfx_timer_pthread, +#endif +#endif // SCUMMVM + NULL +}; + + +sfx_timer_t * +sfx_find_timer(char *name) +{ + if (!name) { + /* Policies go here */ + return sfx_timers[0]; + } else { + int n = 0; + while (sfx_timers[n] + && strcasecmp(sfx_timers[n]->name, name)) + ++n; + + return sfx_timers[n]; + } +} diff --git a/engines/sci/sfx/timetest.c b/engines/sci/sfx/timetest.c new file mode 100644 index 0000000000..80d9ad1b78 --- /dev/null +++ b/engines/sci/sfx/timetest.c @@ -0,0 +1,59 @@ +/*************************************************************************** + timetest.c Copyright (C) 2003 Christoph Reichenbach + + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public Licence as + published by the Free Software Foundaton; either version 2 of the + Licence, or (at your option) any later version. + + It is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + merchantibility or fitness for a particular purpose. See the + GNU General Public Licence for more details. + + You should have received a copy of the GNU General Public Licence + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + + Please contact the maintainer for any program-related bug reports or + inquiries. + + Current Maintainer: + + Christoph Reichenbach (CR) <jameson@linuxgames.com> + +***************************************************************************/ + +#include <stdio.h> +#include <sfx_time.h> +#include <assert.h> + +sfx_timestamp_t a,b,c; + +int +main(int argc, char **argv) +{ + int i; + a = sfx_new_timestamp(10, 0, 1000); + b = sfx_new_timestamp(10, 1000, 1000); + c = sfx_new_timestamp(10, 2000, 1000); + + assert(sfx_timestamp_sample_diff(a, b) == -1); + assert(sfx_timestamp_sample_diff(b, a) == 1); + assert(sfx_timestamp_sample_diff(c, a) == 2); + assert(sfx_timestamp_sample_diff(sfx_timestamp_add(b, 2000), a) == 2001); + assert(sfx_timestamp_sample_diff(sfx_timestamp_add(b, 2000), sfx_timestamp_add(a, -1000)) == 3001); + + for (i = -10000; i < 10000; i++) { + int v = sfx_timestamp_sample_diff(sfx_timestamp_add(c, i), c); + if (v != i) { + fprintf(stderr, "After adding %d samples: Got diff of %d\n", i, v); + return 1; + } + } + + return 0; +} |