[Scummvm-git-logs] scummvm master -> ad4faece761504eb0590faa6ca8ffc00e8d19e0e
elasota
noreply at scummvm.org
Thu Dec 14 06:33:48 UTC 2023
This automated email contains information about 1 new commit which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .
Summary:
ad4faece76 MTROPOLIS: Split MIDI functionality off to a separate plug-in. This mainly avoids loading a MIDI driver when we don't a
Commit: ad4faece761504eb0590faa6ca8ffc00e8d19e0e
https://github.com/scummvm/scummvm/commit/ad4faece761504eb0590faa6ca8ffc00e8d19e0e
Author: elasota (1137273+elasota at users.noreply.github.com)
Date: 2023-12-14T01:33:26-05:00
Commit Message:
MTROPOLIS: Split MIDI functionality off to a separate plug-in. This mainly avoids loading a MIDI driver when we don't actually need it.
Changed paths:
A engines/mtropolis/plugin/midi.cpp
A engines/mtropolis/plugin/midi.h
A engines/mtropolis/plugin/midi_data.cpp
A engines/mtropolis/plugin/midi_data.h
engines/mtropolis/boot.cpp
engines/mtropolis/module.mk
engines/mtropolis/plugin/standard.cpp
engines/mtropolis/plugin/standard.h
engines/mtropolis/plugin/standard_data.cpp
engines/mtropolis/plugin/standard_data.h
engines/mtropolis/plugins.h
diff --git a/engines/mtropolis/boot.cpp b/engines/mtropolis/boot.cpp
index cbb9cd8abe9..641f506c096 100644
--- a/engines/mtropolis/boot.cpp
+++ b/engines/mtropolis/boot.cpp
@@ -1078,6 +1078,7 @@ public:
kPlugInSPQR,
kPlugInStandard,
kPlugInObsidian,
+ kPlugInMIDI,
};
enum BitDepth {
@@ -1259,6 +1260,7 @@ void BootScriptContext::setEnhancedBitDepth(BitDepth bitDepth) {
void BootScriptContext::bootObsidianRetailMacEn() {
addPlugIn(kPlugInObsidian);
+ addPlugIn(kPlugInMIDI);
addPlugIn(kPlugInStandard);
addArchive(kArchiveTypeStuffIt, "installer", "fs:Obsidian Installer");
@@ -1278,6 +1280,7 @@ void BootScriptContext::bootObsidianRetailMacEn() {
void BootScriptContext::bootObsidianRetailMacJp() {
addPlugIn(kPlugInObsidian);
+ addPlugIn(kPlugInMIDI);
addPlugIn(kPlugInStandard);
addArchive(kArchiveTypeStuffIt, "installer", "fs:xn--u9j9ecg0a2fsa1io6k6jkdc2k");
@@ -1295,6 +1298,7 @@ void BootScriptContext::bootObsidianRetailMacJp() {
void BootScriptContext::bootObsidianGeneric() {
addPlugIn(kPlugInObsidian);
+ addPlugIn(kPlugInMIDI);
addPlugIn(kPlugInStandard);
addSubtitles("subtitles_lines_obsidian_en.csv", "subtitles_speakers_obsidian_en.csv", "subtitles_asset_mapping_obsidian_en.csv", "subtitles_modifier_mapping_obsidian_en.csv");
@@ -1302,6 +1306,7 @@ void BootScriptContext::bootObsidianGeneric() {
void BootScriptContext::bootObsidianRetailWinDe() {
addPlugIn(kPlugInObsidian);
+ addPlugIn(kPlugInMIDI);
addPlugIn(kPlugInStandard);
addArchive(kArchiveTypeInstallShieldV3, "installer", "_SETUP.1");
@@ -1405,7 +1410,8 @@ void BootScriptContext::executeFunction(const Common::String &functionName, cons
const EnumBinding plugInEnum[] = {ENUM_BINDING(kPlugInMTI),
ENUM_BINDING(kPlugInStandard),
ENUM_BINDING(kPlugInObsidian),
- ENUM_BINDING(kPlugInSPQR)};
+ ENUM_BINDING(kPlugInSPQR),
+ ENUM_BINDING(kPlugInMIDI)};
const EnumBinding bitDepthEnum[] = {ENUM_BINDING(kBitDepthAuto),
ENUM_BINDING(kBitDepth8),
@@ -1695,6 +1701,11 @@ Common::SharedPtr<MTropolis::PlugIn> loadStandardPlugIn(const MTropolisGameDescr
return standardPlugIn;
}
+Common::SharedPtr<MTropolis::PlugIn> loadMIDIPlugIn(const MTropolisGameDescription &gameDesc) {
+ Common::SharedPtr<MTropolis::PlugIn> midiPlugIn = PlugIns::createMIDI();
+ return midiPlugIn;
+}
+
Common::SharedPtr<MTropolis::PlugIn> loadObsidianPlugIn(const MTropolisGameDescription &gameDesc, Common::Archive &fs, const Common::Path &pluginsLocation) {
bool isMac = (gameDesc.desc.platform == Common::kPlatformMacintosh);
bool isRetail = ((gameDesc.desc.flags & ADGF_DEMO) == 0);
@@ -2420,6 +2431,9 @@ BootConfiguration bootProject(const MTropolisGameDescription &gameDesc) {
case Boot::BootScriptContext::kPlugInStandard:
plugIns.push_back(Boot::loadStandardPlugIn(gameDesc));
break;
+ case Boot::BootScriptContext::kPlugInMIDI:
+ plugIns.push_back(Boot::loadMIDIPlugIn(gameDesc));
+ break;
case Boot::BootScriptContext::kPlugInObsidian:
plugIns.push_back(Boot::loadObsidianPlugIn(gameDesc, *vfs, pluginsLocation));
break;
diff --git a/engines/mtropolis/module.mk b/engines/mtropolis/module.mk
index 21f62aec292..7adee786548 100644
--- a/engines/mtropolis/module.mk
+++ b/engines/mtropolis/module.mk
@@ -16,6 +16,8 @@ MODULE_OBJS = \
modifiers.o \
modifier_factory.o \
mtropolis.o \
+ plugin/midi.o \
+ plugin/midi_data.o \
plugin/mti.o \
plugin/mti_data.o \
plugin/obsidian.o \
diff --git a/engines/mtropolis/plugin/midi.cpp b/engines/mtropolis/plugin/midi.cpp
new file mode 100644
index 00000000000..7a5cb868522
--- /dev/null
+++ b/engines/mtropolis/plugin/midi.cpp
@@ -0,0 +1,1973 @@
+/* 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "common/config-manager.h"
+#include "common/file.h"
+#include "common/random.h"
+
+#include "audio/mididrv.h"
+#include "audio/midiparser.h"
+#include "audio/midiparser_smf.h"
+#include "audio/midiplayer.h"
+
+#include "mtropolis/miniscript.h"
+#include "mtropolis/plugin/midi.h"
+#include "mtropolis/plugins.h"
+
+#include "mtropolis/miniscript.h"
+
+namespace MTropolis {
+
+namespace Midi {
+
+class MultiMidiPlayer;
+
+// I guess this follows QuickTime quirks, but basically, mTropolis pipes multiple inputs to a single
+// output device, and is totally cool with multiple devices stomping each other.
+//
+// Obsidian actually has a timer that plays a MIDI file that fires AllNoteOff on every channel every
+// 30 seconds, presumably to work around stuck notes, along with workarounds for the workaround,
+// i.e. the intro sequence has a silent part exactly 30 seconds in timed to sync up with the mute.
+//
+// NOTE: Due to SharedPtr not currently being atomic, MidiFilePlayers MUST BE DESTROYED ON THE
+// MAIN THREAD so there is no contention over the file refcount.
+class MidiFilePlayer {
+public:
+ virtual ~MidiFilePlayer();
+};
+
+class MidiNotePlayer {
+public:
+ virtual ~MidiNotePlayer();
+};
+
+class MidiCombinerSource : public MidiDriver_BASE {
+public:
+ virtual ~MidiCombinerSource();
+
+ // Do not call this directly, it's not thread-safe, expose via MultiMidiPlayer
+ virtual void setVolume(uint8 volume) = 0;
+ virtual void detach() = 0;
+};
+
+MidiCombinerSource::~MidiCombinerSource() {
+}
+
+class MidiCombiner {
+public:
+ virtual ~MidiCombiner();
+
+ virtual Common::SharedPtr<MidiCombinerSource> createSource() = 0;
+};
+
+MidiCombiner::~MidiCombiner() {
+}
+
+class MidiParser_MTropolis : public MidiParser_SMF {
+public:
+ MidiParser_MTropolis(bool hasTempoOverride, double tempoOverride, uint16 mutedTracks);
+
+ void setTempo(uint32 tempo) override;
+
+ void setTempoOverride(double tempoOverride);
+ void setMutedTracks(uint16 mutedTracks);
+
+protected:
+ bool processEvent(const EventInfo &info, bool fireEvents) override;
+
+private:
+ double _tempoOverride;
+ uint16 _mutedTracks;
+ bool _hasTempoOverride;
+};
+
+MidiParser_MTropolis::MidiParser_MTropolis(bool hasTempoOverride, double tempoOverride, uint16 mutedTracks)
+ : _hasTempoOverride(hasTempoOverride), _tempoOverride(tempoOverride), _mutedTracks(mutedTracks) {
+}
+
+void MidiParser_MTropolis::setTempo(uint32 tempo) {
+ if (_hasTempoOverride)
+ return;
+
+ MidiParser_SMF::setTempo(tempo);
+}
+
+void MidiParser_MTropolis::setTempoOverride(double tempoOverride) {
+ _hasTempoOverride = true;
+
+ if (tempoOverride < 1.0)
+ tempoOverride = 1.0;
+
+ _tempoOverride = tempoOverride;
+
+ uint32 convertedTempo = static_cast<uint32>(60000000.0 / tempoOverride);
+
+ MidiParser_SMF::setTempo(convertedTempo);
+}
+
+void MidiParser_MTropolis::setMutedTracks(uint16 mutedTracks) {
+ _mutedTracks = mutedTracks;
+}
+
+bool MidiParser_MTropolis::processEvent(const EventInfo &info, bool fireEvents) {
+ if ((info.event & 0xf0) == MidiDriver_BASE::MIDI_COMMAND_NOTE_ON) {
+ int track = _noteChannelToTrack[info.event & 0xf];
+ if (track >= 0 && (_mutedTracks & (1 << track)))
+ return true;
+ }
+
+ return MidiParser_SMF::processEvent(info, fireEvents);
+}
+
+class MidiFilePlayerImpl : public MidiFilePlayer {
+public:
+ explicit MidiFilePlayerImpl(const Common::SharedPtr<MidiCombinerSource> &outputDriver, const Common::SharedPtr<Data::Midi::MidiModifier::EmbeddedFile> &file, uint32 baseTempo, bool hasTempoOverride, double tempoOverride, uint8 volume, bool loop, uint16 mutedTracks);
+ ~MidiFilePlayerImpl();
+
+ // Do not call any of these directly since they're not thread-safe, expose them via MultiMidiPlayer
+ void stop();
+ void play();
+ void pause();
+ void resume();
+ void setVolume(uint8 volume);
+ void setLoop(bool loop);
+ void setTempoOverride(double tempo);
+ void setMutedTracks(uint16 mutedTracks);
+ void detach();
+ void onTimer();
+
+private:
+ Common::SharedPtr<Data::Midi::MidiModifier::EmbeddedFile> _file;
+ Common::SharedPtr<MidiParser_MTropolis> _parser;
+ Common::SharedPtr<MidiCombinerSource> _outputDriver;
+ uint16 _mutedTracks;
+ bool _loop;
+};
+
+class MidiNotePlayerImpl : public MidiNotePlayer {
+public:
+ explicit MidiNotePlayerImpl(const Common::SharedPtr<MidiCombinerSource> &outputDriver, uint32 timerRate);
+ ~MidiNotePlayerImpl();
+
+ // Do not call any of these directly since they're not thread-safe, expose them via MultiMidiPlayer
+ void onTimer();
+ void play(uint8 volume, uint8 channel, uint8 program, uint8 note, uint8 velocity, double duration);
+ void stop();
+ void detach();
+
+private:
+ Common::SharedPtr<MidiCombinerSource> _outputDriver;
+ uint64 _durationRemaining;
+ uint32 _timerRate;
+ uint8 _channel;
+ uint8 _note;
+ uint8 _volume;
+ // uint8 _program;
+
+ bool _initialized;
+};
+
+class MultiMidiPlayer : public Audio::MidiPlayer {
+public:
+ explicit MultiMidiPlayer(bool useDynamicMidiMixer);
+ ~MultiMidiPlayer();
+
+ MidiFilePlayer *createFilePlayer(const Common::SharedPtr<Data::Midi::MidiModifier::EmbeddedFile> &file, bool hasTempoOverride, double tempoOverride, uint8 volume, bool loop, uint16 mutedTracks);
+ MidiNotePlayer *createNotePlayer();
+ void deleteFilePlayer(MidiFilePlayer *player);
+ void deleteNotePlayer(MidiNotePlayer *player);
+
+ Common::SharedPtr<MidiCombinerSource> createSource();
+
+ void setPlayerVolume(MidiFilePlayer *player, uint8 volume);
+ void setPlayerLoop(MidiFilePlayer *player, bool loop);
+ void setPlayerTempo(MidiFilePlayer *player, double tempo);
+ void setPlayerMutedTracks(MidiFilePlayer *player, uint16 mutedTracks);
+ void stopPlayer(MidiFilePlayer *player);
+ void playPlayer(MidiFilePlayer *player);
+ void pausePlayer(MidiFilePlayer *player);
+ void resumePlayer(MidiFilePlayer *player);
+
+ void playNote(MidiNotePlayer *player, uint8 volume, uint8 channel, uint8 program, uint8 note, uint8 velocity, double duration);
+ void stopNote(MidiNotePlayer *player);
+
+ uint32 getBaseTempo() const;
+
+ void send(uint32 b) override;
+
+private:
+ void onTimer() override;
+
+ static void timerCallback(void *refCon);
+
+ Common::Mutex _mutex;
+ Common::Array<Common::SharedPtr<MidiFilePlayerImpl> > _filePlayers;
+ Common::Array<Common::SharedPtr<MidiNotePlayerImpl> > _notePlayers;
+ Common::SharedPtr<MidiCombiner> _combiner;
+};
+
+MidiFilePlayer::~MidiFilePlayer() {
+}
+
+MidiNotePlayer::~MidiNotePlayer() {
+}
+
+MidiFilePlayerImpl::MidiFilePlayerImpl(const Common::SharedPtr<MidiCombinerSource> &outputDriver, const Common::SharedPtr<Data::Midi::MidiModifier::EmbeddedFile> &file, uint32 baseTempo, bool hasTempoOverride, double tempo, uint8 volume, bool loop, uint16 mutedTracks)
+ : _file(file), _outputDriver(outputDriver), _parser(nullptr), _loop(loop), _mutedTracks(mutedTracks) {
+ Common::SharedPtr<MidiParser_MTropolis> parser(new MidiParser_MTropolis(hasTempoOverride, tempo, mutedTracks));
+
+ if (file->contents.size() != 0 && parser->loadMusic(&file->contents[0], file->contents.size())) {
+ _parser = parser;
+
+ parser->setTrack(0);
+ parser->startPlaying();
+ parser->setMidiDriver(outputDriver.get());
+ parser->setTimerRate(baseTempo);
+ parser->property(MidiParser::mpAutoLoop, loop ? 1 : 0);
+ }
+}
+
+MidiFilePlayerImpl::~MidiFilePlayerImpl() {
+ assert(!_parser); // Call detach first!
+}
+
+void MidiFilePlayerImpl::stop() {
+ _parser->stopPlaying();
+}
+
+void MidiFilePlayerImpl::play() {
+ _parser->startPlaying();
+}
+
+void MidiFilePlayerImpl::pause() {
+ _parser->pausePlaying();
+}
+
+void MidiFilePlayerImpl::resume() {
+ _parser->resumePlaying();
+}
+
+void MidiFilePlayerImpl::setVolume(uint8 volume) {
+ _outputDriver->setVolume(volume);
+}
+
+void MidiFilePlayerImpl::setMutedTracks(uint16 mutedTracks) {
+ _mutedTracks = mutedTracks;
+ _parser->setMutedTracks(mutedTracks);
+}
+
+void MidiFilePlayerImpl::setLoop(bool loop) {
+ _loop = loop;
+ _parser->property(MidiParser::mpAutoLoop, loop ? 1 : 0);
+}
+
+void MidiFilePlayerImpl::setTempoOverride(double tempo) {
+ _parser->setTempoOverride(tempo);
+}
+
+void MidiFilePlayerImpl::detach() {
+ if (_parser) {
+ _parser->setMidiDriver(nullptr);
+ _parser.reset();
+ }
+
+ if (_outputDriver) {
+ _outputDriver->detach();
+ _outputDriver.reset();
+ }
+}
+
+void MidiFilePlayerImpl::onTimer() {
+ if (_parser)
+ _parser->onTimer();
+}
+
+MidiNotePlayerImpl::MidiNotePlayerImpl(const Common::SharedPtr<MidiCombinerSource> &outputDriver, uint32 timerRate)
+ : _timerRate(timerRate), _durationRemaining(0), _outputDriver(outputDriver), _channel(0), _note(0), /* _program(0), */ _initialized(false), _volume(100) {
+}
+
+MidiNotePlayerImpl::~MidiNotePlayerImpl() {
+}
+
+void MidiNotePlayerImpl::onTimer() {
+ if (_durationRemaining > 0) {
+ if (_durationRemaining <= _timerRate) {
+ stop();
+ assert(_durationRemaining == 0);
+ } else {
+ _durationRemaining -= _timerRate;
+ }
+ }
+}
+
+void MidiNotePlayerImpl::play(uint8 volume, uint8 channel, uint8 program, uint8 note, uint8 velocity, double duration) {
+ if (duration < 0.000001)
+ return;
+
+ if (_durationRemaining)
+ stop();
+
+ _initialized = true;
+
+ _durationRemaining = static_cast<uint64>(duration * 1000000);
+ _channel = channel;
+ _note = note;
+ _volume = volume;
+
+ // GM volume scale to linear is x^4 so we need the 4th root of the volume, and need to rescale it from 0-100 to 0-0x3f80
+ // = 0x3f80 / sqrt(sqrt(100))
+ const double volumeMultiplier = 5140.5985643697174420974013458299;
+
+ if (volume > 100)
+ volume = 100;
+ uint16 hpVolume = static_cast<uint16>(floor(sqrt(sqrt(volume)) * volumeMultiplier));
+
+ _outputDriver->send(MidiDriver_BASE::MIDI_COMMAND_PROGRAM_CHANGE | _channel, program, 0);
+ _outputDriver->send(MidiDriver_BASE::MIDI_COMMAND_CONTROL_CHANGE | _channel, MidiDriver_BASE::MIDI_CONTROLLER_EXPRESSION, 127);
+ _outputDriver->send(MidiDriver_BASE::MIDI_COMMAND_CONTROL_CHANGE | _channel, MidiDriver_BASE::MIDI_CONTROLLER_REVERB, 0);
+ _outputDriver->send(MidiDriver_BASE::MIDI_COMMAND_CONTROL_CHANGE | _channel, MidiDriver_BASE::MIDI_CONTROLLER_VOLUME, (hpVolume >> 7) & 0x7f);
+ _outputDriver->send(MidiDriver_BASE::MIDI_COMMAND_CONTROL_CHANGE | _channel, MidiDriver_BASE::MIDI_CONTROLLER_VOLUME + 32, hpVolume & 0x7f);
+ _outputDriver->send(MidiDriver_BASE::MIDI_COMMAND_NOTE_ON | _channel, note, velocity);
+}
+
+void MidiNotePlayerImpl::stop() {
+ if (!_durationRemaining)
+ return;
+
+ _durationRemaining = 0;
+ _outputDriver->send(MidiDriver_BASE::MIDI_COMMAND_NOTE_OFF | _channel, _note, 0);
+}
+
+void MidiNotePlayerImpl::detach() {
+ if (_outputDriver) {
+ if (_durationRemaining)
+ stop();
+
+ _outputDriver->detach();
+ _outputDriver.reset();
+ }
+}
+
+// Simple combiner - Behaves "QuickTime-like" and all commands are passed through directly.
+// This applies volume by modulating note velocity.
+class MidiCombinerSimple;
+
+class MidiCombinerSourceSimple : public MidiCombinerSource {
+public:
+ explicit MidiCombinerSourceSimple(MidiCombinerSimple *combiner);
+
+ void setVolume(uint8 volume) override;
+ void detach() override;
+ void send(uint32 b) override;
+
+private:
+ MidiCombinerSimple *_combiner;
+ uint8 _volume;
+};
+
+class MidiCombinerSimple : public MidiCombiner {
+public:
+ explicit MidiCombinerSimple(MidiDriver_BASE *outputDriver);
+
+ Common::SharedPtr<MidiCombinerSource> createSource() override;
+
+ void send(uint32 b);
+
+private:
+ MidiDriver_BASE *_outputDriver;
+};
+
+MidiCombinerSourceSimple::MidiCombinerSourceSimple(MidiCombinerSimple *combiner) : _combiner(combiner), _volume(255) {
+}
+
+void MidiCombinerSourceSimple::setVolume(uint8 volume) {
+ _volume = volume;
+}
+
+void MidiCombinerSourceSimple::detach() {
+}
+
+void MidiCombinerSourceSimple::send(uint32 b) {
+ byte command = (b & 0xF0);
+
+ if (command == MIDI_COMMAND_NOTE_ON || command == MIDI_COMMAND_NOTE_OFF) {
+ byte velocity = (b >> 16) & 0xFF;
+ velocity = (velocity * _volume * 257 + 256) >> 16;
+ b = (b & 0xff00ffff) | (velocity << 16);
+ }
+
+ _combiner->send(b);
+}
+
+MidiCombinerSimple::MidiCombinerSimple(MidiDriver_BASE *outputDriver) : _outputDriver(outputDriver) {
+}
+
+Common::SharedPtr<MidiCombinerSource> MidiCombinerSimple::createSource() {
+ return Common::SharedPtr<MidiCombinerSource>(new MidiCombinerSourceSimple(this));
+}
+
+void MidiCombinerSimple::send(uint32 b) {
+ _outputDriver->send(b);
+}
+
+// Dynamic combiner - Dynamic channel allocation, accurate volume control
+class MidiCombinerDynamic;
+
+class MidiCombinerSourceDynamic : public MidiCombinerSource {
+public:
+ MidiCombinerSourceDynamic(MidiCombinerDynamic *combiner, uint sourceID);
+ ~MidiCombinerSourceDynamic();
+
+ void setVolume(uint8 volume) override;
+ void send(uint32 b) override;
+
+ void detach() override;
+
+private:
+ MidiCombinerDynamic *_combiner;
+ uint _sourceID;
+};
+
+class MidiCombinerDynamic : public MidiCombiner {
+public:
+ MidiCombinerDynamic(MidiDriver_BASE *outputDriver);
+
+ Common::SharedPtr<MidiCombinerSource> createSource() override;
+
+ void deallocateSource(uint sourceID);
+
+ void setSourceVolume(uint sourceID, uint8 volume);
+ void sendFromSource(uint sourceID, uint32 b);
+ void sendFromSource(uint sourceID, uint8 cmd, uint8 channel, uint8 param1, uint8 param2);
+
+private:
+ static const uint kMSBMask = 0x3f80u;
+ static const uint kLSBMask = 0x7fu;
+ static const uint kLRControllerStart = 64;
+ static const uint kSostenutoOnThreshold = 64;
+ static const uint kSustainOnThreshold = 64;
+
+ enum DataEntryState {
+ kDataEntryStateNone,
+ kDataEntryStateRPN,
+ kDataEntryStateNRPN,
+ };
+
+ struct MidiChannelState {
+ MidiChannelState();
+
+ void reset();
+ void softReset(); // Executes changes corresponding to Reset All Controllers message
+
+ uint16 _program;
+ uint16 _aftertouch;
+ uint16 _pitchBend;
+ uint16 _rpnNumber;
+ uint16 _nrpnNumber;
+ DataEntryState _dataEntryState;
+
+ uint16 _hrControllers[32];
+ uint8 _lrControllers[32];
+
+ uint16 _registeredParams[5];
+ };
+
+ struct SourceChannelState {
+ SourceChannelState();
+ void reset();
+
+ MidiChannelState _midiChannelState;
+ };
+
+ struct SourceState {
+ SourceState();
+
+ void allocate();
+ void deallocate();
+
+ SourceChannelState _sourceChannelState[MidiDriver_BASE::MIDI_CHANNEL_COUNT];
+ uint16 _root4MasterVolume;
+ bool _isAllocated;
+ };
+
+ struct OutputChannelState {
+ OutputChannelState();
+
+ bool _hasSource;
+ bool _volumeIsAmbiguous;
+ uint _sourceID;
+ uint _channelID;
+ uint _noteOffCounter;
+
+ MidiChannelState _midiChannelState;
+
+ uint _numActiveNotes;
+ };
+
+ struct MidiActiveNote {
+ uint8 _outputChannel;
+ uint16 _tone;
+ bool _affectedBySostenuto;
+
+ // If either of these are set, then the note is off, but is sustained by a pedal
+ bool _isSustainedBySustain;
+ bool _isSustainedBySostenuto;
+ };
+
+ void doNoteOn(uint sourceID, uint8 channel, uint8 param1, uint8 param2);
+ void doNoteOff(uint sourceID, uint8 channel, uint8 param1, uint8 param2);
+ void doPolyphonicAftertouch(uint sourceID, uint8 channel, uint8 param1, uint8 param2);
+ void doControlChange(uint sourceID, uint8 channel, uint8 param1, uint8 param2);
+ void doProgramChange(uint sourceID, uint8 channel, uint8 param1, uint8 param2);
+ void doChannelAftertouch(uint sourceID, uint8 channel, uint8 param1, uint8 param2);
+ void doPitchBend(uint sourceID, uint8 channel, uint8 param1, uint8 param2);
+
+ void doHighRangeControlChange(uint sourceID, uint8 channel, uint8 hrParam, uint16 value);
+ void doLowRangeControlChange(uint sourceID, uint8 channel, uint8 lrParam, uint8 value);
+
+ void doDataEntry(uint sourceID, uint8 channel, int16 existingValueMask, int16 offset);
+ void doChannelMode(uint sourceID, uint8 channel, uint8 param1, uint8 param2);
+ void doAllNotesOff(uint sourceID, uint8 channel, uint8 param2);
+ void doAllSoundOff(uint sourceID, uint8 channel, uint8 param2);
+ void doResetAllControllers(uint sourceID, uint8 channel, uint8 param2);
+
+ void sendToOutput(uint8 command, uint8 channel, uint8 param1, uint8 param2);
+
+ void syncSourceConfiguration(uint outputChannel, OutputChannelState &outChState, const SourceState &sourceState, const SourceChannelState &sourceChState);
+ void syncSourceHRController(uint outputChannel, OutputChannelState &outChState, const SourceState &sourceState, const SourceChannelState &sourceChState, uint hrController);
+ void syncSourceLRController(uint outputChannel, OutputChannelState &outChState, const SourceChannelState &sourceChState, uint lrController);
+ void syncSourceRegisteredParam(uint outputChannel, OutputChannelState &outChState, const SourceChannelState &sourceChState, uint rpn);
+
+ void tryCleanUpUnsustainedNote(uint noteIndex);
+
+ Common::Array<SourceState> _sources;
+ Common::Array<MidiActiveNote> _notes;
+ OutputChannelState _outputChannels[MidiDriver_BASE::MIDI_CHANNEL_COUNT];
+ uint _noteOffCounter;
+
+ MidiDriver_BASE *_outputDriver;
+
+ Common::SharedPtr<Common::DumpFile> _dumpFile;
+ int _eventCounter;
+};
+
+MidiCombinerSourceDynamic::MidiCombinerSourceDynamic(MidiCombinerDynamic *combiner, uint sourceID) : _combiner(combiner), _sourceID(sourceID) {
+}
+
+MidiCombinerSourceDynamic::~MidiCombinerSourceDynamic() {
+ assert(_combiner == nullptr); // Call detach first!
+}
+
+void MidiCombinerSourceDynamic::detach() {
+ _combiner->deallocateSource(_sourceID);
+ _combiner = nullptr;
+}
+
+void MidiCombinerSourceDynamic::setVolume(uint8 volume) {
+ _combiner->setSourceVolume(_sourceID, volume);
+}
+
+void MidiCombinerSourceDynamic::send(uint32 b) {
+ _combiner->sendFromSource(_sourceID, b);
+}
+
+MidiCombinerDynamic::MidiCombinerDynamic(MidiDriver_BASE *outputDriver) : _outputDriver(outputDriver), _noteOffCounter(1) {
+#if 0
+ _dumpFile.reset(new Common::DumpFile());
+ _dumpFile->open("mididump.csv");
+ _dumpFile->writeString("event\ttime\tchannel\tcmd\tparam1\tparam2\n");
+#endif
+
+ _eventCounter = 0;
+}
+
+Common::SharedPtr<MidiCombinerSource> MidiCombinerDynamic::createSource() {
+ uint sourceID = _sources.size();
+
+ for (uint i = 0; i < _sources.size(); i++) {
+ if (!_sources[i]._isAllocated) {
+ sourceID = i;
+ break;
+ }
+ }
+
+ if (sourceID == _sources.size())
+ _sources.push_back(SourceState());
+
+ _sources[sourceID].allocate();
+
+ return Common::SharedPtr<MidiCombinerSource>(new MidiCombinerSourceDynamic(this, sourceID));
+}
+
+void MidiCombinerDynamic::deallocateSource(uint sourceID) {
+ for (uint i = 0; i < ARRAYSIZE(_outputChannels); i++) {
+ OutputChannelState &ch = _outputChannels[i];
+ if (!ch._hasSource || ch._sourceID != sourceID)
+ continue;
+
+ // Stop any outputs and release sustain
+ sendFromSource(sourceID, MidiDriver_BASE::MIDI_COMMAND_CONTROL_CHANGE, i, MidiDriver_BASE::MIDI_CONTROLLER_SUSTAIN, 0);
+ sendFromSource(sourceID, MidiDriver_BASE::MIDI_COMMAND_CONTROL_CHANGE, i, MidiDriver_BASE::MIDI_CONTROLLER_SOSTENUTO, 0);
+ sendFromSource(sourceID, MidiDriver_BASE::MIDI_COMMAND_CONTROL_CHANGE, i, MidiDriver_BASE::MIDI_CONTROLLER_ALL_NOTES_OFF, 0);
+
+ ch._hasSource = false;
+ assert(ch._numActiveNotes == 0);
+ }
+
+ _sources[sourceID].deallocate();
+}
+
+void MidiCombinerDynamic::setSourceVolume(uint sourceID, uint8 volume) {
+ SourceState &src = _sources[sourceID];
+ src._root4MasterVolume = static_cast<uint16>(floor(sqrt(sqrt(volume)) * 16400.0));
+
+ for (uint i = 0; i < ARRAYSIZE(_outputChannels); i++) {
+ OutputChannelState &ch = _outputChannels[i];
+ if (!ch._hasSource || ch._sourceID != sourceID)
+ continue;
+
+ // Synchronize volume control
+ syncSourceHRController(i, ch, src, src._sourceChannelState[ch._channelID], MidiDriver_BASE::MIDI_CONTROLLER_VOLUME);
+ }
+}
+
+void MidiCombinerDynamic::sendFromSource(uint sourceID, uint32 b) {
+ uint8 cmd = static_cast<uint8>(b & 0xf0);
+ uint8 channel = static_cast<uint8>(b & 0x0f);
+ uint8 param1 = static_cast<uint8>((b >> 8) & 0xff);
+ uint8 param2 = static_cast<uint8>((b >> 16) & 0xff);
+
+ sendFromSource(sourceID, cmd, channel, param1, param2);
+}
+
+void MidiCombinerDynamic::sendFromSource(uint sourceID, uint8 cmd, uint8 channel, uint8 param1, uint8 param2) {
+ switch (cmd) {
+ case MidiDriver_BASE::MIDI_COMMAND_NOTE_ON:
+ doNoteOn(sourceID, channel, param1, param2);
+ break;
+ case MidiDriver_BASE::MIDI_COMMAND_NOTE_OFF:
+ doNoteOff(sourceID, channel, param1, param2);
+ break;
+ case MidiDriver_BASE::MIDI_COMMAND_POLYPHONIC_AFTERTOUCH:
+ doPolyphonicAftertouch(sourceID, channel, param1, param2);
+ break;
+ case MidiDriver_BASE::MIDI_COMMAND_CONTROL_CHANGE:
+ doControlChange(sourceID, channel, param1, param2);
+ break;
+ case MidiDriver_BASE::MIDI_COMMAND_PROGRAM_CHANGE:
+ doProgramChange(sourceID, channel, param1, param2);
+ break;
+ case MidiDriver_BASE::MIDI_COMMAND_CHANNEL_AFTERTOUCH:
+ doChannelAftertouch(sourceID, channel, param1, param2);
+ break;
+ case MidiDriver_BASE::MIDI_COMMAND_PITCH_BEND:
+ doPitchBend(sourceID, channel, param1, param2);
+ break;
+ case MidiDriver_BASE::MIDI_COMMAND_SYSTEM:
+ break;
+ }
+}
+
+void MidiCombinerDynamic::doNoteOn(uint sourceID, uint8 channel, uint8 param1, uint8 param2) {
+ uint outputChannel = 0;
+
+ if (channel == MidiDriver_BASE::MIDI_RHYTHM_CHANNEL) {
+ outputChannel = MidiDriver_BASE::MIDI_RHYTHM_CHANNEL;
+ } else {
+ bool foundChannel = false;
+
+ // Find an existing exactly-matching channel
+ for (uint i = 0; i < ARRAYSIZE(_outputChannels); i++) {
+ OutputChannelState &ch = _outputChannels[i];
+ if (ch._hasSource && ch._sourceID == sourceID && ch._channelID == channel) {
+ foundChannel = true;
+ outputChannel = i;
+ break;
+ }
+ }
+
+ if (!foundChannel) {
+ // Find an inactive channel
+ for (uint i = 0; i < ARRAYSIZE(_outputChannels); i++) {
+ if (i == MidiDriver_BASE::MIDI_RHYTHM_CHANNEL)
+ continue;
+
+ if (!_outputChannels[i]._hasSource) {
+ foundChannel = true;
+ outputChannel = i;
+ break;
+ }
+ }
+ }
+
+ if (!foundChannel) {
+ uint bestOffCounter = 0xffffffffu;
+
+ // Find the channel that went quiet the longest time ago
+ for (uint i = 0; i < ARRAYSIZE(_outputChannels); i++) {
+ if (i == MidiDriver_BASE::MIDI_RHYTHM_CHANNEL)
+ continue;
+
+ if (_outputChannels[i]._numActiveNotes == 0 && _outputChannels[i]._noteOffCounter < bestOffCounter) {
+ foundChannel = true;
+ outputChannel = i;
+ bestOffCounter = _outputChannels[i]._noteOffCounter;
+ }
+ }
+ }
+
+ // All eligible channels are playing already
+ if (!foundChannel)
+ return;
+ }
+
+ OutputChannelState &ch = _outputChannels[outputChannel];
+
+ if (!ch._hasSource || ch._sourceID != sourceID || ch._channelID != channel) {
+ ch._sourceID = sourceID;
+ ch._channelID = channel;
+ ch._hasSource = true;
+
+ const SourceState &sourceState = _sources[sourceID];
+ syncSourceConfiguration(outputChannel, ch, sourceState, sourceState._sourceChannelState[channel]);
+ }
+
+ sendToOutput(MidiDriver_BASE::MIDI_COMMAND_NOTE_ON, outputChannel, param1, param2);
+
+ MidiActiveNote note;
+ note._outputChannel = outputChannel;
+ note._tone = param1;
+ note._affectedBySostenuto = (ch._midiChannelState._lrControllers[MidiDriver_BASE::MIDI_CONTROLLER_SOSTENUTO - kLRControllerStart] >= kSostenutoOnThreshold);
+ note._isSustainedBySostenuto = false;
+ note._isSustainedBySustain = false;
+ _notes.push_back(note);
+
+ ch._numActiveNotes++;
+}
+
+void MidiCombinerDynamic::doNoteOff(uint sourceID, uint8 channel, uint8 param1, uint8 param2) {
+ for (uint i = 0; i < ARRAYSIZE(_outputChannels); i++) {
+ OutputChannelState &ch = _outputChannels[i];
+
+ if (ch._hasSource && ch._sourceID == sourceID && ch._channelID == channel) {
+ sendToOutput(MidiDriver_BASE::MIDI_COMMAND_NOTE_OFF, i, param1, param2);
+
+ for (uint ani = 0; ani < _notes.size(); ani++) {
+ MidiActiveNote ¬e = _notes[ani];
+ if (note._outputChannel == i && note._tone == param1 && !note._isSustainedBySostenuto && !note._isSustainedBySustain) {
+ if (ch._midiChannelState._lrControllers[MidiDriver_BASE::MIDI_CONTROLLER_SUSTAIN - kLRControllerStart] >= kSustainOnThreshold)
+ note._isSustainedBySustain = true;
+
+ if (note._affectedBySostenuto && ch._midiChannelState._lrControllers[MidiDriver_BASE::MIDI_CONTROLLER_SOSTENUTO - kLRControllerStart] >= kSostenutoOnThreshold)
+ note._isSustainedBySostenuto = true;
+
+ tryCleanUpUnsustainedNote(ani);
+ break;
+ }
+ }
+
+ break;
+ }
+ }
+}
+
+void MidiCombinerDynamic::doPolyphonicAftertouch(uint sourceID, uint8 channel, uint8 param1, uint8 param2) {
+ for (uint i = 0; i < ARRAYSIZE(_outputChannels); i++) {
+ const OutputChannelState &ch = _outputChannels[i];
+
+ if (ch._hasSource && ch._sourceID == sourceID && ch._channelID == channel) {
+ sendToOutput(MidiDriver_BASE::MIDI_COMMAND_POLYPHONIC_AFTERTOUCH, i, param1, param2);
+ break;
+ }
+ }
+}
+
+void MidiCombinerDynamic::doControlChange(uint sourceID, uint8 channel, uint8 param1, uint8 param2) {
+ SourceState &src = _sources[sourceID];
+ SourceChannelState &sch = src._sourceChannelState[channel];
+
+ if (param1 == MidiDriver_BASE::MIDI_CONTROLLER_DATA_ENTRY_MSB) {
+ doDataEntry(sourceID, channel, kLSBMask, param2 << 7);
+ return;
+ } else if (param1 == MidiDriver_BASE::MIDI_CONTROLLER_DATA_ENTRY_LSB) {
+ doDataEntry(sourceID, channel, kMSBMask, param2);
+ return;
+ } else if (param1 < 32) {
+ uint16 ctrl = ((sch._midiChannelState._hrControllers[param1] & kLSBMask) | ((param2 & 0x7f)) << 7);
+ doHighRangeControlChange(sourceID, channel, param1, ctrl);
+ return;
+ } else if (param1 < 64) {
+ uint16 ctrl = ((sch._midiChannelState._hrControllers[param1 - 32] & kMSBMask) | (param2 & 0x7f));
+ doHighRangeControlChange(sourceID, channel, param1 - 32, ctrl);
+ return;
+ } else if (param1 < 96) {
+ doLowRangeControlChange(sourceID, channel, param1 - 64, param2);
+ return;
+ }
+
+ switch (param1) {
+ case 96:
+ // Data increment
+ doDataEntry(sourceID, channel, 0x3fff, 1);
+ break;
+ case 97:
+ // Data decrement
+ doDataEntry(sourceID, channel, 0x3fff, -1);
+ break;
+ case 98:
+ // NRPN LSB
+ sch._midiChannelState._nrpnNumber = ((sch._midiChannelState._nrpnNumber & kMSBMask) | (param2 & 0x7f));
+ sch._midiChannelState._dataEntryState = kDataEntryStateNRPN;
+ break;
+ case 99:
+ // NRPN MSB
+ sch._midiChannelState._nrpnNumber = ((sch._midiChannelState._nrpnNumber & kLSBMask) | ((param2 & 0x7f) << 7));
+ sch._midiChannelState._dataEntryState = kDataEntryStateNRPN;
+ break;
+ case 100:
+ // RPN LSB
+ sch._midiChannelState._rpnNumber = ((sch._midiChannelState._rpnNumber & kMSBMask) | (param2 & 0x7f));
+ sch._midiChannelState._dataEntryState = kDataEntryStateRPN;
+ break;
+ case 101:
+ // RPN MSB
+ sch._midiChannelState._rpnNumber = ((sch._midiChannelState._rpnNumber & kLSBMask) | ((param2 & 0x7f) << 7));
+ sch._midiChannelState._dataEntryState = kDataEntryStateRPN;
+ break;
+ default:
+ if (param1 >= 120 && param1 < 128)
+ doChannelMode(sourceID, channel, param1, param2);
+ break;
+ };
+}
+
+void MidiCombinerDynamic::doProgramChange(uint sourceID, uint8 channel, uint8 param1, uint8 param2) {
+ for (uint i = 0; i < ARRAYSIZE(_outputChannels); i++) {
+ OutputChannelState &ch = _outputChannels[i];
+
+ if (ch._hasSource && ch._sourceID == sourceID && ch._channelID == channel) {
+ sendToOutput(MidiDriver_BASE::MIDI_COMMAND_PROGRAM_CHANGE, i, param1, param2);
+ ch._midiChannelState._program = param1;
+ break;
+ }
+ }
+
+ _sources[sourceID]._sourceChannelState[channel]._midiChannelState._program = param1;
+}
+
+void MidiCombinerDynamic::doChannelAftertouch(uint sourceID, uint8 channel, uint8 param1, uint8 param2) {
+ for (uint i = 0; i < ARRAYSIZE(_outputChannels); i++) {
+ OutputChannelState &ch = _outputChannels[i];
+
+ if (ch._hasSource && ch._sourceID == sourceID && ch._channelID == channel) {
+ sendToOutput(MidiDriver_BASE::MIDI_COMMAND_CHANNEL_AFTERTOUCH, i, param1, param2);
+ ch._midiChannelState._aftertouch = param1;
+ break;
+ }
+ }
+}
+
+void MidiCombinerDynamic::doPitchBend(uint sourceID, uint8 channel, uint8 param1, uint8 param2) {
+ uint16 pitchBend = (param1 & 0x7f) | ((param2 & 0x7f) << 7);
+ for (uint i = 0; i < ARRAYSIZE(_outputChannels); i++) {
+ OutputChannelState &ch = _outputChannels[i];
+
+ if (ch._hasSource && ch._sourceID == sourceID && ch._channelID == channel) {
+ sendToOutput(MidiDriver_BASE::MIDI_COMMAND_PITCH_BEND, i, param1, param2);
+ ch._midiChannelState._pitchBend = pitchBend;
+ break;
+ }
+ }
+
+ _sources[sourceID]._sourceChannelState[channel]._midiChannelState._pitchBend = pitchBend;
+}
+
+void MidiCombinerDynamic::doHighRangeControlChange(uint sourceID, uint8 channel, uint8 hrParam, uint16 value) {
+ SourceState &src = _sources[sourceID];
+ SourceChannelState &srcCh = src._sourceChannelState[channel];
+ srcCh._midiChannelState._hrControllers[hrParam] = value;
+
+ for (uint i = 0; i < ARRAYSIZE(_outputChannels); i++) {
+ OutputChannelState &ch = _outputChannels[i];
+
+ if (ch._hasSource && ch._sourceID == sourceID && ch._channelID == channel) {
+ syncSourceHRController(i, ch, src, srcCh, hrParam);
+ break;
+ }
+ }
+}
+
+void MidiCombinerDynamic::doLowRangeControlChange(uint sourceID, uint8 channel, uint8 lrParam, uint8 value) {
+ SourceChannelState &srcCh = _sources[sourceID]._sourceChannelState[channel];
+ srcCh._midiChannelState._lrControllers[lrParam] = value;
+
+ for (uint i = 0; i < ARRAYSIZE(_outputChannels); i++) {
+ OutputChannelState &ch = _outputChannels[i];
+
+ if (ch._hasSource && ch._sourceID == sourceID && ch._channelID == channel) {
+ if (lrParam == MidiDriver_BASE::MIDI_CONTROLLER_SUSTAIN - kLRControllerStart && value < kSustainOnThreshold) {
+ for (uint rni = _notes.size(); rni > 0; rni--) {
+ uint noteIndex = rni - 1;
+ MidiActiveNote ¬e = _notes[noteIndex];
+ if (note._isSustainedBySustain) {
+ note._isSustainedBySustain = false;
+ tryCleanUpUnsustainedNote(noteIndex);
+ }
+ }
+ } else if (lrParam == MidiDriver_BASE::MIDI_CONTROLLER_SOSTENUTO - kLRControllerStart && value < kSostenutoOnThreshold) {
+ for (uint rni = _notes.size(); rni > 0; rni--) {
+ uint noteIndex = rni - 1;
+ MidiActiveNote ¬e = _notes[noteIndex];
+ if (note._isSustainedBySostenuto) {
+ note._isSustainedBySostenuto = false;
+ tryCleanUpUnsustainedNote(noteIndex);
+ }
+ }
+ }
+
+ syncSourceLRController(i, ch, srcCh, lrParam);
+ break;
+ }
+ }
+}
+
+void MidiCombinerDynamic::doDataEntry(uint sourceID, uint8 channel, int16 existingValueMask, int16 offset) {
+ SourceChannelState &srcCh = _sources[sourceID]._sourceChannelState[channel];
+
+ if (srcCh._midiChannelState._dataEntryState == kDataEntryStateRPN && srcCh._midiChannelState._rpnNumber < ARRAYSIZE(srcCh._midiChannelState._registeredParams)) {
+ int32 rp = srcCh._midiChannelState._registeredParams[srcCh._midiChannelState._rpnNumber];
+ rp &= existingValueMask;
+ rp += offset;
+
+ srcCh._midiChannelState._registeredParams[srcCh._midiChannelState._rpnNumber] = (rp & existingValueMask) + offset;
+
+ for (uint i = 0; i < ARRAYSIZE(_outputChannels); i++) {
+ OutputChannelState &ch = _outputChannels[i];
+
+ if (ch._hasSource && ch._sourceID == sourceID && ch._channelID == channel) {
+ syncSourceRegisteredParam(i, ch, srcCh, srcCh._midiChannelState._rpnNumber);
+ break;
+ }
+ }
+ }
+}
+
+void MidiCombinerDynamic::doChannelMode(uint sourceID, uint8 channel, uint8 param1, uint8 param2) {
+ // Remap omni/poly/mono modes to all notes off, since we don't do anything with omni/poly
+ switch (param1) {
+ case MidiDriver_BASE::MIDI_CONTROLLER_OMNI_OFF:
+ case MidiDriver_BASE::MIDI_CONTROLLER_OMNI_ON:
+ case MidiDriver_BASE::MIDI_CONTROLLER_MONO_ON:
+ case MidiDriver_BASE::MIDI_CONTROLLER_POLY_ON:
+ case MidiDriver_BASE::MIDI_CONTROLLER_ALL_NOTES_OFF:
+ doAllNotesOff(sourceID, channel, param2);
+ break;
+ case MidiDriver_BASE::MIDI_CONTROLLER_ALL_SOUND_OFF:
+ doAllSoundOff(sourceID, channel, param2);
+ break;
+ case MidiDriver_BASE::MIDI_CONTROLLER_RESET_ALL_CONTROLLERS:
+ doResetAllControllers(sourceID, channel, param2);
+ break;
+ case 122: // Local control (ignore)
+ default:
+ break;
+ }
+}
+
+void MidiCombinerDynamic::doAllNotesOff(uint sourceID, uint8 channel, uint8 param2) {
+ uint outputChannel = 0;
+ bool foundChannel = false;
+
+ for (uint i = 0; i < ARRAYSIZE(_outputChannels); i++) {
+ OutputChannelState &ch = _outputChannels[i];
+ if (ch._hasSource && ch._sourceID == sourceID && ch._channelID == channel) {
+ foundChannel = true;
+ outputChannel = i;
+ break;
+ }
+ }
+
+ if (!foundChannel)
+ return;
+
+ OutputChannelState &ch = _outputChannels[outputChannel];
+
+ bool sustainOn = (ch._midiChannelState._lrControllers[MidiDriver_BASE::MIDI_CONTROLLER_SUSTAIN - kLRControllerStart] >= kSustainOnThreshold);
+ bool sostenutoOn = (ch._midiChannelState._lrControllers[MidiDriver_BASE::MIDI_CONTROLLER_SOSTENUTO - kLRControllerStart] >= kSostenutoOnThreshold);
+
+ for (uint rni = _notes.size(); rni > 0; rni--) {
+ uint noteIndex = rni - 1;
+ MidiActiveNote ¬e = _notes[noteIndex];
+ if (note._outputChannel == outputChannel) {
+ if (note._affectedBySostenuto && sostenutoOn)
+ note._isSustainedBySostenuto = true;
+ if (sustainOn)
+ note._isSustainedBySustain = true;
+
+ tryCleanUpUnsustainedNote(noteIndex);
+ }
+ }
+
+ sendToOutput(MidiDriver_BASE::MIDI_COMMAND_CONTROL_CHANGE, outputChannel, MidiDriver_BASE::MIDI_CONTROLLER_ALL_NOTES_OFF, param2);
+}
+
+void MidiCombinerDynamic::doAllSoundOff(uint sourceID, uint8 channel, uint8 param2) {
+ uint outputChannel = 0;
+ bool foundChannel = false;
+
+ for (uint i = 0; i < ARRAYSIZE(_outputChannels); i++) {
+ OutputChannelState &ch = _outputChannels[i];
+ if (ch._hasSource && ch._sourceID == sourceID && ch._channelID == channel) {
+ foundChannel = true;
+ outputChannel = i;
+ break;
+ }
+ }
+
+ if (!foundChannel)
+ return;
+
+ OutputChannelState &ch = _outputChannels[outputChannel];
+
+ for (uint rni = _notes.size(); rni > 0; rni--) {
+ uint noteIndex = rni - 1;
+ MidiActiveNote ¬e = _notes[noteIndex];
+ if (note._outputChannel == outputChannel) {
+ note._isSustainedBySostenuto = false;
+ note._isSustainedBySustain = false;
+
+ tryCleanUpUnsustainedNote(noteIndex);
+ }
+ }
+
+ sendToOutput(MidiDriver_BASE::MIDI_COMMAND_CONTROL_CHANGE, outputChannel, MidiDriver_BASE::MIDI_CONTROLLER_ALL_SOUND_OFF, param2);
+ ch._noteOffCounter = 0; // All sound is off so this can be recycled quickly
+}
+
+void MidiCombinerDynamic::doResetAllControllers(uint sourceID, uint8 channel, uint8 param2) {
+ SourceChannelState &srcCh = _sources[sourceID]._sourceChannelState[channel];
+
+ srcCh._midiChannelState.softReset();
+
+ uint outputChannel = 0;
+ bool foundChannel = false;
+
+ for (uint i = 0; i < ARRAYSIZE(_outputChannels); i++) {
+ OutputChannelState &ch = _outputChannels[i];
+ if (ch._hasSource && ch._sourceID == sourceID && ch._channelID == channel) {
+ foundChannel = true;
+ outputChannel = i;
+ break;
+ }
+ }
+
+ if (!foundChannel)
+ return;
+
+ OutputChannelState &ch = _outputChannels[outputChannel];
+ ch._midiChannelState.softReset();
+
+ // Release all sustained notes
+ for (uint rni = _notes.size(); rni > 0; rni--) {
+ uint noteIndex = rni - 1;
+ MidiActiveNote ¬e = _notes[noteIndex];
+ if (note._outputChannel == outputChannel) {
+ if (note._isSustainedBySostenuto || note._isSustainedBySustain) {
+ note._isSustainedBySostenuto = false;
+ note._isSustainedBySustain = false;
+ tryCleanUpUnsustainedNote(noteIndex);
+ }
+ }
+ }
+
+ sendToOutput(MidiDriver_BASE::MIDI_COMMAND_CONTROL_CHANGE, outputChannel, MidiDriver_BASE::MIDI_CONTROLLER_RESET_ALL_CONTROLLERS, 0);
+}
+
+void MidiCombinerDynamic::sendToOutput(uint8 command, uint8 channel, uint8 param1, uint8 param2) {
+ uint32 output = static_cast<uint32>(command) | static_cast<uint32>(channel) | static_cast<uint32>(param1 << 8) | static_cast<uint32>(param2 << 16);
+
+ if (_dumpFile) {
+ const int timestamp = g_system->getMillis(true);
+
+ const char *cmdName = "Unknown Command";
+ switch (command) {
+ case MidiDriver_BASE::MIDI_COMMAND_CHANNEL_AFTERTOUCH:
+ cmdName = "ChannelAftertouch";
+ break;
+ case MidiDriver_BASE::MIDI_COMMAND_NOTE_OFF:
+ cmdName = "NoteOff";
+ break;
+ case MidiDriver_BASE::MIDI_COMMAND_NOTE_ON:
+ cmdName = "NoteOn";
+ break;
+ case MidiDriver_BASE::MIDI_COMMAND_POLYPHONIC_AFTERTOUCH:
+ cmdName = "PolyAftertouch";
+ break;
+ case MidiDriver_BASE::MIDI_COMMAND_CONTROL_CHANGE:
+ cmdName = "ControlChange";
+ break;
+ case MidiDriver_BASE::MIDI_COMMAND_PROGRAM_CHANGE:
+ cmdName = "ProgramChange";
+ break;
+ case MidiDriver_BASE::MIDI_COMMAND_PITCH_BEND:
+ cmdName = "PitchBend";
+ break;
+ case MidiDriver_BASE::MIDI_COMMAND_SYSTEM:
+ cmdName = "System";
+ break;
+ default:
+ cmdName = "Unknown";
+ }
+
+ if (command == MidiDriver_BASE::MIDI_COMMAND_CONTROL_CHANGE) {
+ Common::String ctrlName = "Unknown";
+
+ switch (param1) {
+ case MidiDriver_BASE::MIDI_CONTROLLER_BANK_SELECT_MSB:
+ ctrlName = "BankSelect";
+ break;
+ case MidiDriver_BASE::MIDI_CONTROLLER_MODULATION:
+ ctrlName = "Modulation";
+ break;
+ case MidiDriver_BASE::MIDI_CONTROLLER_DATA_ENTRY_MSB:
+ ctrlName = "DataEntryMSB";
+ break;
+ case MidiDriver_BASE::MIDI_CONTROLLER_VOLUME:
+ ctrlName = "VolumeMSB";
+ break;
+ case MidiDriver_BASE::MIDI_CONTROLLER_VOLUME + 32:
+ ctrlName = "VolumeLSB";
+ break;
+ case MidiDriver_BASE::MIDI_CONTROLLER_BALANCE:
+ ctrlName = "Balance";
+ break;
+ case MidiDriver_BASE::MIDI_CONTROLLER_PANNING:
+ ctrlName = "Panning";
+ break;
+ case MidiDriver_BASE::MIDI_CONTROLLER_EXPRESSION:
+ ctrlName = "Expression";
+ break;
+ case MidiDriver_BASE::MIDI_CONTROLLER_BANK_SELECT_LSB:
+ ctrlName = "BankSelectLSB";
+ break;
+ case MidiDriver_BASE::MIDI_CONTROLLER_DATA_ENTRY_LSB:
+ ctrlName = "DataEntryLSB";
+ break;
+ case MidiDriver_BASE::MIDI_CONTROLLER_SUSTAIN:
+ ctrlName = "Sustain";
+ break;
+ case MidiDriver_BASE::MIDI_CONTROLLER_PORTAMENTO:
+ ctrlName = "Portamento";
+ break;
+ case MidiDriver_BASE::MIDI_CONTROLLER_SOSTENUTO:
+ ctrlName = "Sostenuto";
+ break;
+ case MidiDriver_BASE::MIDI_CONTROLLER_SOFT:
+ ctrlName = "Soft";
+ break;
+
+ case MidiDriver_BASE::MIDI_CONTROLLER_REVERB:
+ ctrlName = "Reverb";
+ break;
+ case MidiDriver_BASE::MIDI_CONTROLLER_CHORUS:
+ ctrlName = "Chorus";
+ break;
+ case MidiDriver_BASE::MIDI_CONTROLLER_RPN_LSB:
+ ctrlName = "RPNLSB";
+ break;
+ case MidiDriver_BASE::MIDI_CONTROLLER_RPN_MSB:
+ ctrlName = "RPNMSB";
+ break;
+ case MidiDriver_BASE::MIDI_CONTROLLER_ALL_SOUND_OFF:
+ ctrlName = "AllSoundOff";
+ break;
+ case MidiDriver_BASE::MIDI_CONTROLLER_RESET_ALL_CONTROLLERS:
+ ctrlName = "ResetAllControllers";
+ break;
+ case MidiDriver_BASE::MIDI_CONTROLLER_ALL_NOTES_OFF:
+ ctrlName = "AllNotesOff";
+ break;
+ case MidiDriver_BASE::MIDI_CONTROLLER_OMNI_ON:
+ ctrlName = "OmniOn";
+ break;
+ case MidiDriver_BASE::MIDI_CONTROLLER_OMNI_OFF:
+ ctrlName = "OmniOff";
+ break;
+ case MidiDriver_BASE::MIDI_CONTROLLER_MONO_ON:
+ ctrlName = "MonoOn";
+ break;
+ case MidiDriver_BASE::MIDI_CONTROLLER_POLY_ON:
+ ctrlName = "PolyOn";
+ break;
+ default:
+ ctrlName = Common::String::format("Unknown%02x", static_cast<int>(param1));
+ }
+
+ _dumpFile->writeString(Common::String::format("%i\t%i\t%i\t%s\t%s\t%i\n", _eventCounter, timestamp, static_cast<int>(channel), cmdName, ctrlName.c_str(), static_cast<int>(param2)));
+ } else
+ _dumpFile->writeString(Common::String::format("%i\t%i\t%i\t%s\t%i\t%i\n", _eventCounter, timestamp, static_cast<int>(channel), cmdName, static_cast<int>(param1), static_cast<int>(param2)));
+
+ _eventCounter++;
+ }
+
+ _outputDriver->send(output);
+}
+
+void MidiCombinerDynamic::syncSourceConfiguration(uint outputChannel, OutputChannelState &outChState, const SourceState &srcState, const SourceChannelState &sourceChState) {
+ const MidiChannelState &srcMidiChState = sourceChState._midiChannelState;
+ MidiChannelState &outState = outChState._midiChannelState;
+
+ if (outState._program != srcMidiChState._program) {
+ outState._program = srcMidiChState._program;
+ sendToOutput(MidiDriver_BASE::MIDI_COMMAND_PROGRAM_CHANGE, outputChannel, srcMidiChState._program, 0);
+ }
+
+ if (outState._aftertouch != srcMidiChState._aftertouch) {
+ outState._aftertouch = srcMidiChState._aftertouch;
+ sendToOutput(MidiDriver_BASE::MIDI_COMMAND_CHANNEL_AFTERTOUCH, outputChannel, srcMidiChState._aftertouch, 0);
+ }
+
+ if (outState._pitchBend != srcMidiChState._pitchBend) {
+ outState._pitchBend = srcMidiChState._pitchBend;
+ sendToOutput(MidiDriver_BASE::MIDI_COMMAND_PITCH_BEND, outputChannel, (srcMidiChState._pitchBend & kLSBMask), (srcMidiChState._pitchBend & kMSBMask) >> 7);
+ }
+
+ for (uint i = 0; i < ARRAYSIZE(srcMidiChState._hrControllers); i++)
+ syncSourceHRController(outputChannel, outChState, srcState, sourceChState, i);
+
+ for (uint i = 0; i < ARRAYSIZE(srcMidiChState._lrControllers); i++)
+ syncSourceLRController(outputChannel, outChState, sourceChState, i);
+
+ for (uint i = 0; i < ARRAYSIZE(srcMidiChState._registeredParams); i++)
+ syncSourceRegisteredParam(outputChannel, outChState, sourceChState, i);
+}
+
+void MidiCombinerDynamic::syncSourceHRController(uint outputChannel, OutputChannelState &outChState, const SourceState &srcState, const SourceChannelState &sourceChState, uint hrController) {
+ const MidiChannelState &srcMidiChState = sourceChState._midiChannelState;
+ MidiChannelState &outState = outChState._midiChannelState;
+
+ uint16 effectiveValue = srcMidiChState._hrControllers[hrController];
+
+ if (hrController == MidiDriver_BASE::MIDI_CONTROLLER_VOLUME) {
+ // GM volume to gain control is 40*log10(V/127)
+ // This means linear scale is (volume/0x3f80)^4
+ // To modulate the volume linearly, we must multiply the volume by the 4th root
+ // of the volume.
+ uint32 effectiveValueScaled = static_cast<uint32>(srcState._root4MasterVolume) * static_cast<uint32>(effectiveValue);
+ effectiveValueScaled += (effectiveValueScaled >> 16) + 1u;
+ effectiveValue = static_cast<uint16>(effectiveValueScaled >> 16);
+ }
+
+ if (outState._hrControllers[hrController] == effectiveValue)
+ return;
+
+ uint16 deltaBits = (outState._hrControllers[hrController] ^ effectiveValue);
+
+ if (deltaBits & kMSBMask)
+ sendToOutput(MidiDriver_BASE::MIDI_COMMAND_CONTROL_CHANGE, outputChannel, hrController, (effectiveValue & kMSBMask) >> 7);
+ if (deltaBits & kLSBMask)
+ sendToOutput(MidiDriver_BASE::MIDI_COMMAND_CONTROL_CHANGE, outputChannel, hrController + 32, effectiveValue & kLSBMask);
+
+ outState._hrControllers[hrController] = effectiveValue;
+}
+
+void MidiCombinerDynamic::syncSourceLRController(uint outputChannel, OutputChannelState &outChState, const SourceChannelState &sourceChState, uint lrController) {
+ const MidiChannelState &srcState = sourceChState._midiChannelState;
+ MidiChannelState &outState = outChState._midiChannelState;
+
+ if (outState._lrControllers[lrController] == srcState._lrControllers[lrController])
+ return;
+
+ sendToOutput(MidiDriver_BASE::MIDI_COMMAND_CONTROL_CHANGE, outputChannel, lrController + kLRControllerStart, srcState._lrControllers[lrController] & kLSBMask);
+
+ outState._lrControllers[lrController] = srcState._lrControllers[lrController];
+}
+
+void MidiCombinerDynamic::syncSourceRegisteredParam(uint outputChannel, OutputChannelState &outChState, const SourceChannelState &sourceChState, uint rpn) {
+ const MidiChannelState &srcState = sourceChState._midiChannelState;
+ MidiChannelState &outState = outChState._midiChannelState;
+
+ if (outState._registeredParams[rpn] == srcState._registeredParams[rpn])
+ return;
+
+ outState._registeredParams[rpn] = srcState._registeredParams[rpn];
+
+ if (outState._dataEntryState != kDataEntryStateRPN || outState._rpnNumber != srcState._rpnNumber) {
+ outState._dataEntryState = kDataEntryStateRPN;
+ outState._rpnNumber = srcState._rpnNumber;
+ sendToOutput(MidiDriver_BASE::MIDI_COMMAND_CONTROL_CHANGE, outputChannel, MidiDriver_BASE::MIDI_CONTROLLER_RPN_LSB, rpn & kLSBMask);
+ sendToOutput(MidiDriver_BASE::MIDI_COMMAND_CONTROL_CHANGE, outputChannel, MidiDriver_BASE::MIDI_CONTROLLER_RPN_MSB, (rpn & kMSBMask) >> 7);
+ }
+
+ sendToOutput(MidiDriver_BASE::MIDI_COMMAND_CONTROL_CHANGE, outputChannel, MidiDriver_BASE::MIDI_CONTROLLER_DATA_ENTRY_LSB, srcState._registeredParams[rpn] & kLSBMask);
+ sendToOutput(MidiDriver_BASE::MIDI_COMMAND_CONTROL_CHANGE, outputChannel, MidiDriver_BASE::MIDI_CONTROLLER_DATA_ENTRY_MSB, (srcState._registeredParams[rpn] & kMSBMask) >> 7);
+}
+
+void MidiCombinerDynamic::tryCleanUpUnsustainedNote(uint noteIndex) {
+ MidiActiveNote ¬e = _notes[noteIndex];
+
+ if (!note._isSustainedBySostenuto && !note._isSustainedBySustain) {
+ OutputChannelState &outCh = _outputChannels[note._outputChannel];
+ assert(outCh._numActiveNotes > 0);
+ outCh._numActiveNotes--;
+ if (!outCh._numActiveNotes)
+ outCh._noteOffCounter = _noteOffCounter++;
+
+ _notes.remove_at(noteIndex);
+ }
+}
+
+MidiCombinerDynamic::MidiChannelState::MidiChannelState() {
+ reset();
+}
+
+void MidiCombinerDynamic::MidiChannelState::reset() {
+ _program = 0;
+ _aftertouch = 0;
+ _pitchBend = 0x2000;
+
+ for (uint i = 0; i < ARRAYSIZE(_hrControllers); i++)
+ _hrControllers[i] = 0;
+ for (uint i = 0; i < ARRAYSIZE(_lrControllers); i++)
+ _lrControllers[i] = 0;
+ for (uint i = 0; i < ARRAYSIZE(_registeredParams); i++)
+ _registeredParams[i] = 0;
+
+ _hrControllers[MidiDriver_BASE::MIDI_CONTROLLER_BALANCE] = (64 << 7);
+ _hrControllers[MidiDriver_BASE::MIDI_CONTROLLER_PANNING] = (64 << 7);
+ _hrControllers[MidiDriver_BASE::MIDI_CONTROLLER_VOLUME] = (127 << 7);
+
+ _dataEntryState = kDataEntryStateNone;
+ _rpnNumber = 0;
+ _nrpnNumber = 0;
+}
+
+void MidiCombinerDynamic::MidiChannelState::softReset() {
+ _hrControllers[MidiDriver_BASE::MIDI_CONTROLLER_MODULATION] = 0;
+ _lrControllers[MidiDriver_BASE::MIDI_CONTROLLER_SUSTAIN - kLRControllerStart] = 0;
+ _lrControllers[MidiDriver_BASE::MIDI_CONTROLLER_PORTAMENTO - kLRControllerStart] = 0;
+ _lrControllers[MidiDriver_BASE::MIDI_CONTROLLER_SOSTENUTO - kLRControllerStart] = 0;
+ _lrControllers[MidiDriver_BASE::MIDI_CONTROLLER_SOFT - kLRControllerStart] = 0;
+ _dataEntryState = kDataEntryStateNone;
+ _rpnNumber = 0;
+ _nrpnNumber = 0;
+ _aftertouch = 0;
+ _hrControllers[MidiDriver_BASE::MIDI_CONTROLLER_EXPRESSION] = (127 << 7);
+ _pitchBend = (64 << 7);
+}
+
+MidiCombinerDynamic::SourceChannelState::SourceChannelState() {
+ reset();
+}
+
+void MidiCombinerDynamic::SourceChannelState::reset() {
+}
+
+MidiCombinerDynamic::SourceState::SourceState() : _isAllocated(false), _root4MasterVolume(0xffffu) {
+}
+
+void MidiCombinerDynamic::SourceState::allocate() {
+ _isAllocated = true;
+}
+
+void MidiCombinerDynamic::SourceState::deallocate() {
+ _isAllocated = false;
+}
+
+MidiCombinerDynamic::OutputChannelState::OutputChannelState() : _sourceID(0), _volumeIsAmbiguous(true), _channelID(0), _hasSource(false), _noteOffCounter(0), _numActiveNotes(0) {
+}
+
+MultiMidiPlayer::MultiMidiPlayer(bool dynamicMidiMixer) {
+ if (dynamicMidiMixer)
+ _combiner.reset(new MidiCombinerDynamic(this));
+ else
+ _combiner.reset(new MidiCombinerSimple(this));
+
+ createDriver(MDT_MIDI | MDT_ADLIB | MDT_PREFER_GM);
+
+ if (_driver->open() != 0) {
+ _driver->close();
+ delete _driver;
+ _driver = nullptr;
+ return;
+ }
+
+ _driver->setTimerCallback(this, &timerCallback);
+}
+
+MultiMidiPlayer::~MultiMidiPlayer() {
+ Common::StackLock lock(_mutex);
+ _filePlayers.clear();
+ _notePlayers.clear();
+}
+
+void MultiMidiPlayer::timerCallback(void *refCon) {
+ static_cast<MultiMidiPlayer *>(refCon)->onTimer();
+}
+
+void MultiMidiPlayer::onTimer() {
+ Common::StackLock lock(_mutex);
+
+ for (const Common::SharedPtr<MidiFilePlayerImpl> &player : _filePlayers)
+ player->onTimer();
+
+ for (const Common::SharedPtr<MidiNotePlayerImpl> &player : _notePlayers)
+ player->onTimer();
+}
+
+MidiFilePlayer *MultiMidiPlayer::createFilePlayer(const Common::SharedPtr<Data::Midi::MidiModifier::EmbeddedFile> &file, bool hasTempoOverride, double tempoOverride, uint8 volume, bool loop, uint16 mutedTracks) {
+ Common::SharedPtr<MidiCombinerSource> combinerSource = createSource();
+ Common::SharedPtr<MidiFilePlayerImpl> filePlayer(new MidiFilePlayerImpl(combinerSource, file, getBaseTempo(), hasTempoOverride, tempoOverride, volume, loop, mutedTracks));
+
+ {
+ Common::StackLock lock(_mutex);
+ combinerSource->setVolume(volume);
+ _filePlayers.push_back(filePlayer);
+ }
+
+ return filePlayer.get();
+}
+
+MidiNotePlayer *MultiMidiPlayer::createNotePlayer() {
+ Common::SharedPtr<MidiCombinerSource> combinerSource = createSource();
+ Common::SharedPtr<MidiNotePlayerImpl> notePlayer(new MidiNotePlayerImpl(combinerSource, getBaseTempo()));
+
+ {
+ Common::StackLock lock(_mutex);
+ _notePlayers.push_back(notePlayer);
+ }
+
+ return notePlayer.get();
+}
+
+Common::SharedPtr<MidiCombinerSource> MultiMidiPlayer::createSource() {
+ Common::StackLock lock(_mutex);
+ return _combiner->createSource();
+}
+
+void MultiMidiPlayer::deleteFilePlayer(MidiFilePlayer *player) {
+ Common::SharedPtr<MidiFilePlayerImpl> ref;
+
+ for (Common::Array<Common::SharedPtr<MidiFilePlayerImpl> >::iterator it = _filePlayers.begin(), itEnd = _filePlayers.end(); it != itEnd; ++it) {
+ if (it->get() == player) {
+ {
+ Common::StackLock lock(_mutex);
+ ref = *it;
+ _filePlayers.erase(it);
+ ref->stop();
+ }
+ break;
+ }
+ }
+
+ if (ref)
+ ref->detach();
+}
+
+void MultiMidiPlayer::deleteNotePlayer(MidiNotePlayer *player) {
+ Common::SharedPtr<MidiNotePlayerImpl> ref;
+
+ for (Common::Array<Common::SharedPtr<MidiNotePlayerImpl> >::iterator it = _notePlayers.begin(), itEnd = _notePlayers.end(); it != itEnd; ++it) {
+ if (it->get() == player) {
+ {
+ Common::StackLock lock(_mutex);
+ ref = *it;
+ _notePlayers.erase(it);
+ ref->stop();
+ }
+ break;
+ }
+ }
+
+ if (ref)
+ ref->detach();
+}
+
+void MultiMidiPlayer::setPlayerVolume(MidiFilePlayer *player, uint8 volume) {
+ Common::StackLock lock(_mutex);
+ static_cast<MidiFilePlayerImpl *>(player)->setVolume(volume);
+}
+
+void MultiMidiPlayer::setPlayerLoop(MidiFilePlayer *player, bool loop) {
+ Common::StackLock lock(_mutex);
+ static_cast<MidiFilePlayerImpl *>(player)->setLoop(loop);
+}
+
+void MultiMidiPlayer::setPlayerTempo(MidiFilePlayer *player, double tempo) {
+ Common::StackLock lock(_mutex);
+ static_cast<MidiFilePlayerImpl *>(player)->setTempoOverride(tempo);
+}
+
+void MultiMidiPlayer::setPlayerMutedTracks(MidiFilePlayer *player, uint16 mutedTracks) {
+ Common::StackLock lock(_mutex);
+ static_cast<MidiFilePlayerImpl *>(player)->setMutedTracks(mutedTracks);
+}
+
+void MultiMidiPlayer::stopPlayer(MidiFilePlayer *player) {
+ Common::StackLock lock(_mutex);
+ static_cast<MidiFilePlayerImpl *>(player)->stop();
+}
+
+void MultiMidiPlayer::playPlayer(MidiFilePlayer *player) {
+ Common::StackLock lock(_mutex);
+ static_cast<MidiFilePlayerImpl *>(player)->play();
+}
+
+void MultiMidiPlayer::pausePlayer(MidiFilePlayer *player) {
+ Common::StackLock lock(_mutex);
+ static_cast<MidiFilePlayerImpl *>(player)->pause();
+}
+
+void MultiMidiPlayer::resumePlayer(MidiFilePlayer *player) {
+ Common::StackLock lock(_mutex);
+ static_cast<MidiFilePlayerImpl *>(player)->resume();
+}
+
+void MultiMidiPlayer::playNote(MidiNotePlayer *player, uint8 volume, uint8 channel, uint8 program, uint8 note, uint8 velocity, double duration) {
+ Common::StackLock lock(_mutex);
+ static_cast<MidiNotePlayerImpl *>(player)->play(volume, channel, program, note, velocity, duration);
+}
+
+void MultiMidiPlayer::stopNote(MidiNotePlayer *player) {
+ Common::StackLock lock(_mutex);
+ static_cast<MidiNotePlayerImpl *>(player)->stop();
+}
+
+uint32 MultiMidiPlayer::getBaseTempo() const {
+ if (_driver)
+ return _driver->getBaseTempo();
+ return 1;
+}
+
+void MultiMidiPlayer::send(uint32 b) {
+ if (_driver)
+ _driver->send(b);
+}
+
+MidiModifier::MidiModifier() : _mode(kModeFile), _volume(100), _mutedTracks(0), /* _singleNoteChannel(0), _singleNoteNote(0), */
+ _plugIn(nullptr), _filePlayer(nullptr), _notePlayer(nullptr) /*, _runtime(nullptr) */ {
+
+ memset(&this->_modeSpecific, 0, sizeof(this->_modeSpecific));
+}
+
+MidiModifier::~MidiModifier() {
+ if (_filePlayer)
+ _plugIn->getMidi()->deleteFilePlayer(_filePlayer);
+
+ if (_notePlayer)
+ _plugIn->getMidi()->deleteNotePlayer(_notePlayer);
+}
+
+bool MidiModifier::load(const PlugInModifierLoaderContext &context, const Data::Midi::MidiModifier &data) {
+ _plugIn = static_cast<MidiPlugIn *>(context.plugIn);
+
+ if (data.executeWhen.type != Data::PlugInTypeTaggedValue::kEvent)
+ return false;
+
+ if (!_executeWhen.load(data.executeWhen.value.asEvent))
+ return false;
+
+ if (data.terminateWhen.type != Data::PlugInTypeTaggedValue::kEvent)
+ return false;
+
+ if (!_terminateWhen.load(data.terminateWhen.value.asEvent))
+ return false;
+
+ if (data.embeddedFlag) {
+ _mode = kModeFile;
+ _embeddedFile = data.embeddedFile;
+
+ _modeSpecific.file.loop = (data.modeSpecific.embedded.loop != 0);
+ _modeSpecific.file.overrideTempo = (data.modeSpecific.embedded.overrideTempo != 0);
+ _volume = data.modeSpecific.embedded.volume;
+
+ if (data.embeddedFadeIn.type != Data::PlugInTypeTaggedValue::kFloat || data.embeddedFadeOut.type != Data::PlugInTypeTaggedValue::kFloat || data.embeddedTempo.type != Data::PlugInTypeTaggedValue::kFloat)
+ return false;
+
+ _modeSpecific.file.fadeIn = data.embeddedFadeIn.value.asFloat.toXPFloat().toDouble();
+ _modeSpecific.file.fadeOut = data.embeddedFadeOut.value.asFloat.toXPFloat().toDouble();
+ _modeSpecific.file.tempo = data.embeddedTempo.value.asFloat.toXPFloat().toDouble();
+ } else {
+ _mode = kModeSingleNote;
+
+ if (data.singleNoteDuration.type != Data::PlugInTypeTaggedValue::kFloat)
+ return false;
+
+ _modeSpecific.singleNote.channel = data.modeSpecific.singleNote.channel;
+ _modeSpecific.singleNote.note = data.modeSpecific.singleNote.note;
+ _modeSpecific.singleNote.velocity = data.modeSpecific.singleNote.velocity;
+ _modeSpecific.singleNote.program = data.modeSpecific.singleNote.program;
+ _modeSpecific.singleNote.duration = data.singleNoteDuration.value.asFloat.toXPFloat().toDouble();
+
+ _volume = 100;
+ }
+
+ return true;
+}
+
+bool MidiModifier::respondsToEvent(const Event &evt) const {
+ return _executeWhen.respondsTo(evt) || _terminateWhen.respondsTo(evt);
+}
+
+VThreadState MidiModifier::consumeMessage(Runtime *runtime, const Common::SharedPtr<MessageProperties> &msg) {
+ if (_executeWhen.respondsTo(msg->getEvent())) {
+ const SubtitleTables &subtitleTables = runtime->getProject()->getSubtitles();
+ if (subtitleTables.modifierMapping) {
+ const Common::String *subSetIDPtr = subtitleTables.modifierMapping->findSubtitleSetForModifierGUID(getStaticGUID());
+ if (subSetIDPtr) {
+ // Don't currently support anything except play-on-start subs for MIDI
+ SubtitlePlayer subtitlePlayer(runtime, *subSetIDPtr, subtitleTables);
+ subtitlePlayer.update(0, 1);
+ }
+ }
+
+ if (_mode == kModeFile) {
+ if (_embeddedFile) {
+ debug(2, "MIDI (%x '%s'): Playing embedded file", getStaticGUID(), getName().c_str());
+
+ const double tempo = _modeSpecific.file.overrideTempo ? _modeSpecific.file.tempo : 120.0;
+ if (!_filePlayer)
+ _filePlayer = _plugIn->getMidi()->createFilePlayer(_embeddedFile, _modeSpecific.file.overrideTempo, tempo, getBoostedVolume(runtime) * 255 / 100, _modeSpecific.file.loop, _mutedTracks);
+ _plugIn->getMidi()->playPlayer(_filePlayer);
+ } else {
+ debug(2, "MIDI (%x '%s'): Digested execute event but don't have anything to play", getStaticGUID(), getName().c_str());
+ }
+ } else if (_mode == kModeSingleNote) {
+ playSingleNote();
+ }
+ }
+ if (_terminateWhen.respondsTo(msg->getEvent())) {
+ disable(runtime);
+ }
+
+ return kVThreadReturn;
+}
+
+void MidiModifier::disable(Runtime *runtime) {
+ if (_filePlayer) {
+ _plugIn->getMidi()->deleteFilePlayer(_filePlayer);
+ _filePlayer = nullptr;
+ }
+ if (_notePlayer) {
+ _plugIn->getMidi()->deleteNotePlayer(_notePlayer);
+ _notePlayer = nullptr;
+ }
+}
+
+void MidiModifier::playSingleNote() {
+ if (!_notePlayer)
+ _notePlayer = _plugIn->getMidi()->createNotePlayer();
+ _plugIn->getMidi()->playNote(_notePlayer, _volume, _modeSpecific.singleNote.channel, _modeSpecific.singleNote.program, _modeSpecific.singleNote.note, _modeSpecific.singleNote.velocity, _modeSpecific.singleNote.duration);
+}
+
+void MidiModifier::stopSingleNote() {
+ if (_notePlayer)
+ _plugIn->getMidi()->stopNote(_notePlayer);
+}
+
+bool MidiModifier::readAttribute(MiniscriptThread *thread, DynamicValue &result, const Common::String &attrib) {
+ if (attrib == "volume") {
+ result.setInt(_volume);
+ return true;
+ }
+
+ return Modifier::readAttribute(thread, result, attrib);
+}
+
+MiniscriptInstructionOutcome MidiModifier::writeRefAttribute(MiniscriptThread *thread, DynamicValueWriteProxy &result, const Common::String &attrib) {
+ if (attrib == "volume") {
+ DynamicValueWriteFuncHelper<MidiModifier, &MidiModifier::scriptSetVolume, true>::create(this, result);
+ return kMiniscriptInstructionOutcomeContinue;
+ } else if (attrib == "notevelocity") {
+ DynamicValueWriteFuncHelper<MidiModifier, &MidiModifier::scriptSetNoteVelocity, true>::create(this, result);
+ return kMiniscriptInstructionOutcomeContinue;
+ } else if (attrib == "noteduration") {
+ DynamicValueWriteFuncHelper<MidiModifier, &MidiModifier::scriptSetNoteDuration, true>::create(this, result);
+ return kMiniscriptInstructionOutcomeContinue;
+ } else if (attrib == "notenum") {
+ DynamicValueWriteFuncHelper<MidiModifier, &MidiModifier::scriptSetNoteNum, true>::create(this, result);
+ return kMiniscriptInstructionOutcomeContinue;
+ } else if (attrib == "loop") {
+ DynamicValueWriteFuncHelper<MidiModifier, &MidiModifier::scriptSetLoop, true>::create(this, result);
+ return kMiniscriptInstructionOutcomeContinue;
+ } else if (attrib == "playnote") {
+ DynamicValueWriteFuncHelper<MidiModifier, &MidiModifier::scriptSetPlayNote, true>::create(this, result);
+ return kMiniscriptInstructionOutcomeContinue;
+ } else if (attrib == "tempo") {
+ DynamicValueWriteFuncHelper<MidiModifier, &MidiModifier::scriptSetTempo, true>::create(this, result);
+ return kMiniscriptInstructionOutcomeContinue;
+ } else if (attrib == "mutetrack") {
+ DynamicValueWriteFuncHelper<MidiModifier, &MidiModifier::scriptSetMuteTrack, true>::create(this, result);
+ return kMiniscriptInstructionOutcomeContinue;
+ }
+
+ return Modifier::writeRefAttribute(thread, result, attrib);
+}
+
+MiniscriptInstructionOutcome MidiModifier::writeRefAttributeIndexed(MiniscriptThread *thread, DynamicValueWriteProxy &result, const Common::String &attrib, const DynamicValue &index) {
+ if (attrib == "mutetrack") {
+ int32 asInteger = 0;
+ if (!index.roundToInt(asInteger) || asInteger < 1) {
+ thread->error("Invalid index for mutetrack");
+ return kMiniscriptInstructionOutcomeFailed;
+ }
+
+ result.pod.objectRef = this;
+ result.pod.ptrOrOffset = static_cast<uintptr>(asInteger) - 1;
+ result.pod.ifc = DynamicValueWriteInterfaceGlue<MuteTrackProxyInterface>::getInstance();
+
+ return kMiniscriptInstructionOutcomeContinue;
+ }
+
+ return Modifier::writeRefAttributeIndexed(thread, result, attrib, index);
+}
+
+uint MidiModifier::getBoostedVolume(Runtime *runtime) const {
+ uint boostedVolume = (_volume * runtime->getHacks().midiVolumeScale) >> 8;
+ if (boostedVolume > 100)
+ boostedVolume = 100;
+ return boostedVolume;
+}
+
+Common::SharedPtr<Modifier> MidiModifier::shallowClone() const {
+ Common::SharedPtr<MidiModifier> clone(new MidiModifier(*this));
+
+ clone->_notePlayer = nullptr;
+ clone->_filePlayer = nullptr;
+
+ return clone;
+}
+
+const char *MidiModifier::getDefaultName() const {
+ return "MIDI Modifier";
+}
+
+MiniscriptInstructionOutcome MidiModifier::scriptSetVolume(MiniscriptThread *thread, const DynamicValue &value) {
+ int32 asInteger = 0;
+ if (!value.roundToInt(asInteger))
+ return kMiniscriptInstructionOutcomeFailed;
+
+ if (asInteger < 0)
+ asInteger = 0;
+ else if (asInteger > 100)
+ asInteger = 100;
+
+ _volume = asInteger;
+
+ if (_mode == kModeFile) {
+ debug(2, "MIDI (%x '%s'): Changing volume to %i", getStaticGUID(), getName().c_str(), _volume);
+ if (_filePlayer)
+ _plugIn->getMidi()->setPlayerVolume(_filePlayer, getBoostedVolume(thread->getRuntime()) * 255 / 100);
+ }
+
+ return kMiniscriptInstructionOutcomeContinue;
+}
+
+MiniscriptInstructionOutcome MidiModifier::scriptSetNoteVelocity(MiniscriptThread *thread, const DynamicValue &value) {
+ int32 asInteger = 0;
+ if (!value.roundToInt(asInteger))
+ return kMiniscriptInstructionOutcomeFailed;
+
+ if (asInteger < 0)
+ asInteger = 0;
+ else if (asInteger > 127)
+ asInteger = 127;
+
+ if (_mode == kModeSingleNote) {
+ debug(2, "MIDI (%x '%s'): Changing note velocity to %i", getStaticGUID(), getName().c_str(), asInteger);
+ _modeSpecific.singleNote.velocity = asInteger;
+ }
+
+ return kMiniscriptInstructionOutcomeContinue;
+}
+
+MiniscriptInstructionOutcome MidiModifier::scriptSetNoteDuration(MiniscriptThread *thread, const DynamicValue &value) {
+ double asDouble = 0.0;
+ if (value.getType() == DynamicValueTypes::kFloat) {
+ asDouble = value.getFloat();
+ } else {
+ DynamicValue converted;
+ if (!value.convertToType(DynamicValueTypes::kFloat, converted))
+ return kMiniscriptInstructionOutcomeFailed;
+ asDouble = converted.getFloat();
+ }
+
+ if (_mode == kModeSingleNote) {
+ debug(2, "MIDI (%x '%s'): Changing note duration to %g", getStaticGUID(), getName().c_str(), asDouble);
+ _modeSpecific.singleNote.duration = asDouble;
+ }
+
+ return kMiniscriptInstructionOutcomeContinue;
+}
+
+MiniscriptInstructionOutcome MidiModifier::scriptSetNoteNum(MiniscriptThread *thread, const DynamicValue &value) {
+ int32 asInteger = 0;
+ if (!value.roundToInt(asInteger))
+ return kMiniscriptInstructionOutcomeFailed;
+
+ if (asInteger < 0)
+ asInteger = 0;
+ else if (asInteger > 255)
+ asInteger = 255;
+
+ if (_mode == kModeSingleNote) {
+ debug(2, "MIDI (%x '%s'): Changing note number to %i", getStaticGUID(), getName().c_str(), asInteger);
+ _modeSpecific.singleNote.note = asInteger;
+ }
+
+ return kMiniscriptInstructionOutcomeContinue;
+}
+
+MiniscriptInstructionOutcome MidiModifier::scriptSetLoop(MiniscriptThread *thread, const DynamicValue &value) {
+ if (value.getType() != DynamicValueTypes::kBoolean)
+ return kMiniscriptInstructionOutcomeFailed;
+
+ if (_mode == kModeFile) {
+ const bool loop = value.getBool();
+
+ debug(2, "MIDI (%x '%s'): Changing loop state to %s", getStaticGUID(), getName().c_str(), loop ? "true" : "false");
+ if (_modeSpecific.file.loop != loop) {
+ _modeSpecific.file.loop = loop;
+
+ if (_filePlayer)
+ _plugIn->getMidi()->setPlayerLoop(_filePlayer, loop);
+ }
+ }
+
+ return kMiniscriptInstructionOutcomeContinue;
+}
+
+MiniscriptInstructionOutcome MidiModifier::scriptSetTempo(MiniscriptThread *thread, const DynamicValue &value) {
+ double tempo = 0.0;
+ if (value.getType() == DynamicValueTypes::kInteger)
+ tempo = value.getInt();
+ else if (value.getType() == DynamicValueTypes::kFloat)
+ tempo = value.getFloat();
+ else
+ return kMiniscriptInstructionOutcomeFailed;
+
+ if (_mode == kModeFile) {
+ debug(2, "MIDI (%x '%s'): Changing tempo to %g", getStaticGUID(), getName().c_str(), tempo);
+
+ if (_filePlayer)
+ _plugIn->getMidi()->setPlayerTempo(_filePlayer, tempo);
+ }
+
+ return kMiniscriptInstructionOutcomeContinue;
+}
+
+MiniscriptInstructionOutcome MidiModifier::scriptSetPlayNote(MiniscriptThread *thread, const DynamicValue &value) {
+ if (miniscriptEvaluateTruth(value))
+ playSingleNote();
+ else
+ stopSingleNote();
+
+ return kMiniscriptInstructionOutcomeContinue;
+}
+
+MiniscriptInstructionOutcome MidiModifier::scriptSetMuteTrack(MiniscriptThread *thread, const DynamicValue &value) {
+ if (value.getType() != DynamicValueTypes::kBoolean) {
+ thread->error("Invalid type for mutetrack");
+ return kMiniscriptInstructionOutcomeFailed;
+ }
+
+ uint16 mutedTracks = value.getBool() ? 0xffffu : 0u;
+
+ if (mutedTracks != _mutedTracks) {
+ _mutedTracks = mutedTracks;
+
+ if (_filePlayer)
+ _plugIn->getMidi()->setPlayerMutedTracks(_filePlayer, mutedTracks);
+ }
+
+ return kMiniscriptInstructionOutcomeContinue;
+}
+
+MiniscriptInstructionOutcome MidiModifier::scriptSetMuteTrackIndexed(MiniscriptThread *thread, size_t trackIndex, bool muted) {
+ if (trackIndex >= 16) {
+ thread->error("Invalid track index for mutetrack");
+ return kMiniscriptInstructionOutcomeFailed;
+ }
+
+ uint16 mutedTracks = _mutedTracks;
+ uint16 trackMask = 1 << trackIndex;
+
+ if (muted)
+ mutedTracks |= trackMask;
+ else
+ mutedTracks -= (mutedTracks & trackMask);
+
+ if (mutedTracks != _mutedTracks) {
+ _mutedTracks = mutedTracks;
+
+ if (_filePlayer)
+ _plugIn->getMidi()->setPlayerMutedTracks(_filePlayer, mutedTracks);
+ }
+
+ return kMiniscriptInstructionOutcomeContinue;
+}
+
+MiniscriptInstructionOutcome MidiModifier::MuteTrackProxyInterface::write(MiniscriptThread *thread, const DynamicValue &value, void *objectRef, uintptr ptrOrOffset) {
+ if (value.getType() != DynamicValueTypes::kBoolean) {
+ thread->error("Invalid type for mutetrack");
+ return kMiniscriptInstructionOutcomeFailed;
+ }
+
+ return static_cast<MidiModifier *>(objectRef)->scriptSetMuteTrackIndexed(thread, ptrOrOffset, value.getBool());
+}
+
+MiniscriptInstructionOutcome MidiModifier::MuteTrackProxyInterface::refAttrib(MiniscriptThread *thread, DynamicValueWriteProxy &proxy, void *objectRef, uintptr ptrOrOffset, const Common::String &attrib) {
+ return kMiniscriptInstructionOutcomeFailed;
+}
+
+MiniscriptInstructionOutcome MidiModifier::MuteTrackProxyInterface::refAttribIndexed(MiniscriptThread *thread, DynamicValueWriteProxy &proxy, void *objectRef, uintptr ptrOrOffset, const Common::String &attrib, const DynamicValue &index) {
+ return kMiniscriptInstructionOutcomeFailed;
+}
+
+MidiPlugIn::MidiPlugIn(bool useDynamicMidi)
+ : _midiModifierFactory(this) {
+ _midi.reset(new MultiMidiPlayer(useDynamicMidi));
+}
+
+MidiPlugIn::~MidiPlugIn() {
+}
+
+void MidiPlugIn::registerModifiers(IPlugInModifierRegistrar *registrar) const {
+ registrar->registerPlugInModifier("MIDIModf", &_midiModifierFactory);
+}
+
+MultiMidiPlayer *MidiPlugIn::getMidi() const {
+ return _midi.get();
+}
+
+} // End of namespace Midi
+
+namespace PlugIns {
+
+Common::SharedPtr<PlugIn> createMIDI() {
+ const bool useDynamicMidi = ConfMan.getBool("mtropolis_mod_dynamic_midi");
+
+ return Common::SharedPtr<PlugIn>(new Midi::MidiPlugIn(useDynamicMidi));
+}
+
+} // namespace PlugIns
+
+} // End of namespace MTropolis
diff --git a/engines/mtropolis/plugin/midi.h b/engines/mtropolis/plugin/midi.h
new file mode 100644
index 00000000000..3dd0fc087ec
--- /dev/null
+++ b/engines/mtropolis/plugin/midi.h
@@ -0,0 +1,151 @@
+/* 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef MTROPOLIS_PLUGIN_MIDI_H
+#define MTROPOLIS_PLUGIN_MIDI_H
+
+#include "mtropolis/modifier_factory.h"
+#include "mtropolis/modifiers.h"
+#include "mtropolis/plugin/midi_data.h"
+#include "mtropolis/runtime.h"
+
+class MidiDriver;
+
+namespace MTropolis {
+
+class Runtime;
+
+namespace Midi {
+
+class MidiPlugIn;
+class MidiFilePlayer;
+class MidiNotePlayer;
+class MultiMidiPlayer;
+class MidiCombinerSource;
+
+class MidiModifier : public Modifier {
+public:
+ MidiModifier();
+ ~MidiModifier();
+
+ bool load(const PlugInModifierLoaderContext &context, const Data::Midi::MidiModifier &data);
+
+ bool respondsToEvent(const Event &evt) const override;
+ VThreadState consumeMessage(Runtime *runtime, const Common::SharedPtr<MessageProperties> &msg) override;
+ void disable(Runtime *runtime) override;
+
+ bool readAttribute(MiniscriptThread *thread, DynamicValue &result, const Common::String &attrib) override;
+ MiniscriptInstructionOutcome writeRefAttribute(MiniscriptThread *thread, DynamicValueWriteProxy &result, const Common::String &attrib) override;
+ MiniscriptInstructionOutcome writeRefAttributeIndexed(MiniscriptThread *thread, DynamicValueWriteProxy &result, const Common::String &attrib, const DynamicValue &index) override;
+
+#ifdef MTROPOLIS_DEBUG_ENABLE
+ const char *debugGetTypeName() const override { return "MIDI Modifier"; }
+ SupportStatus debugGetSupportStatus() const override { return kSupportStatusDone; }
+#endif
+
+private:
+ struct MuteTrackProxyInterface {
+ static MiniscriptInstructionOutcome write(MiniscriptThread *thread, const DynamicValue &dest, void *objectRef, uintptr ptrOrOffset);
+ static MiniscriptInstructionOutcome refAttrib(MiniscriptThread *thread, DynamicValueWriteProxy &proxy, void *objectRef, uintptr ptrOrOffset, const Common::String &attrib);
+ static MiniscriptInstructionOutcome refAttribIndexed(MiniscriptThread *thread, DynamicValueWriteProxy &proxy, void *objectRef, uintptr ptrOrOffset, const Common::String &attrib, const DynamicValue &index);
+ };
+
+ Common::SharedPtr<Modifier> shallowClone() const override;
+ const char *getDefaultName() const override;
+
+ MiniscriptInstructionOutcome scriptSetVolume(MiniscriptThread *thread, const DynamicValue &value);
+ MiniscriptInstructionOutcome scriptSetNoteVelocity(MiniscriptThread *thread, const DynamicValue &value);
+ MiniscriptInstructionOutcome scriptSetNoteDuration(MiniscriptThread *thread, const DynamicValue &value);
+ MiniscriptInstructionOutcome scriptSetNoteNum(MiniscriptThread *thread, const DynamicValue &value);
+ MiniscriptInstructionOutcome scriptSetLoop(MiniscriptThread *thread, const DynamicValue &value);
+ MiniscriptInstructionOutcome scriptSetPlayNote(MiniscriptThread *thread, const DynamicValue &value);
+ MiniscriptInstructionOutcome scriptSetTempo(MiniscriptThread *thread, const DynamicValue &value);
+ MiniscriptInstructionOutcome scriptSetMuteTrack(MiniscriptThread *thread, const DynamicValue &value);
+
+ MiniscriptInstructionOutcome scriptSetMuteTrackIndexed(MiniscriptThread *thread, size_t trackIndex, bool muted);
+
+ uint getBoostedVolume(Runtime *runtime) const;
+
+ void playSingleNote();
+ void stopSingleNote();
+
+ struct FilePart {
+ bool loop;
+ bool overrideTempo;
+ double tempo;
+ double fadeIn;
+ double fadeOut;
+ };
+
+ struct SingleNotePart {
+ uint8 channel;
+ uint8 note;
+ uint8 velocity;
+ uint8 program;
+ double duration;
+ };
+
+ union ModeSpecificUnion {
+ FilePart file;
+ SingleNotePart singleNote;
+ };
+
+ enum Mode {
+ kModeFile,
+ kModeSingleNote,
+ };
+
+ Event _executeWhen;
+ Event _terminateWhen;
+
+ Mode _mode;
+ ModeSpecificUnion _modeSpecific;
+ uint8 _volume; // We need this always available because scripts will try to set it and then read it even in single note mode
+
+ Common::SharedPtr<Data::Midi::MidiModifier::EmbeddedFile> _embeddedFile;
+
+ uint16 _mutedTracks;
+
+ MidiPlugIn *_plugIn;
+ MidiFilePlayer *_filePlayer;
+ MidiNotePlayer *_notePlayer;
+};
+
+class MidiPlugIn : public MTropolis::PlugIn {
+public:
+ explicit MidiPlugIn(bool useDynamicMidi);
+ ~MidiPlugIn();
+
+ void registerModifiers(IPlugInModifierRegistrar *registrar) const override;
+
+ MultiMidiPlayer *getMidi() const;
+
+private:
+ PlugInModifierFactory<MidiModifier, Data::Midi::MidiModifier> _midiModifierFactory;
+
+ Common::SharedPtr<MultiMidiPlayer> _midi;
+};
+
+} // End of namespace Midi
+
+} // End of namespace MTropolis
+
+#endif
diff --git a/engines/mtropolis/plugin/midi_data.cpp b/engines/mtropolis/plugin/midi_data.cpp
new file mode 100644
index 00000000000..1121aec4650
--- /dev/null
+++ b/engines/mtropolis/plugin/midi_data.cpp
@@ -0,0 +1,73 @@
+/* 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "mtropolis/plugin/midi.h"
+#include "mtropolis/plugin/midi_data.h"
+
+namespace MTropolis {
+
+namespace Data {
+
+namespace Midi {
+
+MidiModifier::MidiModifier() : embeddedFlag(0) {
+ memset(&this->modeSpecific, 0, sizeof(this->modeSpecific));
+}
+
+DataReadErrorCode MidiModifier::load(PlugIn &plugIn, const PlugInModifier &prefix, DataReader &reader) {
+ if (prefix.plugInRevision != 1 && prefix.plugInRevision != 2)
+ return kDataReadErrorUnsupportedRevision;
+
+ if (!executeWhen.load(reader) || !terminateWhen.load(reader) || !reader.readU8(embeddedFlag))
+ return kDataReadErrorReadFailed;
+
+ if (embeddedFlag) {
+ if (!reader.readU8(modeSpecific.embedded.hasFile))
+ return kDataReadErrorReadFailed;
+ if (modeSpecific.embedded.hasFile) {
+ embeddedFile = Common::SharedPtr<EmbeddedFile>(new EmbeddedFile());
+
+ uint8 bigEndianLength[4];
+ if (!reader.readBytes(bigEndianLength))
+ return kDataReadErrorReadFailed;
+
+ uint32 length = (bigEndianLength[0] << 24) + (bigEndianLength[1] << 16) + (bigEndianLength[2] << 8) + bigEndianLength[3];
+
+ embeddedFile->contents.resize(length);
+ if (length > 0 && !reader.read(&embeddedFile->contents[0], length))
+ return kDataReadErrorReadFailed;
+ }
+
+ if (!reader.readU8(modeSpecific.embedded.loop) || !reader.readU8(modeSpecific.embedded.overrideTempo) || !reader.readU8(modeSpecific.embedded.volume) || !embeddedTempo.load(reader) || !embeddedFadeIn.load(reader) || !embeddedFadeOut.load(reader))
+ return kDataReadErrorReadFailed;
+ } else {
+ if (!reader.readU8(modeSpecific.singleNote.channel) || !reader.readU8(modeSpecific.singleNote.note) || !reader.readU8(modeSpecific.singleNote.velocity) || !reader.readU8(modeSpecific.singleNote.program) || !singleNoteDuration.load(reader))
+ return kDataReadErrorReadFailed;
+ }
+
+ return kDataReadErrorNone;
+}
+
+} // End of namespace Midi
+
+} // End of namespace Data
+
+} // End of namespace MTropolis
diff --git a/engines/mtropolis/plugin/midi_data.h b/engines/mtropolis/plugin/midi_data.h
new file mode 100644
index 00000000000..4cb88481259
--- /dev/null
+++ b/engines/mtropolis/plugin/midi_data.h
@@ -0,0 +1,82 @@
+/* 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef MTROPOLIS_PLUGIN_MIDI_DATA_H
+#define MTROPOLIS_PLUGIN_MIDI_DATA_H
+
+#include "mtropolis/data.h"
+
+namespace MTropolis {
+
+namespace Data {
+
+namespace Midi {
+
+struct MidiModifier : public PlugInModifierData {
+ struct EmbeddedFile {
+ Common::Array<uint8> contents;
+ };
+
+ struct EmbeddedPart {
+ uint8 hasFile;
+ uint8 loop;
+ uint8 overrideTempo;
+ uint8 volume;
+ };
+
+ struct SingleNotePart {
+ uint8 channel;
+ uint8 note;
+ uint8 velocity;
+ uint8 program;
+ };
+
+ union ModeSpecificUnion {
+ EmbeddedPart embedded;
+ SingleNotePart singleNote;
+ };
+
+ MidiModifier();
+
+ PlugInTypeTaggedValue executeWhen;
+ PlugInTypeTaggedValue terminateWhen;
+
+ uint8 embeddedFlag;
+ ModeSpecificUnion modeSpecific;
+
+ PlugInTypeTaggedValue embeddedTempo; // Float
+ PlugInTypeTaggedValue embeddedFadeIn; // Float
+ PlugInTypeTaggedValue embeddedFadeOut; // Float
+ PlugInTypeTaggedValue singleNoteDuration; // Float
+
+ Common::SharedPtr<EmbeddedFile> embeddedFile;
+
+protected:
+ DataReadErrorCode load(PlugIn &plugIn, const PlugInModifier &prefix, DataReader &reader) override;
+};
+
+} // End of namespace Midi
+
+} // End of namespace Data
+
+} // End of namespace MTropolis
+
+#endif
diff --git a/engines/mtropolis/plugin/standard.cpp b/engines/mtropolis/plugin/standard.cpp
index 9521e25b661..5dbed826cfd 100644
--- a/engines/mtropolis/plugin/standard.cpp
+++ b/engines/mtropolis/plugin/standard.cpp
@@ -23,1546 +23,15 @@
#include "common/config-manager.h"
#include "common/file.h"
-#include "audio/mididrv.h"
-#include "audio/midiplayer.h"
-#include "audio/midiparser.h"
-#include "audio/midiparser_smf.h"
-
-#include "mtropolis/miniscript.h"
-#include "mtropolis/plugin/standard.h"
-#include "mtropolis/plugins.h"
-
-#include "mtropolis/miniscript.h"
-
-namespace MTropolis {
-
-namespace Standard {
-
-class MultiMidiPlayer;
-
-// I guess this follows QuickTime quirks, but basically, mTropolis pipes multiple inputs to a single
-// output device, and is totally cool with multiple devices stomping each other.
-//
-// Obsidian actually has a timer that plays a MIDI file that fires AllNoteOff on every channel every
-// 30 seconds, presumably to work around stuck notes, along with workarounds for the workaround,
-// i.e. the intro sequence has a silent part exactly 30 seconds in timed to sync up with the mute.
-//
-// NOTE: Due to SharedPtr not currently being atomic, MidiFilePlayers MUST BE DESTROYED ON THE
-// MAIN THREAD so there is no contention over the file refcount.
-class MidiFilePlayer {
-public:
- virtual ~MidiFilePlayer();
-};
-
-class MidiNotePlayer {
-public:
- virtual ~MidiNotePlayer();
-};
-
-class MidiCombinerSource : public MidiDriver_BASE {
-public:
- virtual ~MidiCombinerSource();
-
- // Do not call this directly, it's not thread-safe, expose via MultiMidiPlayer
- virtual void setVolume(uint8 volume) = 0;
- virtual void detach() = 0;
-};
-
-MidiCombinerSource::~MidiCombinerSource() {
-}
-
-class MidiCombiner {
-public:
- virtual ~MidiCombiner();
-
- virtual Common::SharedPtr<MidiCombinerSource> createSource() = 0;
-};
-
-MidiCombiner::~MidiCombiner() {
-}
-
-class MidiParser_MTropolis : public MidiParser_SMF {
-public:
- MidiParser_MTropolis(bool hasTempoOverride, double tempoOverride, uint16 mutedTracks);
-
- void setTempo(uint32 tempo) override;
-
- void setTempoOverride(double tempoOverride);
- void setMutedTracks(uint16 mutedTracks);
-
-protected:
- bool processEvent(const EventInfo &info, bool fireEvents) override;
-
-private:
- double _tempoOverride;
- uint16 _mutedTracks;
- bool _hasTempoOverride;
-};
-
-MidiParser_MTropolis::MidiParser_MTropolis(bool hasTempoOverride, double tempoOverride, uint16 mutedTracks)
- : _hasTempoOverride(hasTempoOverride), _tempoOverride(tempoOverride), _mutedTracks(mutedTracks) {
-}
-
-void MidiParser_MTropolis::setTempo(uint32 tempo) {
- if (_hasTempoOverride)
- return;
-
- MidiParser_SMF::setTempo(tempo);
-}
-
-void MidiParser_MTropolis::setTempoOverride(double tempoOverride) {
- _hasTempoOverride = true;
-
- if (tempoOverride < 1.0)
- tempoOverride = 1.0;
-
- _tempoOverride = tempoOverride;
-
- uint32 convertedTempo = static_cast<uint32>(60000000.0 / tempoOverride);
-
- MidiParser_SMF::setTempo(convertedTempo);
-}
-
-void MidiParser_MTropolis::setMutedTracks(uint16 mutedTracks) {
- _mutedTracks = mutedTracks;
-}
-
-bool MidiParser_MTropolis::processEvent(const EventInfo &info, bool fireEvents) {
- if ((info.event & 0xf0) == MidiDriver_BASE::MIDI_COMMAND_NOTE_ON) {
- int track = _noteChannelToTrack[info.event & 0xf];
- if (track >= 0 && (_mutedTracks & (1 << track)))
- return true;
- }
-
- return MidiParser_SMF::processEvent(info, fireEvents);
-}
-
-class MidiFilePlayerImpl : public MidiFilePlayer {
-public:
- explicit MidiFilePlayerImpl(const Common::SharedPtr<MidiCombinerSource> &outputDriver, const Common::SharedPtr<Data::Standard::MidiModifier::EmbeddedFile> &file, uint32 baseTempo, bool hasTempoOverride, double tempoOverride, uint8 volume, bool loop, uint16 mutedTracks);
- ~MidiFilePlayerImpl();
-
- // Do not call any of these directly since they're not thread-safe, expose them via MultiMidiPlayer
- void stop();
- void play();
- void pause();
- void resume();
- void setVolume(uint8 volume);
- void setLoop(bool loop);
- void setTempoOverride(double tempo);
- void setMutedTracks(uint16 mutedTracks);
- void detach();
- void onTimer();
-
-private:
- Common::SharedPtr<Data::Standard::MidiModifier::EmbeddedFile> _file;
- Common::SharedPtr<MidiParser_MTropolis> _parser;
- Common::SharedPtr<MidiCombinerSource> _outputDriver;
- uint16 _mutedTracks;
- bool _loop;
-};
-
-class MidiNotePlayerImpl : public MidiNotePlayer {
-public:
- explicit MidiNotePlayerImpl(const Common::SharedPtr<MidiCombinerSource> &outputDriver, uint32 timerRate);
- ~MidiNotePlayerImpl();
-
- // Do not call any of these directly since they're not thread-safe, expose them via MultiMidiPlayer
- void onTimer();
- void play(uint8 volume, uint8 channel, uint8 program, uint8 note, uint8 velocity, double duration);
- void stop();
- void detach();
-
-private:
- Common::SharedPtr<MidiCombinerSource> _outputDriver;
- uint64 _durationRemaining;
- uint32 _timerRate;
- uint8 _channel;
- uint8 _note;
- uint8 _volume;
- //uint8 _program;
-
- bool _initialized;
-};
-
-class MultiMidiPlayer : public Audio::MidiPlayer {
-public:
- explicit MultiMidiPlayer(bool useDynamicMidiMixer);
- ~MultiMidiPlayer();
-
- MidiFilePlayer *createFilePlayer(const Common::SharedPtr<Data::Standard::MidiModifier::EmbeddedFile> &file, bool hasTempoOverride, double tempoOverride, uint8 volume, bool loop, uint16 mutedTracks);
- MidiNotePlayer *createNotePlayer();
- void deleteFilePlayer(MidiFilePlayer *player);
- void deleteNotePlayer(MidiNotePlayer *player);
-
- Common::SharedPtr<MidiCombinerSource> createSource();
-
- void setPlayerVolume(MidiFilePlayer *player, uint8 volume);
- void setPlayerLoop(MidiFilePlayer *player, bool loop);
- void setPlayerTempo(MidiFilePlayer *player, double tempo);
- void setPlayerMutedTracks(MidiFilePlayer *player, uint16 mutedTracks);
- void stopPlayer(MidiFilePlayer *player);
- void playPlayer(MidiFilePlayer *player);
- void pausePlayer(MidiFilePlayer *player);
- void resumePlayer(MidiFilePlayer *player);
-
- void playNote(MidiNotePlayer *player, uint8 volume, uint8 channel, uint8 program, uint8 note, uint8 velocity, double duration);
- void stopNote(MidiNotePlayer *player);
-
- uint32 getBaseTempo() const;
-
- void send(uint32 b) override;
-
-private:
- void onTimer() override;
-
- static void timerCallback(void *refCon);
-
- Common::Mutex _mutex;
- Common::Array<Common::SharedPtr<MidiFilePlayerImpl> > _filePlayers;
- Common::Array<Common::SharedPtr<MidiNotePlayerImpl> > _notePlayers;
- Common::SharedPtr<MidiCombiner> _combiner;
-};
-
-MidiFilePlayer::~MidiFilePlayer() {
-}
-
-MidiNotePlayer::~MidiNotePlayer() {
-}
-
-MidiFilePlayerImpl::MidiFilePlayerImpl(const Common::SharedPtr<MidiCombinerSource> &outputDriver, const Common::SharedPtr<Data::Standard::MidiModifier::EmbeddedFile> &file, uint32 baseTempo, bool hasTempoOverride, double tempo, uint8 volume, bool loop, uint16 mutedTracks)
- : _file(file), _outputDriver(outputDriver), _parser(nullptr), _loop(loop), _mutedTracks(mutedTracks) {
- Common::SharedPtr<MidiParser_MTropolis> parser(new MidiParser_MTropolis(hasTempoOverride, tempo, mutedTracks));
-
- if (file->contents.size() != 0 && parser->loadMusic(&file->contents[0], file->contents.size())) {
- _parser = parser;
-
- parser->setTrack(0);
- parser->startPlaying();
- parser->setMidiDriver(outputDriver.get());
- parser->setTimerRate(baseTempo);
- parser->property(MidiParser::mpAutoLoop, loop ? 1 : 0);
- }
-}
-
-MidiFilePlayerImpl::~MidiFilePlayerImpl() {
- assert(!_parser); // Call detach first!
-}
-
-void MidiFilePlayerImpl::stop() {
- _parser->stopPlaying();
-}
-
-void MidiFilePlayerImpl::play() {
- _parser->startPlaying();
-}
-
-void MidiFilePlayerImpl::pause() {
- _parser->pausePlaying();
-}
-
-void MidiFilePlayerImpl::resume() {
- _parser->resumePlaying();
-}
-
-void MidiFilePlayerImpl::setVolume(uint8 volume) {
- _outputDriver->setVolume(volume);
-}
-
-void MidiFilePlayerImpl::setMutedTracks(uint16 mutedTracks) {
- _mutedTracks = mutedTracks;
- _parser->setMutedTracks(mutedTracks);
-}
-
-void MidiFilePlayerImpl::setLoop(bool loop) {
- _loop = loop;
- _parser->property(MidiParser::mpAutoLoop, loop ? 1 : 0);
-}
-
-void MidiFilePlayerImpl::setTempoOverride(double tempo) {
- _parser->setTempoOverride(tempo);
-}
-
-void MidiFilePlayerImpl::detach() {
- if (_parser) {
- _parser->setMidiDriver(nullptr);
- _parser.reset();
- }
-
- if (_outputDriver) {
- _outputDriver->detach();
- _outputDriver.reset();
- }
-}
-
-void MidiFilePlayerImpl::onTimer() {
- if (_parser)
- _parser->onTimer();
-}
-
-MidiNotePlayerImpl::MidiNotePlayerImpl(const Common::SharedPtr<MidiCombinerSource> &outputDriver, uint32 timerRate)
- : _timerRate(timerRate), _durationRemaining(0), _outputDriver(outputDriver), _channel(0), _note(0), /* _program(0), */_initialized(false), _volume(100) {
-}
-
-MidiNotePlayerImpl::~MidiNotePlayerImpl() {
-}
-
-void MidiNotePlayerImpl::onTimer() {
- if (_durationRemaining > 0) {
- if (_durationRemaining <= _timerRate) {
- stop();
- assert(_durationRemaining == 0);
- } else {
- _durationRemaining -= _timerRate;
- }
- }
-}
-
-void MidiNotePlayerImpl::play(uint8 volume, uint8 channel, uint8 program, uint8 note, uint8 velocity, double duration) {
- if (duration < 0.000001)
- return;
-
- if (_durationRemaining)
- stop();
-
- _initialized = true;
-
- _durationRemaining = static_cast<uint64>(duration * 1000000);
- _channel = channel;
- _note = note;
- _volume = volume;
-
- // GM volume scale to linear is x^4 so we need the 4th root of the volume, and need to rescale it from 0-100 to 0-0x3f80
- // = 0x3f80 / sqrt(sqrt(100))
- const double volumeMultiplier = 5140.5985643697174420974013458299;
-
- if (volume > 100)
- volume = 100;
- uint16 hpVolume = static_cast<uint16>(floor(sqrt(sqrt(volume)) * volumeMultiplier));
-
- _outputDriver->send(MidiDriver_BASE::MIDI_COMMAND_PROGRAM_CHANGE | _channel, program, 0);
- _outputDriver->send(MidiDriver_BASE::MIDI_COMMAND_CONTROL_CHANGE | _channel, MidiDriver_BASE::MIDI_CONTROLLER_EXPRESSION, 127);
- _outputDriver->send(MidiDriver_BASE::MIDI_COMMAND_CONTROL_CHANGE | _channel, MidiDriver_BASE::MIDI_CONTROLLER_REVERB, 0);
- _outputDriver->send(MidiDriver_BASE::MIDI_COMMAND_CONTROL_CHANGE | _channel, MidiDriver_BASE::MIDI_CONTROLLER_VOLUME, (hpVolume >> 7) & 0x7f);
- _outputDriver->send(MidiDriver_BASE::MIDI_COMMAND_CONTROL_CHANGE | _channel, MidiDriver_BASE::MIDI_CONTROLLER_VOLUME + 32, hpVolume & 0x7f);
- _outputDriver->send(MidiDriver_BASE::MIDI_COMMAND_NOTE_ON | _channel, note, velocity);
-}
-
-void MidiNotePlayerImpl::stop() {
- if (!_durationRemaining)
- return;
-
- _durationRemaining = 0;
- _outputDriver->send(MidiDriver_BASE::MIDI_COMMAND_NOTE_OFF | _channel, _note, 0);
-}
-
-void MidiNotePlayerImpl::detach() {
- if (_outputDriver) {
- if (_durationRemaining)
- stop();
-
- _outputDriver->detach();
- _outputDriver.reset();
- }
-}
-
-// Simple combiner - Behaves "QuickTime-like" and all commands are passed through directly.
-// This applies volume by modulating note velocity.
-class MidiCombinerSimple;
-
-class MidiCombinerSourceSimple : public MidiCombinerSource {
-public:
- explicit MidiCombinerSourceSimple(MidiCombinerSimple *combiner);
-
- void setVolume(uint8 volume) override;
- void detach() override;
- void send(uint32 b) override;
-
-private:
- MidiCombinerSimple *_combiner;
- uint8 _volume;
-};
-
-class MidiCombinerSimple : public MidiCombiner {
-public:
- explicit MidiCombinerSimple(MidiDriver_BASE *outputDriver);
-
- Common::SharedPtr<MidiCombinerSource> createSource() override;
-
- void send(uint32 b);
-
-private:
- MidiDriver_BASE *_outputDriver;
-};
-
-
-MidiCombinerSourceSimple::MidiCombinerSourceSimple(MidiCombinerSimple *combiner) : _combiner(combiner), _volume(255) {
-}
-
-void MidiCombinerSourceSimple::setVolume(uint8 volume) {
- _volume = volume;
-}
-
-void MidiCombinerSourceSimple::detach() {
-}
-
-void MidiCombinerSourceSimple::send(uint32 b) {
- byte command = (b & 0xF0);
-
- if (command == MIDI_COMMAND_NOTE_ON || command == MIDI_COMMAND_NOTE_OFF) {
- byte velocity = (b >> 16) & 0xFF;
- velocity = (velocity * _volume * 257 + 256) >> 16;
- b = (b & 0xff00ffff) | (velocity << 16);
- }
-
- _combiner->send(b);
-}
-
-MidiCombinerSimple::MidiCombinerSimple(MidiDriver_BASE *outputDriver) : _outputDriver(outputDriver) {
-}
-
-Common::SharedPtr<MidiCombinerSource> MidiCombinerSimple::createSource() {
- return Common::SharedPtr<MidiCombinerSource>(new MidiCombinerSourceSimple(this));
-}
-
-void MidiCombinerSimple::send(uint32 b) {
- _outputDriver->send(b);
-}
-
-
-// Dynamic combiner - Dynamic channel allocation, accurate volume control
-class MidiCombinerDynamic;
-
-class MidiCombinerSourceDynamic : public MidiCombinerSource {
-public:
- MidiCombinerSourceDynamic(MidiCombinerDynamic *combiner, uint sourceID);
- ~MidiCombinerSourceDynamic();
-
- void setVolume(uint8 volume) override;
- void send(uint32 b) override;
-
- void detach() override;
-
-private:
- MidiCombinerDynamic *_combiner;
- uint _sourceID;
-};
-
-class MidiCombinerDynamic : public MidiCombiner {
-public:
- MidiCombinerDynamic(MidiDriver_BASE *outputDriver);
-
- Common::SharedPtr<MidiCombinerSource> createSource() override;
-
- void deallocateSource(uint sourceID);
-
- void setSourceVolume(uint sourceID, uint8 volume);
- void sendFromSource(uint sourceID, uint32 b);
- void sendFromSource(uint sourceID, uint8 cmd, uint8 channel, uint8 param1, uint8 param2);
-
-private:
- static const uint kMSBMask = 0x3f80u;
- static const uint kLSBMask = 0x7fu;
- static const uint kLRControllerStart = 64;
- static const uint kSostenutoOnThreshold = 64;
- static const uint kSustainOnThreshold = 64;
-
- enum DataEntryState {
- kDataEntryStateNone,
- kDataEntryStateRPN,
- kDataEntryStateNRPN,
- };
-
- struct MidiChannelState {
- MidiChannelState();
-
- void reset();
- void softReset(); // Executes changes corresponding to Reset All Controllers message
-
- uint16 _program;
- uint16 _aftertouch;
- uint16 _pitchBend;
- uint16 _rpnNumber;
- uint16 _nrpnNumber;
- DataEntryState _dataEntryState;
-
- uint16 _hrControllers[32];
- uint8 _lrControllers[32];
-
- uint16 _registeredParams[5];
- };
-
- struct SourceChannelState {
- SourceChannelState();
- void reset();
-
- MidiChannelState _midiChannelState;
- };
-
- struct SourceState {
- SourceState();
-
- void allocate();
- void deallocate();
-
- SourceChannelState _sourceChannelState[MidiDriver_BASE::MIDI_CHANNEL_COUNT];
- uint16 _root4MasterVolume;
- bool _isAllocated;
- };
-
- struct OutputChannelState {
- OutputChannelState();
-
- bool _hasSource;
- bool _volumeIsAmbiguous;
- uint _sourceID;
- uint _channelID;
- uint _noteOffCounter;
-
- MidiChannelState _midiChannelState;
-
- uint _numActiveNotes;
- };
-
- struct MidiActiveNote {
- uint8 _outputChannel;
- uint16 _tone;
- bool _affectedBySostenuto;
-
- // If either of these are set, then the note is off, but is sustained by a pedal
- bool _isSustainedBySustain;
- bool _isSustainedBySostenuto;
- };
-
- void doNoteOn(uint sourceID, uint8 channel, uint8 param1, uint8 param2);
- void doNoteOff(uint sourceID, uint8 channel, uint8 param1, uint8 param2);
- void doPolyphonicAftertouch(uint sourceID, uint8 channel, uint8 param1, uint8 param2);
- void doControlChange(uint sourceID, uint8 channel, uint8 param1, uint8 param2);
- void doProgramChange(uint sourceID, uint8 channel, uint8 param1, uint8 param2);
- void doChannelAftertouch(uint sourceID, uint8 channel, uint8 param1, uint8 param2);
- void doPitchBend(uint sourceID, uint8 channel, uint8 param1, uint8 param2);
-
- void doHighRangeControlChange(uint sourceID, uint8 channel, uint8 hrParam, uint16 value);
- void doLowRangeControlChange(uint sourceID, uint8 channel, uint8 lrParam, uint8 value);
-
- void doDataEntry(uint sourceID, uint8 channel, int16 existingValueMask, int16 offset);
- void doChannelMode(uint sourceID, uint8 channel, uint8 param1, uint8 param2);
- void doAllNotesOff(uint sourceID, uint8 channel, uint8 param2);
- void doAllSoundOff(uint sourceID, uint8 channel, uint8 param2);
- void doResetAllControllers(uint sourceID, uint8 channel, uint8 param2);
-
- void sendToOutput(uint8 command, uint8 channel, uint8 param1, uint8 param2);
-
- void syncSourceConfiguration(uint outputChannel, OutputChannelState &outChState, const SourceState &sourceState, const SourceChannelState &sourceChState);
- void syncSourceHRController(uint outputChannel, OutputChannelState &outChState, const SourceState &sourceState, const SourceChannelState &sourceChState, uint hrController);
- void syncSourceLRController(uint outputChannel, OutputChannelState &outChState, const SourceChannelState &sourceChState, uint lrController);
- void syncSourceRegisteredParam(uint outputChannel, OutputChannelState &outChState, const SourceChannelState &sourceChState, uint rpn);
-
- void tryCleanUpUnsustainedNote(uint noteIndex);
-
- Common::Array<SourceState> _sources;
- Common::Array<MidiActiveNote> _notes;
- OutputChannelState _outputChannels[MidiDriver_BASE::MIDI_CHANNEL_COUNT];
- uint _noteOffCounter;
-
- MidiDriver_BASE *_outputDriver;
-
- Common::SharedPtr<Common::DumpFile> _dumpFile;
- int _eventCounter;
-};
-
-MidiCombinerSourceDynamic::MidiCombinerSourceDynamic(MidiCombinerDynamic *combiner, uint sourceID) : _combiner(combiner), _sourceID(sourceID) {
-}
-
-MidiCombinerSourceDynamic::~MidiCombinerSourceDynamic() {
- assert(_combiner == nullptr); // Call detach first!
-}
-
-void MidiCombinerSourceDynamic::detach() {
- _combiner->deallocateSource(_sourceID);
- _combiner = nullptr;
-}
-
-void MidiCombinerSourceDynamic::setVolume(uint8 volume) {
- _combiner->setSourceVolume(_sourceID, volume);
-}
-
-void MidiCombinerSourceDynamic::send(uint32 b) {
- _combiner->sendFromSource(_sourceID, b);
-}
-
-MidiCombinerDynamic::MidiCombinerDynamic(MidiDriver_BASE *outputDriver) : _outputDriver(outputDriver), _noteOffCounter(1) {
-#if 0
- _dumpFile.reset(new Common::DumpFile());
- _dumpFile->open("mididump.csv");
- _dumpFile->writeString("event\ttime\tchannel\tcmd\tparam1\tparam2\n");
-#endif
-
- _eventCounter = 0;
-}
-
-Common::SharedPtr<MidiCombinerSource> MidiCombinerDynamic::createSource() {
- uint sourceID = _sources.size();
-
- for (uint i = 0; i < _sources.size(); i++) {
- if (!_sources[i]._isAllocated) {
- sourceID = i;
- break;
- }
- }
-
- if (sourceID == _sources.size())
- _sources.push_back(SourceState());
-
- _sources[sourceID].allocate();
-
- return Common::SharedPtr<MidiCombinerSource>(new MidiCombinerSourceDynamic(this, sourceID));
-}
-
-void MidiCombinerDynamic::deallocateSource(uint sourceID) {
- for (uint i = 0; i < ARRAYSIZE(_outputChannels); i++) {
- OutputChannelState &ch = _outputChannels[i];
- if (!ch._hasSource || ch._sourceID != sourceID)
- continue;
-
- // Stop any outputs and release sustain
- sendFromSource(sourceID, MidiDriver_BASE::MIDI_COMMAND_CONTROL_CHANGE, i, MidiDriver_BASE::MIDI_CONTROLLER_SUSTAIN, 0);
- sendFromSource(sourceID, MidiDriver_BASE::MIDI_COMMAND_CONTROL_CHANGE, i, MidiDriver_BASE::MIDI_CONTROLLER_SOSTENUTO, 0);
- sendFromSource(sourceID, MidiDriver_BASE::MIDI_COMMAND_CONTROL_CHANGE, i, MidiDriver_BASE::MIDI_CONTROLLER_ALL_NOTES_OFF, 0);
-
- ch._hasSource = false;
- assert(ch._numActiveNotes == 0);
- }
-
- _sources[sourceID].deallocate();
-}
-
-void MidiCombinerDynamic::setSourceVolume(uint sourceID, uint8 volume) {
- SourceState &src = _sources[sourceID];
- src._root4MasterVolume = static_cast<uint16>(floor(sqrt(sqrt(volume)) * 16400.0));
-
- for (uint i = 0; i < ARRAYSIZE(_outputChannels); i++) {
- OutputChannelState &ch = _outputChannels[i];
- if (!ch._hasSource || ch._sourceID != sourceID)
- continue;
-
- // Synchronize volume control
- syncSourceHRController(i, ch, src, src._sourceChannelState[ch._channelID], MidiDriver_BASE::MIDI_CONTROLLER_VOLUME);
- }
-}
-
-void MidiCombinerDynamic::sendFromSource(uint sourceID, uint32 b) {
- uint8 cmd = static_cast<uint8>(b & 0xf0);
- uint8 channel = static_cast<uint8>(b & 0x0f);
- uint8 param1 = static_cast<uint8>((b >> 8) & 0xff);
- uint8 param2 = static_cast<uint8>((b >> 16) & 0xff);
-
- sendFromSource(sourceID, cmd, channel, param1, param2);
-}
-
-void MidiCombinerDynamic::sendFromSource(uint sourceID, uint8 cmd, uint8 channel, uint8 param1, uint8 param2) {
- switch (cmd) {
- case MidiDriver_BASE::MIDI_COMMAND_NOTE_ON:
- doNoteOn(sourceID, channel, param1, param2);
- break;
- case MidiDriver_BASE::MIDI_COMMAND_NOTE_OFF:
- doNoteOff(sourceID, channel, param1, param2);
- break;
- case MidiDriver_BASE::MIDI_COMMAND_POLYPHONIC_AFTERTOUCH:
- doPolyphonicAftertouch(sourceID, channel, param1, param2);
- break;
- case MidiDriver_BASE::MIDI_COMMAND_CONTROL_CHANGE:
- doControlChange(sourceID, channel, param1, param2);
- break;
- case MidiDriver_BASE::MIDI_COMMAND_PROGRAM_CHANGE:
- doProgramChange(sourceID, channel, param1, param2);
- break;
- case MidiDriver_BASE::MIDI_COMMAND_CHANNEL_AFTERTOUCH:
- doChannelAftertouch(sourceID, channel, param1, param2);
- break;
- case MidiDriver_BASE::MIDI_COMMAND_PITCH_BEND:
- doPitchBend(sourceID, channel, param1, param2);
- break;
- case MidiDriver_BASE::MIDI_COMMAND_SYSTEM:
- break;
- }
-}
-
-void MidiCombinerDynamic::doNoteOn(uint sourceID, uint8 channel, uint8 param1, uint8 param2) {
- uint outputChannel = 0;
-
- if (channel == MidiDriver_BASE::MIDI_RHYTHM_CHANNEL) {
- outputChannel = MidiDriver_BASE::MIDI_RHYTHM_CHANNEL;
- } else {
- bool foundChannel = false;
-
- // Find an existing exactly-matching channel
- for (uint i = 0; i < ARRAYSIZE(_outputChannels); i++) {
- OutputChannelState &ch = _outputChannels[i];
- if (ch._hasSource && ch._sourceID == sourceID && ch._channelID == channel) {
- foundChannel = true;
- outputChannel = i;
- break;
- }
- }
-
- if (!foundChannel) {
- // Find an inactive channel
- for (uint i = 0; i < ARRAYSIZE(_outputChannels); i++) {
- if (i == MidiDriver_BASE::MIDI_RHYTHM_CHANNEL)
- continue;
-
- if (!_outputChannels[i]._hasSource) {
- foundChannel = true;
- outputChannel = i;
- break;
- }
- }
- }
-
- if (!foundChannel) {
- uint bestOffCounter = 0xffffffffu;
-
- // Find the channel that went quiet the longest time ago
- for (uint i = 0; i < ARRAYSIZE(_outputChannels); i++) {
- if (i == MidiDriver_BASE::MIDI_RHYTHM_CHANNEL)
- continue;
-
- if (_outputChannels[i]._numActiveNotes == 0 && _outputChannels[i]._noteOffCounter < bestOffCounter) {
- foundChannel = true;
- outputChannel = i;
- bestOffCounter = _outputChannels[i]._noteOffCounter;
- }
- }
- }
-
- // All eligible channels are playing already
- if (!foundChannel)
- return;
- }
-
- OutputChannelState &ch = _outputChannels[outputChannel];
-
- if (!ch._hasSource || ch._sourceID != sourceID || ch._channelID != channel) {
- ch._sourceID = sourceID;
- ch._channelID = channel;
- ch._hasSource = true;
-
- const SourceState &sourceState = _sources[sourceID];
- syncSourceConfiguration(outputChannel, ch, sourceState, sourceState._sourceChannelState[channel]);
- }
-
- sendToOutput(MidiDriver_BASE::MIDI_COMMAND_NOTE_ON, outputChannel, param1, param2);
-
- MidiActiveNote note;
- note._outputChannel = outputChannel;
- note._tone = param1;
- note._affectedBySostenuto = (ch._midiChannelState._lrControllers[MidiDriver_BASE::MIDI_CONTROLLER_SOSTENUTO - kLRControllerStart] >= kSostenutoOnThreshold);
- note._isSustainedBySostenuto = false;
- note._isSustainedBySustain = false;
- _notes.push_back(note);
-
- ch._numActiveNotes++;
-}
-
-void MidiCombinerDynamic::doNoteOff(uint sourceID, uint8 channel, uint8 param1, uint8 param2) {
- for (uint i = 0; i < ARRAYSIZE(_outputChannels); i++) {
- OutputChannelState &ch = _outputChannels[i];
-
- if (ch._hasSource && ch._sourceID == sourceID && ch._channelID == channel) {
- sendToOutput(MidiDriver_BASE::MIDI_COMMAND_NOTE_OFF, i, param1, param2);
-
- for (uint ani = 0; ani < _notes.size(); ani++) {
- MidiActiveNote ¬e = _notes[ani];
- if (note._outputChannel == i && note._tone == param1 && !note._isSustainedBySostenuto && !note._isSustainedBySustain) {
- if (ch._midiChannelState._lrControllers[MidiDriver_BASE::MIDI_CONTROLLER_SUSTAIN - kLRControllerStart] >= kSustainOnThreshold)
- note._isSustainedBySustain = true;
-
- if (note._affectedBySostenuto && ch._midiChannelState._lrControllers[MidiDriver_BASE::MIDI_CONTROLLER_SOSTENUTO - kLRControllerStart] >= kSostenutoOnThreshold)
- note._isSustainedBySostenuto = true;
-
- tryCleanUpUnsustainedNote(ani);
- break;
- }
- }
-
- break;
- }
- }
-}
-
-void MidiCombinerDynamic::doPolyphonicAftertouch(uint sourceID, uint8 channel, uint8 param1, uint8 param2) {
- for (uint i = 0; i < ARRAYSIZE(_outputChannels); i++) {
- const OutputChannelState &ch = _outputChannels[i];
-
- if (ch._hasSource && ch._sourceID == sourceID && ch._channelID == channel) {
- sendToOutput(MidiDriver_BASE::MIDI_COMMAND_POLYPHONIC_AFTERTOUCH, i, param1, param2);
- break;
- }
- }
-}
-
-void MidiCombinerDynamic::doControlChange(uint sourceID, uint8 channel, uint8 param1, uint8 param2) {
- SourceState &src = _sources[sourceID];
- SourceChannelState &sch = src._sourceChannelState[channel];
-
- if (param1 == MidiDriver_BASE::MIDI_CONTROLLER_DATA_ENTRY_MSB) {
- doDataEntry(sourceID, channel, kLSBMask, param2 << 7);
- return;
- } else if (param1 == MidiDriver_BASE::MIDI_CONTROLLER_DATA_ENTRY_LSB) {
- doDataEntry(sourceID, channel, kMSBMask, param2);
- return;
- } else if (param1 < 32) {
- uint16 ctrl = ((sch._midiChannelState._hrControllers[param1] & kLSBMask) | ((param2 & 0x7f)) << 7);
- doHighRangeControlChange(sourceID, channel, param1, ctrl);
- return;
- } else if (param1 < 64) {
- uint16 ctrl = ((sch._midiChannelState._hrControllers[param1 - 32] & kMSBMask) | (param2 & 0x7f));
- doHighRangeControlChange(sourceID, channel, param1 - 32, ctrl);
- return;
- } else if (param1 < 96) {
- doLowRangeControlChange(sourceID, channel, param1 - 64, param2);
- return;
- }
-
- switch (param1) {
- case 96:
- // Data increment
- doDataEntry(sourceID, channel, 0x3fff, 1);
- break;
- case 97:
- // Data decrement
- doDataEntry(sourceID, channel, 0x3fff, -1);
- break;
- case 98:
- // NRPN LSB
- sch._midiChannelState._nrpnNumber = ((sch._midiChannelState._nrpnNumber & kMSBMask) | (param2 & 0x7f));
- sch._midiChannelState._dataEntryState = kDataEntryStateNRPN;
- break;
- case 99:
- // NRPN MSB
- sch._midiChannelState._nrpnNumber = ((sch._midiChannelState._nrpnNumber & kLSBMask) | ((param2 & 0x7f) << 7));
- sch._midiChannelState._dataEntryState = kDataEntryStateNRPN;
- break;
- case 100:
- // RPN LSB
- sch._midiChannelState._rpnNumber = ((sch._midiChannelState._rpnNumber & kMSBMask) | (param2 & 0x7f));
- sch._midiChannelState._dataEntryState = kDataEntryStateRPN;
- break;
- case 101:
- // RPN MSB
- sch._midiChannelState._rpnNumber = ((sch._midiChannelState._rpnNumber & kLSBMask) | ((param2 & 0x7f) << 7));
- sch._midiChannelState._dataEntryState = kDataEntryStateRPN;
- break;
- default:
- if (param1 >= 120 && param1 < 128)
- doChannelMode(sourceID, channel, param1, param2);
- break;
- };
-}
-
-void MidiCombinerDynamic::doProgramChange(uint sourceID, uint8 channel, uint8 param1, uint8 param2) {
- for (uint i = 0; i < ARRAYSIZE(_outputChannels); i++) {
- OutputChannelState &ch = _outputChannels[i];
-
- if (ch._hasSource && ch._sourceID == sourceID && ch._channelID == channel) {
- sendToOutput(MidiDriver_BASE::MIDI_COMMAND_PROGRAM_CHANGE, i, param1, param2);
- ch._midiChannelState._program = param1;
- break;
- }
- }
-
- _sources[sourceID]._sourceChannelState[channel]._midiChannelState._program = param1;
-}
-
-void MidiCombinerDynamic::doChannelAftertouch(uint sourceID, uint8 channel, uint8 param1, uint8 param2) {
- for (uint i = 0; i < ARRAYSIZE(_outputChannels); i++) {
- OutputChannelState &ch = _outputChannels[i];
-
- if (ch._hasSource && ch._sourceID == sourceID && ch._channelID == channel) {
- sendToOutput(MidiDriver_BASE::MIDI_COMMAND_CHANNEL_AFTERTOUCH, i, param1, param2);
- ch._midiChannelState._aftertouch = param1;
- break;
- }
- }
-}
-
-void MidiCombinerDynamic::doPitchBend(uint sourceID, uint8 channel, uint8 param1, uint8 param2) {
- uint16 pitchBend = (param1 & 0x7f) | ((param2 & 0x7f) << 7);
- for (uint i = 0; i < ARRAYSIZE(_outputChannels); i++) {
- OutputChannelState &ch = _outputChannels[i];
-
- if (ch._hasSource && ch._sourceID == sourceID && ch._channelID == channel) {
- sendToOutput(MidiDriver_BASE::MIDI_COMMAND_PITCH_BEND, i, param1, param2);
- ch._midiChannelState._pitchBend = pitchBend;
- break;
- }
- }
-
- _sources[sourceID]._sourceChannelState[channel]._midiChannelState._pitchBend = pitchBend;
-}
-
-void MidiCombinerDynamic::doHighRangeControlChange(uint sourceID, uint8 channel, uint8 hrParam, uint16 value) {
- SourceState &src = _sources[sourceID];
- SourceChannelState &srcCh = src._sourceChannelState[channel];
- srcCh._midiChannelState._hrControllers[hrParam] = value;
-
- for (uint i = 0; i < ARRAYSIZE(_outputChannels); i++) {
- OutputChannelState &ch = _outputChannels[i];
-
- if (ch._hasSource && ch._sourceID == sourceID && ch._channelID == channel) {
- syncSourceHRController(i, ch, src, srcCh, hrParam);
- break;
- }
- }
-}
-
-void MidiCombinerDynamic::doLowRangeControlChange(uint sourceID, uint8 channel, uint8 lrParam, uint8 value) {
- SourceChannelState &srcCh = _sources[sourceID]._sourceChannelState[channel];
- srcCh._midiChannelState._lrControllers[lrParam] = value;
-
- for (uint i = 0; i < ARRAYSIZE(_outputChannels); i++) {
- OutputChannelState &ch = _outputChannels[i];
-
- if (ch._hasSource && ch._sourceID == sourceID && ch._channelID == channel) {
- if (lrParam == MidiDriver_BASE::MIDI_CONTROLLER_SUSTAIN - kLRControllerStart && value < kSustainOnThreshold) {
- for (uint rni = _notes.size(); rni > 0; rni--) {
- uint noteIndex = rni - 1;
- MidiActiveNote ¬e = _notes[noteIndex];
- if (note._isSustainedBySustain) {
- note._isSustainedBySustain = false;
- tryCleanUpUnsustainedNote(noteIndex);
- }
- }
- } else if (lrParam == MidiDriver_BASE::MIDI_CONTROLLER_SOSTENUTO - kLRControllerStart && value < kSostenutoOnThreshold) {
- for (uint rni = _notes.size(); rni > 0; rni--) {
- uint noteIndex = rni - 1;
- MidiActiveNote ¬e = _notes[noteIndex];
- if (note._isSustainedBySostenuto) {
- note._isSustainedBySostenuto = false;
- tryCleanUpUnsustainedNote(noteIndex);
- }
- }
- }
-
- syncSourceLRController(i, ch, srcCh, lrParam);
- break;
- }
- }
-}
-
-void MidiCombinerDynamic::doDataEntry(uint sourceID, uint8 channel, int16 existingValueMask, int16 offset) {
- SourceChannelState &srcCh = _sources[sourceID]._sourceChannelState[channel];
-
- if (srcCh._midiChannelState._dataEntryState == kDataEntryStateRPN && srcCh._midiChannelState._rpnNumber < ARRAYSIZE(srcCh._midiChannelState._registeredParams)) {
- int32 rp = srcCh._midiChannelState._registeredParams[srcCh._midiChannelState._rpnNumber];
- rp &= existingValueMask;
- rp += offset;
-
- srcCh._midiChannelState._registeredParams[srcCh._midiChannelState._rpnNumber] = (rp & existingValueMask) + offset;
-
- for (uint i = 0; i < ARRAYSIZE(_outputChannels); i++) {
- OutputChannelState &ch = _outputChannels[i];
-
- if (ch._hasSource && ch._sourceID == sourceID && ch._channelID == channel) {
- syncSourceRegisteredParam(i, ch, srcCh, srcCh._midiChannelState._rpnNumber);
- break;
- }
- }
- }
-}
-
-void MidiCombinerDynamic::doChannelMode(uint sourceID, uint8 channel, uint8 param1, uint8 param2) {
- // Remap omni/poly/mono modes to all notes off, since we don't do anything with omni/poly
- switch (param1) {
- case MidiDriver_BASE::MIDI_CONTROLLER_OMNI_OFF:
- case MidiDriver_BASE::MIDI_CONTROLLER_OMNI_ON:
- case MidiDriver_BASE::MIDI_CONTROLLER_MONO_ON:
- case MidiDriver_BASE::MIDI_CONTROLLER_POLY_ON:
- case MidiDriver_BASE::MIDI_CONTROLLER_ALL_NOTES_OFF:
- doAllNotesOff(sourceID, channel, param2);
- break;
- case MidiDriver_BASE::MIDI_CONTROLLER_ALL_SOUND_OFF:
- doAllSoundOff(sourceID, channel, param2);
- break;
- case MidiDriver_BASE::MIDI_CONTROLLER_RESET_ALL_CONTROLLERS:
- doResetAllControllers(sourceID, channel, param2);
- break;
- case 122: // Local control (ignore)
- default:
- break;
- }
-}
-
-void MidiCombinerDynamic::doAllNotesOff(uint sourceID, uint8 channel, uint8 param2) {
- uint outputChannel = 0;
- bool foundChannel = false;
-
- for (uint i = 0; i < ARRAYSIZE(_outputChannels); i++) {
- OutputChannelState &ch = _outputChannels[i];
- if (ch._hasSource && ch._sourceID == sourceID && ch._channelID == channel) {
- foundChannel = true;
- outputChannel = i;
- break;
- }
- }
-
- if (!foundChannel)
- return;
-
- OutputChannelState &ch = _outputChannels[outputChannel];
-
- bool sustainOn = (ch._midiChannelState._lrControllers[MidiDriver_BASE::MIDI_CONTROLLER_SUSTAIN - kLRControllerStart] >= kSustainOnThreshold);
- bool sostenutoOn = (ch._midiChannelState._lrControllers[MidiDriver_BASE::MIDI_CONTROLLER_SOSTENUTO - kLRControllerStart] >= kSostenutoOnThreshold);
-
- for (uint rni = _notes.size(); rni > 0; rni--) {
- uint noteIndex = rni - 1;
- MidiActiveNote ¬e = _notes[noteIndex];
- if (note._outputChannel == outputChannel) {
- if (note._affectedBySostenuto && sostenutoOn)
- note._isSustainedBySostenuto = true;
- if (sustainOn)
- note._isSustainedBySustain = true;
-
- tryCleanUpUnsustainedNote(noteIndex);
- }
- }
-
- sendToOutput(MidiDriver_BASE::MIDI_COMMAND_CONTROL_CHANGE, outputChannel, MidiDriver_BASE::MIDI_CONTROLLER_ALL_NOTES_OFF, param2);
-}
-
-void MidiCombinerDynamic::doAllSoundOff(uint sourceID, uint8 channel, uint8 param2) {
- uint outputChannel = 0;
- bool foundChannel = false;
-
- for (uint i = 0; i < ARRAYSIZE(_outputChannels); i++) {
- OutputChannelState &ch = _outputChannels[i];
- if (ch._hasSource && ch._sourceID == sourceID && ch._channelID == channel) {
- foundChannel = true;
- outputChannel = i;
- break;
- }
- }
-
- if (!foundChannel)
- return;
-
- OutputChannelState &ch = _outputChannels[outputChannel];
-
- for (uint rni = _notes.size(); rni > 0; rni--) {
- uint noteIndex = rni - 1;
- MidiActiveNote ¬e = _notes[noteIndex];
- if (note._outputChannel == outputChannel) {
- note._isSustainedBySostenuto = false;
- note._isSustainedBySustain = false;
-
- tryCleanUpUnsustainedNote(noteIndex);
- }
- }
-
- sendToOutput(MidiDriver_BASE::MIDI_COMMAND_CONTROL_CHANGE, outputChannel, MidiDriver_BASE::MIDI_CONTROLLER_ALL_SOUND_OFF, param2);
- ch._noteOffCounter = 0; // All sound is off so this can be recycled quickly
-}
-
-void MidiCombinerDynamic::doResetAllControllers(uint sourceID, uint8 channel, uint8 param2) {
- SourceChannelState &srcCh = _sources[sourceID]._sourceChannelState[channel];
-
- srcCh._midiChannelState.softReset();
-
- uint outputChannel = 0;
- bool foundChannel = false;
-
- for (uint i = 0; i < ARRAYSIZE(_outputChannels); i++) {
- OutputChannelState &ch = _outputChannels[i];
- if (ch._hasSource && ch._sourceID == sourceID && ch._channelID == channel) {
- foundChannel = true;
- outputChannel = i;
- break;
- }
- }
-
- if (!foundChannel)
- return;
-
- OutputChannelState &ch = _outputChannels[outputChannel];
- ch._midiChannelState.softReset();
-
- // Release all sustained notes
- for (uint rni = _notes.size(); rni > 0; rni--) {
- uint noteIndex = rni - 1;
- MidiActiveNote ¬e = _notes[noteIndex];
- if (note._outputChannel == outputChannel) {
- if (note._isSustainedBySostenuto || note._isSustainedBySustain) {
- note._isSustainedBySostenuto = false;
- note._isSustainedBySustain = false;
- tryCleanUpUnsustainedNote(noteIndex);
- }
- }
- }
-
- sendToOutput(MidiDriver_BASE::MIDI_COMMAND_CONTROL_CHANGE, outputChannel, MidiDriver_BASE::MIDI_CONTROLLER_RESET_ALL_CONTROLLERS, 0);
-}
-
-void MidiCombinerDynamic::sendToOutput(uint8 command, uint8 channel, uint8 param1, uint8 param2) {
- uint32 output = static_cast<uint32>(command) | static_cast<uint32>(channel) | static_cast<uint32>(param1 << 8) | static_cast<uint32>(param2 << 16);
-
- if (_dumpFile) {
- const int timestamp = g_system->getMillis(true);
-
- const char *cmdName = "Unknown Command";
- switch (command) {
- case MidiDriver_BASE::MIDI_COMMAND_CHANNEL_AFTERTOUCH:
- cmdName = "ChannelAftertouch";
- break;
- case MidiDriver_BASE::MIDI_COMMAND_NOTE_OFF:
- cmdName = "NoteOff";
- break;
- case MidiDriver_BASE::MIDI_COMMAND_NOTE_ON:
- cmdName = "NoteOn";
- break;
- case MidiDriver_BASE::MIDI_COMMAND_POLYPHONIC_AFTERTOUCH:
- cmdName = "PolyAftertouch";
- break;
- case MidiDriver_BASE::MIDI_COMMAND_CONTROL_CHANGE:
- cmdName = "ControlChange";
- break;
- case MidiDriver_BASE::MIDI_COMMAND_PROGRAM_CHANGE:
- cmdName = "ProgramChange";
- break;
- case MidiDriver_BASE::MIDI_COMMAND_PITCH_BEND:
- cmdName = "PitchBend";
- break;
- case MidiDriver_BASE::MIDI_COMMAND_SYSTEM:
- cmdName = "System";
- break;
- default:
- cmdName = "Unknown";
- }
-
- if (command == MidiDriver_BASE::MIDI_COMMAND_CONTROL_CHANGE) {
- Common::String ctrlName = "Unknown";
-
- switch (param1) {
- case MidiDriver_BASE::MIDI_CONTROLLER_BANK_SELECT_MSB:
- ctrlName = "BankSelect";
- break;
- case MidiDriver_BASE::MIDI_CONTROLLER_MODULATION:
- ctrlName = "Modulation";
- break;
- case MidiDriver_BASE::MIDI_CONTROLLER_DATA_ENTRY_MSB:
- ctrlName = "DataEntryMSB";
- break;
- case MidiDriver_BASE::MIDI_CONTROLLER_VOLUME:
- ctrlName = "VolumeMSB";
- break;
- case MidiDriver_BASE::MIDI_CONTROLLER_VOLUME + 32:
- ctrlName = "VolumeLSB";
- break;
- case MidiDriver_BASE::MIDI_CONTROLLER_BALANCE:
- ctrlName = "Balance";
- break;
- case MidiDriver_BASE::MIDI_CONTROLLER_PANNING:
- ctrlName = "Panning";
- break;
- case MidiDriver_BASE::MIDI_CONTROLLER_EXPRESSION:
- ctrlName = "Expression";
- break;
- case MidiDriver_BASE::MIDI_CONTROLLER_BANK_SELECT_LSB:
- ctrlName = "BankSelectLSB";
- break;
- case MidiDriver_BASE::MIDI_CONTROLLER_DATA_ENTRY_LSB:
- ctrlName = "DataEntryLSB";
- break;
- case MidiDriver_BASE::MIDI_CONTROLLER_SUSTAIN:
- ctrlName = "Sustain";
- break;
- case MidiDriver_BASE::MIDI_CONTROLLER_PORTAMENTO:
- ctrlName = "Portamento";
- break;
- case MidiDriver_BASE::MIDI_CONTROLLER_SOSTENUTO:
- ctrlName = "Sostenuto";
- break;
- case MidiDriver_BASE::MIDI_CONTROLLER_SOFT:
- ctrlName = "Soft";
- break;
-
- case MidiDriver_BASE::MIDI_CONTROLLER_REVERB:
- ctrlName = "Reverb";
- break;
- case MidiDriver_BASE::MIDI_CONTROLLER_CHORUS:
- ctrlName = "Chorus";
- break;
- case MidiDriver_BASE::MIDI_CONTROLLER_RPN_LSB:
- ctrlName = "RPNLSB";
- break;
- case MidiDriver_BASE::MIDI_CONTROLLER_RPN_MSB:
- ctrlName = "RPNMSB";
- break;
- case MidiDriver_BASE::MIDI_CONTROLLER_ALL_SOUND_OFF:
- ctrlName = "AllSoundOff";
- break;
- case MidiDriver_BASE::MIDI_CONTROLLER_RESET_ALL_CONTROLLERS:
- ctrlName = "ResetAllControllers";
- break;
- case MidiDriver_BASE::MIDI_CONTROLLER_ALL_NOTES_OFF:
- ctrlName = "AllNotesOff";
- break;
- case MidiDriver_BASE::MIDI_CONTROLLER_OMNI_ON:
- ctrlName = "OmniOn";
- break;
- case MidiDriver_BASE::MIDI_CONTROLLER_OMNI_OFF:
- ctrlName = "OmniOff";
- break;
- case MidiDriver_BASE::MIDI_CONTROLLER_MONO_ON:
- ctrlName = "MonoOn";
- break;
- case MidiDriver_BASE::MIDI_CONTROLLER_POLY_ON:
- ctrlName = "PolyOn";
- break;
- default:
- ctrlName = Common::String::format("Unknown%02x", static_cast<int>(param1));
- }
-
- _dumpFile->writeString(Common::String::format("%i\t%i\t%i\t%s\t%s\t%i\n", _eventCounter, timestamp, static_cast<int>(channel), cmdName, ctrlName.c_str(), static_cast<int>(param2)));
- } else
- _dumpFile->writeString(Common::String::format("%i\t%i\t%i\t%s\t%i\t%i\n", _eventCounter, timestamp, static_cast<int>(channel), cmdName, static_cast<int>(param1), static_cast<int>(param2)));
-
- _eventCounter++;
- }
-
- _outputDriver->send(output);
-}
-
-void MidiCombinerDynamic::syncSourceConfiguration(uint outputChannel, OutputChannelState &outChState, const SourceState &srcState, const SourceChannelState &sourceChState) {
- const MidiChannelState &srcMidiChState = sourceChState._midiChannelState;
- MidiChannelState &outState = outChState._midiChannelState;
-
- if (outState._program != srcMidiChState._program) {
- outState._program = srcMidiChState._program;
- sendToOutput(MidiDriver_BASE::MIDI_COMMAND_PROGRAM_CHANGE, outputChannel, srcMidiChState._program, 0);
- }
-
- if (outState._aftertouch != srcMidiChState._aftertouch) {
- outState._aftertouch = srcMidiChState._aftertouch;
- sendToOutput(MidiDriver_BASE::MIDI_COMMAND_CHANNEL_AFTERTOUCH, outputChannel, srcMidiChState._aftertouch, 0);
- }
-
- if (outState._pitchBend != srcMidiChState._pitchBend) {
- outState._pitchBend = srcMidiChState._pitchBend;
- sendToOutput(MidiDriver_BASE::MIDI_COMMAND_PITCH_BEND, outputChannel, (srcMidiChState._pitchBend & kLSBMask), (srcMidiChState._pitchBend & kMSBMask) >> 7);
- }
-
- for (uint i = 0; i < ARRAYSIZE(srcMidiChState._hrControllers); i++)
- syncSourceHRController(outputChannel, outChState, srcState, sourceChState, i);
-
- for (uint i = 0; i < ARRAYSIZE(srcMidiChState._lrControllers); i++)
- syncSourceLRController(outputChannel, outChState, sourceChState, i);
-
- for (uint i = 0; i < ARRAYSIZE(srcMidiChState._registeredParams); i++)
- syncSourceRegisteredParam(outputChannel, outChState, sourceChState, i);
-}
-
-void MidiCombinerDynamic::syncSourceHRController(uint outputChannel, OutputChannelState &outChState, const SourceState &srcState, const SourceChannelState &sourceChState, uint hrController) {
- const MidiChannelState &srcMidiChState = sourceChState._midiChannelState;
- MidiChannelState &outState = outChState._midiChannelState;
-
- uint16 effectiveValue = srcMidiChState._hrControllers[hrController];
-
- if (hrController == MidiDriver_BASE::MIDI_CONTROLLER_VOLUME) {
- // GM volume to gain control is 40*log10(V/127)
- // This means linear scale is (volume/0x3f80)^4
- // To modulate the volume linearly, we must multiply the volume by the 4th root
- // of the volume.
- uint32 effectiveValueScaled = static_cast<uint32>(srcState._root4MasterVolume) * static_cast<uint32>(effectiveValue);
- effectiveValueScaled += (effectiveValueScaled >> 16) + 1u;
- effectiveValue = static_cast<uint16>(effectiveValueScaled >> 16);
- }
-
- if (outState._hrControllers[hrController] == effectiveValue)
- return;
-
- uint16 deltaBits = (outState._hrControllers[hrController] ^ effectiveValue);
-
- if (deltaBits & kMSBMask)
- sendToOutput(MidiDriver_BASE::MIDI_COMMAND_CONTROL_CHANGE, outputChannel, hrController, (effectiveValue & kMSBMask) >> 7);
- if (deltaBits & kLSBMask)
- sendToOutput(MidiDriver_BASE::MIDI_COMMAND_CONTROL_CHANGE, outputChannel, hrController + 32, effectiveValue & kLSBMask);
-
- outState._hrControllers[hrController] = effectiveValue;
-}
-
-void MidiCombinerDynamic::syncSourceLRController(uint outputChannel, OutputChannelState &outChState, const SourceChannelState &sourceChState, uint lrController) {
- const MidiChannelState &srcState = sourceChState._midiChannelState;
- MidiChannelState &outState = outChState._midiChannelState;
-
- if (outState._lrControllers[lrController] == srcState._lrControllers[lrController])
- return;
-
- sendToOutput(MidiDriver_BASE::MIDI_COMMAND_CONTROL_CHANGE, outputChannel, lrController + kLRControllerStart, srcState._lrControllers[lrController] & kLSBMask);
-
- outState._lrControllers[lrController] = srcState._lrControllers[lrController];
-}
-
-void MidiCombinerDynamic::syncSourceRegisteredParam(uint outputChannel, OutputChannelState &outChState, const SourceChannelState &sourceChState, uint rpn) {
- const MidiChannelState &srcState = sourceChState._midiChannelState;
- MidiChannelState &outState = outChState._midiChannelState;
-
- if (outState._registeredParams[rpn] == srcState._registeredParams[rpn])
- return;
-
- outState._registeredParams[rpn] = srcState._registeredParams[rpn];
-
- if (outState._dataEntryState != kDataEntryStateRPN || outState._rpnNumber != srcState._rpnNumber) {
- outState._dataEntryState = kDataEntryStateRPN;
- outState._rpnNumber = srcState._rpnNumber;
- sendToOutput(MidiDriver_BASE::MIDI_COMMAND_CONTROL_CHANGE, outputChannel, MidiDriver_BASE::MIDI_CONTROLLER_RPN_LSB, rpn & kLSBMask);
- sendToOutput(MidiDriver_BASE::MIDI_COMMAND_CONTROL_CHANGE, outputChannel, MidiDriver_BASE::MIDI_CONTROLLER_RPN_MSB, (rpn & kMSBMask) >> 7);
- }
-
- sendToOutput(MidiDriver_BASE::MIDI_COMMAND_CONTROL_CHANGE, outputChannel, MidiDriver_BASE::MIDI_CONTROLLER_DATA_ENTRY_LSB, srcState._registeredParams[rpn] & kLSBMask);
- sendToOutput(MidiDriver_BASE::MIDI_COMMAND_CONTROL_CHANGE, outputChannel, MidiDriver_BASE::MIDI_CONTROLLER_DATA_ENTRY_MSB, (srcState._registeredParams[rpn] & kMSBMask) >> 7);
-}
-
-void MidiCombinerDynamic::tryCleanUpUnsustainedNote(uint noteIndex) {
- MidiActiveNote ¬e = _notes[noteIndex];
-
- if (!note._isSustainedBySostenuto && !note._isSustainedBySustain) {
- OutputChannelState &outCh = _outputChannels[note._outputChannel];
- assert(outCh._numActiveNotes > 0);
- outCh._numActiveNotes--;
- if (!outCh._numActiveNotes)
- outCh._noteOffCounter = _noteOffCounter++;
-
- _notes.remove_at(noteIndex);
- }
-}
-
-MidiCombinerDynamic::MidiChannelState::MidiChannelState() {
- reset();
-}
-
-void MidiCombinerDynamic::MidiChannelState::reset() {
- _program = 0;
- _aftertouch = 0;
- _pitchBend = 0x2000;
-
- for (uint i = 0; i < ARRAYSIZE(_hrControllers); i++)
- _hrControllers[i] = 0;
- for (uint i = 0; i < ARRAYSIZE(_lrControllers); i++)
- _lrControllers[i] = 0;
- for (uint i = 0; i < ARRAYSIZE(_registeredParams); i++)
- _registeredParams[i] = 0;
-
- _hrControllers[MidiDriver_BASE::MIDI_CONTROLLER_BALANCE] = (64 << 7);
- _hrControllers[MidiDriver_BASE::MIDI_CONTROLLER_PANNING] = (64 << 7);
- _hrControllers[MidiDriver_BASE::MIDI_CONTROLLER_VOLUME] = (127 << 7);
-
- _dataEntryState = kDataEntryStateNone;
- _rpnNumber = 0;
- _nrpnNumber = 0;
-}
-
-void MidiCombinerDynamic::MidiChannelState::softReset() {
- _hrControllers[MidiDriver_BASE::MIDI_CONTROLLER_MODULATION] = 0;
- _lrControllers[MidiDriver_BASE::MIDI_CONTROLLER_SUSTAIN - kLRControllerStart] = 0;
- _lrControllers[MidiDriver_BASE::MIDI_CONTROLLER_PORTAMENTO - kLRControllerStart] = 0;
- _lrControllers[MidiDriver_BASE::MIDI_CONTROLLER_SOSTENUTO - kLRControllerStart] = 0;
- _lrControllers[MidiDriver_BASE::MIDI_CONTROLLER_SOFT - kLRControllerStart] = 0;
- _dataEntryState = kDataEntryStateNone;
- _rpnNumber = 0;
- _nrpnNumber = 0;
- _aftertouch = 0;
- _hrControllers[MidiDriver_BASE::MIDI_CONTROLLER_EXPRESSION] = (127 << 7);
- _pitchBend = (64 << 7);
-}
-
-MidiCombinerDynamic::SourceChannelState::SourceChannelState() {
- reset();
-}
-
-void MidiCombinerDynamic::SourceChannelState::reset() {
-}
-
-MidiCombinerDynamic::SourceState::SourceState() : _isAllocated(false), _root4MasterVolume(0xffffu) {
-}
-
-void MidiCombinerDynamic::SourceState::allocate() {
- _isAllocated = true;
-}
-
-void MidiCombinerDynamic::SourceState::deallocate() {
- _isAllocated = false;
-}
-
-MidiCombinerDynamic::OutputChannelState::OutputChannelState() : _sourceID(0), _volumeIsAmbiguous(true), _channelID(0), _hasSource(false), _noteOffCounter(0), _numActiveNotes(0) {
-}
-
-MultiMidiPlayer::MultiMidiPlayer(bool dynamicMidiMixer) {
- if (dynamicMidiMixer)
- _combiner.reset(new MidiCombinerDynamic(this));
- else
- _combiner.reset(new MidiCombinerSimple(this));
-
- createDriver(MDT_MIDI | MDT_ADLIB | MDT_PREFER_GM);
-
- if (_driver->open() != 0) {
- _driver->close();
- delete _driver;
- _driver = nullptr;
- return;
- }
-
- _driver->setTimerCallback(this, &timerCallback);
-}
-
-MultiMidiPlayer::~MultiMidiPlayer() {
- Common::StackLock lock(_mutex);
- _filePlayers.clear();
- _notePlayers.clear();
-}
-
-void MultiMidiPlayer::timerCallback(void *refCon) {
- static_cast<MultiMidiPlayer *>(refCon)->onTimer();
-}
-
-void MultiMidiPlayer::onTimer() {
- Common::StackLock lock(_mutex);
-
- for (const Common::SharedPtr<MidiFilePlayerImpl> &player : _filePlayers)
- player->onTimer();
-
- for (const Common::SharedPtr<MidiNotePlayerImpl> &player : _notePlayers)
- player->onTimer();
-}
-
-MidiFilePlayer *MultiMidiPlayer::createFilePlayer(const Common::SharedPtr<Data::Standard::MidiModifier::EmbeddedFile> &file, bool hasTempoOverride, double tempoOverride, uint8 volume, bool loop, uint16 mutedTracks) {
- Common::SharedPtr<MidiCombinerSource> combinerSource = createSource();
- Common::SharedPtr<MidiFilePlayerImpl> filePlayer(new MidiFilePlayerImpl(combinerSource, file, getBaseTempo(), hasTempoOverride, tempoOverride, volume, loop, mutedTracks));
-
- {
- Common::StackLock lock(_mutex);
- combinerSource->setVolume(volume);
- _filePlayers.push_back(filePlayer);
- }
-
- return filePlayer.get();
-}
-
-MidiNotePlayer *MultiMidiPlayer::createNotePlayer() {
- Common::SharedPtr<MidiCombinerSource> combinerSource = createSource();
- Common::SharedPtr<MidiNotePlayerImpl> notePlayer(new MidiNotePlayerImpl(combinerSource, getBaseTempo()));
-
- {
- Common::StackLock lock(_mutex);
- _notePlayers.push_back(notePlayer);
- }
-
- return notePlayer.get();
-}
-
-Common::SharedPtr<MidiCombinerSource> MultiMidiPlayer::createSource() {
- Common::StackLock lock(_mutex);
- return _combiner->createSource();
-}
-
-void MultiMidiPlayer::deleteFilePlayer(MidiFilePlayer *player) {
- Common::SharedPtr<MidiFilePlayerImpl> ref;
-
- for (Common::Array<Common::SharedPtr<MidiFilePlayerImpl> >::iterator it = _filePlayers.begin(), itEnd = _filePlayers.end(); it != itEnd; ++it) {
- if (it->get() == player) {
- {
- Common::StackLock lock(_mutex);
- ref = *it;
- _filePlayers.erase(it);
- ref->stop();
- }
- break;
- }
- }
-
- if (ref)
- ref->detach();
-}
-
-void MultiMidiPlayer::deleteNotePlayer(MidiNotePlayer *player) {
- Common::SharedPtr<MidiNotePlayerImpl> ref;
-
- for (Common::Array<Common::SharedPtr<MidiNotePlayerImpl> >::iterator it = _notePlayers.begin(), itEnd = _notePlayers.end(); it != itEnd; ++it) {
- if (it->get() == player) {
- {
- Common::StackLock lock(_mutex);
- ref = *it;
- _notePlayers.erase(it);
- ref->stop();
- }
- break;
- }
- }
-
- if (ref)
- ref->detach();
-}
-
-void MultiMidiPlayer::setPlayerVolume(MidiFilePlayer *player, uint8 volume) {
- Common::StackLock lock(_mutex);
- static_cast<MidiFilePlayerImpl *>(player)->setVolume(volume);
-}
-
-void MultiMidiPlayer::setPlayerLoop(MidiFilePlayer *player, bool loop) {
- Common::StackLock lock(_mutex);
- static_cast<MidiFilePlayerImpl *>(player)->setLoop(loop);
-}
-
-void MultiMidiPlayer::setPlayerTempo(MidiFilePlayer *player, double tempo) {
- Common::StackLock lock(_mutex);
- static_cast<MidiFilePlayerImpl *>(player)->setTempoOverride(tempo);
-}
-
-void MultiMidiPlayer::setPlayerMutedTracks(MidiFilePlayer *player, uint16 mutedTracks) {
- Common::StackLock lock(_mutex);
- static_cast<MidiFilePlayerImpl *>(player)->setMutedTracks(mutedTracks);
-}
-
-void MultiMidiPlayer::stopPlayer(MidiFilePlayer *player) {
- Common::StackLock lock(_mutex);
- static_cast<MidiFilePlayerImpl *>(player)->stop();
-}
-
-void MultiMidiPlayer::playPlayer(MidiFilePlayer *player) {
- Common::StackLock lock(_mutex);
- static_cast<MidiFilePlayerImpl *>(player)->play();
-}
-
-void MultiMidiPlayer::pausePlayer(MidiFilePlayer *player) {
- Common::StackLock lock(_mutex);
- static_cast<MidiFilePlayerImpl *>(player)->pause();
-}
-
-void MultiMidiPlayer::resumePlayer(MidiFilePlayer *player) {
- Common::StackLock lock(_mutex);
- static_cast<MidiFilePlayerImpl *>(player)->resume();
-}
-
-void MultiMidiPlayer::playNote(MidiNotePlayer *player, uint8 volume, uint8 channel, uint8 program, uint8 note, uint8 velocity, double duration) {
- Common::StackLock lock(_mutex);
- static_cast<MidiNotePlayerImpl *>(player)->play(volume, channel, program, note, velocity, duration);
-}
+#include "mtropolis/miniscript.h"
+#include "mtropolis/plugin/standard.h"
+#include "mtropolis/plugins.h"
-void MultiMidiPlayer::stopNote(MidiNotePlayer *player) {
- Common::StackLock lock(_mutex);
- static_cast<MidiNotePlayerImpl *>(player)->stop();
-}
+#include "mtropolis/miniscript.h"
-uint32 MultiMidiPlayer::getBaseTempo() const {
- if (_driver)
- return _driver->getBaseTempo();
- return 1;
-}
+namespace MTropolis {
-void MultiMidiPlayer::send(uint32 b) {
- if (_driver)
- _driver->send(b);
-}
+namespace Standard {
CursorModifier::CursorModifier() : _cursorID(0) {
}
@@ -2366,391 +835,6 @@ bool ObjectReferenceVariableStorage::SaveLoad::loadInternal(Common::ReadStream *
return true;
}
-
-MidiModifier::MidiModifier() : _mode(kModeFile), _volume(100), _mutedTracks(0), /* _singleNoteChannel(0), _singleNoteNote(0), */
- _plugIn(nullptr), _filePlayer(nullptr), _notePlayer(nullptr) /*, _runtime(nullptr) */ {
-
- memset(&this->_modeSpecific, 0, sizeof(this->_modeSpecific));
-}
-
-MidiModifier::~MidiModifier() {
- if (_filePlayer)
- _plugIn->getMidi()->deleteFilePlayer(_filePlayer);
-
- if (_notePlayer)
- _plugIn->getMidi()->deleteNotePlayer(_notePlayer);
-}
-
-bool MidiModifier::load(const PlugInModifierLoaderContext &context, const Data::Standard::MidiModifier &data) {
- _plugIn = static_cast<StandardPlugIn *>(context.plugIn);
-
- if (data.executeWhen.type != Data::PlugInTypeTaggedValue::kEvent)
- return false;
-
- if (!_executeWhen.load(data.executeWhen.value.asEvent))
- return false;
-
- if (data.terminateWhen.type != Data::PlugInTypeTaggedValue::kEvent)
- return false;
-
- if (!_terminateWhen.load(data.terminateWhen.value.asEvent))
- return false;
-
- if (data.embeddedFlag) {
- _mode = kModeFile;
- _embeddedFile = data.embeddedFile;
-
- _modeSpecific.file.loop = (data.modeSpecific.embedded.loop != 0);
- _modeSpecific.file.overrideTempo = (data.modeSpecific.embedded.overrideTempo != 0);
- _volume = data.modeSpecific.embedded.volume;
-
- if (data.embeddedFadeIn.type != Data::PlugInTypeTaggedValue::kFloat
- || data.embeddedFadeOut.type != Data::PlugInTypeTaggedValue::kFloat
- || data.embeddedTempo.type != Data::PlugInTypeTaggedValue::kFloat)
- return false;
-
- _modeSpecific.file.fadeIn = data.embeddedFadeIn.value.asFloat.toXPFloat().toDouble();
- _modeSpecific.file.fadeOut = data.embeddedFadeOut.value.asFloat.toXPFloat().toDouble();
- _modeSpecific.file.tempo = data.embeddedTempo.value.asFloat.toXPFloat().toDouble();
- } else {
- _mode = kModeSingleNote;
-
- if (data.singleNoteDuration.type != Data::PlugInTypeTaggedValue::kFloat)
- return false;
-
- _modeSpecific.singleNote.channel = data.modeSpecific.singleNote.channel;
- _modeSpecific.singleNote.note = data.modeSpecific.singleNote.note;
- _modeSpecific.singleNote.velocity = data.modeSpecific.singleNote.velocity;
- _modeSpecific.singleNote.program = data.modeSpecific.singleNote.program;
- _modeSpecific.singleNote.duration = data.singleNoteDuration.value.asFloat.toXPFloat().toDouble();
-
- _volume = 100;
- }
-
- return true;
-}
-
-bool MidiModifier::respondsToEvent(const Event &evt) const {
- return _executeWhen.respondsTo(evt) || _terminateWhen.respondsTo(evt);
-}
-
-VThreadState MidiModifier::consumeMessage(Runtime *runtime, const Common::SharedPtr<MessageProperties> &msg) {
- if (_executeWhen.respondsTo(msg->getEvent())) {
- const SubtitleTables &subtitleTables = runtime->getProject()->getSubtitles();
- if (subtitleTables.modifierMapping) {
- const Common::String *subSetIDPtr = subtitleTables.modifierMapping->findSubtitleSetForModifierGUID(getStaticGUID());
- if (subSetIDPtr) {
- // Don't currently support anything except play-on-start subs for MIDI
- SubtitlePlayer subtitlePlayer(runtime, *subSetIDPtr, subtitleTables);
- subtitlePlayer.update(0, 1);
- }
- }
-
- if (_mode == kModeFile) {
- if (_embeddedFile) {
- debug(2, "MIDI (%x '%s'): Playing embedded file", getStaticGUID(), getName().c_str());
-
- const double tempo = _modeSpecific.file.overrideTempo ? _modeSpecific.file.tempo : 120.0;
- if (!_filePlayer)
- _filePlayer = _plugIn->getMidi()->createFilePlayer(_embeddedFile, _modeSpecific.file.overrideTempo, tempo, getBoostedVolume(runtime) * 255 / 100, _modeSpecific.file.loop, _mutedTracks);
- _plugIn->getMidi()->playPlayer(_filePlayer);
- } else {
- debug(2, "MIDI (%x '%s'): Digested execute event but don't have anything to play", getStaticGUID(), getName().c_str());
- }
- } else if (_mode == kModeSingleNote) {
- playSingleNote();
- }
- }
- if (_terminateWhen.respondsTo(msg->getEvent())) {
- disable(runtime);
- }
-
- return kVThreadReturn;
-}
-
-void MidiModifier::disable(Runtime *runtime) {
- if (_filePlayer) {
- _plugIn->getMidi()->deleteFilePlayer(_filePlayer);
- _filePlayer = nullptr;
- }
- if (_notePlayer) {
- _plugIn->getMidi()->deleteNotePlayer(_notePlayer);
- _notePlayer = nullptr;
- }
-}
-
-void MidiModifier::playSingleNote() {
- if (!_notePlayer)
- _notePlayer = _plugIn->getMidi()->createNotePlayer();
- _plugIn->getMidi()->playNote(_notePlayer, _volume, _modeSpecific.singleNote.channel, _modeSpecific.singleNote.program, _modeSpecific.singleNote.note, _modeSpecific.singleNote.velocity, _modeSpecific.singleNote.duration);
-}
-
-void MidiModifier::stopSingleNote() {
- if (_notePlayer)
- _plugIn->getMidi()->stopNote(_notePlayer);
-}
-
-bool MidiModifier::readAttribute(MiniscriptThread *thread, DynamicValue &result, const Common::String &attrib) {
- if (attrib == "volume") {
- result.setInt(_volume);
- return true;
- }
-
- return Modifier::readAttribute(thread, result, attrib);
-}
-
-MiniscriptInstructionOutcome MidiModifier::writeRefAttribute(MiniscriptThread *thread, DynamicValueWriteProxy &result, const Common::String &attrib) {
- if (attrib == "volume") {
- DynamicValueWriteFuncHelper<MidiModifier, &MidiModifier::scriptSetVolume, true>::create(this, result);
- return kMiniscriptInstructionOutcomeContinue;
- } else if (attrib == "notevelocity") {
- DynamicValueWriteFuncHelper<MidiModifier, &MidiModifier::scriptSetNoteVelocity, true>::create(this, result);
- return kMiniscriptInstructionOutcomeContinue;
- } else if (attrib == "noteduration") {
- DynamicValueWriteFuncHelper<MidiModifier, &MidiModifier::scriptSetNoteDuration, true>::create(this, result);
- return kMiniscriptInstructionOutcomeContinue;
- } else if (attrib == "notenum") {
- DynamicValueWriteFuncHelper<MidiModifier, &MidiModifier::scriptSetNoteNum, true>::create(this, result);
- return kMiniscriptInstructionOutcomeContinue;
- } else if (attrib == "loop") {
- DynamicValueWriteFuncHelper<MidiModifier, &MidiModifier::scriptSetLoop, true>::create(this, result);
- return kMiniscriptInstructionOutcomeContinue;
- } else if (attrib == "playnote") {
- DynamicValueWriteFuncHelper<MidiModifier, &MidiModifier::scriptSetPlayNote, true>::create(this, result);
- return kMiniscriptInstructionOutcomeContinue;
- } else if (attrib == "tempo") {
- DynamicValueWriteFuncHelper<MidiModifier, &MidiModifier::scriptSetTempo, true>::create(this, result);
- return kMiniscriptInstructionOutcomeContinue;
- } else if (attrib == "mutetrack") {
- DynamicValueWriteFuncHelper<MidiModifier, &MidiModifier::scriptSetMuteTrack, true>::create(this, result);
- return kMiniscriptInstructionOutcomeContinue;
- }
-
- return Modifier::writeRefAttribute(thread, result, attrib);
-}
-
-MiniscriptInstructionOutcome MidiModifier::writeRefAttributeIndexed(MiniscriptThread *thread, DynamicValueWriteProxy &result, const Common::String &attrib, const DynamicValue &index) {
- if (attrib == "mutetrack") {
- int32 asInteger = 0;
- if (!index.roundToInt(asInteger) || asInteger < 1) {
- thread->error("Invalid index for mutetrack");
- return kMiniscriptInstructionOutcomeFailed;
- }
-
- result.pod.objectRef = this;
- result.pod.ptrOrOffset = static_cast<uintptr>(asInteger) - 1;
- result.pod.ifc = DynamicValueWriteInterfaceGlue<MuteTrackProxyInterface>::getInstance();
-
- return kMiniscriptInstructionOutcomeContinue;
- }
-
- return Modifier::writeRefAttributeIndexed(thread, result, attrib, index);
-}
-
-
-uint MidiModifier::getBoostedVolume(Runtime *runtime) const {
- uint boostedVolume = (_volume * runtime->getHacks().midiVolumeScale) >> 8;
- if (boostedVolume > 100)
- boostedVolume = 100;
- return boostedVolume;
-}
-
-Common::SharedPtr<Modifier> MidiModifier::shallowClone() const {
- Common::SharedPtr<MidiModifier> clone(new MidiModifier(*this));
-
- clone->_notePlayer = nullptr;
- clone->_filePlayer = nullptr;
-
- return clone;
-}
-
-const char *MidiModifier::getDefaultName() const {
- return "MIDI Modifier";
-}
-
-MiniscriptInstructionOutcome MidiModifier::scriptSetVolume(MiniscriptThread *thread, const DynamicValue &value) {
- int32 asInteger = 0;
- if (!value.roundToInt(asInteger))
- return kMiniscriptInstructionOutcomeFailed;
-
- if (asInteger < 0)
- asInteger = 0;
- else if (asInteger > 100)
- asInteger = 100;
-
- _volume = asInteger;
-
- if (_mode == kModeFile) {
- debug(2, "MIDI (%x '%s'): Changing volume to %i", getStaticGUID(), getName().c_str(), _volume);
- if (_filePlayer)
- _plugIn->getMidi()->setPlayerVolume(_filePlayer, getBoostedVolume(thread->getRuntime()) * 255 / 100);
- }
-
- return kMiniscriptInstructionOutcomeContinue;
-}
-
-MiniscriptInstructionOutcome MidiModifier::scriptSetNoteVelocity(MiniscriptThread *thread, const DynamicValue &value) {
- int32 asInteger = 0;
- if (!value.roundToInt(asInteger))
- return kMiniscriptInstructionOutcomeFailed;
-
- if (asInteger < 0)
- asInteger = 0;
- else if (asInteger > 127)
- asInteger = 127;
-
- if (_mode == kModeSingleNote) {
- debug(2, "MIDI (%x '%s'): Changing note velocity to %i", getStaticGUID(), getName().c_str(), asInteger);
- _modeSpecific.singleNote.velocity = asInteger;
- }
-
- return kMiniscriptInstructionOutcomeContinue;
-}
-
-MiniscriptInstructionOutcome MidiModifier::scriptSetNoteDuration(MiniscriptThread *thread, const DynamicValue &value) {
- double asDouble = 0.0;
- if (value.getType() == DynamicValueTypes::kFloat) {
- asDouble = value.getFloat();
- } else {
- DynamicValue converted;
- if (!value.convertToType(DynamicValueTypes::kFloat, converted))
- return kMiniscriptInstructionOutcomeFailed;
- asDouble = converted.getFloat();
- }
-
- if (_mode == kModeSingleNote) {
- debug(2, "MIDI (%x '%s'): Changing note duration to %g", getStaticGUID(), getName().c_str(), asDouble);
- _modeSpecific.singleNote.duration = asDouble;
- }
-
- return kMiniscriptInstructionOutcomeContinue;
-}
-
-MiniscriptInstructionOutcome MidiModifier::scriptSetNoteNum(MiniscriptThread *thread, const DynamicValue &value) {
- int32 asInteger = 0;
- if (!value.roundToInt(asInteger))
- return kMiniscriptInstructionOutcomeFailed;
-
- if (asInteger < 0)
- asInteger = 0;
- else if (asInteger > 255)
- asInteger = 255;
-
- if (_mode == kModeSingleNote) {
- debug(2, "MIDI (%x '%s'): Changing note number to %i", getStaticGUID(), getName().c_str(), asInteger);
- _modeSpecific.singleNote.note = asInteger;
- }
-
- return kMiniscriptInstructionOutcomeContinue;
-}
-
-MiniscriptInstructionOutcome MidiModifier::scriptSetLoop(MiniscriptThread *thread, const DynamicValue &value) {
- if (value.getType() != DynamicValueTypes::kBoolean)
- return kMiniscriptInstructionOutcomeFailed;
-
- if (_mode == kModeFile) {
- const bool loop = value.getBool();
-
- debug(2, "MIDI (%x '%s'): Changing loop state to %s", getStaticGUID(), getName().c_str(), loop ? "true" : "false");
- if (_modeSpecific.file.loop != loop) {
- _modeSpecific.file.loop = loop;
-
- if (_filePlayer)
- _plugIn->getMidi()->setPlayerLoop(_filePlayer, loop);
- }
- }
-
- return kMiniscriptInstructionOutcomeContinue;
-}
-
-MiniscriptInstructionOutcome MidiModifier::scriptSetTempo(MiniscriptThread *thread, const DynamicValue &value) {
- double tempo = 0.0;
- if (value.getType() == DynamicValueTypes::kInteger)
- tempo = value.getInt();
- else if (value.getType() == DynamicValueTypes::kFloat)
- tempo = value.getFloat();
- else
- return kMiniscriptInstructionOutcomeFailed;
-
- if (_mode == kModeFile) {
- debug(2, "MIDI (%x '%s'): Changing tempo to %g", getStaticGUID(), getName().c_str(), tempo);
-
- if (_filePlayer)
- _plugIn->getMidi()->setPlayerTempo(_filePlayer, tempo);
-
- }
-
- return kMiniscriptInstructionOutcomeContinue;
-}
-
-MiniscriptInstructionOutcome MidiModifier::scriptSetPlayNote(MiniscriptThread *thread, const DynamicValue &value) {
- if (miniscriptEvaluateTruth(value))
- playSingleNote();
- else
- stopSingleNote();
-
- return kMiniscriptInstructionOutcomeContinue;
-}
-
-MiniscriptInstructionOutcome MidiModifier::scriptSetMuteTrack(MiniscriptThread *thread, const DynamicValue &value) {
- if (value.getType() != DynamicValueTypes::kBoolean) {
- thread->error("Invalid type for mutetrack");
- return kMiniscriptInstructionOutcomeFailed;
- }
-
- uint16 mutedTracks = value.getBool() ? 0xffffu : 0u;
-
- if (mutedTracks != _mutedTracks) {
- _mutedTracks = mutedTracks;
-
- if (_filePlayer)
- _plugIn->getMidi()->setPlayerMutedTracks(_filePlayer, mutedTracks);
- }
-
- return kMiniscriptInstructionOutcomeContinue;
-}
-
-MiniscriptInstructionOutcome MidiModifier::scriptSetMuteTrackIndexed(MiniscriptThread *thread, size_t trackIndex, bool muted) {
- if (trackIndex >= 16) {
- thread->error("Invalid track index for mutetrack");
- return kMiniscriptInstructionOutcomeFailed;
- }
-
- uint16 mutedTracks = _mutedTracks;
- uint16 trackMask = 1 << trackIndex;
-
- if (muted)
- mutedTracks |= trackMask;
- else
- mutedTracks -= (mutedTracks & trackMask);
-
- if (mutedTracks != _mutedTracks) {
- _mutedTracks = mutedTracks;
-
- if (_filePlayer)
- _plugIn->getMidi()->setPlayerMutedTracks(_filePlayer, mutedTracks);
- }
-
- return kMiniscriptInstructionOutcomeContinue;
-}
-
-MiniscriptInstructionOutcome MidiModifier::MuteTrackProxyInterface::write(MiniscriptThread *thread, const DynamicValue &value, void *objectRef, uintptr ptrOrOffset) {
- if (value.getType() != DynamicValueTypes::kBoolean) {
- thread->error("Invalid type for mutetrack");
- return kMiniscriptInstructionOutcomeFailed;
- }
-
- return static_cast<MidiModifier *>(objectRef)->scriptSetMuteTrackIndexed(thread, ptrOrOffset, value.getBool());
-}
-
-MiniscriptInstructionOutcome MidiModifier::MuteTrackProxyInterface::refAttrib(MiniscriptThread *thread, DynamicValueWriteProxy &proxy, void *objectRef, uintptr ptrOrOffset, const Common::String &attrib) {
- return kMiniscriptInstructionOutcomeFailed;
-}
-
-MiniscriptInstructionOutcome MidiModifier::MuteTrackProxyInterface::refAttribIndexed(MiniscriptThread *thread, DynamicValueWriteProxy &proxy, void *objectRef, uintptr ptrOrOffset, const Common::String &attrib, const DynamicValue &index) {
- return kMiniscriptInstructionOutcomeFailed;
-}
-
ListVariableModifier::ListVariableModifier() : VariableModifier(Common::SharedPtr<VariableStorage>(new ListVariableStorage())) {
}
@@ -3336,16 +1420,14 @@ const char *PanningModifier::getDefaultName() const {
StandardPlugInHacks::StandardPlugInHacks() : allowGarbledListModData(false) {
}
-StandardPlugIn::StandardPlugIn(bool useDynamicMidi)
+StandardPlugIn::StandardPlugIn()
: _cursorModifierFactory(this)
, _sTransCtModifierFactory(this)
, _mediaCueModifierFactory(this)
, _objRefVarModifierFactory(this)
- , _midiModifierFactory(this)
, _listVarModifierFactory(this)
, _sysInfoModifierFactory(this)
, _panningModifierFactory(this) {
- _midi.reset(new MultiMidiPlayer(useDynamicMidi));
}
StandardPlugIn::~StandardPlugIn() {
@@ -3356,7 +1438,6 @@ void StandardPlugIn::registerModifiers(IPlugInModifierRegistrar *registrar) cons
registrar->registerPlugInModifier("STransCt", &_sTransCtModifierFactory);
registrar->registerPlugInModifier("MediaCue", &_mediaCueModifierFactory);
registrar->registerPlugInModifier("ObjRefP", &_objRefVarModifierFactory);
- registrar->registerPlugInModifier("MIDIModf", &_midiModifierFactory);
registrar->registerPlugInModifier("ListMod", &_listVarModifierFactory);
registrar->registerPlugInModifier("SysInfo", &_sysInfoModifierFactory);
@@ -3371,18 +1452,12 @@ StandardPlugInHacks &StandardPlugIn::getHacks() {
return _hacks;
}
-MultiMidiPlayer *StandardPlugIn::getMidi() const {
- return _midi.get();
-}
-
} // End of namespace Standard
namespace PlugIns {
Common::SharedPtr<PlugIn> createStandard() {
- const bool useDynamicMidi = ConfMan.getBool("mtropolis_mod_dynamic_midi");
-
- return Common::SharedPtr<PlugIn>(new Standard::StandardPlugIn(useDynamicMidi));
+ return Common::SharedPtr<PlugIn>(new Standard::StandardPlugIn());
}
} // End of namespace MTropolis
diff --git a/engines/mtropolis/plugin/standard.h b/engines/mtropolis/plugin/standard.h
index ddb6c59031e..f3bebf57b9f 100644
--- a/engines/mtropolis/plugin/standard.h
+++ b/engines/mtropolis/plugin/standard.h
@@ -27,8 +27,6 @@
#include "mtropolis/runtime.h"
#include "mtropolis/plugin/standard_data.h"
-class MidiDriver;
-
namespace MTropolis {
class Runtime;
@@ -36,10 +34,6 @@ class Runtime;
namespace Standard {
class StandardPlugIn;
-class MidiFilePlayer;
-class MidiNotePlayer;
-class MultiMidiPlayer;
-class MidiCombinerSource;
class CursorModifier : public Modifier {
public:
@@ -241,94 +235,6 @@ private:
mutable ObjectReference _object;
};
-class MidiModifier : public Modifier {
-public:
- MidiModifier();
- ~MidiModifier();
-
- bool load(const PlugInModifierLoaderContext &context, const Data::Standard::MidiModifier &data);
-
- bool respondsToEvent(const Event &evt) const override;
- VThreadState consumeMessage(Runtime *runtime, const Common::SharedPtr<MessageProperties> &msg) override;
- void disable(Runtime *runtime) override;
-
- bool readAttribute(MiniscriptThread *thread, DynamicValue &result, const Common::String &attrib) override;
- MiniscriptInstructionOutcome writeRefAttribute(MiniscriptThread *thread, DynamicValueWriteProxy &result, const Common::String &attrib) override;
- MiniscriptInstructionOutcome writeRefAttributeIndexed(MiniscriptThread *thread, DynamicValueWriteProxy &result, const Common::String &attrib, const DynamicValue &index) override;
-
-#ifdef MTROPOLIS_DEBUG_ENABLE
- const char *debugGetTypeName() const override { return "MIDI Modifier"; }
- SupportStatus debugGetSupportStatus() const override { return kSupportStatusDone; }
-#endif
-
-private:
- struct MuteTrackProxyInterface {
- static MiniscriptInstructionOutcome write(MiniscriptThread *thread, const DynamicValue &dest, void *objectRef, uintptr ptrOrOffset);
- static MiniscriptInstructionOutcome refAttrib(MiniscriptThread *thread, DynamicValueWriteProxy &proxy, void *objectRef, uintptr ptrOrOffset, const Common::String &attrib);
- static MiniscriptInstructionOutcome refAttribIndexed(MiniscriptThread *thread, DynamicValueWriteProxy &proxy, void *objectRef, uintptr ptrOrOffset, const Common::String &attrib, const DynamicValue &index);
- };
-
- Common::SharedPtr<Modifier> shallowClone() const override;
- const char *getDefaultName() const override;
-
- MiniscriptInstructionOutcome scriptSetVolume(MiniscriptThread *thread, const DynamicValue &value);
- MiniscriptInstructionOutcome scriptSetNoteVelocity(MiniscriptThread *thread, const DynamicValue &value);
- MiniscriptInstructionOutcome scriptSetNoteDuration(MiniscriptThread *thread, const DynamicValue &value);
- MiniscriptInstructionOutcome scriptSetNoteNum(MiniscriptThread *thread, const DynamicValue &value);
- MiniscriptInstructionOutcome scriptSetLoop(MiniscriptThread *thread, const DynamicValue &value);
- MiniscriptInstructionOutcome scriptSetPlayNote(MiniscriptThread *thread, const DynamicValue &value);
- MiniscriptInstructionOutcome scriptSetTempo(MiniscriptThread *thread, const DynamicValue &value);
- MiniscriptInstructionOutcome scriptSetMuteTrack(MiniscriptThread *thread, const DynamicValue &value);
-
- MiniscriptInstructionOutcome scriptSetMuteTrackIndexed(MiniscriptThread *thread, size_t trackIndex, bool muted);
-
- uint getBoostedVolume(Runtime *runtime) const;
-
- void playSingleNote();
- void stopSingleNote();
-
- struct FilePart {
- bool loop;
- bool overrideTempo;
- double tempo;
- double fadeIn;
- double fadeOut;
- };
-
- struct SingleNotePart {
- uint8 channel;
- uint8 note;
- uint8 velocity;
- uint8 program;
- double duration;
- };
-
- union ModeSpecificUnion {
- FilePart file;
- SingleNotePart singleNote;
- };
-
- enum Mode {
- kModeFile,
- kModeSingleNote,
- };
-
- Event _executeWhen;
- Event _terminateWhen;
-
- Mode _mode;
- ModeSpecificUnion _modeSpecific;
- uint8 _volume; // We need this always available because scripts will try to set it and then read it even in single note mode
-
- Common::SharedPtr<Data::Standard::MidiModifier::EmbeddedFile> _embeddedFile;
-
- uint16 _mutedTracks;
-
- StandardPlugIn *_plugIn;
- MidiFilePlayer *_filePlayer;
- MidiNotePlayer *_notePlayer;
-};
-
class ListVariableStorage : public VariableStorage {
public:
friend class ListVariableModifier;
@@ -440,7 +346,7 @@ private:
class StandardPlugIn : public MTropolis::PlugIn {
public:
- explicit StandardPlugIn(bool useDynamicMidi);
+ StandardPlugIn();
~StandardPlugIn();
void registerModifiers(IPlugInModifierRegistrar *registrar) const override;
@@ -448,19 +354,15 @@ public:
const StandardPlugInHacks &getHacks() const;
StandardPlugInHacks &getHacks();
- MultiMidiPlayer *getMidi() const;
-
private:
PlugInModifierFactory<CursorModifier, Data::Standard::CursorModifier> _cursorModifierFactory;
PlugInModifierFactory<STransCtModifier, Data::Standard::STransCtModifier> _sTransCtModifierFactory;
PlugInModifierFactory<MediaCueMessengerModifier, Data::Standard::MediaCueMessengerModifier> _mediaCueModifierFactory;
PlugInModifierFactory<ObjectReferenceVariableModifier, Data::Standard::ObjectReferenceVariableModifier> _objRefVarModifierFactory;
- PlugInModifierFactory<MidiModifier, Data::Standard::MidiModifier> _midiModifierFactory;
PlugInModifierFactory<ListVariableModifier, Data::Standard::ListVariableModifier> _listVarModifierFactory;
PlugInModifierFactory<SysInfoModifier, Data::Standard::SysInfoModifier> _sysInfoModifierFactory;
PlugInModifierFactory<PanningModifier, Data::Standard::PanningModifier> _panningModifierFactory;
- Common::SharedPtr<MultiMidiPlayer> _midi;
StandardPlugInHacks _hacks;
};
diff --git a/engines/mtropolis/plugin/standard_data.cpp b/engines/mtropolis/plugin/standard_data.cpp
index b7ee40404a5..998059b89c6 100644
--- a/engines/mtropolis/plugin/standard_data.cpp
+++ b/engines/mtropolis/plugin/standard_data.cpp
@@ -107,47 +107,6 @@ DataReadErrorCode ObjectReferenceVariableModifier::load(PlugIn &plugIn, const Pl
return kDataReadErrorNone;
}
-MidiModifier::MidiModifier() : embeddedFlag(0) {
- memset(&this->modeSpecific, 0, sizeof(this->modeSpecific));
-}
-
-DataReadErrorCode MidiModifier::load(PlugIn &plugIn, const PlugInModifier &prefix, DataReader &reader) {
- if (prefix.plugInRevision != 1 && prefix.plugInRevision != 2)
- return kDataReadErrorUnsupportedRevision;
-
- if (!executeWhen.load(reader) || !terminateWhen.load(reader) || !reader.readU8(embeddedFlag))
- return kDataReadErrorReadFailed;
-
- if (embeddedFlag) {
- if (!reader.readU8(modeSpecific.embedded.hasFile))
- return kDataReadErrorReadFailed;
- if (modeSpecific.embedded.hasFile) {
- embeddedFile = Common::SharedPtr<EmbeddedFile>(new EmbeddedFile());
-
- uint8 bigEndianLength[4];
- if (!reader.readBytes(bigEndianLength))
- return kDataReadErrorReadFailed;
-
- uint32 length = (bigEndianLength[0] << 24) + (bigEndianLength[1] << 16) + (bigEndianLength[2] << 8) + bigEndianLength[3];
-
- embeddedFile->contents.resize(length);
- if (length > 0 && !reader.read(&embeddedFile->contents[0], length))
- return kDataReadErrorReadFailed;
- }
-
- if (!reader.readU8(modeSpecific.embedded.loop) || !reader.readU8(modeSpecific.embedded.overrideTempo)
- || !reader.readU8(modeSpecific.embedded.volume) || !embeddedTempo.load(reader)
- || !embeddedFadeIn.load(reader) || !embeddedFadeOut.load(reader))
- return kDataReadErrorReadFailed;
- } else {
- if (!reader.readU8(modeSpecific.singleNote.channel) || !reader.readU8(modeSpecific.singleNote.note) || !reader.readU8(modeSpecific.singleNote.velocity)
- || !reader.readU8(modeSpecific.singleNote.program) || !singleNoteDuration.load(reader))
- return kDataReadErrorReadFailed;
- }
-
- return kDataReadErrorNone;
-}
-
ListVariableModifier::ListVariableModifier() : unknown1(0), contentsType(0), unknown2{0, 0, 0, 0},
havePersistentData(false), numValues(0), values(nullptr), persistentValuesGarbled(false) {
}
diff --git a/engines/mtropolis/plugin/standard_data.h b/engines/mtropolis/plugin/standard_data.h
index 14620418b8a..ab17f31816b 100644
--- a/engines/mtropolis/plugin/standard_data.h
+++ b/engines/mtropolis/plugin/standard_data.h
@@ -96,49 +96,6 @@ protected:
DataReadErrorCode load(PlugIn &plugIn, const PlugInModifier &prefix, DataReader &reader) override;
};
-struct MidiModifier : public PlugInModifierData {
- struct EmbeddedFile {
- Common::Array<uint8> contents;
- };
-
- struct EmbeddedPart {
- uint8 hasFile;
- uint8 loop;
- uint8 overrideTempo;
- uint8 volume;
- };
-
- struct SingleNotePart {
- uint8 channel;
- uint8 note;
- uint8 velocity;
- uint8 program;
- };
-
- union ModeSpecificUnion {
- EmbeddedPart embedded;
- SingleNotePart singleNote;
- };
-
- MidiModifier();
-
- PlugInTypeTaggedValue executeWhen;
- PlugInTypeTaggedValue terminateWhen;
-
- uint8 embeddedFlag;
- ModeSpecificUnion modeSpecific;
-
- PlugInTypeTaggedValue embeddedTempo; // Float
- PlugInTypeTaggedValue embeddedFadeIn; // Float
- PlugInTypeTaggedValue embeddedFadeOut; // Float
- PlugInTypeTaggedValue singleNoteDuration; // Float
-
- Common::SharedPtr<EmbeddedFile> embeddedFile;
-
-protected:
- DataReadErrorCode load(PlugIn &plugIn, const PlugInModifier &prefix, DataReader &reader) override;
-};
-
struct ListVariableModifier : public PlugInModifierData {
enum ContentsType {
kContentsTypeInteger = 1,
diff --git a/engines/mtropolis/plugins.h b/engines/mtropolis/plugins.h
index 484cdbf6027..415c42d620a 100644
--- a/engines/mtropolis/plugins.h
+++ b/engines/mtropolis/plugins.h
@@ -38,6 +38,7 @@ class PlugIn;
namespace PlugIns {
+Common::SharedPtr<PlugIn> createMIDI();
Common::SharedPtr<PlugIn> createStandard();
Common::SharedPtr<PlugIn> createObsidian(const Common::SharedPtr<Obsidian::WordGameData> &wgData);
Common::SharedPtr<PlugIn> createMTI();
More information about the Scummvm-git-logs
mailing list