diff options
author | Johannes Schickel | 2010-09-17 20:03:20 +0000 |
---|---|---|
committer | Johannes Schickel | 2010-09-17 20:03:20 +0000 |
commit | eb400c87876a306339d27b8ec8510cff2f3abee0 (patch) | |
tree | 46055633cd623d25ea6ce09062c5cadd19b00a9c /engines/sci/sound | |
parent | a6a49b284a43ba04a6b00dd0157daae050074c3e (diff) | |
download | scummvm-rg350-eb400c87876a306339d27b8ec8510cff2f3abee0.tar.gz scummvm-rg350-eb400c87876a306339d27b8ec8510cff2f3abee0.tar.bz2 scummvm-rg350-eb400c87876a306339d27b8ec8510cff2f3abee0.zip |
SCI: Add CMS driver for SCI1-SCI1.1.
svn-id: r52781
Diffstat (limited to 'engines/sci/sound')
-rw-r--r-- | engines/sci/sound/drivers/cms.cpp | 636 | ||||
-rw-r--r-- | engines/sci/sound/drivers/mididriver.h | 1 | ||||
-rw-r--r-- | engines/sci/sound/music.cpp | 15 |
3 files changed, 651 insertions, 1 deletions
diff --git a/engines/sci/sound/drivers/cms.cpp b/engines/sci/sound/drivers/cms.cpp new file mode 100644 index 0000000000..7f3ffa014e --- /dev/null +++ b/engines/sci/sound/drivers/cms.cpp @@ -0,0 +1,636 @@ +/* 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/sound/drivers/mididriver.h" + +#include "sound/softsynth/emumidi.h" +#include "sound/softsynth/cms.h" +#include "sound/mixer.h" + +#include "sci/resource.h" + +namespace Sci { + +class MidiDriver_CMS : public MidiDriver_Emulated { +public: + MidiDriver_CMS(Audio::Mixer *mixer, ResourceManager *resMan) + : MidiDriver_Emulated(mixer), _resMan(resMan), _cms(0), _rate(0), _playSwitch(true), _masterVolume(0) { + } + + int open(); + void close(); + + void send(uint32 b); + uint32 property(int prop, uint32 param); + + MidiChannel *allocateChannel() { return 0; } + MidiChannel *getPercussionChannel() { return 0; } + + bool isStereo() const { return true; } + int getRate() const { return _rate; } + + void playSwitch(bool play); +private: + void generateSamples(int16 *buffer, int len); + + ResourceManager *_resMan; + CMSEmulator *_cms; + + void writeToChip1(int address, int data); + void writeToChip2(int address, int data); + + int32 _samplesPerCallback; + int32 _samplesPerCallbackRemainder; + int32 _samplesTillCallback; + int32 _samplesTillCallbackRemainder; + + int _rate; + bool _playSwitch; + uint16 _masterVolume; + + uint8 *_patchData; + + struct Channel { + Channel() + : patch(0), volume(0), pan(0x40), hold(0), extraVoices(0), + pitchWheel(0x2000), pitchModifier(0), pitchAdditive(false), + lastVoiceUsed(0) { + } + + uint8 patch; + uint8 volume; + uint8 pan; + uint8 hold; + uint8 extraVoices; + uint16 pitchWheel; + uint8 pitchModifier; + bool pitchAdditive; + uint8 lastVoiceUsed; + }; + + Channel _channel[16]; + + struct Voice { + Voice() : channel(0xFF), note(0xFF), sustained(0xFF), ticks(0), + turnOffTicks(0), patchDataPtr(0), patchDataIndex(0), + amplitudeTimer(0), amplitudeModifier(0), turnOff(false), + velocity(0) { + } + + uint8 channel; + uint8 note; + uint8 sustained; + uint16 ticks; + uint16 turnOffTicks; + const uint8 *patchDataPtr; + uint8 patchDataIndex; + uint8 amplitudeTimer; + uint8 amplitudeModifier; + bool turnOff; + uint8 velocity; + }; + + Voice _voice[12]; + + void voiceOn(int voice, int note, int velocity); + void voiceOff(int voice); + + void noteSend(int voice); + + void noteOn(int channel, int note, int velocity); + void noteOff(int channel, int note); + void controlChange(int channel, int control, int value); + void pitchWheel(int channel, int value); + + int findVoiceBasic(int channel); + + void updateVoiceAmplitude(int voice); + void setupVoiceAmplitude(int voice); + + uint8 _octaveRegs[2][3]; + + static const int _timerFreq = 60; + + static const int _frequencyTable[]; + static const int _velocityTable[]; +}; + +const int MidiDriver_CMS::_frequencyTable[] = { + 3, 10, 17, 24, + 31, 38, 46, 51, + 58, 64, 71, 77, + 83, 89, 95, 101, + 107, 113, 119, 124, + 130, 135, 141, 146, + 151, 156, 162, 167, + 172, 177, 182, 186, + 191, 196, 200, 205, + 209, 213, 217, 222, + 226, 230, 234, 238, + 242, 246, 250, 253 +}; + +const int MidiDriver_CMS::_velocityTable[] = { + 1, 3, 6, 8, 9, 10, 11, 12, + 12, 13, 13, 14, 14, 14, 15, 15, + 0, 1, 2, 2, 3, 4, 4, 5, + 6, 6, 7, 8, 8, 9, 10, 10 +}; + +int MidiDriver_CMS::open() { + if (_cms) + return MERR_ALREADY_OPEN; + + assert(_resMan); + Resource *res = _resMan->findResource(ResourceId(kResourceTypePatch, 101), 0); + if (!res) + return -1; + + _patchData = new uint8[res->size]; + memcpy(_patchData, res->data, res->size); + + for (uint i = 0; i < ARRAYSIZE(_channel); ++i) + _channel[i] = Channel(); + + for (uint i = 0; i < ARRAYSIZE(_voice); ++i) + _voice[i] = Voice(); + + _rate = _mixer->getOutputRate(); + _cms = new CMSEmulator(_rate); + assert(_cms); + _playSwitch = true; + _masterVolume = 0; + + for (int i = 0; i < 31; ++i) { + writeToChip1(i, 0); + writeToChip2(i, 0); + } + + writeToChip1(0x14, 0xFF); + writeToChip2(0x14, 0xFF); + + writeToChip1(0x1C, 1); + writeToChip2(0x1C, 1); + + _samplesPerCallback = getRate() / _timerFreq; + _samplesPerCallbackRemainder = getRate() % _timerFreq; + _samplesTillCallback = 0; + _samplesTillCallbackRemainder = 0; + + int retVal = MidiDriver_Emulated::open(); + if (retVal != 0) + return retVal; + + _mixer->playStream(Audio::Mixer::kPlainSoundType, &_mixerSoundHandle, this, -1, _mixer->kMaxChannelVolume, 0, DisposeAfterUse::NO); + return 0; +} + +void MidiDriver_CMS::close() { + _mixer->stopHandle(_mixerSoundHandle); + + delete[] _patchData; + delete _cms; + _cms = 0; +} + +void MidiDriver_CMS::send(uint32 b) { + const uint8 command = b & 0xf0; + const uint8 channel = b & 0xf; + const uint8 op1 = (b >> 8) & 0xff; + const uint8 op2 = (b >> 16) & 0xff; + + switch (command) { + case 0x80: + noteOff(channel, op1); + break; + + case 0x90: + noteOn(channel, op1, op2); + break; + + case 0xB0: + controlChange(channel, op1, op2); + break; + + case 0xC0: + _channel[channel].patch = op1; + break; + + case 0xE0: + pitchWheel(channel, (op1 & 0x7f) | ((op2 & 0x7f) << 7)); + break; + + default: + break; + } +} + +uint32 MidiDriver_CMS::property(int prop, uint32 param) { + switch (prop) { + case MIDI_PROP_MASTER_VOLUME: + if (param != 0xffff) + _masterVolume = param; + return _masterVolume; + + default: + return MidiDriver_Emulated::property(prop, param); + } +} + +void MidiDriver_CMS::playSwitch(bool play) { + _playSwitch = play; +} + +void MidiDriver_CMS::writeToChip1(int address, int data) { + _cms->portWrite(0x221, address); + _cms->portWrite(0x220, data); + + if (address >= 16 && address <= 18) + _octaveRegs[0][address - 16] = data; +} + +void MidiDriver_CMS::writeToChip2(int address, int data) { + _cms->portWrite(0x223, address); + _cms->portWrite(0x222, data); + + if (address >= 16 && address <= 18) + _octaveRegs[1][address - 16] = data; +} + +void MidiDriver_CMS::voiceOn(int voiceNr, int note, int velocity) { + Voice &voice = _voice[voiceNr]; + voice.note = note; + voice.turnOff = false; + voice.patchDataIndex = 0; + voice.amplitudeTimer = 0; + voice.ticks = 0; + voice.turnOffTicks = 0; + voice.patchDataPtr = _patchData + READ_LE_UINT16(&_patchData[_channel[voice.channel].patch * 2]); + if (velocity) + velocity = _velocityTable[(velocity >> 3)]; + voice.velocity = velocity; + noteSend(voiceNr); +} + +void MidiDriver_CMS::voiceOff(int voiceNr) { + Voice &voice = _voice[voiceNr]; + voice.velocity = 0; + voice.note = 0xFF; + voice.sustained = 0; + voice.turnOff = false; + voice.patchDataIndex = 0; + voice.amplitudeTimer = 0; + voice.amplitudeModifier = 0; + voice.ticks = 0; + voice.turnOffTicks = 0; + + setupVoiceAmplitude(voiceNr); +} + +void MidiDriver_CMS::noteSend(int voiceNr) { + Voice &voice = _voice[voiceNr]; + + int frequency = (CLIP<int>(voice.note, 21, 116) - 21) * 4; + if (_channel[voice.channel].pitchModifier) { + int modifier = _channel[voice.channel].pitchModifier; + + if (!_channel[voice.channel].pitchAdditive) { + if (frequency > modifier) + frequency -= modifier; + else + frequency = 0; + } else { + int tempFrequency = 384 - frequency; + if (modifier < tempFrequency) + frequency += modifier; + else + frequency = 383; + } + } + + int chipNumber = 0; + if (voiceNr >= 6) { + voiceNr -= 6; + chipNumber = 1; + } + + int octave = 0; + while (frequency >= 48) { + frequency -= 48; + ++octave; + } + + frequency = _frequencyTable[frequency]; + + if (chipNumber == 1) + writeToChip2(8 + voiceNr, frequency); + else + writeToChip1(8 + voiceNr, frequency); + + uint8 octaveData = _octaveRegs[chipNumber][voiceNr >> 1]; + + if (voiceNr & 1) { + octaveData &= 0x0F; + octaveData |= (octave << 4); + } else { + octaveData &= 0xF0; + octaveData |= octave; + } + + if (chipNumber == 1) + writeToChip2(0x10 + (voiceNr >> 1), octaveData); + else + writeToChip1(0x10 + (voiceNr >> 1), octaveData); +} + +void MidiDriver_CMS::noteOn(int channel, int note, int velocity) { + if (note < 21 || note > 116) + return; + + if (velocity == 0) { + noteOff(channel, note); + return; + } + + for (uint i = 0; i < ARRAYSIZE(_voice); ++i) { + if (_voice[i].channel == channel && _voice[i].note == note) { + _voice[i].sustained = 0; + voiceOff(i); + voiceOn(i, note, velocity); + return; + } + } + + int voice = findVoiceBasic(channel); + if (voice != -1) + voiceOn(voice, note, velocity); +} + +int MidiDriver_CMS::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 < ARRAYSIZE(_voice); i++) { + int v = (_channel[channel].lastVoiceUsed + i + 1) % ARRAYSIZE(_voice); + + if (_voice[v].note == 0xFF) { + voice = v; + break; + } + + // We also keep track of the oldest note in case the search fails + if (_voice[v].ticks > oldestAge) { + oldestAge = _voice[v].ticks; + oldestVoice = v; + } + } + + if (voice == -1) { + if (oldestVoice != -1) { + voiceOff(oldestVoice); + voice = oldestVoice; + } else { + return -1; + } + } + + _voice[voice].channel = channel; + _channel[channel].lastVoiceUsed = voice; + return voice; +} + +void MidiDriver_CMS::noteOff(int channel, int note) { + for (uint i = 0; i < ARRAYSIZE(_voice); ++i) { + if (_voice[i].channel == channel && _voice[i].note == note) { + if (_channel[channel].hold != 0) + _voice[i].sustained = true; + else + _voice[i].turnOff = true; + } + } +} + +void MidiDriver_CMS::controlChange(int channel, int control, int value) { + switch (control) { + case 7: + if (value) { + value >>= 3; + if (!value) + ++value; + } + + _channel[channel].volume = value; + break; + + case 10: + _channel[channel].pan = value; + break; + + case 64: + _channel[channel].hold = value; + + if (!value) { + for (uint i = 0; i < ARRAYSIZE(_voice); ++i) { + if (_voice[i].channel == channel && _voice[i].sustained) { + _voice[i].sustained = 0; + _voice[i].turnOff = true; + } + } + } + break; + + case 75: + //ChannelMapping + break; + + case 123: + for (uint i = 0; i < ARRAYSIZE(_voice); ++i) { + if (_voice[i].channel == channel && _voice[i].note != 0xFF) + voiceOff(i); + } + break; + + default: + return; + } +} + +void MidiDriver_CMS::pitchWheel(int channelNr, int value) { + Channel &channel = _channel[channelNr]; + channel.pitchWheel = value; + channel.pitchAdditive = false; + channel.pitchModifier = 0; + + if (value < 0x2000) { + channel.pitchModifier = (0x2000 - value) / 170; + } else if (value > 0x2000) { + channel.pitchModifier = (value - 0x2000) / 170; + channel.pitchAdditive = true; + } + + for (uint i = 0; i < ARRAYSIZE(_voice); ++i) { + if (_voice[i].channel == channelNr && _voice[i].note != 0xFF) + noteSend(i); + } +} + +void MidiDriver_CMS::updateVoiceAmplitude(int voiceNr) { + Voice &voice = _voice[voiceNr]; + + if (voice.amplitudeTimer != 0 && voice.amplitudeTimer != 254) { + --voice.amplitudeTimer; + return; + } else if (voice.amplitudeTimer == 254) { + if (!voice.turnOff) + return; + + voice.amplitudeTimer = 0; + } + + int nextDataIndex = voice.patchDataIndex; + uint8 timerData = 0; + uint8 amplitudeData = voice.patchDataPtr[nextDataIndex]; + + if (amplitudeData == 255) { + timerData = amplitudeData = 0; + voiceOff(voiceNr); + } else { + timerData = voice.patchDataPtr[nextDataIndex + 1]; + nextDataIndex += 2; + } + + voice.patchDataIndex = nextDataIndex; + voice.amplitudeTimer = timerData; + voice.amplitudeModifier = amplitudeData; +} + +void MidiDriver_CMS::setupVoiceAmplitude(int voiceNr) { + Voice &voice = _voice[voiceNr]; + uint amplitude = 0; + + if (_channel[voice.channel].volume && voice.velocity + && voice.amplitudeModifier && _masterVolume) { + amplitude = _channel[voice.channel].volume * voice.velocity; + amplitude /= 0x0F; + amplitude *= voice.amplitudeModifier; + amplitude /= 0x0F; + amplitude *= _masterVolume; + amplitude /= 0x0F; + + if (!amplitude) + ++amplitude; + } + + uint8 amplitudeData = 0; + int pan = _channel[voice.channel].pan >> 2; + if (pan >= 16) { + amplitudeData = (amplitude * (31 - pan) / 0x0F) & 0x0F; + amplitudeData |= (amplitude << 4); + } else { + amplitudeData = (amplitude * pan / 0x0F) & 0x0F; + amplitudeData <<= 4; + amplitudeData |= amplitude; + } + + if (!_playSwitch) + amplitudeData = 0; + + if (voiceNr >= 6) + writeToChip2(voiceNr - 6, amplitudeData); + else + writeToChip1(voiceNr, amplitudeData); +} + +void MidiDriver_CMS::generateSamples(int16 *buffer, int len) { + while (len) { + if (!_samplesTillCallback) { + for (uint i = 0; i < ARRAYSIZE(_voice); ++i) { + if (_voice[i].note == 0xFF) + continue; + + ++_voice[i].ticks; + if (_voice[i].turnOff) + ++_voice[i].turnOffTicks; + + updateVoiceAmplitude(i); + setupVoiceAmplitude(i); + } + + _samplesTillCallback = _samplesPerCallback; + _samplesTillCallbackRemainder += _samplesPerCallbackRemainder; + if (_samplesTillCallbackRemainder >= _timerFreq) { + _samplesTillCallback++; + _samplesTillCallbackRemainder -= _timerFreq; + } + } + + int32 render = MIN(len, _samplesTillCallback); + len -= render; + _samplesTillCallback -= render; + _cms->readBuffer(buffer, render); + buffer += render * 2; + } +} + + +class MidiPlayer_CMS : public MidiPlayer { +public: + MidiPlayer_CMS(SciVersion version) : MidiPlayer(version) { + } + + int open(ResourceManager *resMan) { + if (_driver) + return MERR_ALREADY_OPEN; + + _driver = new MidiDriver_CMS(g_system->getMixer(), resMan); + int driverRetVal = _driver->open(); + if (driverRetVal != 0) + return driverRetVal; + + return 0; + } + + void close() { + _driver->setTimerCallback(0, 0); + _driver->close(); + delete _driver; + _driver = 0; + } + + bool hasRhythmChannel() const { return false; } + byte getPlayId() const { return 9; } + int getPolyphony() const { return 12; } + + void playSwitch(bool play) { static_cast<MidiDriver_CMS *>(_driver)->playSwitch(play); } +}; + +MidiPlayer *MidiPlayer_CMS_create(SciVersion version) { + return new MidiPlayer_CMS(version); +} + +} // End of namespace SCI + diff --git a/engines/sci/sound/drivers/mididriver.h b/engines/sci/sound/drivers/mididriver.h index e1e4f4fcb8..cf88f3162c 100644 --- a/engines/sci/sound/drivers/mididriver.h +++ b/engines/sci/sound/drivers/mididriver.h @@ -117,6 +117,7 @@ extern MidiPlayer *MidiPlayer_AdLib_create(SciVersion version); extern MidiPlayer *MidiPlayer_AmigaMac_create(SciVersion version); extern MidiPlayer *MidiPlayer_PCJr_create(SciVersion version); extern MidiPlayer *MidiPlayer_PCSpeaker_create(SciVersion version); +extern MidiPlayer *MidiPlayer_CMS_create(SciVersion version); extern MidiPlayer *MidiPlayer_Midi_create(SciVersion version); extern MidiPlayer *MidiPlayer_Fb01_create(SciVersion version); diff --git a/engines/sci/sound/music.cpp b/engines/sci/sound/music.cpp index 61abc8f29f..dbbc28f25f 100644 --- a/engines/sci/sound/music.cpp +++ b/engines/sci/sound/music.cpp @@ -66,7 +66,17 @@ void SciMusic::init() { // Default to MIDI in SCI2.1+ games, as many don't have AdLib support. Common::Platform platform = g_sci->getPlatform(); - uint32 dev = MidiDriver::detectDevice((getSciVersion() >= SCI_VERSION_2_1) ? (MDT_PCSPK | MDT_PCJR | MDT_ADLIB | MDT_MIDI | MDT_PREFER_GM) : (MDT_PCSPK | MDT_PCJR | MDT_ADLIB | MDT_MIDI)); + + uint32 deviceFlags = MDT_PCSPK | MDT_PCJR | MDT_ADLIB | MDT_MIDI; + + if (getSciVersion() >= SCI_VERSION_2_1) + deviceFlags |= MDT_PREFER_GM; + + // Currently our CMS implementation only supports SCI1(.1) + if (getSciVersion() >= SCI_VERSION_1_EGA && getSciVersion() <= SCI_VERSION_1_1) + deviceFlags |= MDT_CMS; + + uint32 dev = MidiDriver::detectDevice(deviceFlags); _musicType = MidiDriver::getMusicType(dev); switch (_musicType) { @@ -83,6 +93,9 @@ void SciMusic::init() { case MT_PCSPK: _pMidiDrv = MidiPlayer_PCSpeaker_create(_soundVersion); break; + case MT_CMS: + _pMidiDrv = MidiPlayer_CMS_create(_soundVersion); + break; default: if (ConfMan.getBool("native_fb01")) _pMidiDrv = MidiPlayer_Fb01_create(_soundVersion); |