diff options
author | Walter van Niftrik | 2009-05-03 21:11:09 +0000 |
---|---|---|
committer | Walter van Niftrik | 2009-05-03 21:11:09 +0000 |
commit | c7a5a17acfa1353c221be8c09576503b395c29ef (patch) | |
tree | 030348142c26e6c2bf1cc7864a60aa77b331088d /engines/sci/sfx/softseq/adlib.cpp | |
parent | 5e955ea04594810e0d46b05937412e52e5212a6a (diff) | |
download | scummvm-rg350-c7a5a17acfa1353c221be8c09576503b395c29ef.tar.gz scummvm-rg350-c7a5a17acfa1353c221be8c09576503b395c29ef.tar.bz2 scummvm-rg350-c7a5a17acfa1353c221be8c09576503b395c29ef.zip |
SCI: adlib support (work-in-progress) for the new music player.
svn-id: r40287
Diffstat (limited to 'engines/sci/sfx/softseq/adlib.cpp')
-rw-r--r-- | engines/sci/sfx/softseq/adlib.cpp | 601 |
1 files changed, 601 insertions, 0 deletions
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 |