diff options
-rw-r--r-- | engines/sci/module.mk | 1 | ||||
-rw-r--r-- | engines/sci/sound/drivers/fmtowns.cpp | 637 | ||||
-rw-r--r-- | engines/sci/sound/drivers/mididriver.h | 1 | ||||
-rw-r--r-- | engines/sci/sound/music.cpp | 6 |
4 files changed, 645 insertions, 0 deletions
diff --git a/engines/sci/module.mk b/engines/sci/module.mk index 25fbbab207..90a0f33f06 100644 --- a/engines/sci/module.mk +++ b/engines/sci/module.mk @@ -71,6 +71,7 @@ MODULE_OBJS := \ sound/drivers/amigamac.o \ sound/drivers/cms.o \ sound/drivers/fb01.o \ + sound/drivers/fmtowns.o \ sound/drivers/midi.o \ sound/drivers/pcjr.o \ video/seq_decoder.o diff --git a/engines/sci/sound/drivers/fmtowns.cpp b/engines/sci/sound/drivers/fmtowns.cpp new file mode 100644 index 0000000000..afa0691d77 --- /dev/null +++ b/engines/sci/sound/drivers/fmtowns.cpp @@ -0,0 +1,637 @@ +/* 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. + * + */ + +#include "sci/sci.h" + +#include "common/file.h" +#include "common/system.h" +#include "common/textconsole.h" + +#include "audio/softsynth/fmtowns_pc98/towns_audio.h" + +#include "sci/resource.h" +#include "sci/sound/drivers/mididriver.h" + +namespace Sci { + +class MidiDriver_FMTowns; + +class TownsChannel { +public: + TownsChannel(MidiDriver_FMTowns *driver, uint8 id); + ~TownsChannel() {} + + void noteOff(); + void noteOn(uint8 note, uint8 velo); + void pitchBend(int16 val); + void updateVolume(); + void updateDuration(); + + uint8 _assign; + uint8 _note; + uint8 _sustain; + uint16 _duration; + +private: + uint8 _id; + uint8 _velo; + uint8 _program; + + MidiDriver_FMTowns *_drv; +}; + +class TownsMidiPart { +friend class MidiDriver_FMTowns; +public: + TownsMidiPart(MidiDriver_FMTowns *driver, uint8 id); + ~TownsMidiPart() {} + + void noteOff(uint8 note); + void noteOn(uint8 note, uint8 velo); + void controlChangeVolume(uint8 vol); + void controlChangeSustain(uint8 sus); + void controlChangePolyphony(uint8 numChan); + void controlChangeAllNotesOff(); + void programChange(uint8 prg); + void pitchBend(int16 val); + + void addChannels(int num); + void dropChannels(int num); + + uint8 currentProgram() const; + +private: + int allocateChannel(); + + uint8 _id; + uint8 _program; + uint8 _volume; + uint8 _sustain; + uint8 _chanMissing; + int16 _pitchBend; + uint8 _outChan; + + MidiDriver_FMTowns *_drv; +}; + +class MidiDriver_FMTowns : public MidiDriver, public TownsAudioInterfacePluginDriver { +friend class TownsChannel; +friend class TownsMidiPart; +public: + MidiDriver_FMTowns(Audio::Mixer *mixer); + ~MidiDriver_FMTowns(); + + int open(); + void loadInstruments(const uint8 *data); + bool isOpen() const { return _isOpen; } + void close(); + + void send(uint32 b); + + uint32 property(int prop, uint32 param); + void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc); + + void setSoundOn(bool toggle); + + uint32 getBaseTempo(); + MidiChannel *allocateChannel() { return 0; } + MidiChannel *getPercussionChannel() { return 0; } + + uint8 currentProgram(); + + void timerCallback(int timerId); + +private: + int getChannelVolume(uint8 midiPart); + void addMissingChannels(); + + void updateParser(); + void updateChannels(); + + Common::TimerManager::TimerProc _timerProc; + void *_timerProcPara; + + TownsMidiPart **_parts; + TownsChannel **_out; + + uint8 _masterVolume; + + bool _soundOn; + + bool _isOpen; + bool _ready; + + const uint16 _baseTempo; + + TownsAudioInterface *_intf; +}; + +class MidiPlayer_FMTowns : public MidiPlayer { +public: + MidiPlayer_FMTowns(SciVersion version); + ~MidiPlayer_FMTowns(); + + int open(ResourceManager *resMan); + + bool hasRhythmChannel() const; + byte getPlayId() const; + int getPolyphony() const; + void playSwitch(bool play); + +private: + MidiDriver_FMTowns *_townsDriver; +}; + +TownsChannel::TownsChannel(MidiDriver_FMTowns *driver, uint8 id) : _drv(driver), _id(id), _assign(0xff), _note(0xff), _velo(0), _sustain(0), _duration(0), _program(0xff) { +} + +void TownsChannel::noteOn(uint8 note, uint8 velo) { + _duration = 0; + + if (_program != _drv->_parts[_assign]->currentProgram() && _drv->_soundOn) { + _program = _drv->_parts[_assign]->currentProgram(); + _drv->_intf->callback(4, _id, _program); + } + + _note = note; + _velo = velo; + _drv->_intf->callback(1, _id, _note, _velo); +} + +void TownsChannel::noteOff() { + if (_sustain) + return; + + _drv->_intf->callback(2, _id); + _note = 0xff; + _duration = 0; +} + +void TownsChannel::pitchBend(int16 val) { + _drv->_intf->callback(7, _id, val); +} + +void TownsChannel::updateVolume() { + if (_assign > 15) + return; + _drv->_intf->callback(8, _id, _drv->getChannelVolume(_assign)); +} + +void TownsChannel::updateDuration() { + if (_note != 0xff) + _duration++; +} + +TownsMidiPart::TownsMidiPart(MidiDriver_FMTowns *driver, uint8 id) : _drv(driver), _id(id), _program(0), _volume(0x3f), _sustain(0), _chanMissing(0), _pitchBend(0x2000), _outChan(0) { +} + +void TownsMidiPart::noteOff(uint8 note) { + for (int i = 0; i < 6; i++) { + if (_drv->_out[i]->_assign != _id || _drv->_out[i]->_note != note) + continue; + if (_sustain) + _drv->_out[i]->_sustain = 1; + else + _drv->_out[i]->noteOff(); + return; + } +} + +void TownsMidiPart::noteOn(uint8 note, uint8 velo) { + if (note < 12 || note > 107) + return; + + if (velo == 0) { + noteOff(note); + return; + } + + velo >>= 1; + + for (int i = 0; i < 6; i++) { + if (_drv->_out[i]->_assign != _id || _drv->_out[i]->_note != note) + continue; + _drv->_out[i]->_sustain = 0; + _drv->_out[i]->noteOff(); + _drv->_out[i]->noteOn(note, velo); + return; + } + + int chan = allocateChannel(); + if (chan != -1) + _drv->_out[chan]->noteOn(note, velo); +} + +void TownsMidiPart::controlChangeVolume(uint8 vol) { + _volume = vol >> 1; + for (int i = 0; i < 6; i++) { + if (_drv->_out[i]->_assign == _id) + _drv->_out[i]->updateVolume(); + } +} + +void TownsMidiPart::controlChangeSustain(uint8 sus) { + _sustain = sus; + if (_sustain) + return; + + for (int i = 0; i < 6; i++) { + if (_drv->_out[i]->_assign == _id && _drv->_out[i]->_sustain) { + _drv->_out[i]->_sustain = 0; + _drv->_out[i]->noteOff(); + } + } +} + +void TownsMidiPart::controlChangePolyphony(uint8 numChan) { + uint8 numAssigned = 0; + for (int i = 0; i < 6; i++) { + if (_drv->_out[i]->_assign == _id) + numAssigned++; + } + + numAssigned += _chanMissing; + if (numAssigned < numChan) { + addChannels(numChan - numAssigned); + } else if (numAssigned > numChan) { + dropChannels(numAssigned - numChan); + _drv->addMissingChannels(); + } +} + +void TownsMidiPart::controlChangeAllNotesOff() { + for (int i = 0; i < 6; i++) { + if (_drv->_out[i]->_assign == _id && _drv->_out[i]->_note != 0xff) + _drv->_out[i]->noteOff(); + } +} + +void TownsMidiPart::programChange(uint8 prg) { + _program = prg; +} + +void TownsMidiPart::pitchBend(int16 val) { + _pitchBend = val; + val -= 0x2000; + for (int i = 0; i < 6; i++) { + if (_drv->_out[i]->_assign == _id) + _drv->_out[i]->pitchBend(val); + } +} + +void TownsMidiPart::addChannels(int num) { + for (int i = 0; i < 6; i++) { + if (_drv->_out[i]->_assign != 0xff) + continue; + + _drv->_out[i]->_assign = _id; + _drv->_out[i]->updateVolume(); + + if (_drv->_out[i]->_note != 0xff) + _drv->_out[i]->noteOff(); + + if (!--num) + break; + } + + _chanMissing += num; + programChange(_program); +} + +void TownsMidiPart::dropChannels(int num) { + if (_chanMissing == num) { + _chanMissing = 0; + return; + } else if (_chanMissing > num) { + _chanMissing -= num; + return; + } + + num -= _chanMissing; + _chanMissing = 0; + + for (int i = 0; i < 6; i++) { + if (_drv->_out[i]->_assign != _id || _drv->_out[i]->_note != 0xff) + continue; + _drv->_out[i]->_assign = 0xff; + if (!--num) + return; + } + + for (int i = 0; i < 6; i++) { + if (_drv->_out[i]->_assign != _id) + continue; + _drv->_out[i]->_sustain = 0; + _drv->_out[i]->noteOff(); + _drv->_out[i]->_assign = 0xff; + if (!--num) + return; + } +} + +uint8 TownsMidiPart::currentProgram() const { + return _program; +} + +int TownsMidiPart::allocateChannel() { + int chan = _outChan; + int ovrChan = 0; + int ld = 0; + bool found = false; + + for (bool loop = true; loop; ) { + if (++chan == 6) + chan = 0; + + if (chan == _outChan) + loop = false; + + if (_id == _drv->_out[chan]->_assign) { + if (_drv->_out[chan]->_note == 0xff) { + found = true; + break; + } + + if (_drv->_out[chan]->_duration >= ld) { + ld = _drv->_out[chan]->_duration; + ovrChan = chan; + } + } + } + + if (!found) { + if (!ld) + return -1; + chan = ovrChan; + _drv->_out[chan]->_sustain = 0; + _drv->_out[chan]->noteOff(); + } + + _outChan = chan; + return chan; +} + +MidiDriver_FMTowns::MidiDriver_FMTowns(Audio::Mixer *mixer) : _timerProc(0), _timerProcPara(0), _baseTempo(10080), _ready(false), _isOpen(false), _masterVolume(0x0f), _soundOn(true) { + _intf = new TownsAudioInterface(mixer, this, true); + _out = new TownsChannel*[6]; + for (int i = 0; i < 6; i++) + _out[i] = new TownsChannel(this, i); + _parts = new TownsMidiPart*[16]; + for (int i = 0; i < 16; i++) + _parts[i] = new TownsMidiPart(this, i); +} + +MidiDriver_FMTowns::~MidiDriver_FMTowns() { + delete _intf; + + if (_parts) { + for (int i = 0; i < 16; i++) { + delete _parts[i]; + _parts[i] = 0; + } + delete[] _parts; + _parts = 0; + } + + if (_out) { + for (int i = 0; i < 6; i++) { + delete _out[i]; + _out[i] = 0; + } + delete[] _out; + _out = 0; + } +} + +int MidiDriver_FMTowns::open() { + if (_isOpen) + return MERR_ALREADY_OPEN; + + if (!_ready) { + if (!_intf->init()) + return MERR_CANNOT_CONNECT; + + _intf->callback(0); + + _intf->callback(21, 255, 1); + _intf->callback(21, 0, 1); + _intf->callback(22, 255, 221); + + _intf->callback(33, 8); + _intf->setSoundEffectChanMask(~0x3f); + + _ready = true; + } + + _isOpen = true; + + return 0; +} + +void MidiDriver_FMTowns::loadInstruments(const uint8 *data) { + if (data) { + data += 6; + for (int i = 0; i < 128; i++) { + _intf->callback(5, 0, i, data); + data += 48; + } + } + _intf->callback(70, 3); + property(MIDI_PROP_MASTER_VOLUME, _masterVolume); +} + +void MidiDriver_FMTowns::close() { + _isOpen = false; +} + +void MidiDriver_FMTowns::send(uint32 b) { + if (!_isOpen) + return; + + byte para2 = (b >> 16) & 0xFF; + byte para1 = (b >> 8) & 0xFF; + byte cmd = b & 0xF0; + + TownsMidiPart *chan = _parts[b & 0x0F]; + + switch (cmd) { + case 0x80: + chan->noteOff(para1); + break; + case 0x90: + chan->noteOn(para1, para2); + break; + case 0xb0: + switch (para1) { + case 7: + chan->controlChangeVolume(para2); + break; + case 64: + chan->controlChangeSustain(para2); + break; + case SCI_MIDI_SET_POLYPHONY: + chan->controlChangePolyphony(para2); + break; + case SCI_MIDI_CHANNEL_NOTES_OFF: + chan->controlChangeAllNotesOff(); + break; + default: + break; + } + break; + case 0xc0: + chan->programChange(para1); + break; + case 0xe0: + chan->pitchBend(para1 | (para2 << 7)); + break; + default: + break; + } +} + +uint32 MidiDriver_FMTowns::property(int prop, uint32 param) { + switch(prop) { + case MIDI_PROP_MASTER_VOLUME: + if (param != 0xffff) { + _masterVolume = param; + for (int i = 0; i < 6; i++) + _out[i]->updateVolume(); + } + return _masterVolume; + default: + break; + } + return 0; +} + +void MidiDriver_FMTowns::setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) { + _timerProc = timer_proc; + _timerProcPara = timer_param; +} + +void MidiDriver_FMTowns::setSoundOn(bool toggle) { + _soundOn = toggle; +} + +uint32 MidiDriver_FMTowns::getBaseTempo() { + return _baseTempo; +} + +void MidiDriver_FMTowns::timerCallback(int timerId) { + if (!_isOpen) + return; + + switch (timerId) { + case 1: + updateParser(); + updateChannels(); + break; + default: + break; + } +} + +int MidiDriver_FMTowns::getChannelVolume(uint8 midiPart) { + static const uint8 volumeTable[] = { 0x00, 0x0D, 0x1B, 0x28, 0x36, 0x43, 0x51, 0x5F, 0x63, 0x67, 0x6B, 0x6F, 0x73, 0x77, 0x7B, 0x7F }; + int tableIndex = (_parts[midiPart]->_volume * (_masterVolume + 1)) >> 6; + assert(tableIndex < 16); + return volumeTable[tableIndex];; +} + +void MidiDriver_FMTowns::addMissingChannels() { + uint8 avlChan = 0; + for (int i = 0; i < 6; i++) { + if (_out[i]->_assign == 0xff) + avlChan++; + } + + if (!avlChan) + return; + + for (int i = 0; i < 16; i++) { + if (!_parts[i]->_chanMissing) + continue; + + if (_parts[i]->_chanMissing < avlChan) { + avlChan -= _parts[i]->_chanMissing; + uint8 m = _parts[i]->_chanMissing; + _parts[i]->_chanMissing = 0; + _parts[i]->addChannels(m); + } else { + _parts[i]->_chanMissing -= avlChan; + _parts[i]->addChannels(avlChan); + return; + } + } +} + +void MidiDriver_FMTowns::updateParser() { + if (_timerProc) + _timerProc(_timerProcPara); +} + +void MidiDriver_FMTowns::updateChannels() { + for (int i = 0; i < 6; i++) + _out[i]->updateDuration(); +} + +MidiPlayer_FMTowns::MidiPlayer_FMTowns(SciVersion version) : MidiPlayer(version) { + _driver = _townsDriver = new MidiDriver_FMTowns(g_system->getMixer()); +} + +MidiPlayer_FMTowns::~MidiPlayer_FMTowns() { + delete _driver; +} + +int MidiPlayer_FMTowns::open(ResourceManager *resMan) { + int result = MidiDriver::MERR_DEVICE_NOT_AVAILABLE; + if (_townsDriver) { + result = _townsDriver->open(); + if (!result) + _townsDriver->loadInstruments((resMan->findResource(ResourceId(kResourceTypePatch, 8), true))->data); + } + return result; +} + +bool MidiPlayer_FMTowns::hasRhythmChannel() const { + return false; +} + +byte MidiPlayer_FMTowns::getPlayId() const { + return 0x16; +} + +int MidiPlayer_FMTowns::getPolyphony() const { + return 6; +} + +void MidiPlayer_FMTowns::playSwitch(bool play) { + if (_townsDriver) + _townsDriver->setSoundOn(play); +} + +MidiPlayer *MidiPlayer_FMTowns_create(SciVersion _soundVersion) { + return new MidiPlayer_FMTowns(_soundVersion); +} + +} // End of namespace Sci
\ No newline at end of file diff --git a/engines/sci/sound/drivers/mididriver.h b/engines/sci/sound/drivers/mididriver.h index ec66984bd4..8938eef62f 100644 --- a/engines/sci/sound/drivers/mididriver.h +++ b/engines/sci/sound/drivers/mididriver.h @@ -130,6 +130,7 @@ 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); +extern MidiPlayer *MidiPlayer_FMTowns_create(SciVersion version); } // End of namespace Sci diff --git a/engines/sci/sound/music.cpp b/engines/sci/sound/music.cpp index 9610b6f847..6461c93988 100644 --- a/engines/sci/sound/music.cpp +++ b/engines/sci/sound/music.cpp @@ -76,6 +76,9 @@ void SciMusic::init() { if (getSciVersion() >= SCI_VERSION_1_EGA_ONLY && getSciVersion() <= SCI_VERSION_1_1) deviceFlags |= MDT_CMS; + if (g_sci->getPlatform() == Common::kPlatformFMTowns) + deviceFlags = MDT_TOWNS; + uint32 dev = MidiDriver::detectDevice(deviceFlags); _musicType = MidiDriver::getMusicType(dev); @@ -96,6 +99,9 @@ void SciMusic::init() { case MT_CMS: _pMidiDrv = MidiPlayer_CMS_create(_soundVersion); break; + case MT_TOWNS: + _pMidiDrv = MidiPlayer_FMTowns_create(_soundVersion); + break; default: if (ConfMan.getBool("native_fb01")) _pMidiDrv = MidiPlayer_Fb01_create(_soundVersion); |