diff options
author | athrxx | 2018-12-01 02:33:12 +0100 |
---|---|---|
committer | Filippos Karapetis | 2019-04-02 20:45:35 +0300 |
commit | acfc0c90ce740cbcc5b4b8662ee9c3ae84a6e013 (patch) | |
tree | 9a6b7cef47ae14ca767b2dbf8d4b3bd0c289017e /engines/sci/sound | |
parent | 3dc0c3ec5998cea96bd0426ec7eaae438c3beacd (diff) | |
download | scummvm-rg350-acfc0c90ce740cbcc5b4b8662ee9c3ae84a6e013.tar.gz scummvm-rg350-acfc0c90ce740cbcc5b4b8662ee9c3ae84a6e013.tar.bz2 scummvm-rg350-acfc0c90ce740cbcc5b4b8662ee9c3ae84a6e013.zip |
SCI: add PC-9801 sound driver
(supports SCI1 for now)
Diffstat (limited to 'engines/sci/sound')
-rw-r--r-- | engines/sci/sound/drivers/mididriver.h | 1 | ||||
-rw-r--r-- | engines/sci/sound/drivers/pc9801.cpp | 1488 | ||||
-rw-r--r-- | engines/sci/sound/music.cpp | 6 |
3 files changed, 1495 insertions, 0 deletions
diff --git a/engines/sci/sound/drivers/mididriver.h b/engines/sci/sound/drivers/mididriver.h index c7c0198a92..f51851bde6 100644 --- a/engines/sci/sound/drivers/mididriver.h +++ b/engines/sci/sound/drivers/mididriver.h @@ -133,6 +133,7 @@ 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); +extern MidiPlayer *MidiPlayer_PC9801_create(SciVersion version); } // End of namespace Sci diff --git a/engines/sci/sound/drivers/pc9801.cpp b/engines/sci/sound/drivers/pc9801.cpp new file mode 100644 index 0000000000..767779e875 --- /dev/null +++ b/engines/sci/sound/drivers/pc9801.cpp @@ -0,0 +1,1488 @@ +/* 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/pc98_audio.h" + +#include "sci/resource.h" +#include "sci/sound/drivers/mididriver.h" + +/* PC-98 sound driver for: + Quest for Glory 1 SCI_VERSION_0_LATE first driver type + Police Quest 2 SCI_VERSION_0_LATE second driver type + Castle of Dr. Brain SCI_VERSION_1_LATE third driver type + Space Quest IV SCI_VERSION_1_LATE third driver type + King's Quest V SCI_VERSION_1_LATE latest driver type +*/ + +//#define SCI_PC98_AUDIO_EXTENDED + +namespace Sci { + +class MidiPart_PC9801; +class SoundChannel_PC9801 { +public: + SoundChannel_PC9801(PC98AudioCore *pc98a, MidiPart_PC9801 **parts, SciVersion version, int type, bool &soundOn); + virtual ~SoundChannel_PC9801() {} + + void noteOff(); + void noteOn(uint8 note, uint8 velo); + + virtual void processNoteEvent(uint8 note, bool noteOn); + void update(); + void setVolume(uint8 volume); + + virtual void reset(); + + uint8 _assign; + uint8 _note; + uint8 _sustain; + uint16 _duration; + + static const uint8 *_instrumentData; + +protected: + enum ChannelStatusFlags { + kChanVbrEnable = 0x01, + kChanVbrRestartEnv = 0x02, + kChanKeyOn = 0x04, + kChanNgEnable = 0x08, + kChanNgRestartEnv = 0x10, + kChanNgDecrease = 0x20, + kChanVbrMode = 0x40, + kChanVbrDecrease = 0x80 + }; + + int recalculateFrequency(uint16 note, uint16 modifier, uint8 *destOctaveBlock, uint16 *destFrequency, uint8 *destVbrFrequencyModifier); + uint8 getVolume(); + virtual void processSounds(); + void programChangeInit(const uint8 *data); + void writeReg(uint8 part, uint8 reg, uint8 val); + + int8 _transpose; + uint8 _vbrInitialDelay; + uint8 _vbrEnvelopeTimer; + uint8 _vbrEnvelopeSpeed; + + uint16 _frequencyCourse; + uint8 _frequencyBlock; + uint8 _frequencyNoteModifier; + + uint8 _vbrDepthIncr; + uint8 _vbrDecrTime; + uint8 _vbrDepthDecr; + uint8 _vbrIncrTime; + uint8 _vbrSensitivity; + uint16 _vbrIncrStep; + uint16 _vbrDecrStep; + uint8 _vbrModulationTimer; + int16 _vbrFrequencyModifier; + uint8 _vbrCur; + uint8 _flags; + + const uint16 *_noteFrequency; + const uint16 *_noteFrequencyModifier; + const uint8 *_selectedInstrument; + +private: + virtual void programChange(uint8 program) = 0; + virtual void prepareFrequencyAndVolume(bool updateVolume); + virtual void sendSoundOnOff(bool noteOn) = 0; + virtual void sendFrequency() = 0; + virtual void sendVolume() = 0; + + uint8 _velo; + uint8 _volume; + uint8 _program; + bool &_soundOn; + + MidiPart_PC9801 **_parts; + PC98AudioCore *_pc98a; + const uint8 _noteRangeLow; + const uint8 _noteRangeHigh; + const int _type; + const SciVersion _version; +}; + +class SoundChannel_PC9801_FM4OP : public SoundChannel_PC9801 { +public: + SoundChannel_PC9801_FM4OP(uint8 id, PC98AudioCore *pc98a, MidiPart_PC9801 **parts, SciVersion version, uint8 patchSize, bool &soundOn); + virtual ~SoundChannel_PC9801_FM4OP() {} + +private: + void programChange(uint8 program); + void sendSoundOnOff(bool noteOn); + void sendVolume(); + void sendFrequency(); + + uint8 _operatorLevel[4]; + uint8 _carrier; + + const uint8 _regPrt; + const uint8 _regOffs; + const uint8 _patchSize; +}; + +class SoundChannel_PC9801_FM2OP : public SoundChannel_PC9801 { +public: + SoundChannel_PC9801_FM2OP(uint8 id, PC98AudioCore *pc98a, MidiPart_PC9801 **parts, SciVersion version, uint8 patchSize, bool &soundOn); + virtual ~SoundChannel_PC9801_FM2OP() {} + + void processNoteEvent(uint8 note, bool noteOn); + void reset(); + +private: + void programChange(uint8 program); + void prepareFrequencyAndVolume(bool updateVolume); + void processSounds(); + void sendSoundOnOff(bool noteOn); + void sendVolume(); + void sendFrequency(); + + uint8 _operatorLevel[2]; + uint8 _operatorFrqIndex[2]; + uint16 _frequencyCourse2; + uint8 _frequencyNoteModifier2; + int16 _vbrFrequencyModifier2; + uint16 _vbrIncrStep2; + uint16 _vbrDecrStep2; + uint8 _vbrCur2; + static uint8 _activeOperators; + + const uint16 *_opFreqOffset; + const uint8 _patchOffset; + const uint8 _regPrt; + const uint8 _regOffs; + const uint8 _patchSize; +}; + +class SoundChannel_PC9801_SSG : public SoundChannel_PC9801 { +public: + SoundChannel_PC9801_SSG(uint8 id, PC98AudioCore *pc98a, MidiPart_PC9801 **parts, SciVersion version, uint8 patchOffset, uint8 patchSize, bool &soundOn); + virtual ~SoundChannel_PC9801_SSG() {} + void reset(); + +private: + enum { + kEnvSSG_silent = 0x00, + kEnvSSG_decay = 0x01, + kEnvSSG_sustain = 0x02, + kEnvSSG_attack = 0x03, + kEnvSSG_keyOn = 0x10 + }; + + void programChange(uint8 program); + void processSounds(); + void sendSoundOnOff(bool noteOn); + void sendVolume(); + void sendFrequency(); + void updateNg(); + void sendActiveChannelsStatus(); + + uint8 _instrumentChanMask; + uint8 _chanDisableMask; + uint8 _currentLevel; + uint8 _ngPhaseStep; + uint8 _ngPhase; + uint8 _ngEnvelopeTimer; + uint8 _ngSpeed; + uint8 _ssgEnvelopeTimer; + uint8 _ssgLevel; + uint8 _ssgSpeed; + uint8 _ssgEnvelopeState; + + static uint8 _activeChannnelsStatus; + + const uint8 *_ngFreq; + const uint8 _regOffs; + const uint8 _patchOffset; + const uint8 _patchSize; +}; + +class MidiPart_PC9801 { +public: + MidiPart_PC9801(uint8 id, SoundChannel_PC9801 **channels, uint8 numChan, SciVersion version); + ~MidiPart_PC9801() {} + + 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, int resetMissingChannels = -1); + void dropChannels(int num); + + uint8 program() const { return _program; } + uint8 volume() const { return _volume; } + uint16 pitchBend() const { return _pitchBend; } + uint8 missingChannels() const { return _chanMissing; } + +private: + int allocateChannel(); + void assignFreeChannels(); + + uint8 _id; + uint8 _program; + uint8 _volume; + uint8 _sustain; + uint16 _pitchBend; + uint8 _outChan; + uint8 _chanMissing; + + SoundChannel_PC9801 **_chan; + const SciVersion _version; + const int _numChan; + const uint8 _noteRangeLow; + const uint8 _noteRangeHigh; +}; + +class MidiDriver_PC9801 : public MidiDriver, public PC98AudioPluginDriver { +public: + enum { + MIDI_PROP_PLAYSWITCH = 1, + MIDI_PROP_POLYPHONY = 2, + MIDI_PROP_CHANNEL_ID = 3 + }; + + MidiDriver_PC9801(Audio::Mixer *mixer, SciVersion version); + ~MidiDriver_PC9801(); + + int open(); + bool isOpen() const { return _isOpen; } + void close(); + + void send(uint32 b); + static void assignFreeChannels(int num); + uint32 property(int prop, uint32 param); + + void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc); + uint32 getBaseTempo(); + MidiChannel *allocateChannel() { return 0; } + MidiChannel *getPercussionChannel() { return 0; } + + void timerCallbackB(); + +private: + void loadInstruments(const SciSpan<const uint8> &data); + void updateParser(); + void updateChannels(); + void reset(); + + Common::TimerManager::TimerProc _timerProc; + void *_timerProcPara; + + static MidiPart_PC9801 **_parts; + SoundChannel_PC9801 **_chan; + uint8 *_instrumentData; + + uint8 _masterVolume; + bool _soundOn; + + uint8 _numChan; + uint8 _internalVersion; + uint8 _ssgPatchOffset; + uint8 _patchSize; + + const uint8 _playID; + const uint8 _channelMask1; + const uint8 _channelMask2; + + bool _isOpen; + bool _ready; + + const uint16 _baseTempo; + + PC98AudioCore *_pc98a; + const SciVersion _version; +}; + +class MidiPlayer_PC9801 : public MidiPlayer { +public: + enum { + MIDI_PROP_PLAYSWITCH = 1, + MIDI_PROP_POLYPHONY = 2, + MIDI_PROP_CHANNEL_ID = 3 + }; + + MidiPlayer_PC9801(SciVersion version); + ~MidiPlayer_PC9801(); + + bool hasRhythmChannel() const; + byte getPlayId() const; + int getPolyphony() const; + void playSwitch(bool play); +}; + +const uint8 *SoundChannel_PC9801::_instrumentData = 0; + +SoundChannel_PC9801::SoundChannel_PC9801(PC98AudioCore *pc98a, MidiPart_PC9801 **parts, SciVersion version, int type, bool &soundOn) : _pc98a(pc98a), _parts(parts), _version(version), +_type(type), _soundOn(soundOn), _assign(0xff), _note(0xff), _velo(0x7F), _volume(0), _transpose(0), _sustain(0), _duration(0), _program(0xff), _vbrInitialDelay(0), +_vbrEnvelopeTimer(0), _vbrEnvelopeSpeed(0), _vbrDepthIncr(0), _vbrDecrTime(0), _vbrDepthDecr(0), _vbrIncrTime(0), _vbrSensitivity(0), _vbrFrequencyModifier(0), _vbrIncrStep(0), +_vbrDecrStep(0), _vbrModulationTimer(0), _flags(0), _vbrCur(0x80), _frequencyBlock(0), _frequencyCourse(0), _frequencyNoteModifier(0), +_noteRangeLow(version > SCI_VERSION_0_LATE ? 12: 24), _noteRangeHigh(version > SCI_VERSION_0_LATE ? 107 : 119) { + static const uint16 courseV0[] = { 0x269, 0x28D, 0x2B5, 0x2DE, 0x30A, 0x339, 0x368, 0x39D, 0x3D4, 0x40E, 0x44A, 0x48C }; + static const uint16 courseV1[] = { 0x26A, 0x28E, 0x2B5, 0x2DF, 0x30A, 0x339, 0x36A, 0x39E, 0x3D5, 0x40F, 0x44D, 0x48F }; + static const uint16 fine[] = { 0x24, 0x27, 0x2A, 0x2B, 0x2F, 0x31, 0x34, 0x37, 0x3A, 0x3E, 0x42, 0x45 }; + _noteFrequency = (version > SCI_VERSION_0_LATE) ? courseV1 : courseV0; + _noteFrequencyModifier = fine; +} + +void SoundChannel_PC9801::noteOff() { + if (_sustain) + return; + + processNoteEvent(_note, false); + + _note = 0xFF; + _duration = 0; +} + +void SoundChannel_PC9801::noteOn(uint8 note, uint8 velo) { + _duration = 0; + + if (_program != _parts[_assign]->program() && _soundOn) { + _program = _parts[_assign]->program(); + programChange(_program); + } + + if (_version > SCI_VERSION_0_LATE) { + velo >>= 1; + if (velo > 63) + velo = 63; + _velo = velo; + } + + processNoteEvent(note, true); +} + +void SoundChannel_PC9801::processNoteEvent(uint8 note, bool noteOn) { + if (_note != note) { + _note = note; + _vbrEnvelopeTimer = _vbrInitialDelay; + _vbrFrequencyModifier = 0; + _vbrCur = 0x80; + _flags |= kChanVbrRestartEnv; + } + + prepareFrequencyAndVolume(noteOn); + sendSoundOnOff(noteOn); +} + +void SoundChannel_PC9801::update() { + processSounds(); + if (_note != 0xFF) + _duration++; +} + +void SoundChannel_PC9801::setVolume(uint8 volume) { + static const uint8 volumeTable[] = { 0x7F, 0x7F, 0x30, 0x30, 0x28, 0x28, 0x20, 0x20, 0x18, 0x18, 0x10, 0x10, 0x08, 0x08, 0x00, 0x00 }; + assert(volume < 16); + _volume = volumeTable[volume]; +} + +void SoundChannel_PC9801::reset() { + _assign = 0xFF; + _volume = 0; +} + +int SoundChannel_PC9801::recalculateFrequency(uint16 note, uint16 modifier, uint8 *destOctaveBlock, uint16 *destFrequency, uint8 *destVbrFrequencyModifier) { + note += _transpose; + uint16 pb = _parts[_assign]->pitchBend(); + + if (pb == 0x2000) { + pb = 0; + } else { + int dir = 1; + if (pb > 0x2000) { + pb -= 0x2000; + } else if (pb < 0x2000) { + pb = 0x2000 - pb; + dir = -1; + } + + uint8 noteDiff = pb / 684; + note += (noteDiff * dir); + + pb %= 684; + if (pb > 682) + pb = 682; + pb = ((pb * 6) >> 4); + pb *= dir; + } + + note += (modifier >> 8); + pb += (modifier & 0xff); + if ((pb >> 8) == 1) { + pb &= 0xFF; + note++; + } + + if (note < _noteRangeLow || note > _noteRangeHigh) + return -1; + + uint8 block = (note / 12) - 1; + note %= 12; + uint16 res = _noteFrequency[note]; + uint16 pitchVbrMultiplier = _noteFrequencyModifier[note]; + + if (_type != 2) + res |= (block << 11); + + if (pb) + res += (((pb * pitchVbrMultiplier) & 0x0FF0) >> 8); + + if (res < 0x4000) { + if (destFrequency) + *destFrequency = res; + if (destOctaveBlock) + *destOctaveBlock = block; + if (destVbrFrequencyModifier) + *destVbrFrequencyModifier = pitchVbrMultiplier; + return res; + } + + return -1; +} + +uint8 SoundChannel_PC9801::getVolume() { + static const uint8 volumeTable1[] = { + 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 uint8 volumeTable2[] = { + 0x00, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x61, + 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x6f, 0x70, + 0x71, 0x72, 0x72, 0x73, 0x74, 0x74, 0x75, 0x76, 0x76, 0x77, 0x78, 0x78, 0x79, 0x79, 0x7a, 0x7a, + 0x7b, 0x7b, 0x7b, 0x7c, 0x7c, 0x7c, 0x7d, 0x7d, 0x7d, 0x7e, 0x7e, 0x7e, 0x7e, 0x7f, 0x7f, 0x7f + }; + + uint16 partVolume = ((_assign != 0xFF) ? _parts[_assign]->volume() : 0) + 1; + uint16 velocity = volumeTable1[_velo] + 1; + uint16 volume = _soundOn ? (partVolume * velocity) >> 6 : 0; + volume = volumeTable2[volume] - _volume; + + return (volume < 0x7F ? volume : 0); +} + +void SoundChannel_PC9801::processSounds() { + if (!(_flags & kChanVbrEnable)) + return; + + if (_flags & kChanVbrRestartEnv) { + if (--_vbrEnvelopeTimer) + return; + + _vbrIncrStep = _vbrDepthIncr * _vbrSensitivity * _frequencyNoteModifier; + _vbrDecrStep = _vbrDepthDecr * _vbrSensitivity * _frequencyNoteModifier; + _vbrModulationTimer = ((_flags & kChanVbrMode) ? _vbrIncrTime : _vbrDecrTime) >> 1; + _vbrCur = 0x80; + + _flags &= ~(kChanVbrDecrease | kChanVbrRestartEnv); + if (_flags & kChanVbrMode) + _flags |= kChanVbrDecrease; + } + + uint16 t = _vbrEnvelopeTimer + _vbrEnvelopeSpeed; + _vbrEnvelopeTimer = t & 0xFF; + if (t & 0x100) + return; + + if (!--_vbrModulationTimer) { + _vbrModulationTimer = (_flags & kChanVbrDecrease) ? _vbrDecrTime : _vbrIncrTime; + _flags ^= kChanVbrDecrease; + } + + if (_flags & kChanVbrDecrease) { + int16 s = _vbrCur - (_vbrDecrStep & 0xFF); + _vbrCur = s & 0xFF; + if (s >= 0) + return; + _vbrFrequencyModifier -= ((_vbrDecrStep >> 8) + 1); + } else { + uint16 s = _vbrCur + (_vbrIncrStep & 0xFF); + _vbrCur = s & 0xFF; + if (s <= 255) + return; + _vbrFrequencyModifier += ((_vbrIncrStep >> 8) + 1); + } + + sendFrequency(); +} + +void SoundChannel_PC9801::programChangeInit(const uint8 *data) { + _transpose = (int8)(data[0] + data[0]); + _transpose >>= 1; + _vbrInitialDelay = data[1]; + _vbrDepthIncr = data[2]; + _vbrDecrTime = data[3]; + _vbrDepthDecr = data[4]; + _vbrIncrTime = data[5]; + _vbrSensitivity = (data[6] & 3) + 1; + _vbrEnvelopeSpeed = data[6] >> 3; + _flags = (_flags & ~(kChanVbrEnable | kChanVbrMode)) | ((data[6] << 4) & 0x40); + if (_vbrInitialDelay) + _flags |= (kChanVbrEnable | kChanVbrRestartEnv); +} + +void SoundChannel_PC9801::writeReg(uint8 part, uint8 reg, uint8 val) { + _pc98a->writeReg(part, reg, val); +} + +void SoundChannel_PC9801::prepareFrequencyAndVolume(bool updateVolume) { + if (recalculateFrequency(_note, 0, &_frequencyBlock, &_frequencyCourse, &_frequencyNoteModifier) == -1) + return; + sendFrequency(); + if (updateVolume) + sendVolume(); +} + +SoundChannel_PC9801_FM4OP::SoundChannel_PC9801_FM4OP(uint8 id, PC98AudioCore *pc98a, MidiPart_PC9801 **parts, SciVersion version, uint8 patchSize, bool &soundOn) : SoundChannel_PC9801(pc98a, parts, version, 0, soundOn), +_carrier(0), _regPrt(id > 3 ? 1 : 0), _regOffs(id % 3), _patchSize(patchSize) { + _operatorLevel[0] = _operatorLevel[1] = _operatorLevel[2] = _operatorLevel[3] = 0x7F; +} + +void SoundChannel_PC9801_FM4OP::programChange(uint8 program) { + static const uint8 carrier[] = { 0x10, 0x10, 0x10, 0x10, 0x30, 0x70, 0x70, 0xF0 }; + const uint8 *pos = _instrumentData + program * _patchSize; + + programChangeInit(pos); + pos += 8; + + for (uint8 i = 0x40 + _regOffs; i < 0x50 + _regOffs; i += 4) + writeReg(_regPrt, i, 0xFF); + for (uint8 i = 0x30 + _regOffs; i < 0x40 + _regOffs; i += 4) + writeReg(_regPrt, i, *pos++); + for (int i = 0; i < 4; ++i) + _operatorLevel[i] = *pos++; + for (uint8 i = 0x50 + _regOffs; i < 0xA0 + _regOffs; i += 4) + writeReg(_regPrt, i, *pos++); + writeReg(_regPrt, 0xB0 + _regOffs, *pos); + _carrier = carrier[*pos & 7]; +} + +void SoundChannel_PC9801_FM4OP::sendSoundOnOff(bool noteOn) { + _flags = noteOn ? (_flags | kChanKeyOn) : (_flags & ~kChanKeyOn); + writeReg(_regPrt, 0x28, _regOffs | (noteOn ? 0xF0 : 0)); +} + +void SoundChannel_PC9801_FM4OP::sendVolume() { + uint8 vol = getVolume(); + uint16 c = _carrier; + + for (uint8 i = 0; i < 4; ++i) { + uint8 r = _operatorLevel[i]; + c += c; + if (c & 0x100) { + r = (((r ^ 0x7F) * vol) / 0x7F) * 2; + r = ((r < 0x7F) ? 0x7F - r : 0) + 20; + if (r > 0x7F) + r = 0x7F; + } + writeReg(_regPrt, 0x40 + (i << 2) + _regOffs, r); + } +} + +void SoundChannel_PC9801_FM4OP::sendFrequency() { + uint16 freq = _frequencyCourse + _vbrFrequencyModifier; + writeReg(_regPrt, 0xA4 + _regOffs, freq >> 8); + writeReg(_regPrt, 0xA0 + _regOffs, freq & 0xFF); +} + +uint8 SoundChannel_PC9801_FM2OP::_activeOperators = 0; + +SoundChannel_PC9801_FM2OP::SoundChannel_PC9801_FM2OP(uint8 id, PC98AudioCore *pc98a, MidiPart_PC9801 **parts, SciVersion version, uint8 patchSize, bool &soundOn) : SoundChannel_PC9801(pc98a, parts, version, 1, soundOn), +_patchOffset(37), _patchSize(patchSize), _frequencyCourse2(0), _frequencyNoteModifier2(0), _vbrCur2(0x80), _vbrIncrStep2(0), _vbrDecrStep2(0), _regPrt(id > 3 ? 1 : 0), _regOffs(id & 1) { + static const uint16 opFreqOffset[] = { 0x0000, 0x0600, 0x07CF, 0x0980 }; + _operatorLevel[0] = _operatorLevel[1] = 0x7F; + _operatorFrqIndex[0] = _operatorFrqIndex[1] = 0; + _opFreqOffset = opFreqOffset; +} + +void SoundChannel_PC9801_FM2OP::processNoteEvent(uint8 note, bool noteOn) { + if (_note != note) { + _vbrCur2 = 0x80; + _vbrFrequencyModifier2 = 0; + } + SoundChannel_PC9801::processNoteEvent(note, noteOn); +} + +void SoundChannel_PC9801_FM2OP::reset() { + SoundChannel_PC9801::reset(); + _activeOperators = 0; +} + +void SoundChannel_PC9801_FM2OP::programChange(uint8 program) { + const uint8 *pos = _instrumentData + program * _patchSize + _patchOffset; + + programChangeInit(pos); + pos += 7; + + for (uint8 i = 0x42 + (_regOffs << 2); i < 0x52 + (_regOffs << 2); i += 8) + writeReg(_regPrt, i, 0xFF); + for (uint8 i = 0x32 + (_regOffs << 2); i < 0x42 + (_regOffs << 2); i += 8) + writeReg(_regPrt, i, *pos++); + for (int i = 0; i < 2; ++i) + _operatorLevel[i] = *pos++; + for (uint8 i = 0x52 + (_regOffs << 2); i < 0x72 + (_regOffs << 2); i += 8) + writeReg(_regPrt, i, *pos++); + _operatorFrqIndex[0] = pos[0] >> 6; + _operatorFrqIndex[1] = pos[1] >> 6; + for (uint8 i = 0x72 + (_regOffs << 2); i < 0xA2 + (_regOffs << 2); i += 8) + writeReg(_regPrt, i, *pos++); +} + +void SoundChannel_PC9801_FM2OP::prepareFrequencyAndVolume(bool updateVolume) { + if (recalculateFrequency(_note, _opFreqOffset[_operatorFrqIndex[0]], 0, &_frequencyCourse, &_frequencyNoteModifier) == -1) + return; + if (recalculateFrequency(_note, _opFreqOffset[_operatorFrqIndex[1]], 0, &_frequencyCourse2, &_frequencyNoteModifier2) == -1) + return; + sendFrequency(); + if (updateVolume) + sendVolume(); +} + +void SoundChannel_PC9801_FM2OP::sendSoundOnOff(bool noteOn) { + uint8 op = 0x30 << (_regOffs << 1); + + if (noteOn) { + _flags |= kChanKeyOn; + _activeOperators |= op; + } else { + _flags &= ~kChanKeyOn; + _activeOperators &= ~op; + } + + writeReg(_regPrt, 0x28, _activeOperators | 2); +} + +void SoundChannel_PC9801_FM2OP::sendVolume() { + writeReg(_regPrt, 0x42 + (_regOffs << 2), _operatorLevel[0]); + uint8 r = (((_operatorLevel[1] ^ 0x7F) * getVolume()) / 0x7F) * 2; + r = ((r < 0x7F) ? 0x7F - r : 0) + 16; + writeReg(_regPrt, 0x4A + (_regOffs << 2), r); +} + +void SoundChannel_PC9801_FM2OP::processSounds() { + if (!(_flags & kChanVbrEnable)) + return; + + if (_flags & kChanVbrRestartEnv) { + if (--_vbrEnvelopeTimer) + return; + + _vbrIncrStep = _vbrDepthIncr * _vbrSensitivity * _frequencyNoteModifier; + _vbrIncrStep2 = _vbrDepthIncr * _vbrSensitivity * _frequencyNoteModifier2; + _vbrDecrStep = _vbrDepthDecr * _vbrSensitivity * _frequencyNoteModifier; + _vbrDecrStep2 = _vbrDepthDecr * _vbrSensitivity * _frequencyNoteModifier2; + _vbrModulationTimer = ((_flags & kChanVbrMode) ? _vbrIncrTime : _vbrDecrTime) >> 1; + _vbrCur = 0x80; + + _flags &= ~(kChanVbrDecrease | kChanVbrRestartEnv); + if (_flags & kChanVbrMode) + _flags |= kChanVbrDecrease; + } + + uint16 t = _vbrEnvelopeTimer + _vbrEnvelopeSpeed; + _vbrEnvelopeTimer = t & 0xFF; + if (t & 0x100) + return; + + if (!--_vbrModulationTimer) { + _vbrModulationTimer = (_flags & kChanVbrDecrease) ? _vbrDecrTime : _vbrIncrTime; + _flags ^= kChanVbrDecrease; + } + + if (_flags & kChanVbrDecrease) { + int16 s = _vbrCur - (_vbrDecrStep & 0xFF); + _vbrCur = s & 0xFF; + if (s < 0) + _vbrFrequencyModifier -= ((_vbrDecrStep >> 8) + 1); + s = _vbrCur2 - (_vbrDecrStep2 & 0xFF); + _vbrCur2 = s & 0xFF; + if (s < 0) + _vbrFrequencyModifier2 -= ((_vbrDecrStep2 >> 8) + 1); + } else { + uint16 s = _vbrCur + (_vbrIncrStep & 0xFF); + _vbrCur = s & 0xFF; + if (s > 255) + _vbrFrequencyModifier += ((_vbrIncrStep >> 8) + 1); + s = _vbrCur2 + (_vbrIncrStep2 & 0xFF); + _vbrCur2 = s & 0xFF; + if (s > 255) + _vbrFrequencyModifier2 += ((_vbrIncrStep2 >> 8) + 1); + } + + sendFrequency(); +} + +void SoundChannel_PC9801_FM2OP::sendFrequency() { + uint16 freq = _frequencyCourse + _vbrFrequencyModifier; + writeReg(_regPrt, 0xAD - _regOffs, freq >> 8); + writeReg(_regPrt, 0xA9 - _regOffs, freq & 0xFF); + freq = _frequencyCourse2 + _vbrFrequencyModifier2; + writeReg(_regPrt, 0xAE - (_regOffs << 3), freq >> 8); + writeReg(_regPrt, 0xAA - (_regOffs << 3), freq & 0xFF); +} + +uint8 SoundChannel_PC9801_SSG::_activeChannnelsStatus = 0x3F; + +SoundChannel_PC9801_SSG::SoundChannel_PC9801_SSG(uint8 id, PC98AudioCore *pc98a, MidiPart_PC9801 **parts, SciVersion version, uint8 patchOffset, uint8 patchSize, bool &soundOn) : SoundChannel_PC9801(pc98a, parts, version, 2, soundOn), +_patchOffset(patchOffset), _patchSize(patchSize), _regOffs(id & 3), _instrumentChanMask(0x3F), _ngPhaseStep(0), _currentLevel(0), _ssgEnvelopeState(kEnvSSG_silent), _ngEnvelopeTimer(0), +_ngSpeed(0), _ssgEnvelopeTimer(0), _ssgLevel(0), _ssgSpeed(0) { + static const uint16 courseV0[] = { 0x82D, 0x8A9, 0x92D, 0x9B6, 0xA4D, 0xAEA, 0xB90, 0xC40, 0xCFA, 0xDC0, 0xE90, 0xF6F }; + static const uint16 courseV1[] = { 0x82D, 0x8A9, 0x92D, 0x9B6, 0xA4D, 0xAEA, 0xB90, 0xC40, 0xCFA, 0xDC0, 0xE90, 0xF6F }; + static const uint16 fine[] = { 0x7C, 0x84, 0x8B, 0x94, 0x9D, 0xA6, 0xB0, 0xBA, 0xC5, 0xD0, 0xDE, 0xEB }; + static const uint8 noiseFrq[] = { 0x00, 0x01, 0x04, 0x07, 0x0A, 0x0D, 0x10, 0x13, 0x16, 0x19, 0x1C, 0x1F }; + static const uint8 disableMask[] = { 0xF6, 0xED, 0xDB }; + _noteFrequency = (version > SCI_VERSION_0_LATE) ? courseV1 : courseV0; + _noteFrequencyModifier = fine; + _ngFreq = noiseFrq; + _chanDisableMask = disableMask[id & 3]; +} + +void SoundChannel_PC9801_SSG::reset() { + SoundChannel_PC9801::reset(); + _activeChannnelsStatus = 0x3F; + sendActiveChannelsStatus(); +} + +void SoundChannel_PC9801_SSG::programChange(uint8 program) { + _selectedInstrument = _instrumentData + program * _patchSize + _patchOffset; + programChangeInit(_selectedInstrument); + + _flags &= ~kChanNgEnable; + if (_selectedInstrument[7]) + _flags |= (kChanNgEnable | kChanNgRestartEnv); + _instrumentChanMask = _selectedInstrument[22]; + _activeChannnelsStatus = (_activeChannnelsStatus & _chanDisableMask) | (~_chanDisableMask & (_instrumentChanMask & 0x3F)); + if (!(_instrumentChanMask & 8)) { + _ngPhaseStep = _selectedInstrument[21] & 0x1F; + updateNg(); + } + sendActiveChannelsStatus(); + _currentLevel = 0; + _ssgEnvelopeState = kEnvSSG_silent; +} + +void SoundChannel_PC9801_SSG::processSounds() { + if (_ssgEnvelopeState == kEnvSSG_silent) + return; + + SoundChannel_PC9801::processSounds(); + + if (!(_instrumentChanMask & 0x38) && (_flags & kChanNgEnable)) { + bool cont = false; + if (_flags & kChanNgRestartEnv) { + if (--_ngEnvelopeTimer) { + cont = true; + } else { + uint8 flg = _selectedInstrument[10] & 4; + _ngSpeed = ((flg ? _selectedInstrument[9] : _selectedInstrument[8]) & 0x1F) >> 1; + _flags = (_flags & ~(kChanNgDecrease | kChanNgRestartEnv)) | (flg << 3); + } + } + if (!cont) { + uint16 s = _ngEnvelopeTimer + (_selectedInstrument[10] >> 3); + _ngEnvelopeTimer = s & 0xFF; + if (s & 0x100) + cont = true; + if (!cont) { + if (--_ngSpeed) { + if (_flags & kChanNgDecrease) { + _ngSpeed = _selectedInstrument[8] & 0x1F; + _flags &= ~kChanNgDecrease; + } else { + _ngSpeed = _selectedInstrument[9] & 0x1F; + _flags |= kChanNgDecrease; + } + } + + if (_flags & kChanNgDecrease) { + _ngPhase -= (_selectedInstrument[9] >> 5) * ((_selectedInstrument[10] & 3) + 1); + } else { + _ngPhase += (_selectedInstrument[8] >> 5) * ((_selectedInstrument[10] & 3) + 1); + } + updateNg(); + } + } + } + + uint16 s = _ssgEnvelopeTimer + (_selectedInstrument[21] & 0x60); + _ssgEnvelopeTimer = s & 0xFF; + if ((s & 0x100) || !(_ssgEnvelopeState & 0x0F)) + return; + + uint8 vol = _currentLevel; + if (_ssgLevel & 1) + vol = (vol > (_ssgLevel & ~1)) ? vol - (_ssgLevel & ~1) : 0; + else + vol = (vol + _ssgLevel > 255) ? 255 : vol + _ssgLevel; + + if (!--_ssgSpeed) { + if (--_ssgEnvelopeState == (kEnvSSG_keyOn | kEnvSSG_sustain)) { + _ssgLevel = _selectedInstrument[13]; + _ssgSpeed = _selectedInstrument[14]; + vol = (uint8)CLIP<int16>(vol + (int8)(_selectedInstrument[19] & 0xF0), 0, 255); + } + if (_ssgEnvelopeState == (kEnvSSG_keyOn | kEnvSSG_decay)) { + _ssgLevel = _selectedInstrument[15]; + _ssgSpeed = _selectedInstrument[16]; + vol = (uint8)CLIP<int16>(vol + (int8)(_selectedInstrument[20] << 4), 0, 255); + } + } + + if (_ssgEnvelopeState == kEnvSSG_silent) + vol = 0; + + if (_currentLevel != vol) { + _currentLevel = vol; + sendVolume(); + } +} + +void SoundChannel_PC9801_SSG::sendSoundOnOff(bool noteOn) { + if (noteOn && !(_ssgEnvelopeState & kEnvSSG_keyOn)) { + _currentLevel = _selectedInstrument[19] << 4; + _ssgEnvelopeState = (kEnvSSG_keyOn | kEnvSSG_attack); + _ssgLevel = _selectedInstrument[11]; + _ssgSpeed = _selectedInstrument[12]; + _ssgEnvelopeTimer = 1; + if (!(_selectedInstrument[22] & 0x38)) { + if (_selectedInstrument[21] & 0x80) + _ngPhaseStep = _ngFreq[_note % 12]; + _ngEnvelopeTimer = _selectedInstrument[7]; + _ngPhase = 0; + updateNg(); + _flags |= kChanNgRestartEnv; + } + } else if (!noteOn) { + int8 v = (int8)(_selectedInstrument[20] & 0xF0); + int16 l = _currentLevel + v; + _currentLevel = uint8(CLIP<int16>(l, 0, 255) & 0xFF); + _ssgEnvelopeState = kEnvSSG_decay; + _ssgLevel = _selectedInstrument[17]; + _ssgSpeed = _selectedInstrument[18]; + _note = 0xFF; + } + sendVolume(); +} + +void SoundChannel_PC9801_SSG::sendVolume() { + uint8 v1 = getVolume(); + uint8 vol = v1 + (((v1 >> 1) + v1) >> 2); + + if (vol > 0x7F) + vol = 0x7F; + vol = (vol >> 3) & 0x0F; + + uint16 r = _currentLevel & 0xF0; + for (uint8 i = 0; i < 4; ++i) { + r += r; + if (r & 0x100) + r = (r + vol) & 0xFF; + } + + writeReg(0, 8 + _regOffs, (r + 15) >> 4); +} + +void SoundChannel_PC9801_SSG::sendFrequency() { + uint16 freq = (_frequencyCourse + _vbrFrequencyModifier) >> (8 - _frequencyBlock); + if (!freq) + return; + freq = 62400 / freq; + writeReg(0, _regOffs << 1, freq & 0xFF); + writeReg(0, (_regOffs << 1) + 1, freq >> 8); +} + +void SoundChannel_PC9801_SSG::updateNg() { + int p = _ngPhase + _ngPhaseStep; + if (p > 255) + p = 0; + if (p > 31) + p = 31; + _ngPhase = p & 0xFF; + writeReg(0, 6, _ngPhase); +} + +void SoundChannel_PC9801_SSG::sendActiveChannelsStatus() { + writeReg(0, 7, (_activeChannnelsStatus & 0x3F) | 0x80); +} + +MidiPart_PC9801::MidiPart_PC9801(uint8 id, SoundChannel_PC9801 **channels, uint8 numChan, SciVersion version) : _chan(channels), _id(id), _version(version), _numChan(numChan), + _program(0), _volume(0x3f), _sustain(0), _chanMissing(0), _pitchBend(0x2000), _outChan(0), _noteRangeLow(version > SCI_VERSION_0_LATE ? 12 : 24), _noteRangeHigh(version > SCI_VERSION_0_LATE ? 107 : 119) { +} + +void MidiPart_PC9801::noteOff(uint8 note) { + for (int i = 0; i < _numChan; ++i) { + if (_chan[i]->_assign != _id || _chan[i]->_note != note) + continue; + if (_sustain && _version > SCI_VERSION_0_LATE) + _chan[i]->_sustain = 1; + else + _chan[i]->noteOff(); + return; + } +} + +void MidiPart_PC9801::noteOn(uint8 note, uint8 velo) { + if (note < _noteRangeLow || note > _noteRangeHigh) + return; + + if (velo == 0) { + noteOff(note); + return; + } + + velo >>= 1; + + for (int i = 0; i < _numChan; ++i) { + if (_chan[i]->_assign != _id || _chan[i]->_note != note) + continue; + _chan[i]->_sustain = 0; + _chan[i]->noteOff(); + _chan[i]->noteOn(note, velo); + return; + } + + int chan = allocateChannel(); + if (chan != -1) + _chan[chan]->noteOn(note, velo); +} + +void MidiPart_PC9801::controlChangeVolume(uint8 vol) { + _volume = CLIP(vol >> 1, 0, 0x3f); + for (int i = 0; i < _numChan; ++i) { + if (_chan[i]->_assign == _id && _chan[i]->_note != 0xff) + _chan[i]->processNoteEvent(_chan[i]->_note, true); + } +} + +void MidiPart_PC9801::controlChangeSustain(uint8 sus) { + _sustain = sus; + if (_sustain) + return; + + for (int i = 0; i < _numChan; ++i) { + if (_chan[i]->_assign == _id && _chan[i]->_sustain) { + _chan[i]->_sustain = 0; + _chan[i]->noteOff(); + } + } +} + +void MidiPart_PC9801::controlChangePolyphony(uint8 numChan) { + uint8 numAssigned = 0; + for (int i = 0; i < _numChan; ++i) { + if (_chan[i]->_assign == _id) + numAssigned++; + } + + numAssigned += _chanMissing; + if (numAssigned < numChan) { + addChannels(numChan - numAssigned); + } else if (numAssigned > numChan) { + dropChannels(numAssigned - numChan); + assignFreeChannels(); + } +} + +void MidiPart_PC9801::controlChangeAllNotesOff() { + for (int i = 0; i < _numChan; ++i) { + if (_chan[i]->_assign == _id && _chan[i]->_note != 0xff) + _chan[i]->noteOff(); + } +} + +void MidiPart_PC9801::programChange(uint8 prg) { + _program = prg; +} + +void MidiPart_PC9801::pitchBend(int16 val) { + _pitchBend = val; + for (int i = 0; i < _numChan; ++i) { + if (_chan[i]->_assign == _id && _chan[i]->_note != 0xff) + _chan[i]->processNoteEvent(_chan[i]->_note, true); + } +} + + +void MidiPart_PC9801::addChannels(int num, int resetMissingChannels) { + if (resetMissingChannels != -1) + _chanMissing = resetMissingChannels; + + for (int i = 0; i < _numChan; ++i) { + if (_chan[i]->_assign != 0xFF) + continue; + + _chan[i]->_assign = _id; + +#ifdef DEBUG_REMAP + debug("===== MidiDriver_PC9801: hardware channel %d is assigned to device channel %d =====", i, _id); +#endif + if (_chan[i]->_note != 0xff) + _chan[i]->noteOff(); + + if (!--num) + break; + } + _chanMissing += num; +} + +void MidiPart_PC9801::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 < _numChan; i++) { + if (_chan[i]->_assign != _id || _chan[i]->_note != 0xff) + continue; + _chan[i]->_assign = 0xff; + +#ifdef DEBUG_REMAP + debug("===== MidiDriver_PC9801: hardware channel %d is dropped from device channel %d =====", i, _id); +#endif + + if (!--num) + return; + } + + for (int i = 0; i < _numChan; i++) { + if (_chan[i]->_assign != _id) + continue; + _chan[i]->_sustain = 0; + _chan[i]->noteOff(); + _chan[i]->_assign = 0xff; + if (!--num) + return; + } +} + +int MidiPart_PC9801::allocateChannel() { + int chan = _outChan; + int ovrChan = 0; + int ld = 0; + bool found = false; + + for (bool loop = true; loop; ) { + if (++chan == _numChan) + chan = 0; + + if (chan == _outChan) + loop = false; + + if (_id == _chan[chan]->_assign) { + if (_chan[chan]->_note == 0xff || _version < SCI_VERSION_1_LATE) { + found = true; + break; + } + + if (_chan[chan]->_duration >= ld) { + ld = _chan[chan]->_duration; + ovrChan = chan; + } + } + } + + if (!found) { + if (!ld) + return -1; + chan = ovrChan; + _chan[chan]->_sustain = 0; + _chan[chan]->noteOff(); + } + + _outChan = chan; + return chan; +} + +void MidiPart_PC9801::assignFreeChannels() { + uint8 freeChan = 0; + for (int i = 0; i < _numChan; i++) { + if (_chan[i]->_assign == 0xff) + freeChan++; + } + + if (!freeChan) + return; + + MidiDriver_PC9801::assignFreeChannels(freeChan); +} + +MidiPart_PC9801 **MidiDriver_PC9801::_parts = 0; + +MidiDriver_PC9801::MidiDriver_PC9801(Audio::Mixer *mixer, SciVersion version) : _version(version), _pc98a(0), _chan(0), _numChan(6), _internalVersion(0xFF), _ssgPatchOffset(0xFF), +_patchSize(0), _instrumentData(0), _timerProc(0), _timerProcPara(0), _baseTempo(10080), _ready(false), _isOpen(false), _masterVolume(0x0f) ,_soundOn(true), +_playID(0), _channelMask1(0x10), _channelMask2(0x02) { + _pc98a = +#ifdef SCI_PC98_AUDIO_EXTENDED + new PC98AudioCore(mixer, this, kType86, true); +#else + new PC98AudioCore(mixer, this, kType26, true); +#endif +} + +MidiDriver_PC9801::~MidiDriver_PC9801() { + _ready = false; + delete _pc98a; + close(); +} + +int MidiDriver_PC9801::open() { + if (_isOpen) + return MERR_ALREADY_OPEN; + + if (!_ready) { + if (!_pc98a->init()) + return MERR_CANNOT_CONNECT; + _pc98a->setSoundEffectChanMask(0); + _pc98a->ssgSetVolume(205); + _ready = true; + } + + ResourceManager *resMan = g_sci->getResMan(); + loadInstruments(*resMan->findResource(ResourceId(kResourceTypePatch, _version < SCI_VERSION_1_LATE ? 2 : 8), false)); + + if (_ssgPatchOffset == 0xFF || _patchSize == 0) + return MERR_CANNOT_CONNECT; + + if (_version < SCI_VERSION_1_LATE) + _internalVersion = 0; + else if (_patchSize == 60 && _ssgPatchOffset == 37) + _internalVersion = 1; + else if (_patchSize == 81 && _ssgPatchOffset == 58) + _internalVersion = 2; + else + return MERR_CANNOT_CONNECT; + + if (_internalVersion == 2) + _numChan++; + +#ifdef SCI_PC98_AUDIO_EXTENDED + _numChan += 3; + if (_internalVersion == 2) + _numChan++; +#endif + + static const int channelConfig[5][11] = { + { 0, 0, 0, 2, 2, 2, -1, -1, -1, -1, -1 }, + { 0, 0, 0, 2, 2, 2, -1, -1, -1, -1, -1 }, + { 0, 0, 1, 1, 2, 2, 2, -1, -1, -1, -1 }, + { 0, 0, 0, 0, 0, 0, 2, 2, 2, -1, -1 }, + { 0, 0, 1, 1, 0, 0, 1, 1, 2, 2, 2 } + }; + + _parts = new MidiPart_PC9801*[16]; + _chan = new SoundChannel_PC9801*[_numChan]; + int config = _internalVersion; + +#ifdef SCI_PC98_AUDIO_EXTENDED + config = (_internalVersion >> 1) + 3; +#endif + + int numSSG = 0; + for (int i = 0; i < _numChan; ++i) { + if (channelConfig[config][i] == 0) + _chan[i] = new SoundChannel_PC9801_FM4OP(i, _pc98a, _parts, _version, _patchSize, _soundOn); + else if (channelConfig[config][i] == 1) + _chan[i] = new SoundChannel_PC9801_FM2OP(i, _pc98a, _parts, _version, _patchSize, _soundOn); + else if (channelConfig[config][i] == 2) + _chan[i] = new SoundChannel_PC9801_SSG(numSSG++, _pc98a, _parts, _version, _ssgPatchOffset, _patchSize, _soundOn); + else + _chan[i] = 0; + } + + for (int i = 0; i < 16; ++i) + _parts[i] = new MidiPart_PC9801(i, _chan, _numChan, _version); + + _isOpen = true; + + reset(); + + return 0; +} + +void MidiDriver_PC9801::close() { + if (!_isOpen) + return; + + bool ready = _ready; + _isOpen = _ready = false; + + if (_parts) { + for (int i = 0; i < 16; ++i) { + delete _parts[i]; + _parts[i] = 0; + } + delete[] _parts; + _parts = 0; + } + + if (_chan) { + for (int i = 0; i < _numChan; ++i) { + delete _chan[i]; + _chan[i] = 0; + } + delete[] _chan; + _chan = 0; + } + + delete[] _instrumentData; + _instrumentData = 0; + + _ready = ready; +} + +void MidiDriver_PC9801::loadInstruments(const SciSpan<const uint8> &data) { + if (!data) + return; + + SciSpan<const uint8> src = data; + delete[] _instrumentData; + + if (_version == SCI_VERSION_0_LATE) { + _ssgPatchOffset = 48; + _patchSize = 52; + + _instrumentData = new uint8[96 * _patchSize]; + uint8 *dst = _instrumentData; + + for (bool load = true; load; ) { + for (int i = 0; i < 48; ++i) { + src.subspan(0, _patchSize).unsafeCopyDataTo(dst); + src += 64; + dst += _patchSize; + } + uint16 id = (src.byteSize() >= 2) ? src.getInt16BEAt(0) : 0; + if (id == 0xABCD || id == 0xCDAB) + src += 2; + else + load = false; + } + } else if (_version == SCI_VERSION_1_LATE) { + int len = data.byteSize() - 1; + _instrumentData = new uint8[len]; + uint8 *dst = _instrumentData; + (++src).unsafeCopyDataTo(dst); + + _patchSize = len / 96; + _ssgPatchOffset = (_patchSize == 81) ? 58 : 37; + } + + SoundChannel_PC9801::_instrumentData = _instrumentData; +} + +void MidiDriver_PC9801::send(uint32 b) { + if (!_isOpen) + return; + + byte para2 = (b >> 16) & 0xFF; + byte para1 = (b >> 8) & 0xFF; + byte cmd = b & 0xF0; + + MidiPart_PC9801 *part = _parts[b & 0x0F]; + + switch (cmd) { + case 0x80: + part->noteOff(para1); + break; + case 0x90: + part->noteOn(para1, para2); + break; + case 0xb0: + switch (para1) { + case 7: + part->controlChangeVolume(para2); + break; + case 64: + part->controlChangeSustain(para2); + break; + case SCI_MIDI_SET_POLYPHONY: + part->controlChangePolyphony(para2); + break; + case SCI_MIDI_CHANNEL_NOTES_OFF: + part->controlChangeAllNotesOff(); + break; + default: + break; + } + break; + case 0xc0: + part->programChange(para1); + break; + case 0xe0: + part->pitchBend(para1 | (para2 << 7)); + break; + default: + break; + } +} + +void MidiDriver_PC9801::assignFreeChannels(int num) { + assert(_parts); + for (int i = 0; i < 16; ++i) { + uint8 missing = _parts[i]->missingChannels(); + if (!missing) + continue; + if (missing < num) { + num -= missing; + _parts[i]->addChannels(num, 0); + } else { + _parts[i]->addChannels(num, missing - num); + return; + } + } +} + +uint32 MidiDriver_PC9801::property(int prop, uint32 param) { + if (!_isOpen) + return 0; + + switch(prop) { + case MIDI_PROP_MASTER_VOLUME: + if (param != 0xffff && param != _masterVolume && param < 16) { + _masterVolume = param; + for (int i = 0; i < _numChan; ++i) { + _chan[i]->setVolume(_masterVolume); + if (_chan[i]->_note != 0xFF) + _chan[i]->processNoteEvent(_chan[i]->_note, true); + } + } + return _masterVolume; + case MIDI_PROP_PLAYSWITCH: + _soundOn = param; + break; + case MIDI_PROP_POLYPHONY: +#ifdef SCI_PC98_AUDIO_EXTENDED + return _version < SCI_VERSION_1_LATE ? 9 : 12; +#else + return _version < SCI_VERSION_1_LATE ? 6 : 9; +#endif + case MIDI_PROP_CHANNEL_ID: + return _version < SCI_VERSION_1_LATE ? (_channelMask1 | _channelMask2) : _playID; + default: + break; + } + return 0; +} + +void MidiDriver_PC9801::setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) { + _timerProc = timer_proc; + _timerProcPara = timer_param; +} + +uint32 MidiDriver_PC9801::getBaseTempo() { + return _baseTempo; +} + +void MidiDriver_PC9801::timerCallbackB() { + if (!_isOpen) + return; + updateParser(); + updateChannels(); +} + +void MidiDriver_PC9801::updateParser() { + if (_timerProc) + _timerProc(_timerProcPara); +} + +void MidiDriver_PC9801::updateChannels() { + for (int i = 0; i < _numChan; ++i) + _chan[i]->update(); +} + +void MidiDriver_PC9801::reset() { + if (!_ready) + return; + + for (int i = 0; i < 3; ++i) { + _pc98a->writeReg(0, 0x28, i); + _pc98a->writeReg(0, i, 0); + _pc98a->writeReg(0, 8 + i, 0); + } + + uint8 flag = 0; + if (_internalVersion == 2) { + _pc98a->writeReg(0, 0xB2, 0x04); + flag = 0x40; + } + + _pc98a->writeReg(0, 0x27, 0x38); + _pc98a->writeReg(0, 0x27, 0x3a | flag); + + if (!_isOpen) + return; + + for (int i = 0; i < _numChan; ++i) + _chan[i]->reset(); +} + +MidiPlayer_PC9801::MidiPlayer_PC9801(SciVersion version) : MidiPlayer(version) { + _driver = new MidiDriver_PC9801(g_system->getMixer(), version); +} + +MidiPlayer_PC9801::~MidiPlayer_PC9801() { + delete _driver; +} + +bool MidiPlayer_PC9801::hasRhythmChannel() const { + return false; +} + +byte MidiPlayer_PC9801::getPlayId() const { + return _driver ? _driver->property(MIDI_PROP_CHANNEL_ID, 0) : 0; +} + +int MidiPlayer_PC9801::getPolyphony() const { + return _driver ? _driver->property(MIDI_PROP_POLYPHONY, 0) : 0; +} + +void MidiPlayer_PC9801::playSwitch(bool play) { + if (_driver) + _driver->property(MIDI_PROP_PLAYSWITCH, play ? 1 : 0); +} + +MidiPlayer *MidiPlayer_PC9801_create(SciVersion _soundVersion) { + return new MidiPlayer_PC9801(_soundVersion); +} + +} // End of namespace Sci + diff --git a/engines/sci/sound/music.cpp b/engines/sci/sound/music.cpp index 0902ff8a81..700be3d05f 100644 --- a/engines/sci/sound/music.cpp +++ b/engines/sci/sound/music.cpp @@ -95,6 +95,9 @@ void SciMusic::init() { deviceFlags |= MDT_TOWNS; } + if (g_sci->getPlatform() == Common::kPlatformPC98) + deviceFlags |= MDT_PC98; + uint32 dev = MidiDriver::detectDevice(deviceFlags); _musicType = MidiDriver::getMusicType(dev); @@ -129,6 +132,9 @@ void SciMusic::init() { case MT_TOWNS: _pMidiDrv = MidiPlayer_FMTowns_create(_soundVersion); break; + case MT_PC98: + _pMidiDrv = MidiPlayer_PC9801_create(_soundVersion); + break; default: if (ConfMan.getBool("native_fb01")) _pMidiDrv = MidiPlayer_Fb01_create(_soundVersion); |