Files
2026-02-02 04:50:13 +01:00

1550 lines
44 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/>.
*
*/
#ifdef ENABLE_EOB
#include "kyra/kyra_v1.h"
#include "kyra/sound/drivers/capcom98.h"
#include "audio/mixer.h"
#include "audio/softsynth/fmtowns_pc98/pc98_audio.h"
#include "common/debug.h"
#include "common/func.h"
#include "common/system.h"
namespace Kyra {
class CapcomPC98Player {
public:
enum Flags {
kStdPlay = 1 << 0,
kPrioPlay = 1 << 1,
kPrioClaim = 1 << 2,
kFadeOut = 1 << 3,
kStdStop = 1 << 8,
kPrioStop = 1 << 9,
kSavedState = 1 << 10
};
CapcomPC98Player(bool playerPrio, uint16 playFlags, uint16 chanReserveFlags, uint16 chanDisableFlags);
virtual ~CapcomPC98Player() {}
virtual bool init() = 0;
virtual void deinit() = 0;
virtual void reset() = 0;
virtual void loadInstruments(const uint8 *data, uint16 number) {}
void startSound(const uint8 *data, uint8 volume, bool loop);
void stopSound();
uint8 getMarker(uint8 id) const { return _soundMarkers[id & 0x0F]; }
static uint16 getStatus() { return _flags; }
void fadeOut(uint16 speed);
void allNotesOff(uint16 chanFlags = 0xFFFF);
void setMusicVolume (int vol);
void setSoundEffectVolume (int vol);
void nextTick();
virtual void processSounds() = 0;
protected:
uint16 _soundMarkers[16];
uint8 _fadeState;
uint16 _musicVolume;
uint16 _sfxVolume;
const uint16 _chanReserveFlags;
const uint16 _chanDisableFlags;
private:
virtual void send(uint32 evt) = 0;
virtual PC98AudioCore::MutexLock lockMutex() = 0;
virtual void updateMasterVolume() = 0;
void storeEvent(uint32 evt);
void restorePlayer();
virtual void setVolControlMask() {}
virtual void restoreStateIntern() {}
uint16 playFlag() const { return _playFlags & (kStdPlay | kPrioPlay); }
uint16 extraFlag() const { return _playFlags & kPrioClaim; }
uint16 stopFlag() const { return (_playFlags & (kStdPlay | kPrioPlay)) << 8; }
const uint8 *_data;
const uint8 *_curPos;
uint16 _numEventsTotal;
uint16 _numEventsLeft;
uint8 _volume;
uint16 _midiTicker;
static uint16 _flags;
bool _loop;
uint16 _fadeSpeed;
uint16 _fadeTicker;
Common::Array<uint32> _storedEvents;
const bool _playerPrio;
const uint16 _playFlags;
};
class CapcomPC98Player_MIDI final : public CapcomPC98Player {
public:
typedef Common::Functor0Mem<PC98AudioCore::MutexLock, CapcomPC98AudioDriverInternal> MutexProc;
CapcomPC98Player_MIDI(MidiDriver::DeviceHandle dev, bool isMT32, MutexProc &mutexProc);
~CapcomPC98Player_MIDI();
bool init() override;
void deinit() override {}
void reset() override;
void processSounds() override;
private:
void send(uint32 evt) override;
PC98AudioCore::MutexLock lockMutex() override;
void updateMasterVolume() override;
MidiDriver *_midi;
const bool _isMT32;
const uint8 *_programMapping;
uint8 _chanVolume[16];
static const uint8 _programMapping_mt32ToGM[128];
MutexProc &_mproc;
};
class CapcomPC98_FMChannel {
public:
CapcomPC98_FMChannel(uint8 id, PC98AudioCore *&ac, const Common::Array<const uint8*>&instruments, const uint8 &fadeState);
~CapcomPC98_FMChannel();
void reset();
void keyOff();
void noteOff(uint8 note);
void noteOn(uint8 note, uint8 velo);
void programChange(uint8 prg);
void pitchBend(uint16 pb);
void restore();
void modWheel(uint8 mw);
void breathControl(uint8 bc);
void pitchBendSensitivity(uint8 pbs);
void portamentoTime(uint8 pt);
void volume(uint8 vol);
void togglePortamento(uint8 enable);
void allNotesOff();
void processSounds();
private:
typedef Common::Functor0Mem<void, CapcomPC98_FMChannel> VbrHandlerProc;
const uint8 _regOffset;
uint8 _program;
bool _isPlaying;
uint8 _note;
uint8 _carrier;
uint8 _volume;
uint8 _velocity;
uint8 _noteEffCur;
uint8 _noteEffPrev;
uint8 _breathControl;
uint16 _frequency;
uint8 _modWheel;
uint16 _pitchBendSensitivity;
int16 _pitchBendPara;
int16 _pitchBendEff;
int32 _vbrState;
uint32 _vbrStep;
uint16 _vbrCycleTicker;
uint16 _vbrDelayTicker;
VbrHandlerProc *_vbrHandler;
bool _prtEnable;
uint8 _prtTime;
int32 _prtState;
int32 _prtStep;
uint16 _prtCycleLength;
struct Instrument {
char name[9];
uint8 fbl_alg;
uint8 vbrType;
uint8 vbrCycleLength;
uint8 vbrSensitivity;
uint8 resetEffect;
uint8 vbrDelay;
/*uint8 ff;
uint8 f10;
uint8 f11;
uint8 f12;
uint8 f13;
uint8 f14;*/
uint8 *regData;
} _instrument;
private:
void loadInstrument(const uint8 *in);
void updatePitchBend();
void updateVolume();
void updatePortamento();
void updateFrequency();
void setupPortamento();
void setupVibrato();
void dummyProc() {}
void vbrHandler0();
void vbrHandler1();
void vbrHandler2();
void vbrHandler3();
VbrHandlerProc *_vbrHandlers[5];
PC98AudioCore *&_ac;
const Common::Array<const uint8*>&_instruments;
const uint8 &_fadeState;
static const uint16 _freqMSBTable[12];
static const uint8 _freqLSBTables[12][64];
static const uint8 _volTablesInst[4][128];
static const uint8 _volTableCarrier[128];
static const uint8 _volTablePara[128];
};
class CapcomPC98Player_FM final : public CapcomPC98Player, PC98AudioPluginDriver {
public:
typedef Common::Functor0Mem<void, CapcomPC98AudioDriverInternal> CBProc;
CapcomPC98Player_FM(Audio::Mixer *mixer, CBProc &cbproc, bool playerPrio, uint16 playFlags, uint8 chanReserveFlags, uint8 chanDisableFlags, uint16 volControlMask, bool needsTimer);
~CapcomPC98Player_FM() override;
bool init() override;
void deinit() override;
void reset() override;
void loadInstruments(const uint8 *data, uint16 number) override;
PC98AudioCore::MutexLock lockMutex() override;
private:
void send(uint32 evt) override;
void updateMasterVolume() override;
void timerCallbackA() override;
void processSounds() override;
void controlChange(uint8 ch, uint8 control, uint8 val);
void setVolControlMask() override;
void restoreStateIntern() override;
PC98AudioCore *_ac;
CapcomPC98_FMChannel **_chan;
Common::Array<const uint8*>_instruments;
CBProc &_cbProc;
bool _ready;
const uint16 _volControlMask;
static const uint8 _initData[72];
};
class CapcomPC98AudioDriverInternal {
public:
CapcomPC98AudioDriverInternal(Audio::Mixer *mixer, MidiDriver::DeviceHandle dev);
~CapcomPC98AudioDriverInternal();
static CapcomPC98AudioDriverInternal *open(Audio::Mixer *mixer, MidiDriver::DeviceHandle dev);
static void close();
bool isUsable() const { return _ready; }
void reset();
void loadFMInstruments(const uint8 *data);
void startSong(const uint8 *data, uint8 volume, bool loop);
void stopSong();
void startSoundEffect(const uint8 *data, uint8 volume);
void stopSoundEffect();
int checkSoundMarker() const;
bool songIsPlaying() const;
bool soundEffectIsPlaying() const;
void fadeOut();
void allNotesOff();
void setMusicVolume(int volume);
void setSoundEffectVolume(int volume);
void timerCallback();
PC98AudioCore::MutexLock lockMutex();
private:
void updateMasterVolume();
CapcomPC98Player *_players[2];
CapcomPC98Player_FM *_fmDevice;
CapcomPC98Player_FM::CBProc *_timerProc;
CapcomPC98Player_MIDI::MutexProc *_mutexProc;
static CapcomPC98AudioDriverInternal *_refInstance;
static int _refCount;
int _musicVolume;
int _sfxVolume;
int _marker;
bool _ready;
};
uint16 CapcomPC98Player::_flags = 0;
CapcomPC98Player::CapcomPC98Player(bool playerPrio, uint16 playFlags, uint16 chanReserveFlags, uint16 chanDisableFlags) : _playerPrio(playerPrio), _playFlags(playFlags), _chanReserveFlags(chanReserveFlags), _chanDisableFlags(chanDisableFlags),
_data(nullptr), _curPos(nullptr), _numEventsTotal(0), _numEventsLeft(0), _volume(0), _midiTicker(0), _loop(false), _fadeState(0), _fadeSpeed(1), _fadeTicker(0), _musicVolume(0), _sfxVolume(0) {
memset(_soundMarkers, 0, sizeof(_soundMarkers));
_flags = 0;
}
void CapcomPC98Player::startSound(const uint8 *data, uint8 volume, bool loop) {
stopSound();
PC98AudioCore::MutexLock lock = lockMutex();
_numEventsTotal = _numEventsLeft = READ_LE_UINT16(data);
_data = _curPos = data + 2;
_volume = volume & 0x7F;
_loop = loop;
_midiTicker = 0;
for (int i = 0; i < 16; ++i) {
if ((1 << i) & _chanReserveFlags)
send(0x0007B0 | i | (_volume << 16));
}
_flags &= ~(stopFlag() | kFadeOut);
_flags |= (playFlag() | extraFlag());
}
void CapcomPC98Player::stopSound() {
while (_flags & playFlag()) {
g_system->delayMillis(4);
PC98AudioCore::MutexLock lock = lockMutex();
_flags |= stopFlag();
}
g_system->delayMillis(8);
}
void CapcomPC98Player::fadeOut(uint16 speed) {
if (speed) {
_fadeTicker =_fadeSpeed = speed;
_fadeState = 0;
PC98AudioCore::MutexLock lock = lockMutex();
_flags |= kFadeOut;
} else {
stopSound();
}
}
void CapcomPC98Player::allNotesOff(uint16 chanFlags) {
PC98AudioCore::MutexLock lock = lockMutex();
for (int i = 0; i < 16; ++i) {
if ((1 << i) & chanFlags)
send(0x007BB0 | i);
}
}
void CapcomPC98Player::setMusicVolume (int vol) {
PC98AudioCore::MutexLock lock = lockMutex();
_musicVolume = vol;
updateMasterVolume();
}
void CapcomPC98Player::setSoundEffectVolume (int vol) {
PC98AudioCore::MutexLock lock = lockMutex();
_sfxVolume = vol;
updateMasterVolume();
}
void CapcomPC98Player::nextTick() {
if (_flags & playFlag()) {
if (_flags & kFadeOut) {
if (_fadeTicker) {
--_fadeTicker;
} else {
_fadeTicker = _fadeSpeed;
if (++_fadeState == 100)
_flags |= stopFlag();
}
} else {
_fadeState = 0;
_fadeTicker = _fadeSpeed;
}
if (_playerPrio && (_flags & kPrioClaim)) {
_flags &= ~kPrioClaim;
setVolControlMask();
} else if (!_playerPrio) {
if (_flags & kPrioClaim) {
_flags |= kSavedState;
allNotesOff(~_chanReserveFlags);
} else if (((_flags & kPrioStop) || !(_flags & kPrioPlay)) && (_flags & kSavedState)) {
_flags &= ~kSavedState;
restorePlayer();
setVolControlMask();
}
}
if (_flags & stopFlag()) {
allNotesOff((_playerPrio || (_flags & kPrioPlay)) ? _chanReserveFlags : 0xFFFF);
_flags &= ~playFlag();
if (!(_flags & (kStdPlay | kPrioPlay)))
_flags &= ~kFadeOut;
} else if (_numEventsLeft) {
bool eot = false;
while (_numEventsLeft && !eot) {
eot = false;
uint32 in = READ_LE_UINT32(_curPos);
if ((in & 0xFF) > _midiTicker)
break;
_midiTicker -= (in & 0xFF);
uint8 chanFlag = 1 << ((in >> 8) & 0x0F);
if (!(chanFlag & _chanDisableFlags)) {
if (_playerPrio || !(_flags & kPrioPlay) || ((_flags & kPrioPlay) && (chanFlag & _chanReserveFlags))) {
if (_volume == 0x7F || ((in >> 12) & 0xFFF) != 0x07B)
send(in >> 8);
} else {
storeEvent(in >> 8);
}
}
_curPos += 4;
eot = (--_numEventsLeft == 0);
}
if (eot) {
if (_loop) {
_numEventsLeft = _numEventsTotal;
_curPos = _data;
} else {
allNotesOff((_playerPrio || (_flags & kPrioPlay)) ? _chanReserveFlags : 0xFFFF);
_flags &= ~playFlag();
}
}
}
}
processSounds();
_midiTicker++;
}
void CapcomPC98Player::storeEvent(uint32 evt) {
if ((1 << (evt & 0x0F)) & _chanReserveFlags)
return;
uint8 st = evt & 0xFF;
for (Common::Array<uint32>::iterator i = _storedEvents.begin(); i != _storedEvents.end(); ++i) {
if ((*i & 0xFF) == st) {
*i = evt;
return;
}
}
st >>= 4;
if (st == 0x0B || st == 0x0C || st == 0x0E)
_storedEvents.push_back(evt);
}
void CapcomPC98Player::restorePlayer() {
restoreStateIntern();
for (Common::Array<uint32>::iterator i = _storedEvents.begin(); i != _storedEvents.end(); ++i)
send(*i);
_storedEvents.clear();
}
CapcomPC98Player_MIDI::CapcomPC98Player_MIDI(MidiDriver::DeviceHandle dev, bool isMT32, MutexProc &mutexProc) : CapcomPC98Player(true, kStdPlay, 0xFFFF, 0), _isMT32(isMT32), _mproc(mutexProc), _midi(nullptr), _programMapping(nullptr) {
_midi = MidiDriver::createMidi(dev);
uint8 *map = new uint8[128];
assert(map);
if (isMT32) {
memcpy(map, _programMapping_mt32ToGM, 128);
} else {
for (uint8 i = 0; i < 128; ++i)
map[i] = i;
}
_programMapping = map;
memset(_chanVolume, 0, sizeof(_chanVolume));
}
CapcomPC98Player_MIDI::~CapcomPC98Player_MIDI() {
_midi->close();
delete _midi;
delete[] _programMapping;
}
bool CapcomPC98Player_MIDI::init() {
if (!_midi || !_programMapping)
return false;
if (_midi->open())
return false;
if (_isMT32) {
_midi->sendMT32Reset();
} else {
// Don't send the reset from the common code here, since that also forces a GS reset
// which we don't want. A GS reset will change the sound output in a quite obvious way
// and make it sound different from the original output.
static const byte gmResetSysEx[] = { 0x7E, 0x7F, 0x09, 0x01 };
_midi->sysEx(gmResetSysEx, sizeof(gmResetSysEx));
g_system->delayMillis(50);
}
reset();
return true;
}
void CapcomPC98Player_MIDI::reset() {
memset(_chanVolume, 0x7F, sizeof(_chanVolume));
}
void CapcomPC98Player_MIDI::send(uint32 evt) {
uint8 cmd = evt & 0xF0;
uint8 ch = evt & 0x0F;
uint8 p1 = (evt >> 8) & 0xFF;
uint8 p2 = (evt >> 16) & 0xFF;
debugC(5, kDebugLevelSound, "CapcomPC98Player_MIDI::send(): [0x%02x] [0x%02x] [0x%02x]", evt & 0xff, p1, p2);
if (cmd == 0xC0) {
evt = (evt & 0xFF) | (_programMapping[p1] << 8);
} else if (cmd == 0xB0) {
if (p1 == 3) {
_soundMarkers[ch] = p2;
return;
} else if (((evt >> 8) & 0xFF) == 7) {
_chanVolume[ch] = p2;
}
} else if (evt == 0xF0) {
// This doesn't seem to be used at all. So I haven't implemented it (yet).
warning("CapcomPC98Player_MIDI::send(): Unhandled sysex message encountered");
}
_midi->send(evt);
}
void CapcomPC98Player_MIDI::processSounds() {
if (_fadeState) {
for (int i = 0; i < 16; ++i)
_midi->send(0x0007B0 | i | (CLIP<int>(_chanVolume[i] - _fadeState, 0, 127) << 16));
}
}
PC98AudioCore::MutexLock CapcomPC98Player_MIDI::lockMutex() {
if (!_mproc.isValid())
error("CapcomPC98_MIDI::lockMutex(): Invalid call");
return _mproc();
}
void CapcomPC98Player_MIDI::updateMasterVolume() {
// This player is not used for sound effects. So I don't add support for sound effects volume control here.
if (_isMT32) {
byte vol = _musicVolume * 100 / Audio::Mixer::kMaxMixerVolume;
byte mt32VolSysEx[9] = { 0x41, 0x10, 0x16, 0x12, 0x10, 0x00, 0x16, vol, 0x00 };
byte chk = 0;
for (int i = 4; i < 8; ++i)
chk += mt32VolSysEx[i];
mt32VolSysEx[8] = 0x80 - (chk & 0x7f);
_midi->sysEx(mt32VolSysEx, sizeof(mt32VolSysEx));
} else {
uint16 vol = _musicVolume * 0x3FFF / Audio::Mixer::kMaxMixerVolume;
byte vl = vol & 0x7F;
byte vh = (vol >> 7) & 0x7F;
byte gmVolSysEx[6] = { 0x7F, 0x7F, 0x04, 0x01, vl, vh };
_midi->sysEx(gmVolSysEx, sizeof(gmVolSysEx));
}
g_system->delayMillis(40);
}
// This is not identical to the one we have in our common code (not even similar).
const uint8 CapcomPC98Player_MIDI::_programMapping_mt32ToGM[128] = {
0x00, 0x02, 0x01, 0x03, 0x04, 0x05, 0x10, 0x13, 0x16, 0x65, 0x0a, 0x00, 0x68, 0x67, 0x2e, 0x25,
0x08, 0x09, 0x0a, 0x0c, 0x0d, 0x0e, 0x57, 0x38, 0x3b, 0x3c, 0x3d, 0x3e, 0x3b, 0x3b, 0x3b, 0x1f,
0x3d, 0x1c, 0x1c, 0x1c, 0x1c, 0x1e, 0x1f, 0x1f, 0x35, 0x38, 0x37, 0x38, 0x36, 0x33, 0x39, 0x70,
0x30, 0x30, 0x31, 0x22, 0x22, 0x22, 0x22, 0x7a, 0x58, 0x5a, 0x5e, 0x59, 0x5b, 0x60, 0x60, 0x1a,
0x51, 0x4f, 0x4e, 0x50, 0x54, 0x55, 0x56, 0x52, 0x4a, 0x49, 0x4c, 0x4d, 0x6e, 0x6b, 0x6d, 0x6c,
0x2f, 0x2f, 0x5e, 0x52, 0x57, 0x22, 0x56, 0x38, 0x20, 0x24, 0x5d, 0x22, 0x21, 0x5d, 0x4d, 0x5d,
0x29, 0x24, 0x66, 0x39, 0x22, 0x65, 0x22, 0x5c, 0x57, 0x69, 0x6a, 0x69, 0x6c, 0x6d, 0x0f, 0x35,
0x70, 0x71, 0x72, 0x76, 0x75, 0x74, 0x73, 0x77, 0x78, 0x79, 0x7a, 0x7c, 0x7b, 0x7d, 0x7e, 0x7f
};
CapcomPC98_FMChannel::CapcomPC98_FMChannel(uint8 id, PC98AudioCore *&ac, const Common::Array<const uint8*>&instruments, const uint8 &fadeState) : _regOffset(id), _ac(ac), _instruments(instruments),
_isPlaying(false), _note(0), _carrier(0), _volume(0), _velocity(0), _noteEffCur(0), _program(0), _noteEffPrev(0), _breathControl(0), _frequency(0), _modWheel(0), _pitchBendSensitivity(0),
_pitchBendPara(0), _pitchBendEff(0), _vbrState(0), _vbrStep(0), _vbrCycleTicker(0), _vbrDelayTicker(0), _vbrHandler(nullptr), _prtEnable(false),
_prtTime(0), _prtState(0), _prtStep(0), _prtCycleLength(0), _fadeState(fadeState) {
typedef void (CapcomPC98_FMChannel::*Proc)();
static const Proc procs[] = {
&CapcomPC98_FMChannel::vbrHandler0,
&CapcomPC98_FMChannel::vbrHandler1,
&CapcomPC98_FMChannel::vbrHandler2,
&CapcomPC98_FMChannel::vbrHandler3,
&CapcomPC98_FMChannel::dummyProc
};
assert(ARRAYSIZE(_vbrHandlers) == ARRAYSIZE(procs));
for (int i = 0; i < ARRAYSIZE(_vbrHandlers); ++i) {
_vbrHandlers[i] = new VbrHandlerProc(this, procs[i]);
assert(_vbrHandlers[i]);
}
memset(&_instrument, 0, sizeof(_instrument));
_instrument.regData = new uint8[52];
memset(_instrument.regData, 0, 52);
reset();
}
CapcomPC98_FMChannel::~CapcomPC98_FMChannel() {
for (int i = 0; i < ARRAYSIZE(_vbrHandlers); ++i)
delete _vbrHandlers[i];
delete[] _instrument.regData;
}
void CapcomPC98_FMChannel::reset() {
_vbrHandler = _vbrHandlers[4];
_frequency = 0xFFFF;
_vbrState = 0;
_prtState = 0;
_prtCycleLength = 0;
_prtEnable = false;
_pitchBendSensitivity = 3072;
_pitchBendEff = 0;
_pitchBendPara = 0;
_isPlaying = false;
_note = 0xFF;
_volume = 0x7F;
_velocity = _breathControl = 0x40;
_noteEffCur = 60;
_modWheel = 0;
}
void CapcomPC98_FMChannel::keyOff() {
_ac->writeReg(0, 0x28, _regOffset);
}
void CapcomPC98_FMChannel::noteOff(uint8 note) {
if (!_isPlaying || _note != note)
return;
keyOff();
_isPlaying = false;
}
void CapcomPC98_FMChannel::noteOn(uint8 note, uint8 velo) {
_noteEffPrev = _noteEffCur;
_note = note;
_velocity = velo;
if (note > 11)
note -= 12;
if (note > 127)
note += 12;
_noteEffCur = note;
if (!_isPlaying && _instrument.resetEffect)
setupVibrato();
setupPortamento();
updateFrequency();
if (!_isPlaying) {
updateVolume();
_ac->writeReg(0, 0x28, _regOffset | 0xF0);
}
_isPlaying = true;
}
void CapcomPC98_FMChannel::programChange(uint8 prg) {
if (prg >= _instruments.size())
return;
_program = prg;
loadInstrument(_instruments[prg]);
_ac->writeReg(0, 0xB0 + _regOffset, _instrument.fbl_alg);
static const uint8 carriers[] = { 0x08, 0x08, 0x08, 0x08, 0x0C, 0x0E, 0x0E, 0x0F };
_carrier = carriers[_instrument.fbl_alg & 7];
const uint8 *s = _instrument.regData;
for (int i = _regOffset; i < 16 + _regOffset; i += 4) {
_ac->writeReg(0, 0x30 + i, s[0]);
_ac->writeReg(0, 0x50 + i, s[2]);
_ac->writeReg(0, 0x60 + i, s[3]);
_ac->writeReg(0, 0x70 + i, s[4]);
_ac->writeReg(0, 0x80 + i, s[5]);
_ac->writeReg(0, 0x90 + i, s[6]);
s += 13;
}
setupVibrato();
}
void CapcomPC98_FMChannel::pitchBend(uint16 pb) {
_pitchBendPara = (pb - 0x2000) << 2;
updatePitchBend();
}
void CapcomPC98_FMChannel::restore() {
programChange(_program);
_frequency = 0xFFFF;
}
void CapcomPC98_FMChannel::modWheel(uint8 mw) {
_modWheel = mw;
}
void CapcomPC98_FMChannel::breathControl(uint8 bc) {
_breathControl = bc;
updateVolume();
}
void CapcomPC98_FMChannel::pitchBendSensitivity(uint8 pbs) {
_pitchBendSensitivity = pbs;
pitchBend(_pitchBendPara);
}
void CapcomPC98_FMChannel::portamentoTime(uint8 pt) {
_prtTime = pt;
}
void CapcomPC98_FMChannel::volume(uint8 vol) {
_volume = vol;
if (_isPlaying)
updateVolume();
}
void CapcomPC98_FMChannel::togglePortamento(uint8 enable) {
_prtEnable = (enable >= 0x40);
}
void CapcomPC98_FMChannel::allNotesOff() {
_isPlaying = false;
for (int i = _regOffset; i < 16 + _regOffset; i += 4) {
_ac->writeReg(0, 0x40 + i, 0x7F);
_ac->writeReg(0, 0x80 + i, 0xFF);
}
_ac->writeReg(0, 0x28, _regOffset);
}
void CapcomPC98_FMChannel::processSounds() {
if (!_isPlaying)
return;
if (_vbrHandler->isValid())
(*_vbrHandler)();
updatePortamento();
updateFrequency();
if (_fadeState)
updateVolume();
}
void CapcomPC98_FMChannel::loadInstrument(const uint8 *in) {
memcpy(_instrument.name, in, 8);
in += 8;
_instrument.fbl_alg = *in++;
_instrument.vbrType = *in++;
_instrument.vbrCycleLength = *in++;
_instrument.vbrSensitivity = *in++;
_instrument.resetEffect = *in++;
_instrument.vbrDelay = *in++;
/*_instrument.ff = **/in++;
/*_instrument.f10 = **/in++;
/*_instrument.f11 = **/in++;
/*_instrument.f12 = **/in++;
/*_instrument.f13 = **/in++;
/*_instrument.f14 = **/in++;
assert(_instrument.regData);
memcpy(_instrument.regData, in, 52);
}
void CapcomPC98_FMChannel::updatePitchBend() {
_pitchBendEff = (_pitchBendPara * (_pitchBendSensitivity << 1)) >> 16;
updateFrequency();
}
void CapcomPC98_FMChannel::updateVolume() {
uint8 cr = _carrier;
const uint8 *s = _instrument.regData;
for (int i = _regOffset; i < 16 + _regOffset; i += 4) {
uint16 vol = 0;
if (cr & 1) {
vol += _volTableCarrier[_volume];
vol += _fadeState;
}
uint8 a = _breathControl;
uint8 b = s[10];
if (b & 0x80) {
a = ~a & 0x7F;
b &= 0x7F;
}
vol += (((_volTablePara[a] * b) & 0x7FFF) >> 7);
a = _velocity;
b = s[7];
if (b & 0x80) {
a = ~a & 0x7F;
b &= 0x7F;
}
vol += (((_volTablePara[a] * b) & 0x7FFF) >> 7);
a = _volTablesInst[s[8] & 3][_noteEffCur];
b = s[9];
if (b & 0x80) {
a = ~a & 0x7F;
b &= 0x7F;
}
vol += (((a * b) & 0x7FFF) >> 7);
vol += s[1];
vol = MIN<uint16>(vol, 0x7F);
_ac->writeReg(0, 0x40 + i, vol & 0xFF);
s += 13;
cr >>= 1;
}
}
void CapcomPC98_FMChannel::updatePortamento() {
if (_prtCycleLength) {
_prtCycleLength--;
_prtState += _prtStep;
} else {
_prtState = 0;
}
}
void CapcomPC98_FMChannel::updateFrequency() {
int16 tone = (MIN<int16>(_modWheel + _instrument.vbrSensitivity, 255) * (_vbrState >> 16)) >> 8;
tone = CLIP<int16>(tone + (_noteEffCur << 8), 0, 0x5FFF);
tone = CLIP<int16>(tone + _pitchBendEff, 0, 0x5FFF);
tone = CLIP<int16>(tone + (_prtState >> 16), 0, 0x5FFF);
uint8 block = ((tone >> 8) / 12) & 7;
uint8 msb = (tone >> 8) % 12;
uint8 lsb = (tone & 0xFF) >> 2;
uint16 freq = (block << 11) + _freqMSBTable[msb] + _freqLSBTables[msb][lsb];
if (_frequency == freq)
return;
_frequency = freq;
_ac->writeReg(0, 0xA4 + _regOffset, freq >> 8);
_ac->writeReg(0, 0xA0 + _regOffset, freq & 0xFF);
}
void CapcomPC98_FMChannel::setupPortamento() {
if (!_prtTime || !_prtEnable) {
_prtCycleLength = 0;
_prtState = 0;
return;
}
int16 diff = (_noteEffCur << 8) - CLIP<int16>((_prtState >> 16) | (_noteEffPrev << 8), 0, 0x5FFF);
_prtCycleLength = _prtTime;
_prtStep = (diff << 16) / _prtTime;
_prtState = (-diff << 16);
}
void CapcomPC98_FMChannel::setupVibrato() {
_vbrHandler = _vbrHandlers[4];
if (_instrument.vbrCycleLength == 0 || _instrument.vbrType > 4)
return;
_vbrDelayTicker = _instrument.vbrDelay;
switch (_instrument.vbrType) {
case 0:
case 4:
_vbrStep = ((_instrument.vbrType == 4 ? 512 : 3072) << 16) / _instrument.vbrCycleLength;
_vbrCycleTicker = 0;
_vbrState = 0;
_vbrHandler = _vbrHandlers[0];
break;
case 1:
_vbrState = _vbrDelayTicker ? 0 : (3072 << 16);
_vbrCycleTicker = 0;
_vbrHandler = _vbrHandlers[1];
break;
case 2:
case 3:
_vbrStep = (6144 << 16) / _instrument.vbrCycleLength;
_vbrState = _vbrDelayTicker ? 0 : ((_instrument.vbrType == 2 ? -3072 : 3072) << 16);
_vbrCycleTicker = _instrument.vbrCycleLength - 1;
_vbrHandler = _vbrHandlers[_instrument.vbrType];
break;
default:
break;
}
}
void CapcomPC98_FMChannel::vbrHandler0() {
if (_vbrDelayTicker) {
_vbrDelayTicker--;
return;
}
if ((_vbrCycleTicker < _instrument.vbrCycleLength) || (_vbrCycleTicker >= _instrument.vbrCycleLength * 3))
_vbrState += _vbrStep;
else
_vbrState -= _vbrStep;
if (++_vbrCycleTicker >= _instrument.vbrCycleLength * 4) {
_vbrCycleTicker = 0;
_vbrState = 0;
}
}
void CapcomPC98_FMChannel::vbrHandler1() {
if (_vbrDelayTicker) {
_vbrDelayTicker--;
return;
}
_vbrState = ((_vbrCycleTicker >= _instrument.vbrCycleLength) ? -3072 : 3072) << 16;
if (++_vbrCycleTicker >= (_instrument.vbrCycleLength << 1))
_vbrCycleTicker = 0;
}
void CapcomPC98_FMChannel::vbrHandler2() {
if (_vbrDelayTicker) {
_vbrDelayTicker--;
return;
}
_vbrState += _vbrStep;
if (++_vbrCycleTicker >= _instrument.vbrCycleLength) {
_vbrCycleTicker = 0;
_vbrState = -(3072 << 16);
}
}
void CapcomPC98_FMChannel::vbrHandler3() {
if (_vbrDelayTicker) {
_vbrDelayTicker--;
return;
}
_vbrState -= _vbrStep;
if (++_vbrCycleTicker >= _instrument.vbrCycleLength) {
_vbrCycleTicker = 0;
_vbrState = 3072 << 16;
}
}
const uint16 CapcomPC98_FMChannel::_freqMSBTable[] = {
0x026a, 0x028f, 0x02b6, 0x02df, 0x030b, 0x0339, 0x036a, 0x039e, 0x03d5, 0x0410, 0x044e, 0x048f
};
const uint8 CapcomPC98_FMChannel::_freqLSBTables[12][64] = {
{
0x00, 0x01, 0x01, 0x02, 0x02, 0x03, 0x04, 0x04, 0x05, 0x05, 0x06, 0x06, 0x07, 0x08, 0x08, 0x09,
0x09, 0x0a, 0x0a, 0x0b, 0x0c, 0x0c, 0x0d, 0x0d, 0x0e, 0x0e, 0x0f, 0x0f, 0x10, 0x11, 0x11, 0x12,
0x12, 0x13, 0x14, 0x14, 0x15, 0x15, 0x16, 0x16, 0x17, 0x18, 0x18, 0x19, 0x19, 0x1a, 0x1a, 0x1b,
0x1c, 0x1c, 0x1d, 0x1d, 0x1e, 0x1f, 0x1f, 0x20, 0x20, 0x21, 0x21, 0x22, 0x23, 0x23, 0x24, 0x24
},
{
0x00, 0x01, 0x01, 0x02, 0x02, 0x03, 0x04, 0x04, 0x05, 0x05, 0x06, 0x07, 0x07, 0x08, 0x08, 0x09,
0x0a, 0x0a, 0x0b, 0x0b, 0x0c, 0x0d, 0x0d, 0x0e, 0x0e, 0x0f, 0x10, 0x10, 0x11, 0x11, 0x12, 0x13,
0x13, 0x14, 0x14, 0x15, 0x16, 0x16, 0x17, 0x17, 0x18, 0x19, 0x19, 0x1a, 0x1b, 0x1b, 0x1c, 0x1c,
0x1d, 0x1e, 0x1e, 0x1f, 0x1f, 0x20, 0x21, 0x21, 0x22, 0x23, 0x23, 0x24, 0x24, 0x25, 0x26, 0x26
},
{
0x00, 0x01, 0x01, 0x02, 0x02, 0x03, 0x04, 0x04, 0x05, 0x06, 0x06, 0x07, 0x08, 0x08, 0x09, 0x09,
0x0a, 0x0b, 0x0b, 0x0c, 0x0d, 0x0d, 0x0e, 0x0f, 0x0f, 0x10, 0x10, 0x11, 0x12, 0x12, 0x13, 0x14,
0x14, 0x15, 0x16, 0x16, 0x17, 0x18, 0x18, 0x19, 0x19, 0x1a, 0x1b, 0x1b, 0x1c, 0x1d, 0x1d, 0x1e,
0x1f, 0x1f, 0x20, 0x21, 0x21, 0x22, 0x23, 0x23, 0x24, 0x25, 0x25, 0x26, 0x27, 0x27, 0x28, 0x29
},
{
0x00, 0x01, 0x02, 0x02, 0x03, 0x04, 0x04, 0x05, 0x06, 0x06, 0x07, 0x08, 0x08, 0x09, 0x0a, 0x0a,
0x0b, 0x0c, 0x0c, 0x0d, 0x0e, 0x0e, 0x0f, 0x10, 0x10, 0x11, 0x12, 0x12, 0x13, 0x14, 0x14, 0x15,
0x16, 0x16, 0x17, 0x18, 0x18, 0x19, 0x1a, 0x1b, 0x1b, 0x1c, 0x1d, 0x1d, 0x1e, 0x1f, 0x1f, 0x20,
0x21, 0x21, 0x22, 0x23, 0x24, 0x24, 0x25, 0x26, 0x26, 0x27, 0x28, 0x28, 0x29, 0x2a, 0x2b, 0x2b
},
{
0x00, 0x01, 0x01, 0x02, 0x03, 0x03, 0x04, 0x05, 0x06, 0x06, 0x07, 0x08, 0x08, 0x09, 0x0a, 0x0b,
0x0b, 0x0c, 0x0d, 0x0d, 0x0e, 0x0f, 0x10, 0x10, 0x11, 0x12, 0x12, 0x13, 0x14, 0x15, 0x15, 0x16,
0x17, 0x17, 0x18, 0x19, 0x1a, 0x1a, 0x1b, 0x1c, 0x1d, 0x1d, 0x1e, 0x1f, 0x1f, 0x20, 0x21, 0x22,
0x22, 0x23, 0x24, 0x25, 0x25, 0x26, 0x27, 0x28, 0x28, 0x29, 0x2a, 0x2b, 0x2b, 0x2c, 0x2d, 0x2e
},
{
0x00, 0x01, 0x02, 0x02, 0x03, 0x04, 0x05, 0x05, 0x06, 0x07, 0x08, 0x08, 0x09, 0x0a, 0x0b, 0x0c,
0x0c, 0x0d, 0x0e, 0x0f, 0x0f, 0x10, 0x11, 0x12, 0x12, 0x13, 0x14, 0x15, 0x15, 0x16, 0x17, 0x18,
0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1c, 0x1d, 0x1e, 0x1f, 0x1f, 0x20, 0x21, 0x22, 0x22, 0x23, 0x24,
0x25, 0x26, 0x26, 0x27, 0x28, 0x29, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2d, 0x2e, 0x2f, 0x30, 0x31
},
{
0x00, 0x01, 0x02, 0x03, 0x03, 0x04, 0x05, 0x06, 0x07, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0b, 0x0c,
0x0d, 0x0e, 0x0f, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x18, 0x19,
0x1a, 0x1b, 0x1c, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x25, 0x26,
0x27, 0x28, 0x29, 0x2a, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x33
},
{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0a, 0x0b, 0x0c, 0x0d,
0x0e, 0x0f, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x28,
0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37
},
{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
0x0f, 0x10, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1a, 0x1b, 0x1c,
0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b,
0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a
},
{
0x00, 0x01, 0x02, 0x03, 0x04, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d,
0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d,
0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d
},
{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
0x30, 0x31, 0x32, 0x33, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40
},
{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21,
0x22, 0x23, 0x24, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x32, 0x33,
0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44
}
};
const uint8 CapcomPC98_FMChannel::_volTablesInst[4][128] = {
{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f
},
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x05, 0x0a, 0x0f, 0x14, 0x19, 0x1e, 0x23, 0x28, 0x2d, 0x32, 0x37, 0x3c,
0x41, 0x46, 0x4b, 0x50, 0x55, 0x5a, 0x5f, 0x64, 0x69, 0x6e, 0x73, 0x78, 0x7f, 0x7f, 0x7f, 0x7f,
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f
},
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f
},
{
0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f,
0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f,
0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f,
0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f,
0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f,
0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f,
0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f,
0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f
}
};
const uint8 CapcomPC98_FMChannel::_volTableCarrier[] = {
0x2a, 0x2a, 0x29, 0x29, 0x29, 0x28, 0x28, 0x28, 0x27, 0x27, 0x27, 0x26, 0x26, 0x26, 0x25, 0x25,
0x25, 0x24, 0x24, 0x24, 0x23, 0x23, 0x23, 0x22, 0x22, 0x22, 0x21, 0x21, 0x21, 0x20, 0x20, 0x20,
0x1f, 0x1f, 0x1f, 0x1e, 0x1e, 0x1e, 0x1d, 0x1d, 0x1d, 0x1c, 0x1c, 0x1c, 0x1b, 0x1b, 0x1b, 0x1a,
0x1a, 0x1a, 0x19, 0x19, 0x19, 0x18, 0x18, 0x18, 0x17, 0x17, 0x17, 0x16, 0x16, 0x16, 0x15, 0x15,
0x15, 0x14, 0x14, 0x14, 0x13, 0x13, 0x13, 0x12, 0x12, 0x12, 0x11, 0x11, 0x11, 0x10, 0x10, 0x10,
0x0f, 0x0f, 0x0f, 0x0e, 0x0e, 0x0e, 0x0d, 0x0d, 0x0d, 0x0c, 0x0c, 0x0c, 0x0b, 0x0b, 0x0b, 0x0a,
0x0a, 0x0a, 0x09, 0x09, 0x09, 0x08, 0x08, 0x08, 0x07, 0x07, 0x07, 0x06, 0x06, 0x06, 0x05, 0x05,
0x05, 0x04, 0x04, 0x04, 0x03, 0x03, 0x03, 0x02, 0x02, 0x02, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00
};
const uint8 CapcomPC98_FMChannel::_volTablePara[] = {
0x7f, 0x7e, 0x7d, 0x7c, 0x7b, 0x7a, 0x79, 0x78, 0x77, 0x76, 0x75, 0x74, 0x73, 0x72, 0x71, 0x70,
0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x69, 0x68, 0x67, 0x66, 0x65, 0x64, 0x63, 0x62, 0x61, 0x60,
0x5f, 0x5e, 0x5d, 0x5c, 0x5b, 0x5a, 0x59, 0x58, 0x57, 0x56, 0x55, 0x54, 0x53, 0x52, 0x51, 0x50,
0x4f, 0x4e, 0x4d, 0x4c, 0x4b, 0x4a, 0x49, 0x48, 0x47, 0x46, 0x45, 0x44, 0x43, 0x42, 0x41, 0x40,
0x3f, 0x3e, 0x3d, 0x3c, 0x3b, 0x3a, 0x39, 0x38, 0x37, 0x36, 0x35, 0x34, 0x33, 0x32, 0x31, 0x30,
0x2f, 0x2e, 0x2d, 0x2c, 0x2b, 0x2a, 0x29, 0x28, 0x27, 0x26, 0x25, 0x24, 0x23, 0x22, 0x21, 0x20,
0x1f, 0x1e, 0x1d, 0x1c, 0x1b, 0x1a, 0x19, 0x18, 0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10,
0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00
};
CapcomPC98Player_FM::CapcomPC98Player_FM(Audio::Mixer *mixer, CBProc &proc, bool playerPrio, uint16 playFlags, uint8 chanReserveFlags, uint8 chanDisableFlags, uint16 volControlMask, bool needsTimer) : CapcomPC98Player(playerPrio, playFlags, chanReserveFlags, chanDisableFlags), PC98AudioPluginDriver(), _cbProc(proc), _volControlMask(volControlMask), _ac(nullptr), _chan(nullptr), _ready(false) {
_ac = new PC98AudioCore(mixer, needsTimer ? this : nullptr, kType26);
assert(_ac);
_chan = new CapcomPC98_FMChannel*[3];
assert(_chan);
for (int i = 0; i < 3; ++i)
_chan[i] = new CapcomPC98_FMChannel(i, _ac, _instruments, _fadeState);
}
CapcomPC98Player_FM::~CapcomPC98Player_FM() {
delete _ac;
if (_chan) {
for (int i = 0; i < 3; ++i)
delete _chan[i];
delete[] _chan;
}
}
bool CapcomPC98Player_FM::init() {
if (!(_chan && _ac && _ac->init()))
return false;
if (_volControlMask == 0xFFFF)
setVolControlMask();
_ac->writeReg(0, 7, 0xBF);
for (int i = 0; i < 14; ++i) {
if (i != 7)
_ac->writeReg(0, i, 0);
}
static const uint8 iniData[] = { 0x00, 0x7F, 0x1F, 0x1F, 0x1F, 0xFF, 0x00 };
for (int i = 0; i < 7; ++i) {
for (int ii = 0; ii < 16; ++ii) {
if ((ii & 3) != 3)
_ac->writeReg(0, 0x30 + (i << 4) + ii, iniData[i]);
}
}
for (int i = 0; i < 3; ++i)
_ac->writeReg(0, 0xB0 + i, 0xC0);
_ac->writeReg(0, 0x24, 0x91);
_ac->writeReg(0, 0x25, 0x00);
_ac->writeReg(0, 0x27, 0x15);
loadInstruments(_initData, 1);
reset();
_ready = true;
return true;
}
void CapcomPC98Player_FM::deinit() {
PC98AudioCore::MutexLock lock = _ac->stackLockMutex();
_ready = false;
}
void CapcomPC98Player_FM::reset() {
for (int i = 0; i < 3; ++i)
_chan[i]->reset();
for (int i = 0; i < 3; ++i)
_chan[i]->keyOff();
for (int i = 0; i < 3; ++i)
_chan[i]->programChange(0);
}
void CapcomPC98Player_FM::loadInstruments(const uint8 *data, uint16 number) {
_instruments.clear();
while (number--) {
_instruments.push_back(data);
data += 72;
}
}
PC98AudioCore::MutexLock CapcomPC98Player_FM::lockMutex() {
if (!_ready)
error("CapcomPC98_FM::lockMutex(): Invalid call");
return _ac->stackLockMutex();
}
void CapcomPC98Player_FM::send(uint32 evt) {
uint8 ch = evt & 0x0F;
uint8 p1 = (evt >> 8) & 0xFF;
uint8 p2 = (evt >> 16) & 0xFF;
debugC(5, kDebugLevelSound, "CapcomPC98Player_FM::send(): [0x%02x] [0x%02x] [0x%02x]", evt & 0xff, p1, p2);
if (ch > 2)
return;
switch (evt & 0xF0) {
case 0x80:
_chan[ch]->noteOff(p1);
break;
case 0x90:
if (p2)
_chan[ch]->noteOn(p1, p2);
else
_chan[ch]->noteOff(p1);
break;
case 0xB0:
controlChange(ch, p1, p2);
break;
case 0xC0:
_chan[ch]->programChange(p1);
break;
case 0xE0:
_chan[ch]->pitchBend(((p2 & 0x7F) << 7) | (p1 & 0x7F));
break;
default:
break;
}
}
void CapcomPC98Player_FM::timerCallbackA() {
if (_ready && _cbProc.isValid()) {
PC98AudioCore::MutexLock lock = _ac->stackLockMutex();
_cbProc();
}
}
void CapcomPC98Player_FM::processSounds() {
for (int i = 0; i < 3; ++i)
_chan[i]->processSounds();
}
void CapcomPC98Player_FM::updateMasterVolume() {
_ac->setMusicVolume(_musicVolume);
_ac->setSoundEffectVolume(_sfxVolume);
}
void CapcomPC98Player_FM::controlChange(uint8 ch, uint8 control, uint8 val) {
if (ch > 2)
return;
switch (control) {
case 1:
_chan[ch]->modWheel(val);
break;
case 2:
_chan[ch]->breathControl(val);
break;
case 3:
_soundMarkers[ch] = val;
break;
case 4:
_chan[ch]->pitchBendSensitivity(val);
break;
case 5:
_chan[ch]->portamentoTime(val);
break;
case 7:
_chan[ch]->volume(val);
break;
case 65:
_chan[ch]->togglePortamento(val);
break;
case 123:
_chan[ch]->allNotesOff();
break;
default:
break;
}
}
void CapcomPC98Player_FM::restoreStateIntern() {
for (int i = 0; i < 3; ++i) {
if ((1 << i) & _chanReserveFlags)
continue;
_chan[i]->restore();
}
}
void CapcomPC98Player_FM::setVolControlMask() {
_ac->setSoundEffectChanMask(_volControlMask);
}
const uint8 CapcomPC98Player_FM::_initData[72] = {
0x49, 0x4e, 0x49, 0x54, 0x5f, 0x56, 0x4f, 0x49, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x01, 0x7f, 0x1f, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x7f, 0x1f,
0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x7f, 0x1f, 0x00, 0x00, 0x0f, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1f, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
CapcomPC98AudioDriverInternal *CapcomPC98AudioDriverInternal::_refInstance = 0;
int CapcomPC98AudioDriverInternal::_refCount = 0;
CapcomPC98AudioDriverInternal::CapcomPC98AudioDriverInternal(Audio::Mixer *mixer, MidiDriver::DeviceHandle dev) : _ready(false), _fmDevice(nullptr), _timerProc(nullptr), _mutexProc(nullptr), _marker(0), _musicVolume(0), _sfxVolume(0) {
MusicType type = MidiDriver::getMusicType(dev);
_timerProc = new CapcomPC98Player_FM::CBProc(this, &CapcomPC98AudioDriverInternal::timerCallback);
assert(_timerProc);
_mutexProc = new CapcomPC98Player_MIDI::MutexProc(this, &CapcomPC98AudioDriverInternal::lockMutex);
assert(_mutexProc);
if (type == MT_MT32 || type == MT_GM) {
_players[0] = new CapcomPC98Player_MIDI(dev, type == MT_MT32, *_mutexProc);
_players[1] = _fmDevice = new CapcomPC98Player_FM(mixer, *_timerProc, true, CapcomPC98Player::kPrioPlay, 4, (uint8)~4, 0xFFFF, true);
_marker = 1;
} else {
_players[0] = new CapcomPC98Player_FM(mixer, *_timerProc, false, CapcomPC98Player::kStdPlay, 3, 0, 0, false);
_players[1] = _fmDevice = new CapcomPC98Player_FM(mixer, *_timerProc, true, CapcomPC98Player::kPrioPlay | CapcomPC98Player::kPrioClaim, 4, (uint8)~4, 4, true);
}
bool ready = true;
for (int i = 0; i < 2; ++i) {
if (!(_players[i] && _players[i]->init()))
ready = false;
}
_ready = ready;
}
CapcomPC98AudioDriverInternal::~CapcomPC98AudioDriverInternal() {
_ready = false;
for (int i = 0; i < 2; ++i)
_players[i]->deinit();
for (int i = 0; i < 2; ++i)
delete _players[i];
delete _timerProc;
delete _mutexProc;
}
CapcomPC98AudioDriverInternal *CapcomPC98AudioDriverInternal::open(Audio::Mixer *mixer, MidiDriver::DeviceHandle dev) {
_refCount++;
if (_refCount == 1 && _refInstance == 0)
_refInstance = new CapcomPC98AudioDriverInternal(mixer, dev);
else if (_refCount < 2 || _refInstance == 0)
error("CapcomPC98AudioDriverInternal::open(): Internal instance management failure");
return _refInstance;
}
void CapcomPC98AudioDriverInternal::close() {
if (!_refCount)
return;
_refCount--;
if (!_refCount) {
delete _refInstance;
_refInstance = 0;
}
}
void CapcomPC98AudioDriverInternal::reset() {
for (int i = 0; i < 2; ++i)
_players[i]->reset();
}
void CapcomPC98AudioDriverInternal::loadFMInstruments(const uint8 *data) {
for (int i = 0; i < 2; ++i)
_players[i]->loadInstruments(data + 2, READ_LE_UINT16(data));
}
void CapcomPC98AudioDriverInternal::startSong(const uint8 *data, uint8 volume, bool loop) {
stopSong();
if (_ready)
_players[0]->startSound(data, volume, loop);
}
void CapcomPC98AudioDriverInternal::stopSong() {
if (_ready)
_players[0]->stopSound();
}
void CapcomPC98AudioDriverInternal::startSoundEffect(const uint8 *data, uint8 volume) {
stopSoundEffect();
if (_ready)
_players[1]->startSound(data, volume, false);
}
void CapcomPC98AudioDriverInternal::stopSoundEffect() {
if (_ready)
_players[1]->stopSound();
}
int CapcomPC98AudioDriverInternal::checkSoundMarker() const {
return _players[0]->getMarker(_marker);
}
bool CapcomPC98AudioDriverInternal::songIsPlaying() const {
return CapcomPC98Player::getStatus() & CapcomPC98Player::kStdPlay;
}
bool CapcomPC98AudioDriverInternal::soundEffectIsPlaying() const {
return CapcomPC98Player::getStatus() & CapcomPC98Player::kPrioPlay;
}
void CapcomPC98AudioDriverInternal::fadeOut() {
if (!_ready)
return;
for (int i = 0; i < 2; ++i)
_players[i]->fadeOut(1);
}
void CapcomPC98AudioDriverInternal::allNotesOff() {
if (!_ready)
return;
for (int i = 0; i < 2; ++i)
_players[i]->allNotesOff();
}
void CapcomPC98AudioDriverInternal::setMusicVolume(int volume) {
_musicVolume = volume;
updateMasterVolume();
}
void CapcomPC98AudioDriverInternal::setSoundEffectVolume(int volume) {
_sfxVolume = volume;
updateMasterVolume();
}
void CapcomPC98AudioDriverInternal::timerCallback() {
for (int i = 0; i < 2; ++i)
_players[i]->nextTick();
}
PC98AudioCore::MutexLock CapcomPC98AudioDriverInternal::lockMutex() {
if (!_ready)
error("CapcomPC98AudioDriverInternal::lockMutex(): Invalid call");
return _fmDevice->lockMutex();
}
void CapcomPC98AudioDriverInternal::updateMasterVolume() {
if (!_ready)
return;
for (int i = 0; i < 2; ++i) {
_players[i]->setMusicVolume(_musicVolume);
_players[i]->setSoundEffectVolume(_sfxVolume);
}
}
CapcomPC98AudioDriver::CapcomPC98AudioDriver(Audio::Mixer *mixer, MidiDriver::DeviceHandle dev) {
_drv = CapcomPC98AudioDriverInternal::open(mixer, dev);
}
CapcomPC98AudioDriver::~CapcomPC98AudioDriver() {
CapcomPC98AudioDriverInternal::close();
_drv = 0;
}
bool CapcomPC98AudioDriver::isUsable() const {
return (_drv && _drv->isUsable());
}
void CapcomPC98AudioDriver::reset() {
if (_drv)
_drv->reset();
}
void CapcomPC98AudioDriver::loadFMInstruments(const uint8 *data) {
if (_drv)
_drv->loadFMInstruments(data);
}
void CapcomPC98AudioDriver::startSong(const uint8 *data, uint8 volume, bool loop) {
if (_drv)
_drv->startSong(data, volume, loop);
}
void CapcomPC98AudioDriver::stopSong() {
if (_drv)
_drv->stopSong();
}
void CapcomPC98AudioDriver::startSoundEffect(const uint8 *data, uint8 volume) {
if (_drv)
_drv->startSoundEffect(data, volume);
}
void CapcomPC98AudioDriver::stopSoundEffect() {
if (_drv)
_drv->stopSoundEffect();
}
int CapcomPC98AudioDriver::checkSoundMarker() const {
return _drv ? _drv->checkSoundMarker() : 99;
}
bool CapcomPC98AudioDriver::songIsPlaying() const {
return _drv ? _drv->songIsPlaying() : false;
}
bool CapcomPC98AudioDriver::soundEffectIsPlaying() const {
return _drv ? _drv->soundEffectIsPlaying() : false;
}
void CapcomPC98AudioDriver::fadeOut() {
if (_drv)
_drv->fadeOut();
}
void CapcomPC98AudioDriver::allNotesOff() {
if (_drv)
_drv->allNotesOff();
}
void CapcomPC98AudioDriver::setMusicVolume(int volume) {
if (_drv)
_drv->setMusicVolume(volume);
}
void CapcomPC98AudioDriver::setSoundEffectVolume(int volume) {
if (_drv)
_drv->setSoundEffectVolume(volume);
}
} // End of namespace Kyra
#endif