From 695bc36b9affeaaf2d2e635b0d72278915a3997b Mon Sep 17 00:00:00 2001 From: Walter van Niftrik Date: Wed, 15 Apr 2009 10:45:59 +0000 Subject: SCI: Added a new song player (work-in-progress). svn-id: r39953 --- engines/sci/module.mk | 2 + engines/sci/sfx/core.cpp | 8 +- engines/sci/sfx/player/player.cpp | 234 +++++++++++++++++++++++++++++++++++++ engines/sci/sfx/player/players.cpp | 2 + engines/sci/sfx/sci_midi.h | 38 ++++++ engines/sci/sfx/softseq/pcjr.cpp | 196 +++++++++++++++++++++++++++++++ engines/sci/sfx/softseq/pcjr.h | 86 ++++++++++++++ 7 files changed, 565 insertions(+), 1 deletion(-) create mode 100644 engines/sci/sfx/player/player.cpp create mode 100644 engines/sci/sfx/softseq/pcjr.cpp create mode 100644 engines/sci/sfx/softseq/pcjr.h (limited to 'engines') diff --git a/engines/sci/module.mk b/engines/sci/module.mk index 46786f26f0..0c680934f4 100644 --- a/engines/sci/module.mk +++ b/engines/sci/module.mk @@ -63,6 +63,7 @@ MODULE_OBJS = \ sfx/iterator.o \ sfx/songlib.o \ sfx/device/devices.o \ + sfx/player/player.o \ sfx/player/players.o \ sfx/player/polled.o \ sfx/player/realtime.o \ @@ -71,6 +72,7 @@ MODULE_OBJS = \ sfx/seq/map-mt32-to-gm.o \ sfx/seq/sequencers.o \ sfx/softseq/amiga.o \ + sfx/softseq/pcjr.o \ sfx/softseq/opl2.o \ sfx/softseq/pcspeaker.o \ sfx/softseq/SN76496.o \ diff --git a/engines/sci/sfx/core.cpp b/engines/sci/sfx/core.cpp index a0fc166f9c..b573a52630 100644 --- a/engines/sci/sfx/core.cpp +++ b/engines/sci/sfx/core.cpp @@ -432,11 +432,17 @@ void sfx_exit(sfx_state_t *self) { // WARNING: The mixer may hold feeds from the player, so we must // stop the mixer BEFORE stopping the player. - g_system->getMixer()->stopAll(); + // FIXME Player "new" frees its own feeds, so we only need to stop any + // remaining sfx feeds after stopping the player. + if (strcmp(player->name, "new") != 0) + g_system->getMixer()->stopAll(); if (player) // See above: This must happen AFTER stopping the mixer player->exit(); + + if (strcmp(player->name, "new") == 0) + g_system->getMixer()->stopAll(); } void sfx_suspend(sfx_state_t *self, int suspend) { diff --git a/engines/sci/sfx/player/player.cpp b/engines/sci/sfx/player/player.cpp new file mode 100644 index 0000000000..ada2dc4357 --- /dev/null +++ b/engines/sci/sfx/player/player.cpp @@ -0,0 +1,234 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "sci/tools.h" +#include "sci/sfx/player.h" +#include "sci/sfx/sequencer.h" +#include "sci/sfx/iterator.h" +#include "sci/sfx/core.h" + +#include "common/system.h" + +#include "sci/sfx/softseq/pcjr.h" + +namespace Sci { + +extern sfx_player_t sfx_player_player; +static MidiPlayer *mididrv; + +static SongIterator *play_it = NULL; +static Audio::Timestamp wakeup_time; +static Audio::Timestamp current_time; +static uint32 play_pause_diff; + +static int play_paused = 0; +static int play_it_done = 0; +static uint32 tempo; + +static Common::Mutex *mutex; + +static void play_song(SongIterator *it) { + while (play_it && wakeup_time.msecsDiff(current_time) <= 0) { + int delay; + byte buf[8]; + int result; + + switch ((delay = songit_next(&(play_it), + buf, &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: + static_cast(mididrv)->send(buf[0], buf[1], buf[2]); + + break; + + default: + wakeup_time = wakeup_time.addFrames(delay); + } + } +} + +static void player_tell_synth(int buf_nr, byte *buf) { + byte op1 = (buf_nr < 2 ? 0 : buf[1]); + byte op2 = (buf_nr < 3 ? 0 : buf[2]); + + static_cast(mididrv)->send(buf[0], op1, op2); +} + +static void player_timer_callback(void *refCon) { + mutex->lock(); + if (play_it && !play_it_done && !play_paused) { + play_song(play_it); + } + + current_time = current_time.addFrames(1); + mutex->unlock(); +} + +static void player_void_callback(void) { +} + +/* API implementation */ + +static int player_set_option(char *name, char *value) { + return SFX_ERROR; +} + +static int player_init(ResourceManager *resmgr, int expected_latency) { + MidiDriverType musicDriver = MidiDriver::detectMusicDriver(MDT_PCSPK); + + switch(musicDriver) { + case MD_PCJR: + mididrv = new MidiPlayer_PCJr(); + break; + case MD_PCSPK: + mididrv = new MidiPlayer_PCSpeaker(); + break; + default: + break; + } + + assert(mididrv); + + sfx_player_player.polyphony = mididrv->getPolyphony(); + + tempo = mididrv->getBaseTempo(); + uint32 time = g_system->getMillis(); + current_time = Audio::Timestamp(time, 1000000 / tempo); + wakeup_time = Audio::Timestamp(time, SFX_TICKS_PER_SEC); + + mutex = new Common::Mutex(); + + mididrv->setTimerCallback(NULL, player_timer_callback); + mididrv->open(resmgr); + + return SFX_OK; +} + +static int player_add_iterator(SongIterator *it, uint32 start_time) { + mutex->lock(); + SIMSG_SEND(it, SIMSG_SET_PLAYMASK(mididrv->getPlayMask())); + SIMSG_SEND(it, SIMSG_SET_RHYTHM(mididrv->hasRhythmChannel())); + + current_time = Audio::Timestamp(g_system->getMillis(), 1000000 / tempo); + wakeup_time = Audio::Timestamp(start_time, SFX_TICKS_PER_SEC); + play_it = sfx_iterator_combine(play_it, it); + play_it_done = 0; + mutex->unlock(); + + return SFX_OK; +} + +static int player_fade_out(void) { + warning("Attempt to fade out - not implemented yet"); + return SFX_ERROR; +} + +static int player_stop(void) { + mutex->lock(); + delete play_it; + play_it = NULL; + mididrv->allSoundOff(); + mutex->unlock(); + + return SFX_OK; +} + +static int player_send_iterator_message(const SongIterator::Message &msg) { + mutex->lock(); + if (!play_it) { + mutex->unlock(); + return SFX_ERROR; + } + + songit_handle_message(&play_it, msg); + mutex->unlock(); + + return SFX_OK; +} + +static int player_pause(void) { + mutex->lock(); + play_paused = 1; + play_pause_diff = wakeup_time.msecsDiff(current_time); + + mididrv->allSoundOff(); + mutex->unlock(); + + return SFX_OK; +} + +static int player_resume(void) { + mutex->lock(); + wakeup_time = Audio::Timestamp(current_time.msecs() + play_pause_diff, SFX_TICKS_PER_SEC); + play_paused = 0; + mutex->unlock(); + + return SFX_OK; +} + +static int player_exit(void) { + mididrv->close(); + delete mididrv; + delete mutex; + + return SFX_OK; +} + +sfx_player_t sfx_player_player = { + "new", + "0.1", + &player_set_option, + &player_init, + &player_add_iterator, + &player_fade_out, + &player_stop, + &player_send_iterator_message, + &player_pause, + &player_resume, + &player_exit, + &player_void_callback, + &player_tell_synth, + 0 /* polyphony */ +}; + +} // End of namespace Sci + diff --git a/engines/sci/sfx/player/players.cpp b/engines/sci/sfx/player/players.cpp index b07a00b036..1c9fdcea83 100644 --- a/engines/sci/sfx/player/players.cpp +++ b/engines/sci/sfx/player/players.cpp @@ -29,8 +29,10 @@ namespace Sci { extern sfx_player_t sfx_player_realtime; extern sfx_player_t sfx_player_polled; +extern sfx_player_t sfx_player_player; sfx_player_t *sfx_players[] = { +// &sfx_player_player, &sfx_player_polled, &sfx_player_realtime, NULL diff --git a/engines/sci/sfx/sci_midi.h b/engines/sci/sfx/sci_midi.h index 68650f738a..c026bdff5f 100644 --- a/engines/sci/sfx/sci_midi.h +++ b/engines/sci/sfx/sci_midi.h @@ -26,8 +26,13 @@ #ifndef SCI_SFX_MIDI_H #define SCI_SFX_MIDI_H +#include "sound/mididrv.h" +#include "sci/sfx/sfx.h" + namespace Sci { +class ResourceManager; + #define MIDI_RHYTHM_CHANNEL 9 /* Special SCI sound stuff */ @@ -43,6 +48,7 @@ namespace Sci { #define SCI_MIDI_SET_REVERB 0x50 #define SCI_MIDI_HOLD 0x52 #define SCI_MIDI_CUMULATIVE_CUE 0x60 +#define SCI_MIDI_CHANNEL_SOUND_OFF 0x78 /* all-sound-off for Bn */ #define SCI_MIDI_CHANNEL_NOTES_OFF 0x7B /* all-notes-off for Bn */ #define SCI_MIDI_SET_SIGNAL_LOOP 0x7F @@ -50,6 +56,38 @@ namespace Sci { #define SCI_MIDI_CONTROLLER(status) ((status & 0xF0) == 0xB0) +class MidiPlayer : public MidiDriver { +protected: + MidiDriver *_driver; +public: + int open() { return open(NULL); } + int open(ResourceManager *resmgr) { return _driver->open(); } + void close() { _driver->close(); } + void send(uint32 b) { _driver->send(b); } + uint32 getBaseTempo() { return _driver->getBaseTempo(); } + bool hasRhythmChannel() const { return true; } + MidiChannel *allocateChannel() { return _driver->allocateChannel(); } + MidiChannel *getPercussionChannel() { return _driver->getPercussionChannel(); } + void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) { _driver->setTimerCallback(timer_param, timer_proc); } + + virtual int getPlayMask() const = 0; + virtual int getPolyphony() const = 0; + virtual int getPatchNr() const { return -1; } + + virtual void setVolume(byte volume) { + // Master Volume SysEx message + const byte message[] = {0x7f, 0x7f, 0x04, 0x01, volume & 0x7f, volume & 0x7f}; + + _driver->sysEx(message, 6); + } + + virtual void allSoundOff() { + // Send "All Sound Off" on all channels + for (int i = 0; i < MIDI_CHANNELS; ++i) + _driver->send(0xb0 + i, SCI_MIDI_CHANNEL_SOUND_OFF, 0); + } +}; + } // End of namespace Sci #endif // SCI_SFX_MIDI_H diff --git a/engines/sci/sfx/softseq/pcjr.cpp b/engines/sci/sfx/softseq/pcjr.cpp new file mode 100644 index 0000000000..b4afa1c593 --- /dev/null +++ b/engines/sci/sfx/softseq/pcjr.cpp @@ -0,0 +1,196 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "sci/sfx/softseq.h" +#include "sci/sfx/sci_midi.h" +#include "sci/sfx/softseq/pcjr.h" + +namespace Sci { + +#define FREQUENCY 44100 +#define VOLUME_SHIFT 3 + +#define BASE_NOTE 129 // A10 +#define BASE_OCTAVE 10 // A10, as I said + +const 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 MidiDriver_PCJr::send(uint32 b) { + byte command = b & 0xff; + byte op1 = (b >> 8) & 0xff; + byte op2 = (b >> 16) & 0xff; + int i; + int mapped_chan = -1; + 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) { + mapped_chan = i; + break; + } + } else if ((command & 0xe0) == 0x80) { + // 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: + mapped_chan = _channel_assigner; + // Round-ropin iterate channel assigner: + _channel_assigner = (_channel_assigner + 1) % _channels_nr; + } + + if (mapped_chan == -1) + return; + + switch (command & 0xf0) { + + case 0x80: + if (op1 == _notes[mapped_chan]) + _notes[mapped_chan] = 0; + break; + + case 0x90: + if (!op2) { + if (op1 == _notes[mapped_chan]) + _notes[mapped_chan] = 0; + } else { + _notes[mapped_chan] = op1; + _volumes[mapped_chan] = op2; + } + break; + + case 0xb0: + if ((op1 == SCI_MIDI_CHANNEL_NOTES_OFF) || (op1 == SCI_MIDI_CHANNEL_SOUND_OFF)) + _notes[mapped_chan] = 0; + break; + + default: + debug(2, "Unused MIDI command %02x %02x %02x", command, op1, op2); + break; /* ignore */ + } +} + +void MidiDriver_PCJr::generateSamples(int16 *data, int len) { + int i; + int chan; + int freq[kMaxChannels]; + + for (chan = 0; chan < _channels_nr; chan++) + freq[chan] = get_freq(_notes[chan]); + + for (i = 0; i < len; i++) { + int16 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; + } + } + data[i] = result; + } +} + +int MidiDriver_PCJr::open(int channels) { + if (_isOpen) + return MERR_ALREADY_OPEN; + + if (channels > kMaxChannels) + return -1; + + _channels_nr = channels; + _global_volume = 100; + for (int i = 0; i < _channels_nr; i++) { + _volumes[i] = 100; + _notes[i] = 0; + _freq_count[i] = 0; + _chan_nrs[i] = -1; + } + _channel_assigner = 0; + _channels_assigned = 0; + + MidiDriver_Emulated::open(); + + _mixer->playInputStream(Audio::Mixer::kPlainSoundType, &_mixerSoundHandle, this, -1, Audio::Mixer::kMaxChannelVolume * (int) _global_volume / 127); + + return 0; +} + +void MidiDriver_PCJr::close() { + _mixer->stopHandle(_mixerSoundHandle); +} + +} // End of namespace Sci diff --git a/engines/sci/sfx/softseq/pcjr.h b/engines/sci/sfx/softseq/pcjr.h new file mode 100644 index 0000000000..6fd582e53f --- /dev/null +++ b/engines/sci/sfx/softseq/pcjr.h @@ -0,0 +1,86 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "sci/sfx/sci_midi.h" + +#include "sound/softsynth/emumidi.h" + +namespace Sci { + +class MidiDriver_PCJr : public MidiDriver_Emulated { +public: + friend class MidiPlayer_PCJr; + + enum { + kMaxChannels = 3 + }; + + MidiDriver_PCJr(Audio::Mixer *mixer) : MidiDriver_Emulated(mixer) { } + ~MidiDriver_PCJr() { } + + // MidiDriver + int open() { return open(kMaxChannels); } + void close(); + void send(uint32 b); + MidiChannel *allocateChannel() { return NULL; } + MidiChannel *getPercussionChannel() { return NULL; } + + // AudioStream + bool isStereo() const { return false; } + int getRate() const { return _mixer->getOutputRate(); } + + // MidiDriver_Emulated + void generateSamples(int16 *buf, int len); + + int open(int channels); +private: + int _channels_nr; + int _global_volume; // Base volume + int _volumes[kMaxChannels]; + int _notes[kMaxChannels]; // Current halftone, or 0 if off + int _freq_count[kMaxChannels]; + int _channel_assigner; + int _channels_assigned; + int _chan_nrs[kMaxChannels]; +}; + +class MidiPlayer_PCJr : public MidiPlayer { +public: + MidiPlayer_PCJr() { _driver = new MidiDriver_PCJr(g_system->getMixer()); } + int open(ResourceManager *resmgr) { return static_cast(_driver)->open(getPolyphony()); } + int getPlayMask() const { return 0x10; } + int getPolyphony() const { return 3; } + bool hasRhythmChannel() const { return false; } + void setVolume(byte volume) { static_cast(_driver)->_global_volume = volume; } +}; + +class MidiPlayer_PCSpeaker : public MidiPlayer_PCJr { +public: + int getPlayMask() const { return 0x20; } + int getPolyphony() const { return 1; } +}; + +} // End of namespace Sci + -- cgit v1.2.3