From c7a5a17acfa1353c221be8c09576503b395c29ef Mon Sep 17 00:00:00 2001 From: Walter van Niftrik Date: Sun, 3 May 2009 21:11:09 +0000 Subject: SCI: adlib support (work-in-progress) for the new music player. svn-id: r40287 --- engines/sci/module.mk | 1 + engines/sci/sfx/adlib.h | 2 +- engines/sci/sfx/player/player.cpp | 16 +- engines/sci/sfx/sci_midi.h | 22 +- engines/sci/sfx/softseq/adlib.cpp | 601 ++++++++++++++++++++++++++++++++++++++ engines/sci/sfx/softseq/adlib.h | 153 ++++++++++ engines/sci/sfx/softseq/pcjr.cpp | 2 +- engines/sci/sfx/softseq/pcjr.h | 2 - 8 files changed, 782 insertions(+), 17 deletions(-) create mode 100644 engines/sci/sfx/softseq/adlib.cpp create mode 100644 engines/sci/sfx/softseq/adlib.h diff --git a/engines/sci/module.mk b/engines/sci/module.mk index 0c680934f4..8fd492938d 100644 --- a/engines/sci/module.mk +++ b/engines/sci/module.mk @@ -71,6 +71,7 @@ MODULE_OBJS = \ sfx/seq/instrument-map.o \ sfx/seq/map-mt32-to-gm.o \ sfx/seq/sequencers.o \ + sfx/softseq/adlib.o \ sfx/softseq/amiga.o \ sfx/softseq/pcjr.o \ sfx/softseq/opl2.o \ diff --git a/engines/sci/sfx/adlib.h b/engines/sci/sfx/adlib.h index d6efcefb0f..6b5ee2d8b4 100644 --- a/engines/sci/sfx/adlib.h +++ b/engines/sci/sfx/adlib.h @@ -31,7 +31,7 @@ namespace Sci { -#define ADLIB_VOICES 12 +#define ADLIB_VOICES 9 struct adlib_def { uint8 keyscale1; /* 0-3 !*/ diff --git a/engines/sci/sfx/player/player.cpp b/engines/sci/sfx/player/player.cpp index ada2dc4357..90d99c8092 100644 --- a/engines/sci/sfx/player/player.cpp +++ b/engines/sci/sfx/player/player.cpp @@ -32,6 +32,7 @@ #include "common/system.h" #include "sci/sfx/softseq/pcjr.h" +#include "sci/sfx/softseq/adlib.h" namespace Sci { @@ -48,6 +49,7 @@ static int play_it_done = 0; static uint32 tempo; static Common::Mutex *mutex; +static int volume = 15; static void play_song(SongIterator *it) { while (play_it && wakeup_time.msecsDiff(current_time) <= 0) { @@ -62,6 +64,8 @@ static void play_song(SongIterator *it) { | IT_READER_MAY_CLEAN))) { case SI_FINISHED: + delete play_it; + play_it = NULL; play_it_done = 1; return; @@ -113,9 +117,12 @@ static int player_set_option(char *name, char *value) { } static int player_init(ResourceManager *resmgr, int expected_latency) { - MidiDriverType musicDriver = MidiDriver::detectMusicDriver(MDT_PCSPK); + MidiDriverType musicDriver = MidiDriver::detectMusicDriver(MDT_PCSPK | MDT_ADLIB); switch(musicDriver) { + case MD_ADLIB: + mididrv = new MidiPlayer_Adlib(); + break; case MD_PCJR: mididrv = new MidiPlayer_PCJr(); break; @@ -139,6 +146,7 @@ static int player_init(ResourceManager *resmgr, int expected_latency) { mididrv->setTimerCallback(NULL, player_timer_callback); mididrv->open(resmgr); + mididrv->setVolume(volume); return SFX_OK; } @@ -166,7 +174,6 @@ static int player_stop(void) { mutex->lock(); delete play_it; play_it = NULL; - mididrv->allSoundOff(); mutex->unlock(); return SFX_OK; @@ -190,7 +197,7 @@ static int player_pause(void) { play_paused = 1; play_pause_diff = wakeup_time.msecsDiff(current_time); - mididrv->allSoundOff(); + mididrv->playSwitch(false); mutex->unlock(); return SFX_OK; @@ -199,6 +206,7 @@ static int player_pause(void) { static int player_resume(void) { mutex->lock(); wakeup_time = Audio::Timestamp(current_time.msecs() + play_pause_diff, SFX_TICKS_PER_SEC); + mididrv->playSwitch(true); play_paused = 0; mutex->unlock(); @@ -209,6 +217,8 @@ static int player_exit(void) { mididrv->close(); delete mididrv; delete mutex; + delete play_it; + play_it = NULL; return SFX_OK; } diff --git a/engines/sci/sfx/sci_midi.h b/engines/sci/sfx/sci_midi.h index c026bdff5f..31e3912942 100644 --- a/engines/sci/sfx/sci_midi.h +++ b/engines/sci/sfx/sci_midi.h @@ -27,6 +27,7 @@ #define SCI_SFX_MIDI_H #include "sound/mididrv.h" +#include "sound/softsynth/emumidi.h" #include "sci/sfx/sfx.h" namespace Sci { @@ -61,30 +62,31 @@ 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); } + virtual int open(ResourceManager *resmgr) { return _driver->open(); } + virtual void close() { _driver->close(); } + virtual void send(uint32 b) { _driver->send(b); } uint32 getBaseTempo() { return _driver->getBaseTempo(); } - bool hasRhythmChannel() const { return true; } + virtual bool hasRhythmChannel() const = 0; 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}; + const byte message[] = {0x7f, 0x7f, 0x04, 0x01, (volume * 127 / 15) & 0x7f, (volume * 127 / 15) & 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); + virtual void playSwitch(bool play) { + if (!play) { + // Send "All Sound Off" on all channels + for (int i = 0; i < MIDI_CHANNELS; ++i) + _driver->send(0xb0 + i, SCI_MIDI_CHANNEL_NOTES_OFF, 0); + } } }; diff --git a/engines/sci/sfx/softseq/adlib.cpp b/engines/sci/sfx/softseq/adlib.cpp new file mode 100644 index 0000000000..d12b4356cc --- /dev/null +++ b/engines/sci/sfx/softseq/adlib.cpp @@ -0,0 +1,601 @@ +/* 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/iterator.h" + +#include "sound/fmopl.h" + +#include "sci/scicore/resource.h" +#include "sci/sfx/softseq/adlib.h" + +namespace Sci { + +#ifdef __DC__ +#define STEREO false +#else +#define STEREO true +#endif + +// FIXME: We don't seem to be sending the polyphony init data, so disable this for now +#define ADLIB_DISABLE_VOICE_MAPPING +#define ADLIB_SCI1 + +static const byte registerOffset[MidiDriver_Adlib::kVoices] = { + 0x00, 0x01, 0x02, 0x08, 0x09, 0x0A, 0x10, 0x11, 0x12 +}; + +static const byte velocityMap1[64] = { + 0x00, 0x0c, 0x0d, 0x0e, 0x0f, 0x11, 0x12, 0x13, + 0x14, 0x16, 0x17, 0x18, 0x1a, 0x1b, 0x1c, 0x1d, + 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, + 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2d, 0x2d, 0x2e, + 0x2f, 0x30, 0x31, 0x32, 0x32, 0x33, 0x34, 0x34, + 0x35, 0x36, 0x36, 0x37, 0x38, 0x38, 0x39, 0x3a, + 0x3b, 0x3b, 0x3b, 0x3c, 0x3c, 0x3c, 0x3d, 0x3d, + 0x3d, 0x3e, 0x3e, 0x3e, 0x3e, 0x3f, 0x3f, 0x3f +}; + +static const byte velocityMap2[64] = { + 0x00, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, + 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x21, + 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, + 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x2f, 0x30, + 0x31, 0x32, 0x32, 0x33, 0x34, 0x34, 0x35, 0x36, + 0x36, 0x37, 0x38, 0x38, 0x39, 0x39, 0x3a, 0x3a, + 0x3b, 0x3b, 0x3b, 0x3c, 0x3c, 0x3c, 0x3d, 0x3d, + 0x3d, 0x3e, 0x3e, 0x3e, 0x3e, 0x3f, 0x3f, 0x3f +}; + +static const int ym3812_note[13] = { + 0x157, 0x16b, 0x181, 0x198, 0x1b0, 0x1ca, + 0x1e5, 0x202, 0x220, 0x241, 0x263, 0x287, + 0x2ae +}; + +int MidiDriver_Adlib::open() { + int rate = _mixer->getOutputRate(); + + _stereo = STEREO; + + for (int i = 0; i < (isStereo() ? 2 : 1); i++) { + _fmopl[i] = makeAdlibOPL(rate); + + if (!_fmopl[i]) + return -1; + + OPLResetChip(_fmopl[i]); + } + + setRegister(0xBD, 0); + setRegister(0x08, 0); + setRegister(0x01, 0x20); + + MidiDriver_Emulated::open(); + + _mixer->playInputStream(Audio::Mixer::kMusicSoundType, &_mixerSoundHandle, this, -1, _mixer->kMaxChannelVolume, 0, false); + + return 0; +} + +void MidiDriver_Adlib::close() { + _mixer->stopHandle(_mixerSoundHandle); + + for (int i = 0; i < (isStereo() ? 2 : 1); i++) + OPLDestroy(_fmopl[i]); +} + +void MidiDriver_Adlib::setVolume(byte volume) { + _masterVolume = volume; + renewNotes(-1, true); +} + +void MidiDriver_Adlib::send(uint32 b) { + byte command = b & 0xf0; + byte channel = b & 0xf; + byte op1 = (b >> 8) & 0xff; + byte op2 = (b >> 16) & 0xff; + + switch (command) { + case 0x80: + noteOff(channel, op1); + break; + case 0x90: + noteOn(channel, op1, op2); + break; + case 0xe0: + _channels[channel].pitchWheel = (op1 & 0x7f) | ((op2 & 0x7f) << 7); + renewNotes(channel, true); + break; + case 0xb0: + switch (op1) { + case 0x07: + _channels[channel].volume = op2 >> 1; + renewNotes(channel, true); + break; + case 0x0a: + _channels[channel].pan = op2; + renewNotes(channel, true); + break; + case 0x40: + _channels[channel].holdPedal = op2; + if (op2 == 0) { + for (int i = 0; i < kVoices; i++) { + if ((_voices[i].channel == channel) && _voices[i].isSustained) + voiceOff(i); + } + } + break; + case 0x4b: +#ifndef ADLIB_DISABLE_VOICE_MAPPING + voiceMapping(channel, op2); +#endif + break; + case SCI_MIDI_CHANNEL_NOTES_OFF: + for (int i = 0; i < kVoices; i++) + if ((_voices[i].channel == channel) && (_voices[i].note != -1)) + voiceOff(i); + break; + default: + warning("ADLIB: ignoring MIDI command %02x %02x %02x", command | channel, op1, op2); + break; + } + break; + case 0xc0: + _channels[channel].patch = op1; + break; + case 0xd0: // Aftertouch + // Aftertouch in the OPL thing? + break; + default: + warning("ADLIB: Unknown event %02x\n", command); + } +} + +void MidiDriver_Adlib::generateSamples(int16 *data, int len) { + if (isStereo()) { + YM3812UpdateOne(_fmopl[0], data, len, 1); + YM3812UpdateOne(_fmopl[1], data + 1, len, 1); + } else { + YM3812UpdateOne(_fmopl[0], data, len, 0); + } + + // Increase the age of the notes + for (int i = 0; i < kVoices; i++) { + if (_voices[i].note != -1) + _voices[i].age++; + } +} + +void MidiDriver_Adlib::sysEx(const byte *msg, uint16 length) { + AdlibPatch patch; + + assert(length == 28); + + // Set data for the operators + for (int i = 0; i < 2; i++) { + const byte *op = msg + i * 13; + patch.op[i].kbScaleLevel = op[0] & 0x3; + patch.op[i].frequencyMult = op[1] & 0xf; + patch.op[i].attackRate = op[3] & 0xf; + patch.op[i].sustainLevel = op[4] & 0xf; + patch.op[i].envelopeType = op[5]; + patch.op[i].decayRate = op[6] & 0xf; + patch.op[i].releaseRate = op[7] & 0xf; + patch.op[i].totalLevel = op[8] & 0x3f; + patch.op[i].amplitudeMod = op[9]; + patch.op[i].vibrato = op[10]; + patch.op[i].kbScaleRate = op[11]; + } + patch.op[0].waveForm = msg[26] & 0x3; + patch.op[1].waveForm = msg[27] & 0x3; + + // Set data for the modulator + patch.mod.feedback = msg[2] & 0x7; + patch.mod.algorithm = !msg[12]; // Flag is inverted + + _patches.push_back(patch); +} + +void MidiDriver_Adlib::voiceMapping(int channel, int voices) { + int curVoices = 0; + + for (int i = 0; i < kVoices; i++) + if (_voices[i].channel == channel) + curVoices++; + + curVoices += _channels[channel].extraVoices; + + if (curVoices < voices) { + debug(3, "ADLIB: assigning %i additional voices to channel %i", voices - curVoices, channel); + assignVoices(channel, voices - curVoices); + } else if (curVoices > voices) { + debug(3, "ADLIB: releasing %i voices from channel %i", curVoices - voices, channel); + releaseVoices(channel, curVoices - voices); + donateVoices(); + } +} + +void MidiDriver_Adlib::assignVoices(int channel, int voices) { + assert(voices > 0); + + for (int i = 0; i < kVoices; i++) + if (_voices[i].channel == -1) { + _voices[i].channel = channel; + if (--voices == 0) + return; + } + + _channels[channel].extraVoices += voices; +} + +void MidiDriver_Adlib::releaseVoices(int channel, int voices) { + if (_channels[channel].extraVoices >= voices) { + _channels[channel].extraVoices -= voices; + return; + } + + voices -= _channels[channel].extraVoices; + _channels[channel].extraVoices = 0; + + for (int i = 0; i < kVoices; i++) { + if ((_voices[i].channel == channel) && (_voices[i].note == -1)) { + _voices[i].channel = -1; + if (--voices == 0) + return; + } + } + + for (int i = 0; i < kVoices; i++) { + if (_voices[i].channel == channel) { + voiceOff(i); + _voices[i].channel = -1; + if (--voices == 0) + return; + } + } +} + +void MidiDriver_Adlib::donateVoices() { + int freeVoices = 0; + + for (int i = 0; i < kVoices; i++) + if (_voices[i].channel == -1) + freeVoices++; + + if (freeVoices == 0) + return; + + for (int i = 0; i < MIDI_CHANNELS; i++) { + if (_channels[i].extraVoices >= freeVoices) { + assignVoices(i, freeVoices); + _channels[i].extraVoices -= freeVoices; + return; + } else if (_channels[i].extraVoices > 0) { + assignVoices(i, _channels[i].extraVoices); + freeVoices -= _channels[i].extraVoices; + _channels[i].extraVoices = 0; + } + } +} + +void MidiDriver_Adlib::renewNotes(int channel, bool key) { + for (int i = 0; i < kVoices; i++) { + // Update all notes playing this channel + if ((channel == -1) || (_voices[i].channel == channel)) { + if (_voices[i].note != -1) + setNote(i, _voices[i].note, key); + } + } +} + +void MidiDriver_Adlib::noteOn(int channel, int note, int velocity) { + if (velocity == 0) + return noteOff(channel, note); + + velocity >>= 1; + + // Check for playable notes + if ((note < 12) || (note > 107)) + return; + + for (int i = 0; i < kVoices; i++) { + if ((_voices[i].channel == channel) && (_voices[i].note == note)) { + voiceOff(i); + voiceOn(i, note, velocity); + return; + } + } + +#ifdef ADLIB_DISABLE_VOICE_MAPPING + int voice = findVoiceBasic(channel); +#else + int voice = findVoice(channel); +#endif + + if (voice == -1) { + debug(3, "ADLIB: failed to find free voice assigned to channel %i", channel); + return; + } + + voiceOn(voice, note, velocity); +} + +// FIXME: Temporary, see comment at top of file regarding ADLIB_DISABLE_VOICE_MAPPING +int MidiDriver_Adlib::findVoiceBasic(int channel) { + int voice = -1; + int oldestVoice = -1; + int oldestAge = -1; + + // Try to find a voice assigned to this channel that is free (round-robin) + for (int i = 0; i < kVoices; i++) { + int v = (_channels[channel].lastVoice + i + 1) % kVoices; + + if (_voices[v].note == -1) { + voice = v; + break; + } + + // We also keep track of the oldest note in case the search fails + if (_voices[v].age > oldestAge) { + oldestAge = _voices[v].age; + oldestVoice = v; + } + } + + if (voice == -1) { + if (oldestVoice != -1) { + voiceOff(oldestVoice); + voice = oldestVoice; + } else { + return -1; + } + } + + _voices[voice].channel = channel; + _channels[channel].lastVoice = voice; + return voice; +} + +int MidiDriver_Adlib::findVoice(int channel) { + int voice = -1; + int oldestVoice = -1; + uint32 oldestAge = 0; + + // Try to find a voice assigned to this channel that is free (round-robin) + for (int i = 0; i < kVoices; i++) { + int v = (_channels[channel].lastVoice + i + 1) % kVoices; + + if (_voices[v].channel == channel) { + if (_voices[v].note == -1) { + voice = v; + break; + } + + // We also keep track of the oldest note in case the search fails + // Notes started in the current time slice will not be selected + if (_voices[v].age > oldestAge) { + oldestAge = _voices[v].age; + oldestVoice = v; + } + } + } + + if (voice == -1) { + if (oldestVoice != -1) { + voiceOff(oldestVoice); + voice = oldestVoice; + } else { + return -1; + } + } + + _channels[channel].lastVoice = voice; + return voice; +} + +void MidiDriver_Adlib::noteOff(int channel, int note) { + for (int i = 0; i < kVoices; i++) { + if ((_voices[i].channel == channel) && (_voices[i].note == note)) { + if (_channels[channel].holdPedal) + _voices[i].isSustained = true; + else + voiceOff(i); + return; + } + } +} + +void MidiDriver_Adlib::voiceOn(int voice, int note, int velocity) { + int channel = _voices[voice].channel; + int patch = _channels[channel].patch; + + _voices[voice].age = 0; + + // Set patch if different from current patch + if ((patch != _voices[voice].patch) && _playSwitch) { + _voices[voice].patch = patch; + setPatch(voice, patch); + } + + _voices[voice].velocity = velocity; + setNote(voice, note, true); +} + +void MidiDriver_Adlib::voiceOff(int voice) { + _voices[voice].isSustained = false; + setNote(voice, _voices[voice].note, 0); + _voices[voice].note = -1; + _voices[voice].age = 0; +} + +void MidiDriver_Adlib::setNote(int voice, int note, bool key) { + int n, fre, oct; + float delta; + int bend = _channels[_voices[voice].channel].pitchWheel; + + _voices[voice].note = note; + + delta = 0; + + n = note % 12; + + if (bend < 8192) + bend = 8192 - bend; + delta = pow(2.0, (float)(bend % 8192) / 8192.0); + + if (bend > 8192) + fre = (int)(ym3812_note[n] * delta); + else + fre = (int)(ym3812_note[n] / delta); + + oct = note / 12 - 1; + + if (oct < 0) + oct = 0; + + if (oct > 7) + oct = 7; + + setRegister(0xA0 + voice, fre & 0xff); + setRegister(0xB0 + voice, (key << 5) | (oct << 2) | (fre >> 8)); +// FIXME +#ifdef ADLIB_SCI1 + int velocity = _channels[_voices[voice].channel].volume + 1; + velocity = velocity * (velocityMap1[_voices[voice].velocity] + 1) / 64; + velocity = velocity * (_masterVolume + 1) / 16; + + if (--velocity < 0) + velocity = 0; +#else + int velocity = _masterVolume + 3; + if (velocity > 15) + velocity = 15; + velocity *= 4; +#endif + if (!_playSwitch) + velocity = 0; + + setVelocity(voice, velocity); +} + +void MidiDriver_Adlib::setVelocity(int voice, int velocity) { + AdlibPatch &patch = _patches[_voices[voice].patch]; + int pan = _channels[_voices[voice].channel].pan; + setVelocityReg(registerOffset[voice] + 3, velocity, pan, patch.op[1]); + + // In AM mode we need to set the level for both operators + if (_patches[_voices[voice].patch].mod.algorithm == 1) + setVelocityReg(registerOffset[voice], velocity, pan, patch.op[0]); +} + +void MidiDriver_Adlib::setVelocityReg(int regOffset, int velocity, int pan, AdlibOperator &op) { +// FIXME +#ifdef ADLIB_SCI1 + int vel = (velocityMap2[velocity] * (63 - op.totalLevel) / 63); +#else + int vel = (velocity / 4 * ((63 - op.totalLevel) / 15)); +#endif + + if (isStereo()) { + int velLeft = vel; + int velRight = vel; + + if (pan > 0x40) + velLeft = velLeft * (0x7f - pan) / 0x3f; + else if (pan < 0x40) + velRight = velRight * pan / 0x40; + + setRegister(0x40 + regOffset, (op.kbScaleLevel << 6) | (63 - velLeft), kLeftChannel); + setRegister(0x40 + regOffset, (op.kbScaleLevel << 6) | (63 - velRight), kRightChannel); + } else { + setRegister(0x40 + regOffset, (op.kbScaleLevel << 6) | (63 - vel)); + } +} + +void MidiDriver_Adlib::setPatch(int voice, int patch) { + AdlibModulator &mod = _patches[patch].mod; + + // Set the common settings for both operators + setOperator(registerOffset[voice], _patches[patch].op[0]); + setOperator(registerOffset[voice] + 3, _patches[patch].op[1]); + + // Set the additional settings for the modulator + setRegister(0xC0 + voice, (mod.feedback << 1) | mod.algorithm); +} + +void MidiDriver_Adlib::setOperator(int reg, AdlibOperator &op) { + setRegister(0x40 + reg, (op.kbScaleLevel << 6) | op.totalLevel); + setRegister(0x60 + reg, (op.attackRate << 4) | op.decayRate); + setRegister(0x80 + reg, (op.sustainLevel << 4) | op.releaseRate); + setRegister(0x20 + reg, (op.amplitudeMod << 7) | (op.vibrato << 6) + | (op.envelopeType << 5) | (op.kbScaleRate << 4) | op.frequencyMult); + setRegister(0xE0 + reg, op.waveForm); +} + +void MidiDriver_Adlib::setRegister(int reg, int value, int channels) { + if (channels & kLeftChannel) { + OPLWrite(_fmopl[0], 0x388, reg); + OPLWrite(_fmopl[0], 0x389, value); + } + + if (isStereo()) { + if (channels & kRightChannel) { + OPLWrite(_fmopl[1], 0x388, reg); + OPLWrite(_fmopl[1], 0x389, value); + } + } +} + +void MidiDriver_Adlib::playSwitch(bool play) { + _playSwitch = play; + renewNotes(-1, play); +} + +int MidiPlayer_Adlib::open(ResourceManager *resmgr) { + // Load up the patch.003 file, parse out the instruments + Resource *res = resmgr->findResource(kResourceTypePatch, 3, 0); + + if (!res) { + error("ADLIB: Failed to load patch.003"); + return -1; + } + + if (res->size < 1344) { + error("ADLIB: Expected patch.003 of at least %d bytes, got %d", 1344, res->size); + return -1; + } + + for (int i = 0; i < 48; i++) + _driver->sysEx(res->data + (28 * i), 28); + + if (res->size > 1344) + for (int i = 48; i < 96; i++) + _driver->sysEx(res->data + 2 + (28 * i), 28); + + return _driver->open(); +} + +} // End of namespace Sci diff --git a/engines/sci/sfx/softseq/adlib.h b/engines/sci/sfx/softseq/adlib.h new file mode 100644 index 0000000000..836a92ef07 --- /dev/null +++ b/engines/sci/sfx/softseq/adlib.h @@ -0,0 +1,153 @@ +/* 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/fmopl.h" + +namespace Sci { + +class MidiDriver_Adlib : public MidiDriver_Emulated { +public: + enum { + kVoices = 9 + }; + + enum ChannelID { + kLeftChannel = 1, + kRightChannel = 2 + }; + + struct AdlibOperator { + bool amplitudeMod; + bool vibrato; + bool envelopeType; + bool kbScaleRate; + byte frequencyMult; // (0-15) + byte kbScaleLevel; // (0-3) + byte totalLevel; // (0-63, 0=max, 63=min) + byte attackRate; // (0-15) + byte decayRate; // (0-15) + byte sustainLevel; // (0-15) + byte releaseRate; // (0-15) + byte waveForm; // (0-3) + }; + + struct AdlibModulator { + byte feedback; // (0-7) + bool algorithm; + }; + + struct AdlibPatch { + AdlibOperator op[2]; + AdlibModulator mod; + }; + + struct Channel { + uint8 patch; // Patch setting + uint8 volume; // Channel volume (0-63) + uint8 pan; // Pan setting (0-127, 64 is center) + uint8 holdPedal; // Hold pedal setting (0 to 63 is off, 127 to 64 is on) + uint8 extraVoices; // The number of additional voices this channel optimally needs + uint16 pitchWheel; // Pitch wheel setting (0-16383, 8192 is center) + uint8 lastVoice; // Last voice used for this MIDI channel + + Channel() : patch(0), volume(63), pan(64), holdPedal(0), extraVoices(0), pitchWheel(8192), lastVoice(0) { } + }; + + struct AdlibVoice { + int8 channel; // MIDI channel that this voice is assigned to or -1 + int8 note; // Currently playing MIDI note or -1 + int8 patch; // Currently playing patch or -1 + uint8 velocity; // Note velocity + bool isSustained; // Flag indicating a note that is being sustained by the hold pedal + uint16 age; // Age of the current note + + AdlibVoice() : channel(-1), note(-1), patch(-1), velocity(0), isSustained(false), age(0) { } + }; + + MidiDriver_Adlib(Audio::Mixer *mixer) : MidiDriver_Emulated(mixer), _playSwitch(true), _masterVolume(15) { } + ~MidiDriver_Adlib() { } + + // MidiDriver + int open(); + void close(); + void send(uint32 b); + MidiChannel *allocateChannel() { return NULL; } + MidiChannel *getPercussionChannel() { return NULL; } + + // AudioStream + bool isStereo() const { return _stereo; } + int getRate() const { return _mixer->getOutputRate(); } + + // MidiDriver_Emulated + void generateSamples(int16 *buf, int len); + + void setVolume(byte volume); + void playSwitch(bool play); + +private: + bool _stereo; + FM_OPL *_fmopl[2]; + bool _playSwitch; + int _masterVolume; + Channel _channels[MIDI_CHANNELS]; + AdlibVoice _voices[kVoices]; + Common::Array _patches; + + void sysEx(const byte *msg, uint16 length); + AdlibPatch *loadPatch(byte *data); + void voiceOn(int voice, int note, int velocity); + void voiceOff(int voice); + void setPatch(int voice, int patch); + void setNote(int voice, int note, bool key); + void setVelocity(int voice, int velocity); + void setOperator(int oper, AdlibOperator &op); + void setRegister(int reg, int value, int channels = kLeftChannel | kRightChannel); + void renewNotes(int channel, bool key); + void noteOn(int channel, int note, int velocity); + void noteOff(int channel, int note); + int findVoice(int channel); + void voiceMapping(int channel, int voices); + void assignVoices(int channel, int voices); + void releaseVoices(int channel, int voices); + void donateVoices(); + int findVoiceBasic(int channel); + void setVelocityReg(int regOffset, int velocity, int pan, AdlibOperator &op); +}; + +class MidiPlayer_Adlib : public MidiPlayer { +public: + MidiPlayer_Adlib() { _driver = new MidiDriver_Adlib(g_system->getMixer()); } + int open(ResourceManager *resmgr); + int getPlayMask() const { return 0x04; } + int getPolyphony() const { return MidiDriver_Adlib::kVoices; } + bool hasRhythmChannel() const { return false; } + void setVolume(byte volume) { static_cast(_driver)->setVolume(volume); } + void playSwitch(bool play) { static_cast(_driver)->playSwitch(play); } + void loadInstrument(int idx, byte *data); +}; + +} // End of namespace Sci + diff --git a/engines/sci/sfx/softseq/pcjr.cpp b/engines/sci/sfx/softseq/pcjr.cpp index b4afa1c593..108595e8a2 100644 --- a/engines/sci/sfx/softseq/pcjr.cpp +++ b/engines/sci/sfx/softseq/pcjr.cpp @@ -184,7 +184,7 @@ int MidiDriver_PCJr::open(int channels) { MidiDriver_Emulated::open(); - _mixer->playInputStream(Audio::Mixer::kPlainSoundType, &_mixerSoundHandle, this, -1, Audio::Mixer::kMaxChannelVolume * (int) _global_volume / 127); + _mixer->playInputStream(Audio::Mixer::kPlainSoundType, &_mixerSoundHandle, this, -1); return 0; } diff --git a/engines/sci/sfx/softseq/pcjr.h b/engines/sci/sfx/softseq/pcjr.h index 6fd582e53f..8bfa5ab8b1 100644 --- a/engines/sci/sfx/softseq/pcjr.h +++ b/engines/sci/sfx/softseq/pcjr.h @@ -25,8 +25,6 @@ #include "sci/sfx/sci_midi.h" -#include "sound/softsynth/emumidi.h" - namespace Sci { class MidiDriver_PCJr : public MidiDriver_Emulated { -- cgit v1.2.3