aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorathrxx2018-12-01 02:33:12 +0100
committerFilippos Karapetis2019-04-02 20:45:35 +0300
commitacfc0c90ce740cbcc5b4b8662ee9c3ae84a6e013 (patch)
tree9a6b7cef47ae14ca767b2dbf8d4b3bd0c289017e
parent3dc0c3ec5998cea96bd0426ec7eaae438c3beacd (diff)
downloadscummvm-rg350-acfc0c90ce740cbcc5b4b8662ee9c3ae84a6e013.tar.gz
scummvm-rg350-acfc0c90ce740cbcc5b4b8662ee9c3ae84a6e013.tar.bz2
scummvm-rg350-acfc0c90ce740cbcc5b4b8662ee9c3ae84a6e013.zip
SCI: add PC-9801 sound driver
(supports SCI1 for now)
-rw-r--r--engines/sci/module.mk1
-rw-r--r--engines/sci/sound/drivers/mididriver.h1
-rw-r--r--engines/sci/sound/drivers/pc9801.cpp1488
-rw-r--r--engines/sci/sound/music.cpp6
4 files changed, 1496 insertions, 0 deletions
diff --git a/engines/sci/module.mk b/engines/sci/module.mk
index 7decff6e7a..6a341b666f 100644
--- a/engines/sci/module.mk
+++ b/engines/sci/module.mk
@@ -77,6 +77,7 @@ MODULE_OBJS := \
sound/drivers/fmtowns.o \
sound/drivers/midi.o \
sound/drivers/pcjr.o \
+ sound/drivers/pc9801.o \
video/seq_decoder.o
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);