[Scummvm-git-logs] scummvm master -> 38eff6c7c3921e8e55c89e40f61bfba383dd09ff
NMIError
noreply at scummvm.org
Mon May 16 11:25:45 UTC 2022
This automated email contains information about 4 new commits which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .
Summary:
021a828e2e AUDIO: Decouple CMS emulator from engines
07a6b54f54 AGOS: Add CMS support for Elvira 1
287490fe0b AGOS: Fix some Elvira 1 tracks not looping
38eff6c7c3 AGOS: Add OPL3 mode for Elvira 1
Commit: 021a828e2ecef816c2079bfcb99aa8d6e6095c47
https://github.com/scummvm/scummvm/commit/021a828e2ecef816c2079bfcb99aa8d6e6095c47
Author: Coen Rampen (crampen at gmail.com)
Date: 2022-05-16T12:18:10+02:00
Commit Message:
AUDIO: Decouple CMS emulator from engines
This commit adds a mediator class to decouple the CMS emulator from the engines
that use it. This allows for easier updating or replacement of the emulator or
addition of new emulators. It also enables using the emulator via a callback
mechanism.
Changed paths:
A audio/cms.cpp
A audio/cms.h
audio/module.mk
audio/softsynth/cms.cpp
audio/softsynth/cms.h
diff --git a/audio/cms.cpp b/audio/cms.cpp
new file mode 100644
index 00000000000..64dc2d359f2
--- /dev/null
+++ b/audio/cms.cpp
@@ -0,0 +1,162 @@
+/* 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 "audio/cms.h"
+
+namespace CMS {
+
+CMS *Config::create() {
+ // For now this is fixed to the DOSBox emulator.
+ return new DOSBoxCMS();
+}
+
+bool CMS::_hasInstance = false;
+
+CMS::CMS() {
+ if (_hasInstance)
+ error("There are multiple CMS output instances running.");
+ _hasInstance = true;
+}
+
+CMS::~CMS() {
+ _hasInstance = false;
+}
+
+void CMS::start(TimerCallback *callback, int timerFrequency) {
+ _callback.reset(callback);
+ startCallbacks(timerFrequency);
+}
+
+void CMS::stop() {
+ stopCallbacks();
+ _callback.reset();
+}
+
+EmulatedCMS::EmulatedCMS() :
+ _nextTick(0),
+ _samplesPerTick(0),
+ _baseFreq(0),
+ _handle(new Audio::SoundHandle()) { }
+
+EmulatedCMS::~EmulatedCMS() {
+ // Stop callbacks, just in case. If it's still playing at this
+ // point, there's probably a bigger issue, though. The subclass
+ // needs to call stop() or the pointer can still use be used in
+ // the mixer thread at the same time.
+ stop();
+
+ delete _handle;
+}
+
+int EmulatedCMS::readBuffer(int16 *buffer, const int numSamples) {
+ int len = numSamples / 2;
+ int step;
+
+ do {
+ step = len;
+ if (step > (_nextTick >> FIXP_SHIFT))
+ step = (_nextTick >> FIXP_SHIFT);
+
+ generateSamples(buffer, step);
+
+ _nextTick -= step << FIXP_SHIFT;
+ if (!(_nextTick >> FIXP_SHIFT)) {
+ if (_callback && _callback->isValid())
+ (*_callback)();
+
+ _nextTick += _samplesPerTick;
+ }
+
+ buffer += step * 2;
+ len -= step;
+ } while (len);
+
+ return numSamples;
+}
+
+int EmulatedCMS::getRate() const {
+ return g_system->getMixer()->getOutputRate();
+}
+
+bool EmulatedCMS::endOfData() const {
+ return false;
+}
+
+bool EmulatedCMS::isStereo() const {
+ return true;
+}
+
+void EmulatedCMS::startCallbacks(int timerFrequency) {
+ setCallbackFrequency(timerFrequency);
+ g_system->getMixer()->playStream(Audio::Mixer::kMusicSoundType, _handle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
+}
+
+void EmulatedCMS::stopCallbacks() {
+ g_system->getMixer()->stopHandle(*_handle);
+}
+
+void EmulatedCMS::setCallbackFrequency(int timerFrequency) {
+ _baseFreq = timerFrequency;
+ assert(_baseFreq != 0);
+
+ int d = getRate() / _baseFreq;
+ int r = getRate() % _baseFreq;
+
+ // This is equivalent to (getRate() << FIXP_SHIFT) / BASE_FREQ
+ // but less prone to arithmetic overflow.
+
+ _samplesPerTick = (d << FIXP_SHIFT) + (r << FIXP_SHIFT) / _baseFreq;
+}
+
+DOSBoxCMS::DOSBoxCMS() : _cms(nullptr) { }
+
+DOSBoxCMS::~DOSBoxCMS() {
+ if (_cms)
+ delete _cms;
+}
+
+bool DOSBoxCMS::init() {
+ _cms = new CMSEmulator(getRate());
+ return _cms != nullptr;
+}
+
+void DOSBoxCMS::reset() {
+ _cms->reset();
+}
+
+void DOSBoxCMS::write(int a, int v) {
+ _cms->portWrite(a, v);
+}
+
+void DOSBoxCMS::writeReg(int r, int v) {
+ int address = 0x220;
+ if (r >= 0x100)
+ address += 0x002;
+
+ _cms->portWrite(address + 1, r & 0x1F);
+ _cms->portWrite(address, v);
+}
+
+void DOSBoxCMS::generateSamples(int16 *buffer, int numSamples) {
+ _cms->readBuffer(buffer, numSamples);
+}
+
+} // End of namespace CMS
diff --git a/audio/cms.h b/audio/cms.h
new file mode 100644
index 00000000000..fce144646f4
--- /dev/null
+++ b/audio/cms.h
@@ -0,0 +1,194 @@
+/* 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 AUDIO_CMS_H
+#define AUDIO_CMS_H
+
+#include "common/func.h"
+#include "common/ptr.h"
+
+#include "audio/audiostream.h"
+#include "audio/mixer.h"
+#include "audio/softsynth/cms.h"
+
+namespace CMS {
+
+class CMS;
+
+class Config {
+public:
+ /**
+ * Creates a CMS driver.
+ */
+ static CMS *create();
+};
+
+/**
+ * The type of the CMS timer callback functor.
+ */
+typedef Common::Functor0<void> TimerCallback;
+
+class CMS {
+private:
+ static bool _hasInstance;
+
+public:
+ // The default number of timer callbacks per second.
+ static const int DEFAULT_CALLBACK_FREQUENCY = 250;
+
+ CMS();
+ virtual ~CMS();
+
+ /**
+ * Initializes the CMS emulator.
+ *
+ * @return true on success, false on failure
+ */
+ virtual bool init() = 0;
+
+ /**
+ * Reinitializes the CMS emulator
+ */
+ virtual void reset() = 0;
+
+ /**
+ * Writes a byte to the given I/O port. CMS responds to 2 sets of 2 ports:
+ * 0x220/0x222 - value for the 1st/2nd chip (channels 0-5/6-B)
+ * 0x221/0x223 - register for the 1st/2nd chip
+ *
+ * @param a port address
+ * @param v value, which will be written
+ */
+ virtual void write(int a, int v) = 0;
+
+ /**
+ * Function to directly write to a specific CMS register. We allow writing
+ * to secondary CMS chip registers by using register values >= 0x100.
+ *
+ * @param r hardware register number to write to
+ * @param v value, which will be written
+ */
+ virtual void writeReg(int r, int v) = 0;
+
+ /**
+ * Start the CMS with callbacks.
+ */
+ void start(TimerCallback *callback, int timerFrequency = DEFAULT_CALLBACK_FREQUENCY);
+
+ /**
+ * Stop the CMS
+ */
+ void stop();
+
+ /**
+ * Change the callback frequency. This must only be called from a
+ * timer proc.
+ */
+ virtual void setCallbackFrequency(int timerFrequency) = 0;
+
+protected:
+ /**
+ * Start the callbacks.
+ */
+ virtual void startCallbacks(int timerFrequency) = 0;
+
+ /**
+ * Stop the callbacks.
+ */
+ virtual void stopCallbacks() = 0;
+
+ /**
+ * The functor for callbacks.
+ */
+ Common::ScopedPtr<TimerCallback> _callback;
+};
+
+/**
+ * A CMS that represents an emulated CMS.
+ *
+ * This will send callbacks based on the number of samples
+ * decoded in readBuffer().
+ */
+class EmulatedCMS : public CMS, protected Audio::AudioStream {
+protected:
+ static const int FIXP_SHIFT = 16;
+
+public:
+ EmulatedCMS();
+ virtual ~EmulatedCMS();
+
+ // CMS API
+ void setCallbackFrequency(int timerFrequency) override;
+
+ // AudioStream API
+ int readBuffer(int16 *buffer, const int numSamples) override;
+ int getRate() const override;
+ bool endOfData() const override;
+ bool isStereo() const override;
+
+protected:
+ // CMS API
+ void startCallbacks(int timerFrequency) override;
+ void stopCallbacks() override;
+
+ /**
+ * Read up to 'length' samples.
+ *
+ * Data will be in native endianess, 16 bit per sample, signed. buffer will
+ * be filled with interleaved left and right channel samples, starting with
+ * a left sample. The requested number of samples is stereo samples, so if
+ * you request 2 samples, you will get a total of two left channel and two
+ * right channel samples.
+ */
+ virtual void generateSamples(int16 *buffer, int numSamples) = 0;
+
+private:
+ int _baseFreq;
+
+ int _nextTick;
+ int _samplesPerTick;
+
+ Audio::SoundHandle *_handle;
+};
+
+/**
+ * A CMS that represents the DOSBox CMS emulator.
+ */
+class DOSBoxCMS : public EmulatedCMS {
+public:
+ DOSBoxCMS();
+ ~DOSBoxCMS() override;
+
+ bool init() override;
+ void reset() override;
+ void write(int a, int v) override;
+ void writeReg(int r, int v) override;
+
+protected:
+ void generateSamples(int16 *buffer, int numSamples) override;
+
+private:
+ CMSEmulator *_cms;
+};
+
+} // End of namespace CMS
+
+#endif
diff --git a/audio/module.mk b/audio/module.mk
index 8519db4438c..5f670ab18ef 100644
--- a/audio/module.mk
+++ b/audio/module.mk
@@ -5,6 +5,7 @@ MODULE_OBJS := \
adlib_ms.o \
audiostream.o \
casio.o \
+ cms.o \
fmopl.o \
mididrv.o \
mididrv_ms.o \
diff --git a/audio/softsynth/cms.cpp b/audio/softsynth/cms.cpp
index 4426b3b8aca..1c390153270 100644
--- a/audio/softsynth/cms.cpp
+++ b/audio/softsynth/cms.cpp
@@ -124,6 +124,10 @@ void CMSEmulator::readBuffer(int16 *buffer, const int numSamples) {
update(1, &buffer[0], numSamples);
}
+void CMSEmulator::reset() {
+ memset(_saa1099, 0, sizeof(SAA1099) * 2);
+}
+
void CMSEmulator::envelope(int chip, int ch) {
SAA1099 *saa = &_saa1099[chip];
if (saa->env_enable[ch]) {
diff --git a/audio/softsynth/cms.h b/audio/softsynth/cms.h
index 7acdd01543b..950c272b6c2 100644
--- a/audio/softsynth/cms.h
+++ b/audio/softsynth/cms.h
@@ -77,6 +77,8 @@ public:
void portWrite(int port, int val);
void readBuffer(int16 *buffer, const int numSamples);
+ void reset();
+
private:
uint32 _sampleRate;
Commit: 07a6b54f54c12acfda1fe952e89cced81193e0a6
https://github.com/scummvm/scummvm/commit/07a6b54f54c12acfda1fe952e89cced81193e0a6
Author: Coen Rampen (crampen at gmail.com)
Date: 2022-05-16T12:18:11+02:00
Commit Message:
AGOS: Add CMS support for Elvira 1
This adds support for the Creative Music System / Gameblaster to Elvira 1.
Changed paths:
A engines/agos/drivers/accolade/cms.cpp
A engines/agos/drivers/accolade/cms.h
engines/agos/midi.cpp
engines/agos/module.mk
diff --git a/engines/agos/drivers/accolade/cms.cpp b/engines/agos/drivers/accolade/cms.cpp
new file mode 100644
index 00000000000..96640437215
--- /dev/null
+++ b/engines/agos/drivers/accolade/cms.cpp
@@ -0,0 +1,263 @@
+/* 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 "agos/drivers/accolade/cms.h"
+
+#include "common/debug.h"
+
+namespace AGOS {
+
+// Hardcoded in RUNVGA.EXE.
+const byte MidiDriver_Accolade_Cms::CMS_NOTE_FREQUENCIES[] = {
+ 0x03, 0x1F, 0x3A, 0x53, 0x6B, 0x82,
+ 0x97, 0xAC, 0xBF, 0xD1, 0xE2, 0xF2
+};
+
+// Hardcoded in RUNVGA.EXE; probably also used for Tandy.
+const byte MidiDriver_Accolade_Cms::CMS_VOLUME_ADJUSTMENTS[] = {
+ 0xF7, 0xFD, 0xFB, 0x00, 0xEC, 0xEE, 0x00, 0x1E, 0x08, 0x07, 0x02, 0x03, 0x08, 0x0A, 0x00, 0x00,
+ 0xF6, 0xF4, 0xEF, 0x05, 0xFB, 0xFC, 0xF5, 0xF5, 0xF6, 0x28, 0x00, 0xEF, 0x00, 0x00, 0x00, 0x00,
+ 0x29, 0x03, 0xE9, 0x00, 0xF5, 0x50, 0xF9, 0xF9, 0xF2, 0x1E, 0xFE, 0x0B, 0xF7, 0xF7, 0xF5, 0xF8,
+ 0xFD, 0xFD, 0xFD, 0xE4, 0xED, 0xFB, 0xFE, 0xFB, 0xD1, 0x1F, 0x00, 0xF1, 0xF1, 0xF6, 0x00, 0x0A,
+ 0xEE, 0xDB, 0x0E, 0xE5, 0x0B, 0x00, 0x0A, 0x0D, 0x03, 0x06, 0xF3, 0xF5, 0x1E, 0x1E, 0x0A, 0xFA,
+ 0xF8, 0xF9, 0x0A, 0x00, 0xFA, 0xFA, 0xF9, 0x00, 0xF2, 0xF9, 0x00, 0xF9, 0x00, 0x04, 0xF8, 0xF7,
+ 0xFC, 0xF1, 0x0A, 0xF1, 0x20, 0x20, 0xF7, 0xF2, 0xF6, 0xF7, 0xFA, 0xFF, 0x20, 0x20, 0x00, 0x00,
+ 0xEA, 0x15, 0x00, 0xFB, 0x0E, 0x00, 0x14, 0x12, 0x00, 0xE2, 0xF7, 0xFB, 0x00, 0x00, 0xF1, 0xFF
+};
+
+MidiDriver_Accolade_Cms::MidiDriver_Accolade_Cms() : _cms(nullptr), _isOpen(false), _timer_proc(nullptr), _timer_param(nullptr) {
+ Common::fill(_instruments, _instruments + ARRAYSIZE(_instruments), 0);
+ Common::fill(_activeNotes, _activeNotes + ARRAYSIZE(_activeNotes), 0xFF);
+ Common::fill(_octaveRegisterValues, _octaveRegisterValues + ARRAYSIZE(_octaveRegisterValues), 0);
+}
+
+MidiDriver_Accolade_Cms::~MidiDriver_Accolade_Cms() {
+ if (_isOpen)
+ close();
+}
+
+int MidiDriver_Accolade_Cms::open() {
+ if (_isOpen)
+ return MERR_ALREADY_OPEN;
+
+ _cms = CMS::Config::create();
+ if (!_cms || !_cms->init())
+ return MERR_CANNOT_CONNECT;
+
+ _isOpen = true;
+
+ cmsInit();
+
+ _cms->start(new Common::Functor0Mem<void, MidiDriver_Accolade_Cms>(this, &MidiDriver_Accolade_Cms::onTimer));
+
+ return 0;
+}
+
+void MidiDriver_Accolade_Cms::close() {
+ if (_cms) {
+ _cms->stop();
+ delete _cms;
+ _cms = nullptr;
+ }
+ _isOpen = false;
+}
+
+bool MidiDriver_Accolade_Cms::isOpen() const {
+ return _isOpen;
+}
+
+uint32 MidiDriver_Accolade_Cms::getBaseTempo() {
+ return 1000000 / CMS::CMS::DEFAULT_CALLBACK_FREQUENCY;
+}
+
+MidiChannel *MidiDriver_Accolade_Cms::allocateChannel() {
+ return nullptr;
+}
+
+MidiChannel *MidiDriver_Accolade_Cms::getPercussionChannel() {
+ return nullptr;
+}
+
+void MidiDriver_Accolade_Cms::cmsInit() {
+ for (int i = 0; i < 2; i++) {
+ int chipOffset = i * 0x100;
+ // Set registers 0x00 - 0x19 to 0 (note that this includes a few unused
+ // registers).
+ for (int j = 0; j <= 0x19; j++) {
+ writeRegister(j + chipOffset, 0);
+ }
+ // Frequency reset.
+ writeRegister(REGISTER_RESET_SOUND_ENABLE + chipOffset, 0x02);
+ // Sound enable.
+ writeRegister(REGISTER_RESET_SOUND_ENABLE + chipOffset, 0x01);
+ }
+}
+
+void MidiDriver_Accolade_Cms::send(uint32 b) {
+ byte channel = b & 0x0F;
+
+ // WORKAROUND This check is bugged in the original interpreter. The channel
+ // is first resolved through the channel mapping, which is 1 on 1 for
+ // channels 0-5, and the default FF for the other channels, which means
+ // they should not be played. Next, the mapped channel is checked against
+ // the maximum channels for the device, >= 6. However, this check is
+ // signed, which means FF is interpreted as -1, and it passes the check.
+ // This comes into play when drums are played on channel 9. The code for
+ // handling events does not expect channel FF and this results in writes to
+ // invalid registers and interference with notes on the other channels.
+ // This check is fixed here to restore the intended behavior.
+ // Note that this does result in an extended period of silence during the
+ // intro track where only drums are played.
+ if (channel >= 6)
+ return;
+
+ byte command = b & 0xF0;
+ byte op1 = (b >> 8) & 0xFF;
+ byte op2 = (b >> 16) & 0xFF;
+
+ switch (command) {
+ case MIDI_COMMAND_NOTE_OFF:
+ noteOff(channel, op1);
+ break;
+ case MIDI_COMMAND_NOTE_ON:
+ noteOn(channel, op1, op2);
+ break;
+ case MIDI_COMMAND_CONTROL_CHANGE:
+ controlChange(channel, op1, op2);
+ break;
+ case MIDI_COMMAND_PROGRAM_CHANGE:
+ programChange(channel, op1);
+ break;
+ default:
+ // Other MIDI events are ignored.
+ break;
+ }
+}
+
+void MidiDriver_Accolade_Cms::noteOff(uint8 channel, uint8 note) {
+ if (_activeNotes[channel] != note)
+ return;
+
+ // Remove the note from the active note registry.
+ _activeNotes[channel] = 0xFF;
+
+ // Turn off the frequency enable bit for the channel.
+ byte freqEnableRegValue = determineFrequencyEnableRegisterValue();
+ writeRegister(REGISTER_FREQUENCY_ENABLE, freqEnableRegValue);
+}
+
+void MidiDriver_Accolade_Cms::noteOn(uint8 channel, uint8 note, uint8 velocity) {
+ if (velocity == 0) {
+ // Note on with velocity 0 is a note off.
+ noteOff(channel, note);
+ return;
+ }
+
+ // Add the note to the active note registry.
+ _activeNotes[channel] = note;
+
+ // Add octaves to bring the note up into the 0x15 - 0x7F range, then lower
+ // the note with 0x15.
+ while (note <= 0x15)
+ note += 0xC;
+ note -= 0x15;
+
+ // Determine the octave and note within the octave.
+ byte octave = note / 0xC;
+ byte octaveNote = note % 0xC;
+
+ // Apply the volume adjustment for the current instrument on the channel.
+ int8 volumeAdjustment = static_cast<int8>(CMS_VOLUME_ADJUSTMENTS[_instruments[channel]]);
+ uint8 volume = CLIP(velocity + volumeAdjustment, 0, 0x7F);
+ // Calculate and write the amplitude register value.
+ byte amplitude = volume >> 4;
+ writeRegister(REGISTER_BASE_AMPLITUDE + channel, amplitude | (amplitude << 4));
+
+ // Look up and write the note frequecy.
+ byte frequency = CMS_NOTE_FREQUENCIES[octaveNote];
+ writeRegister(REGISTER_BASE_FREQUENCY + channel, frequency);
+
+ // An octave register contains the octaves for 2 channels.
+ // Get the current value of the register containing the octave value for
+ // this channel.
+ byte octaveRegisterValue = _octaveRegisterValues[channel / 2];
+ // Clear the bits containing the octave value for this channel.
+ if (channel & 1) {
+ // Octave is in the upper nibble.
+ octave <<= 4;
+ octaveRegisterValue &= 0x0F;
+ } else {
+ // Octave is in the lower nibble.
+ octaveRegisterValue &= 0xF0;
+ }
+ // Set and write the new octave value.
+ octaveRegisterValue |= octave;
+ _octaveRegisterValues[channel / 2] = octaveRegisterValue;
+ writeRegister(REGISTER_BASE_OCTAVE + (channel / 2), octaveRegisterValue);
+
+ // Turn on the frequency enable bit for the channel.
+ byte freqEnableRegValue = determineFrequencyEnableRegisterValue();
+ writeRegister(REGISTER_FREQUENCY_ENABLE, freqEnableRegValue);
+}
+
+void MidiDriver_Accolade_Cms::programChange(uint8 channel, uint8 instrument) {
+ // Just keep track of the current instrument.
+ _instruments[channel] = instrument;
+}
+
+void MidiDriver_Accolade_Cms::controlChange(uint8 channel, uint8 controller, uint8 value) {
+ // Only All Note Off is processed; it will turn off the active note on this
+ // channel (if there is one).
+ if (controller != MIDI_CONTROLLER_ALL_NOTES_OFF || _activeNotes[channel] == 0xFF)
+ return;
+
+ noteOff(channel, _activeNotes[channel]);
+}
+
+byte MidiDriver_Accolade_Cms::determineFrequencyEnableRegisterValue() {
+ byte freqEnableRegValue = 0;
+ for (int i = 0; i < 6; i++) {
+ // If a note is active on a channel, set the frequency enable bit for
+ // that channel.
+ if (_activeNotes[i] != 0xFF)
+ freqEnableRegValue |= 1 << i;
+ }
+
+ return freqEnableRegValue;
+}
+
+void MidiDriver_Accolade_Cms::writeRegister(uint16 reg, uint8 value) {
+ //debug("Writing register %02X %02X", reg, value);
+
+ _cms->writeReg(reg, value);
+}
+
+void MidiDriver_Accolade_Cms::setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) {
+ _timer_param = timer_param;
+ _timer_proc = timer_proc;
+}
+
+void MidiDriver_Accolade_Cms::onTimer() {
+ if (_timer_proc && _timer_param)
+ _timer_proc(_timer_param);
+}
+
+}
diff --git a/engines/agos/drivers/accolade/cms.h b/engines/agos/drivers/accolade/cms.h
new file mode 100644
index 00000000000..0614549d89a
--- /dev/null
+++ b/engines/agos/drivers/accolade/cms.h
@@ -0,0 +1,98 @@
+/* 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 AGOS_DRIVERS_ACCOLADE_CMS_H
+#define AGOS_DRIVERS_ACCOLADE_CMS_H
+
+#include "audio/mididrv.h"
+
+#include "audio/cms.h"
+
+namespace AGOS {
+
+// MIDI driver for the Creative Music System / Gameblaster.
+// This driver uses only 6 of the available 12 channels and does not support
+// stereo. It only supports note on and off and program change; the selected
+// instrument only affects the note volume.
+class MidiDriver_Accolade_Cms : public MidiDriver {
+protected:
+ static const byte REGISTER_BASE_AMPLITUDE = 0x00;
+ static const byte REGISTER_BASE_FREQUENCY = 0x08;
+ static const byte REGISTER_BASE_OCTAVE = 0x10;
+ static const byte REGISTER_FREQUENCY_ENABLE = 0x14;
+ static const byte REGISTER_RESET_SOUND_ENABLE = 0x1C;
+
+ // Frequency register values for octave notes.
+ static const byte CMS_NOTE_FREQUENCIES[12];
+ // Volume adjustments for all instruments.
+ static const byte CMS_VOLUME_ADJUSTMENTS[128];
+
+public:
+ MidiDriver_Accolade_Cms();
+ ~MidiDriver_Accolade_Cms() override;
+
+ int open() override;
+ void close() override;
+ bool isOpen() const override;
+ uint32 getBaseTempo() override;
+ // This driver does not support MidiChannel objects.
+ MidiChannel *allocateChannel() override;
+ // This driver does not support MidiChannel objects.
+ MidiChannel *getPercussionChannel() override;
+
+ using MidiDriver::send;
+ void send(uint32 b) override;
+
+ void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) override;
+ void onTimer();
+
+protected:
+ void cmsInit();
+
+ void noteOff(uint8 channel, uint8 note);
+ void noteOn(uint8 channel, uint8 note, uint8 velocity);
+ void programChange(uint8 channel, uint8 instrument);
+ void controlChange(uint8 channel, uint8 controller, uint8 value);
+
+ // Returns the value for the frequency enable register of the first CMS
+ // chip based on the _activeNotes array (if a note is active, the bit for
+ // the corresponding channel is set).
+ byte determineFrequencyEnableRegisterValue();
+ void writeRegister(uint16 reg, uint8 value);
+
+ CMS::CMS *_cms;
+ bool _isOpen;
+
+ // The selected instrument on each MIDI channel.
+ byte _instruments[16];
+ // The active note on each CMS channel (0xFF if no note is active).
+ byte _activeNotes[12];
+ // The current values of the CMS octave registers (0x10 - 0x12).
+ byte _octaveRegisterValues[6];
+
+ // External timer callback
+ void *_timer_param;
+ Common::TimerManager::TimerProc _timer_proc;
+};
+
+} // End of namespace AGOS
+
+#endif
diff --git a/engines/agos/midi.cpp b/engines/agos/midi.cpp
index acc3ddcf718..360f3ad8fc3 100644
--- a/engines/agos/midi.cpp
+++ b/engines/agos/midi.cpp
@@ -32,6 +32,7 @@
#include "agos/drivers/accolade/mididriver.h"
#include "agos/drivers/accolade/adlib.h"
+#include "agos/drivers/accolade/cms.h"
#include "agos/drivers/accolade/mt32.h"
#include "agos/drivers/simon1/adlib.h"
// Miles Audio for Simon 2
@@ -146,6 +147,9 @@ int MidiPlayer::open() {
// instruments compared to the MT-32 tracks, so GM is preferred.
int devFlags = MDT_MIDI | (_pc98 ? MDT_PC98 : MDT_ADLIB) |
(_vm->getGameType() == GType_SIMON2 ? MDT_PREFER_GM : MDT_PREFER_MT32);
+ if (_vm->getGameType() == GType_ELVIRA1 && _vm->getPlatform() == Common::kPlatformDOS)
+ // CMS is only supported by Elvira 1 DOS.
+ devFlags |= MDT_CMS;
// Check the type of device that the user has configured.
MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(devFlags);
@@ -257,11 +261,19 @@ int MidiPlayer::open() {
// Casio devices are supported by Elvira 1 DOS only.
_driverMsMusic = MidiDriver_Accolade_Casio_create(accoladeDriverFilename);
break;
+ case MT_CMS:
+ // CMS is supported by Elvira 1 DOS only.
+ if (_vm->getGameType() == GType_ELVIRA1 && _vm->getPlatform() == Common::kPlatformDOS) {
+ _driver = new MidiDriver_Accolade_Cms();
+ break;
+ }
+ // fall through
default:
_driverMsMusic = new MidiDriver_NULL_Multisource();
break;
}
- _driver = _driverMsMusic;
+ if (!_driver)
+ _driver = _driverMsMusic;
// These games use the MUS MIDI format used by several Accolade games.
// The Elvira 2 / Waxworks version of the AdLib driver has a mechanism
diff --git a/engines/agos/module.mk b/engines/agos/module.mk
index fc68522b55d..37432e1edde 100644
--- a/engines/agos/module.mk
+++ b/engines/agos/module.mk
@@ -3,6 +3,7 @@ MODULE := engines/agos
MODULE_OBJS := \
drivers/accolade/adlib.o \
drivers/accolade/casio.o \
+ drivers/accolade/cms.o \
drivers/accolade/driverfile.o \
drivers/accolade/pc98.o \
drivers/accolade/mt32.o \
Commit: 287490fe0b31f869a989b3ed28ef32476c9357f8
https://github.com/scummvm/scummvm/commit/287490fe0b31f869a989b3ed28ef32476c9357f8
Author: Coen Rampen (crampen at gmail.com)
Date: 2022-05-16T12:18:11+02:00
Commit Message:
AGOS: Fix some Elvira 1 tracks not looping
Some Elvira 1 music tracks would not loop properly. These tracks make use of
the loop event, which stores the position in the MIDI stream where the parser
will later loop to. An event is optionally preceded by a delta, as indicated by
the _noDelta property of the parser. However, when looping, this flag was not
set to the state matching the event after the loop point. This would sometimes
cause the parser to interpret a MIDI event as a delta, which would break the
parsing.
This is fixed by storing the _noDelta flag with the loop data and setting it
to the stored value when looping.
Changed paths:
engines/agos/midiparser_s1d.cpp
diff --git a/engines/agos/midiparser_s1d.cpp b/engines/agos/midiparser_s1d.cpp
index ede991aa038..c3fedff15bd 100644
--- a/engines/agos/midiparser_s1d.cpp
+++ b/engines/agos/midiparser_s1d.cpp
@@ -39,6 +39,7 @@ private:
struct Loop {
uint16 timer;
byte *start, *end;
+ bool noDelta;
} _loops[16];
// Data for monophonic chords mode.
@@ -65,7 +66,7 @@ protected:
public:
MidiParser_S1D(uint8 source = 0, bool monophonicChords = false) : MidiParser(source),
_monophonicChords(monophonicChords), _data(nullptr), _noDelta(false) {
- Common::fill(_loops, _loops + ARRAYSIZE(_loops), Loop { 0, 0, 0 });
+ Common::fill(_loops, _loops + ARRAYSIZE(_loops), Loop { 0, 0, 0, false });
Common::fill(_highestNote, _highestNote + ARRAYSIZE(_highestNote), 0);
Common::fill(_lastPlayedNoteTime, _lastPlayedNoteTime + ARRAYSIZE(_lastPlayedNoteTime), 0);
}
@@ -133,6 +134,7 @@ void MidiParser_S1D::parseNextEvent(EventInfo &info) {
const int16 loopIterations = int8(*_position._playPos++);
if (!loopIterations) {
_loops[info.channel()].start = _position._playPos;
+ _loops[info.channel()].noDelta = _noDelta;
} else {
if (!_loops[info.channel()].timer) {
if (_loops[info.channel()].start) {
@@ -141,11 +143,13 @@ void MidiParser_S1D::parseNextEvent(EventInfo &info) {
// Go to the start of the loop
_position._playPos = _loops[info.channel()].start;
+ _noDelta = _loops[info.channel()].noDelta;
info.loop = true;
}
} else {
if (_loops[info.channel()].timer) {
_position._playPos = _loops[info.channel()].start;
+ _noDelta = _loops[info.channel()].noDelta;
info.loop = true;
}
--_loops[info.channel()].timer;
@@ -277,7 +281,7 @@ void MidiParser_S1D::resetTracking() {
MidiParser::resetTracking();
// The first event never contains any delta.
_noDelta = true;
- memset(_loops, 0, sizeof(_loops));
+ Common::fill(_loops, _loops + ARRAYSIZE(_loops), Loop { 0, 0, 0, false });
}
MidiParser *MidiParser_createS1D(uint8 source, bool monophonicChords) { return new MidiParser_S1D(source, monophonicChords); }
Commit: 38eff6c7c3921e8e55c89e40f61bfba383dd09ff
https://github.com/scummvm/scummvm/commit/38eff6c7c3921e8e55c89e40f61bfba383dd09ff
Author: Coen Rampen (crampen at gmail.com)
Date: 2022-05-16T12:18:11+02:00
Commit Message:
AGOS: Add OPL3 mode for Elvira 1
This commit enables the Accolade AdLib driver's OPL3 mode for Elvira 1. It adds
some extra notes, most notable in the intro track. A small fix is applied to
one of the instruments in the intro track to make it work properly with this
mode.
Changed paths:
engines/agos/detection.cpp
engines/agos/drivers/accolade/adlib.cpp
engines/agos/drivers/accolade/adlib.h
engines/agos/midi.cpp
diff --git a/engines/agos/detection.cpp b/engines/agos/detection.cpp
index 44b125e2172..2a920794680 100644
--- a/engines/agos/detection.cpp
+++ b/engines/agos/detection.cpp
@@ -134,9 +134,9 @@ public:
const Common::String extra = ConfMan.get("extra", target);
ExtraGuiOptions options;
- if (target.empty() || ((gameid == "elvira2" || gameid == "waxworks" || gameid == "simon1") && platform == "pc")) {
- // DOS versions of Elvira 2, Waxworks and Simon 1 can optionally
- // make use of AdLib OPL3 features.
+ if (target.empty() || ((gameid == "elvira1" || gameid == "elvira2" || gameid == "waxworks" || gameid == "simon1") && platform == "pc")) {
+ // DOS versions of Elvira 1 and 2, Waxworks and Simon 1 can
+ // optionally make use of AdLib OPL3 features.
options.push_back(opl3Mode);
}
if (target.empty() || (gameid == "simon1" && ((platform == "pc" && extra != "Floppy Demo") || platform == "windows" ||
diff --git a/engines/agos/drivers/accolade/adlib.cpp b/engines/agos/drivers/accolade/adlib.cpp
index 6f6d703a25e..7b55962b2a7 100644
--- a/engines/agos/drivers/accolade/adlib.cpp
+++ b/engines/agos/drivers/accolade/adlib.cpp
@@ -254,6 +254,20 @@ void MidiDriver_Accolade_AdLib::updateSfxNote(uint8 source) {
writeFrequency(_channelAllocations[source][0]);
}
+void MidiDriver_Accolade_AdLib::patchE1Instruments() {
+ // WORKAROUND One instrument in Elvira 1 has a very slow attack. This
+ // causes a problem in OPL3 mode (see patchWwInstruments for more details).
+ // This is fixed by shortening the attack and compensating by making the
+ // decay longer.
+
+ if (_oplType != OPL::Config::kOpl3)
+ // This workaround is only needed for OPL3 mode.
+ return;
+
+ // Patch the attack and decay of instrument 0x18.
+ _instrumentBank[0x18].operator0.decayAttack = 0x42; // Was 0x24
+}
+
void MidiDriver_Accolade_AdLib::patchWwInstruments() {
// WORKAROUND Several instruments in Waxworks have a very slow attack (it
// takes a long time for a note to reach maximum volume). When a note
diff --git a/engines/agos/drivers/accolade/adlib.h b/engines/agos/drivers/accolade/adlib.h
index 1e223651e75..af2aa2fa953 100644
--- a/engines/agos/drivers/accolade/adlib.h
+++ b/engines/agos/drivers/accolade/adlib.h
@@ -54,6 +54,8 @@ public:
void setSfxNoteFraction(uint8 source, uint16 noteFraction);
// Writes out the current frequency for the specified SFX source.
void updateSfxNote(uint8 source);
+ // Applies a workaround for an Elvira 1 OPL3 instrument issue.
+ void patchE1Instruments();
// Applies a workaround for a Waxworks OPL3 instrument issue.
void patchWwInstruments();
diff --git a/engines/agos/midi.cpp b/engines/agos/midi.cpp
index 360f3ad8fc3..ab35a26a6db 100644
--- a/engines/agos/midi.cpp
+++ b/engines/agos/midi.cpp
@@ -221,6 +221,10 @@ int MidiPlayer::open() {
} else {
_driverMsMusic = _driverMsSfx = MidiDriver_Accolade_AdLib_create(accoladeDriverFilename, oplType);
}
+ if (_vm->getGameType() == GType_ELVIRA1 && oplType == OPL::Config::kOpl3)
+ // WORKAROUND Some Elvira 1 OPL instruments do not work
+ // well in OPL3 mode and need some adjustments.
+ static_cast<MidiDriver_Accolade_AdLib *>(_driverMsMusic)->patchE1Instruments();
if (_vm->getGameType() == GType_WW) {
// WORKAROUND Some Waxworks tracks do not set an instrument on
// a MIDI channel. This will cause that channel to play with
More information about the Scummvm-git-logs
mailing list