989 lines
22 KiB
C++
989 lines
22 KiB
C++
/* 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/softsynth/fmtowns_pc98/towns_euphony.h"
|
|
#include "common/endian.h"
|
|
#include "common/util.h"
|
|
#include "common/textconsole.h"
|
|
|
|
#define EUP_EVENT(x) _euphonyEvents.push_back(new EuphonyEvent(this, &EuphonyPlayer::event_##x))
|
|
|
|
#ifdef EUP_USE_MEMPOOL
|
|
#define EUP_EVENTS_DELETE(a) _pendingEventsPool.deleteChunk(a)
|
|
#define EUP_EVENTS_NEW new (_pendingEventsPool)
|
|
#else
|
|
#define EUP_EVENTS_DELETE(a) delete a
|
|
#define EUP_EVENTS_NEW new
|
|
#endif
|
|
|
|
EuphonyPlayer::EuphonyPlayer(Audio::Mixer *mixer) : _partConfig_enable(nullptr), _partConfig_type(nullptr), _partConfig_ordr(nullptr), _partConfig_volume(nullptr),
|
|
_partConfig_transpose(nullptr), _musicPos(nullptr), _musicStart(nullptr), _playing(false), _pendingEventsChain(nullptr), _tempoModifier(0), _bar(0),
|
|
_beat(0), _defaultBarLength(0), _barLength(0), _playerUpdatesLeft(0), _updatesPerPulseRemainder(0), _updatesPerPulse(0),
|
|
_deltaTicks(0), _defaultTempo(0), _trackTempo(0), _tempoControlMode(0), _timerSetting(0), _tempoMode1PulseCounter(0),
|
|
_parseToBar(0), _tempoMode1UpdateF8(0), _loop(false), _endOfTrack(false), _paused(false), _musicTrackSize(0) {
|
|
EUP_EVENT(notImpl);
|
|
EUP_EVENT(noteOn);
|
|
EUP_EVENT(polyphonicAftertouch);
|
|
EUP_EVENT(controlChange_pitchWheel);
|
|
EUP_EVENT(programChange_channelAftertouch);
|
|
EUP_EVENT(programChange_channelAftertouch);
|
|
EUP_EVENT(controlChange_pitchWheel);
|
|
EUP_EVENT(sysex);
|
|
EUP_EVENT(advanceBar);
|
|
EUP_EVENT(notImpl);
|
|
EUP_EVENT(notImpl);
|
|
EUP_EVENT(setTempo);
|
|
EUP_EVENT(notImpl);
|
|
EUP_EVENT(typeOrdrChange);
|
|
|
|
_drivers[0] = _eupDriver = new EuphonyDriver(mixer, this);
|
|
_drivers[1] = new Type0Driver(this);
|
|
_drivers[2] = nullptr;
|
|
resetTempo();
|
|
}
|
|
|
|
#undef EUP_EVENT
|
|
|
|
EuphonyPlayer::~EuphonyPlayer() {
|
|
for (int i = 0; i < 3; i++)
|
|
delete _drivers[i];
|
|
|
|
while (_pendingEventsChain) {
|
|
PendingEvent *evt = _pendingEventsChain;
|
|
_pendingEventsChain = _pendingEventsChain->next;
|
|
EUP_EVENTS_DELETE(evt);
|
|
}
|
|
|
|
delete[] _partConfig_enable;
|
|
delete[] _partConfig_type;
|
|
delete[] _partConfig_ordr;
|
|
delete[] _partConfig_volume;
|
|
delete[] _partConfig_transpose;
|
|
|
|
for (auto *event : _euphonyEvents)
|
|
delete event;
|
|
}
|
|
|
|
bool EuphonyPlayer::init() {
|
|
for (int i = 0; i < 3; i++) {
|
|
if (_drivers[i]) {
|
|
if (!_drivers[i]->init()) {
|
|
warning("EuphonyPlayer:: Driver initialization failed: %d", i);
|
|
delete _drivers[i];
|
|
_drivers[i] = nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!_drivers[0] || !_drivers[1])
|
|
return false;
|
|
|
|
while (_pendingEventsChain) {
|
|
PendingEvent *evt = _pendingEventsChain;
|
|
_pendingEventsChain = _pendingEventsChain->next;
|
|
EUP_EVENTS_DELETE(evt);
|
|
}
|
|
|
|
delete[] _partConfig_enable;
|
|
delete[] _partConfig_type;
|
|
delete[] _partConfig_ordr;
|
|
delete[] _partConfig_volume;
|
|
delete[] _partConfig_transpose;
|
|
|
|
_partConfig_enable = new uint8[32];
|
|
_partConfig_type = new uint8[32];
|
|
_partConfig_ordr = new uint8[32];
|
|
_partConfig_volume = new int8[32];
|
|
_partConfig_transpose = new int8[32];
|
|
|
|
reset();
|
|
|
|
return true;
|
|
}
|
|
|
|
int EuphonyPlayer::startTrack(const uint8 *data, int trackSize, int barLen) {
|
|
if (_playing)
|
|
return 2;
|
|
|
|
_musicPos = _musicStart = data;
|
|
_defaultBarLength = _barLength = barLen;
|
|
_musicTrackSize = trackSize;
|
|
_parseToBar = _bar = 0;
|
|
_beat = 0;
|
|
_playing = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void EuphonyPlayer::stop() {
|
|
if (_playing) {
|
|
_playing = false;
|
|
_playerUpdatesLeft = 0;
|
|
_endOfTrack = false;
|
|
clearHangingNotes();
|
|
resetAllControls();
|
|
}
|
|
}
|
|
|
|
void EuphonyPlayer::pause() {
|
|
_paused = true;
|
|
clearHangingNotes();
|
|
allPartsOff();
|
|
}
|
|
|
|
void EuphonyPlayer::resume() {
|
|
_paused = false;
|
|
}
|
|
|
|
int EuphonyPlayer::setTempo(int tempo) {
|
|
if (tempo > 250)
|
|
return 3;
|
|
_defaultTempo = tempo;
|
|
_trackTempo = tempo;
|
|
sendTempo(tempo);
|
|
return 0;
|
|
}
|
|
|
|
void EuphonyPlayer::setLoopStatus(bool loop) {
|
|
_loop = loop;
|
|
}
|
|
|
|
int EuphonyPlayer::configPart_enable(int part, int val) {
|
|
uint8 enable = val & 0xff;
|
|
if (part > 31 || ((enable + 1) & 0xff) > 1)
|
|
return 3;
|
|
_partConfig_enable[part] = enable;
|
|
return 0;
|
|
}
|
|
|
|
int EuphonyPlayer::configPart_setType(int part, int val) {
|
|
uint8 type = val & 0xff;
|
|
if (part > 31 || ((type + 1) & 0xff) > 8)
|
|
return 3;
|
|
_partConfig_type[part] = type;
|
|
return 0;
|
|
}
|
|
|
|
int EuphonyPlayer::configPart_remap(int part, int val) {
|
|
uint8 remap = val & 0xff;
|
|
if (part > 31 || ((remap + 1) & 0xff) > 16)
|
|
return 3;
|
|
_partConfig_ordr[part] = remap;
|
|
return 0;
|
|
}
|
|
|
|
int EuphonyPlayer::configPart_adjustVolume(int part, int val) {
|
|
int8 adjvol = val & 0xff;
|
|
if (part > 31 || adjvol < -40 || adjvol > 40)
|
|
return 3;
|
|
_partConfig_volume[part] = adjvol;
|
|
return 0;
|
|
}
|
|
|
|
int EuphonyPlayer::configPart_setTranspose(int part, int val) {
|
|
int8 trans = val & 0xff;
|
|
if (part > 31 || trans < -40 || trans > 40)
|
|
return 3;
|
|
_partConfig_transpose[part] = trans;
|
|
return 0;
|
|
}
|
|
|
|
void EuphonyPlayer::timerCallback(int timerId) {
|
|
switch (timerId) {
|
|
case 0:
|
|
updatePulseCounters();
|
|
while (_playerUpdatesLeft) {
|
|
--_playerUpdatesLeft;
|
|
updateBeat();
|
|
if (!_playing)
|
|
continue;
|
|
updateHangingNotes();
|
|
updateParser();
|
|
updateCheckEot();
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void EuphonyPlayer::reset() {
|
|
_eupDriver->reset();
|
|
_eupDriver->setTimerA(true, 1);
|
|
_eupDriver->setTimerA(false, 1);
|
|
_eupDriver->setTimerB(true, 221);
|
|
|
|
resetPartConfig();
|
|
|
|
while (_pendingEventsChain) {
|
|
PendingEvent *evt = _pendingEventsChain;
|
|
_pendingEventsChain = _pendingEventsChain->next;
|
|
EUP_EVENTS_DELETE(evt);
|
|
}
|
|
|
|
_playing = _endOfTrack = _paused = _loop = false;
|
|
_tempoMode1UpdateF8 = 0;
|
|
_tempoMode1PulseCounter = 0;
|
|
|
|
resetTempo();
|
|
|
|
// NB: Original did _tempoControlMode == 1 check here.
|
|
// Not required as this was the original driver's offering of
|
|
// alternative methods for the timed update calllbacks.
|
|
// Not required in the ScummVM implmentation as the outcome is
|
|
// identical.
|
|
#if 0
|
|
if (_tempoControlMode == 1) {
|
|
if (/*???*/)
|
|
return;
|
|
}
|
|
#endif
|
|
sendTempo(_defaultTempo);
|
|
|
|
resetAllControls();
|
|
}
|
|
|
|
void EuphonyPlayer::resetPartConfig() {
|
|
memset(_partConfig_enable, 0xff, 32);
|
|
memset(_partConfig_type, 0xff, 16);
|
|
memset(_partConfig_type + 16, 0, 16);
|
|
for (int i = 0; i < 32; i++)
|
|
_partConfig_ordr[i] = i & 0x0f;
|
|
memset(_partConfig_volume, 0, 32);
|
|
memset(_partConfig_transpose, 0, 32);
|
|
}
|
|
|
|
void EuphonyPlayer::resetTempo() {
|
|
_defaultBarLength = _barLength = 0x33;
|
|
_playerUpdatesLeft = 0;
|
|
_updatesPerPulseRemainder = 0;
|
|
_updatesPerPulse = 0x10;
|
|
_tempoModifier = 0;
|
|
_bar = 0;
|
|
_deltaTicks = 0;
|
|
_beat = 0;
|
|
_defaultTempo = 90;
|
|
_trackTempo = 90;
|
|
}
|
|
|
|
void EuphonyPlayer::updatePulseCounters() {
|
|
int tc = _updatesPerPulse + _updatesPerPulseRemainder;
|
|
_updatesPerPulseRemainder = tc & 0x0f;
|
|
tc >>= 4;
|
|
_tempoMode1PulseCounter -= tc;
|
|
|
|
while (_tempoMode1PulseCounter < 0) {
|
|
_tempoMode1UpdateF8++;
|
|
_tempoMode1PulseCounter += 4;
|
|
}
|
|
|
|
if (_playing && !_paused)
|
|
_playerUpdatesLeft += tc;
|
|
}
|
|
|
|
void EuphonyPlayer::updateBeat() {
|
|
static const uint16 beatLengthTable[] = { 0x180, 0xC0, 0x80, 0x60, 0x40, 0x30, 0x20, 0x18 };
|
|
uint8 beatsPersBar = (_barLength & 0x0f) + 1;
|
|
uint8 beatNoteValue = _barLength >> 4;
|
|
|
|
if ((uint32)(beatLengthTable[beatNoteValue] * beatsPersBar) > ++_beat)
|
|
return;
|
|
|
|
++_bar;
|
|
_beat = 0;
|
|
_deltaTicks = 0;
|
|
}
|
|
|
|
void EuphonyPlayer::updateParser() {
|
|
for (bool loop = true; loop;) {
|
|
uint8 cmd = _musicPos[0];
|
|
|
|
if (cmd == 0xff || cmd == 0xf7) {
|
|
proceedToNextEvent();
|
|
|
|
} else if (cmd < 0x90) {
|
|
_endOfTrack = true;
|
|
clearHangingNotes();
|
|
loop = false;
|
|
|
|
} else if (_parseToBar > _bar) {
|
|
loop = false;
|
|
|
|
} else {
|
|
if (_parseToBar == _bar) {
|
|
uint16 parseToBeat = ((_musicPos[3] << 8) | ((_musicPos[2] << 1) & 0xff)) >> 1;
|
|
if (parseToBeat > _beat)
|
|
loop = false;
|
|
}
|
|
|
|
if (loop) {
|
|
if (parseEvent())
|
|
loop = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void EuphonyPlayer::updateCheckEot() {
|
|
if (!_endOfTrack || _pendingEventsChain)
|
|
return;
|
|
stop();
|
|
}
|
|
|
|
bool EuphonyPlayer::parseEvent() {
|
|
uint cmd = _musicPos[0];
|
|
if (cmd != 0xfe && cmd != 0xfd) {
|
|
bool result = (cmd >= 0xf0) ? (*_euphonyEvents[((cmd - 0xf0) >> 1) + 7])() : (*_euphonyEvents[(cmd - 0x80) >> 4])();
|
|
if (!result) {
|
|
proceedToNextEvent();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (cmd == 0xfd) {
|
|
_paused = true;
|
|
return true;
|
|
}
|
|
|
|
if (!_loop) {
|
|
_endOfTrack = true;
|
|
return true;
|
|
}
|
|
|
|
_endOfTrack = false;
|
|
_musicPos = _musicStart;
|
|
_parseToBar = _bar = _beat = 0;
|
|
_barLength = _defaultBarLength;
|
|
|
|
return false;
|
|
}
|
|
|
|
void EuphonyPlayer::proceedToNextEvent() {
|
|
_musicPos += 6;
|
|
if (_musicPos >= _musicStart + _musicTrackSize)
|
|
_musicPos = _musicStart;
|
|
}
|
|
|
|
void EuphonyPlayer::updateHangingNotes() {
|
|
PendingEvent *l = nullptr;
|
|
PendingEvent *e = _pendingEventsChain;
|
|
|
|
while (e) {
|
|
if (--e->len) {
|
|
l = e;
|
|
e = e->next;
|
|
continue;
|
|
}
|
|
|
|
PendingEvent *n = e->next;
|
|
if (l)
|
|
l->next = n;
|
|
if (_pendingEventsChain == e)
|
|
_pendingEventsChain = n;
|
|
|
|
sendPendingEvent(e->type, e->evt, e->note, e->velo);
|
|
EUP_EVENTS_DELETE(e);
|
|
|
|
e = n;
|
|
}
|
|
}
|
|
|
|
void EuphonyPlayer::clearHangingNotes() {
|
|
while (_pendingEventsChain) {
|
|
PendingEvent *e = _pendingEventsChain;
|
|
_pendingEventsChain = _pendingEventsChain->next;
|
|
sendPendingEvent(e->type, e->evt, e->note, e->velo);
|
|
EUP_EVENTS_DELETE(e);
|
|
}
|
|
}
|
|
|
|
void EuphonyPlayer::resetAllControls() {
|
|
for (int i = 0; i < 32; i++) {
|
|
if (_partConfig_ordr[i] > 15) {
|
|
for (int ii = 0; ii < 16; ii++)
|
|
sendControllerReset(_partConfig_type[i], ii);
|
|
} else {
|
|
sendControllerReset(_partConfig_type[i], _partConfig_ordr[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
void EuphonyPlayer::allPartsOff() {
|
|
for (int i = 0; i < 32; i++) {
|
|
if (_partConfig_ordr[i] > 15) {
|
|
for (int ii = 0; ii < 16; ii++)
|
|
sendAllNotesOff(_partConfig_type[i], ii);
|
|
} else {
|
|
sendAllNotesOff(_partConfig_type[i], _partConfig_ordr[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
uint8 EuphonyPlayer::appendEvent(uint8 evt, uint8 chan) {
|
|
if (evt >= 0x80 && evt < 0xf0 && _partConfig_ordr[chan] < 16)
|
|
return (evt & 0xf0) | _partConfig_ordr[chan];
|
|
return evt;
|
|
}
|
|
|
|
bool EuphonyPlayer::event_notImpl() {
|
|
return false;
|
|
}
|
|
|
|
bool EuphonyPlayer::event_noteOn() {
|
|
if (_musicPos[1] > 31)
|
|
return false;
|
|
if (!_partConfig_enable[_musicPos[1]]) {
|
|
proceedToNextEvent();
|
|
return (_musicPos[0] == 0xfe || _musicPos[0] == 0xfd) ? true : false;
|
|
}
|
|
uint8 evt = appendEvent(_musicPos[0], _musicPos[1]);
|
|
uint8 type = _partConfig_type[_musicPos[1]];
|
|
uint8 note = _musicPos[4];
|
|
uint8 velo = _musicPos[5];
|
|
|
|
sendByte(type, evt);
|
|
sendByte(type, applyTranspose(note));
|
|
sendByte(type, applyVolumeAdjust(velo));
|
|
|
|
proceedToNextEvent();
|
|
if (_musicPos[0] == 0xfe || _musicPos[0] == 0xfd)
|
|
return true;
|
|
|
|
velo = _musicPos[5];
|
|
uint16 len = (_musicPos[1] & 0x0f) | ((_musicPos[2] & 0x0f) << 4) | ((_musicPos[3] & 0x0f) << 8) | ((_musicPos[4] & 0x0f) << 12);
|
|
|
|
_pendingEventsChain = EUP_EVENTS_NEW PendingEvent(evt, type, note, velo, len ? len : 1, _pendingEventsChain);
|
|
|
|
return false;
|
|
}
|
|
|
|
bool EuphonyPlayer::event_polyphonicAftertouch() {
|
|
if (_musicPos[1] > 31)
|
|
return false;
|
|
if (!_partConfig_enable[_musicPos[1]])
|
|
return false;
|
|
|
|
uint8 evt = appendEvent(_musicPos[0], _musicPos[1]);
|
|
uint8 type = _partConfig_type[_musicPos[1]];
|
|
|
|
sendByte(type, evt);
|
|
sendByte(type, applyTranspose(_musicPos[4]));
|
|
sendByte(type, _musicPos[5]);
|
|
|
|
return false;
|
|
}
|
|
|
|
bool EuphonyPlayer::event_controlChange_pitchWheel() {
|
|
if (_musicPos[1] > 31)
|
|
return false;
|
|
if (!_partConfig_enable[_musicPos[1]])
|
|
return false;
|
|
|
|
uint8 evt = appendEvent(_musicPos[0], _musicPos[1]);
|
|
uint8 type = _partConfig_type[_musicPos[1]];
|
|
|
|
sendByte(type, evt);
|
|
sendByte(type, _musicPos[4]);
|
|
sendByte(type, _musicPos[5]);
|
|
|
|
return false;
|
|
}
|
|
|
|
bool EuphonyPlayer::event_programChange_channelAftertouch() {
|
|
if (_musicPos[1] > 31)
|
|
return false;
|
|
if (!_partConfig_enable[_musicPos[1]])
|
|
return false;
|
|
|
|
uint8 evt = appendEvent(_musicPos[0], _musicPos[1]);
|
|
uint8 type = _partConfig_type[_musicPos[1]];
|
|
|
|
sendByte(type, evt);
|
|
sendByte(type, _musicPos[4]);
|
|
|
|
return false;
|
|
}
|
|
|
|
bool EuphonyPlayer::event_sysex() {
|
|
uint8 type = _partConfig_type[_musicPos[1]];
|
|
sendByte(type, 0xF0);
|
|
proceedToNextEvent();
|
|
|
|
for (bool loop = true; loop; ) {
|
|
for (int i = 0; i < 6; i++) {
|
|
if (_musicPos[i] != 0xFF) {
|
|
sendByte(type, _musicPos[i]);
|
|
if (_musicPos[i] >= 0x80) {
|
|
loop = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (loop)
|
|
proceedToNextEvent();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool EuphonyPlayer::event_advanceBar() {
|
|
++_parseToBar;
|
|
_barLength = _musicPos[1];
|
|
return false;
|
|
}
|
|
|
|
bool EuphonyPlayer::event_setTempo() {
|
|
_trackTempo = ((_musicPos[5] << 8) | ((_musicPos[4] << 1) & 0xff)) >> 1;
|
|
sendTempo(_trackTempo);
|
|
return false;
|
|
}
|
|
|
|
bool EuphonyPlayer::event_typeOrdrChange() {
|
|
if (_musicPos[1] > 31)
|
|
return false;
|
|
if (!_partConfig_enable[_musicPos[1]])
|
|
return false;
|
|
|
|
if (_musicPos[4] == 1)
|
|
_partConfig_type[_musicPos[1]] = _musicPos[5];
|
|
else if (_musicPos[4] == 2)
|
|
_partConfig_ordr[_musicPos[1]] = _musicPos[5];
|
|
|
|
return false;
|
|
}
|
|
|
|
uint8 EuphonyPlayer::applyTranspose(uint8 in) {
|
|
int out = _partConfig_transpose[_musicPos[1]];
|
|
if (!out)
|
|
return in;
|
|
out += (in & 0x7f);
|
|
|
|
if (out > 127)
|
|
out -= 12;
|
|
|
|
if (out < 0)
|
|
out += 12;
|
|
|
|
return out & 0xff;
|
|
}
|
|
|
|
uint8 EuphonyPlayer::applyVolumeAdjust(uint8 in) {
|
|
int out = _partConfig_volume[_musicPos[1]];
|
|
out += (in & 0x7f);
|
|
out = CLIP(out, 1, 127);
|
|
|
|
return out & 0xff;
|
|
}
|
|
|
|
void EuphonyPlayer::sendByte(uint8 type, uint8 command) {
|
|
int drv = ((type >> 4) + 1) & 3;
|
|
if (_drivers[drv])
|
|
_drivers[drv]->send(command);
|
|
}
|
|
|
|
void EuphonyPlayer::sendPendingEvent(int type, int evt, int note, int velo) {
|
|
if (velo)
|
|
evt &= 0x8f;
|
|
sendByte(type, evt);
|
|
sendByte(type, note);
|
|
sendByte(type, velo);
|
|
}
|
|
|
|
void EuphonyPlayer::sendControllerReset(int type, int part) {
|
|
sendByte(type, 0xb0 | part);
|
|
sendByte(type, 0x40);
|
|
sendByte(type, 0);
|
|
sendByte(type, 0xb0 | part);
|
|
sendByte(type, 0x7b);
|
|
sendByte(type, 0);
|
|
sendByte(type, 0xb0 | part);
|
|
sendByte(type, 0x79);
|
|
sendByte(type, 0x40);
|
|
}
|
|
|
|
void EuphonyPlayer::sendAllNotesOff(int type, int part) {
|
|
sendByte(type, 0xb0 | part);
|
|
sendByte(type, 0x40);
|
|
sendByte(type, 0);
|
|
}
|
|
|
|
void EuphonyPlayer::sendTempo(int tempo) {
|
|
tempo = CLIP(tempo + _tempoModifier, 0, 500);
|
|
if (_tempoControlMode == 0) {
|
|
_timerSetting = 34750 / (tempo + 30);
|
|
_updatesPerPulse = 0x10;
|
|
|
|
while (_timerSetting < 126) {
|
|
_timerSetting <<= 1;
|
|
_updatesPerPulse <<= 1;
|
|
}
|
|
|
|
while (_timerSetting > 383) {
|
|
_timerSetting >>= 1;
|
|
_updatesPerPulse >>= 1;
|
|
}
|
|
|
|
_eupDriver->setTimerA(true, -(_timerSetting - 2));
|
|
|
|
} else if (_tempoControlMode == 1) {
|
|
_timerSetting = 312500 / (tempo + 30);
|
|
_updatesPerPulse = 0x10;
|
|
while (_timerSetting < 1105) {
|
|
_timerSetting <<= 1;
|
|
_updatesPerPulse <<= 1;
|
|
}
|
|
|
|
} else if (_tempoControlMode == 2) {
|
|
_timerSetting = 625000 / (tempo + 30);
|
|
_updatesPerPulseRemainder = 0;
|
|
}
|
|
}
|
|
|
|
EuphonyDriver::EuphonyDriver(Audio::Mixer *mixer, EuphonyPlayer *pl) : EuphonyBaseDriver(), _channels(nullptr), _partToChanMapping(nullptr), _sustainChannels(nullptr) {
|
|
_intf = new TownsAudioInterface(mixer, pl);
|
|
}
|
|
|
|
EuphonyDriver::~EuphonyDriver() {
|
|
delete _intf;
|
|
delete[] _partToChanMapping;
|
|
delete[] _sustainChannels;
|
|
delete[] _channels;
|
|
}
|
|
|
|
|
|
bool EuphonyDriver::init() {
|
|
if (!_intf->init())
|
|
return false;
|
|
|
|
delete[] _channels;
|
|
delete[] _partToChanMapping;
|
|
delete[] _sustainChannels;
|
|
|
|
_channels = new Channel[128];
|
|
_partToChanMapping = new int8[16];
|
|
_sustainChannels = new int8[16];
|
|
|
|
return true;
|
|
}
|
|
|
|
void EuphonyDriver::reset() {
|
|
_intf->callback(0);
|
|
_intf->callback(74);
|
|
_intf->callback(70, 0);
|
|
_intf->callback(75, 3);
|
|
|
|
_currentEvent.clear();
|
|
memset(_sustainChannels, 0, 16);
|
|
memset(_partToChanMapping, -1, 16);
|
|
|
|
for (int i = 0; i < 128; i++) {
|
|
_channels[i].part = _channels[i].next = -1;
|
|
_channels[i].note = _channels[i].pri = 0;
|
|
}
|
|
|
|
int e = 0;
|
|
for (int i = 0; i < 6; i++)
|
|
assignPartToChannel(i, e++);
|
|
for (int i = 0x40; i < 0x48; i++)
|
|
assignPartToChannel(i, e++);
|
|
}
|
|
|
|
int EuphonyDriver::assignPartToChannel(int chan, int part) {
|
|
if (part > 15 || chan > 127 || chan < 0)
|
|
return 3;
|
|
|
|
Channel *a = &_channels[chan];
|
|
if (a->part == part)
|
|
return 0;
|
|
|
|
if (a->part != -1) {
|
|
int8 *b = &_partToChanMapping[a->part];
|
|
while (*b != chan) {
|
|
b = &_channels[*b].next;
|
|
if (*b == -1 && *b != chan)
|
|
return 3;
|
|
}
|
|
|
|
*b = a->next;
|
|
|
|
if (a->note)
|
|
_intf->callback(2, chan);
|
|
|
|
a->part = a->next = -1;
|
|
a->note = 0;
|
|
}
|
|
|
|
a->next = _partToChanMapping[part];
|
|
_partToChanMapping[part] = chan;
|
|
a->part = part;
|
|
a->note = a->pri = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void EuphonyDriver::send(uint8 command) {
|
|
if (command >= 0x80) {
|
|
_currentEvent.clear();
|
|
_currentEvent.push_back(command >= 0xf0 ? 0 : command);
|
|
} else if (_currentEvent[0] >= 0x80) {
|
|
uint8 cmd = (_currentEvent[0] - 0x80) >> 4;
|
|
_currentEvent.push_back(command);
|
|
|
|
static const uint8 eventSize[] = { 3, 3, 3, 3, 2, 2, 3 };
|
|
if (_currentEvent.size() != eventSize[cmd])
|
|
return;
|
|
|
|
switch (cmd) {
|
|
case 0:
|
|
noteOff();
|
|
break;
|
|
case 1:
|
|
if (_currentEvent[2])
|
|
noteOn();
|
|
else
|
|
noteOff();
|
|
break;
|
|
case 3:
|
|
if (_currentEvent[1] == 7)
|
|
controlChange_volume();
|
|
else if (_currentEvent[1] == 10)
|
|
controlChange_panPos();
|
|
else if (_currentEvent[1] == 64)
|
|
controlChange_allNotesOff();
|
|
break;
|
|
case 4:
|
|
programChange();
|
|
break;
|
|
case 6:
|
|
pitchWheel();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void EuphonyDriver::setTimerA(bool enable, int tempo) {
|
|
_intf->callback(21, enable ? 255 : 0, tempo);
|
|
}
|
|
|
|
void EuphonyDriver::setTimerB(bool enable, int tempo) {
|
|
_intf->callback(22, enable ? 255 : 0, tempo);
|
|
}
|
|
|
|
void EuphonyDriver::loadInstrument(int chanType, int id, const uint8 *data) {
|
|
_intf->callback(5, chanType, id, data);
|
|
}
|
|
|
|
void EuphonyDriver::setInstrument(int chan, int instrID) {
|
|
_intf->callback(4, chan, instrID);
|
|
}
|
|
|
|
void EuphonyDriver::loadWaveTable(const uint8 *data) {
|
|
_intf->callback(34, data);
|
|
}
|
|
|
|
void EuphonyDriver::unloadWaveTable(int id) {
|
|
_intf->callback(35, id);
|
|
}
|
|
|
|
void EuphonyDriver::reserveSoundEffectChannels(int num) {
|
|
_intf->callback(33, num);
|
|
uint32 volMask = 0;
|
|
|
|
if (num > 8)
|
|
return;
|
|
|
|
for (uint32 v = 1 << 13; num; num--) {
|
|
volMask |= v;
|
|
v >>= 1;
|
|
}
|
|
|
|
_intf->setSoundEffectChanMask(volMask);
|
|
}
|
|
|
|
void EuphonyDriver::playSoundEffect(int chan, int note, int velo, const uint8 *data) {
|
|
_intf->callback(37, chan, note, velo, data);
|
|
}
|
|
|
|
void EuphonyDriver::stopSoundEffect(int chan) {
|
|
_intf->callback(39, chan);
|
|
}
|
|
|
|
bool EuphonyDriver::soundEffectIsPlaying(int chan) {
|
|
return _intf->callback(40, chan) ? true : false;
|
|
}
|
|
|
|
void EuphonyDriver::channelPan(int chan, int mode) {
|
|
_intf->callback(3, chan, mode);
|
|
}
|
|
|
|
void EuphonyDriver::channelPitch(int chan, int pitch) {
|
|
_intf->callback(7, chan, pitch);
|
|
}
|
|
|
|
void EuphonyDriver::channelVolume(int chan, int vol) {
|
|
_intf->callback(8, chan, vol);
|
|
}
|
|
|
|
void EuphonyDriver::setOutputVolume(int mode, int volLeft, int volRight) {
|
|
_intf->callback(67, mode, volLeft, volRight);
|
|
}
|
|
|
|
void EuphonyDriver::cdaToggle(int a) {
|
|
_intf->callback(73, a);
|
|
}
|
|
|
|
void EuphonyDriver::setMusicVolume(int volume) {
|
|
_intf->setMusicVolume(volume);
|
|
}
|
|
|
|
void EuphonyDriver::setSoundEffectVolume(int volume) {
|
|
_intf->setSoundEffectVolume(volume);
|
|
}
|
|
|
|
void EuphonyDriver::noteOff() {
|
|
int8 *chan = &_partToChanMapping[_currentEvent[0] & 0x0f];
|
|
if (*chan == -1)
|
|
return;
|
|
|
|
while (_channels[*chan].note != _currentEvent[1]) {
|
|
chan = &_channels[*chan].next;
|
|
if (*chan == -1)
|
|
return;
|
|
}
|
|
|
|
if (_sustainChannels[_currentEvent[0] & 0x0f]) {
|
|
_channels[*chan].note |= 0x80;
|
|
} else {
|
|
_channels[*chan].note = 0;
|
|
_intf->callback(2, *chan);
|
|
}
|
|
}
|
|
|
|
void EuphonyDriver::noteOn() {
|
|
if (!_currentEvent[1])
|
|
return;
|
|
int8 *chan = &_partToChanMapping[_currentEvent[0] & 0x0f];
|
|
if (*chan == -1)
|
|
return;
|
|
|
|
do {
|
|
_channels[*chan].pri++;
|
|
chan = &_channels[*chan].next;
|
|
} while (*chan != -1);
|
|
|
|
chan = &_partToChanMapping[_currentEvent[0] & 0x0f];
|
|
|
|
int d = 0;
|
|
int c = 0;
|
|
bool found = false;
|
|
|
|
do {
|
|
if (!_channels[*chan].note) {
|
|
found = true;
|
|
break;
|
|
}
|
|
if (d <= _channels[*chan].pri) {
|
|
c = *chan;
|
|
d = _channels[*chan].pri;
|
|
}
|
|
chan = &_channels[*chan].next;
|
|
} while (*chan != -1);
|
|
|
|
if (found)
|
|
c = *chan;
|
|
else
|
|
_intf->callback(2, c);
|
|
|
|
_channels[c].note = _currentEvent[1];
|
|
_channels[c].pri = 0;
|
|
_intf->callback(1, c, _currentEvent[1], _currentEvent[2]);
|
|
}
|
|
|
|
void EuphonyDriver::controlChange_volume() {
|
|
int8 *chan = &_partToChanMapping[_currentEvent[0] & 0x0f];
|
|
while (*chan != -1) {
|
|
_intf->callback(8, *chan, _currentEvent[2] & 0x7f);
|
|
chan = &_channels[*chan].next;
|
|
}
|
|
}
|
|
|
|
void EuphonyDriver::controlChange_panPos() {
|
|
int8 *chan = &_partToChanMapping[_currentEvent[0] & 0x0f];
|
|
while (*chan != -1) {
|
|
_intf->callback(3, *chan, _currentEvent[2] & 0x7f);
|
|
chan = &_channels[*chan].next;
|
|
}
|
|
}
|
|
|
|
void EuphonyDriver::controlChange_allNotesOff() {
|
|
if (_currentEvent[2] > 63) {
|
|
_sustainChannels[_currentEvent[0] & 0x0f] = -1;
|
|
return;
|
|
}
|
|
|
|
_sustainChannels[_currentEvent[0] & 0x0f] = 0;
|
|
int8 *chan = &_partToChanMapping[_currentEvent[0] & 0x0f];
|
|
while (*chan != -1) {
|
|
if (_channels[*chan].note & 0x80) {
|
|
_channels[*chan].note = 0;
|
|
_intf->callback(2, *chan);
|
|
}
|
|
chan = &_channels[*chan].next;
|
|
}
|
|
}
|
|
|
|
void EuphonyDriver::programChange() {
|
|
int8 *chan = &_partToChanMapping[_currentEvent[0] & 0x0f];
|
|
while (*chan != -1) {
|
|
_intf->callback(4, *chan, _currentEvent[1]);
|
|
_intf->callback(7, *chan, 0);
|
|
chan = &_channels[*chan].next;
|
|
}
|
|
}
|
|
|
|
void EuphonyDriver::pitchWheel() {
|
|
int8 *chan = &_partToChanMapping[_currentEvent[0] & 0x0f];
|
|
while (*chan != -1) {
|
|
_currentEvent[1] += _currentEvent[1];
|
|
int16 pitch = ((((_currentEvent[2] << 8) | _currentEvent[1]) >> 1) & 0x3fff) - 0x2000;
|
|
_intf->callback(7, *chan, pitch);
|
|
chan = &_channels[*chan].next;
|
|
}
|
|
}
|
|
|
|
Type0Driver::Type0Driver(EuphonyPlayer *pl) : EuphonyBaseDriver() {
|
|
}
|
|
|
|
Type0Driver::~Type0Driver() {
|
|
}
|
|
|
|
bool Type0Driver::init() {
|
|
return true;
|
|
}
|
|
|
|
void Type0Driver::send(uint8 command) {
|
|
}
|
|
|
|
#undef EUP_EVENTS_DELETE
|
|
#undef EUP_EVENTS_NEW
|