Initial commit
This commit is contained in:
263
engines/agos/drivers/accolade/cms.cpp
Normal file
263
engines/agos/drivers/accolade/cms.cpp
Normal file
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user