Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

View File

@@ -0,0 +1,314 @@
/* 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/pc98_audio.h"
#include "audio/softsynth/fmtowns_pc98/towns_pc98_fmsynth.h"
#include "common/mutex.h"
class PC98AudioCoreInternal final : public TownsPC98_FmSynth {
private:
PC98AudioCoreInternal(Audio::Mixer *mixer, PC98AudioCore *owner, PC98AudioPluginDriver *driver, PC98AudioPluginDriver::EmuType type);
public:
~PC98AudioCoreInternal();
static PC98AudioCoreInternal *addNewRef(Audio::Mixer *mixer, PC98AudioCore *owner, PC98AudioPluginDriver *driver, PC98AudioPluginDriver::EmuType type);
static void releaseRef(PC98AudioCore *owner);
bool init() override;
void writePort(uint16 port, uint8 value);
uint8 readPort(uint16 port);
void setMusicVolume(int volume);
void setSoundEffectVolume(int volume);
// Defines the channels used as sound effect channels for the purpose of ScummVM GUI volume control.
// The first 6 bits are 6 fm channels. The next 3 bits are ssg channels. The next bit is the rhythm channel.
void setSoundEffectChanMask(int mask);
void ssgSetVolume(int volume);
Common::Mutex &mutex();
int mixerThreadLockCounter() const;
private:
bool assignPluginDriver(PC98AudioCore *owner, PC98AudioPluginDriver *driver, bool externalMutexHandling = false);
void removePluginDriver(PC98AudioCore *owner);
void timerCallbackA() override;
void timerCallbackB() override;
uint16 _musicVolume;
uint16 _sfxVolume;
const uint16 _port1, _port2, _port3, _port4;
uint8 _address[2];
PC98AudioPluginDriver *_drv;
void *_drvOwner;
bool _ready;
static PC98AudioCoreInternal *_refInstance;
static int _refCount;
};
PC98AudioCoreInternal::PC98AudioCoreInternal(Audio::Mixer *mixer, PC98AudioCore *owner, PC98AudioPluginDriver *driver, PC98AudioPluginDriver::EmuType type) :
TownsPC98_FmSynth(mixer, (TownsPC98_FmSynth::EmuType)type),
_drv(driver), _drvOwner(owner),
_musicVolume(Audio::Mixer::kMaxMixerVolume), _sfxVolume(Audio::Mixer::kMaxMixerVolume),
_port1(type == PC98AudioPluginDriver::kTypeTowns ? 0x4D8 : 0x188), _port2(type == PC98AudioPluginDriver::kTypeTowns ? 0x4DA : 0x18A),
_port3(type == PC98AudioPluginDriver::kTypeTowns ? 0x4DC : 0x18C), _port4(type == PC98AudioPluginDriver::kTypeTowns ? 0x4DE : 0x18E),
_ready(false) {
_address[0] = _address[1] = 0xFF;
}
PC98AudioCoreInternal::~PC98AudioCoreInternal() {
deinit();
Common::StackLock lock(_mutex);
_ready = false;
/*
*/
}
PC98AudioCoreInternal *PC98AudioCoreInternal::addNewRef(Audio::Mixer *mixer, PC98AudioCore *owner, PC98AudioPluginDriver *driver, PC98AudioPluginDriver::EmuType type) {
_refCount++;
if (_refCount == 1 && _refInstance == nullptr)
_refInstance = new PC98AudioCoreInternal(mixer, owner, driver, type);
else if (_refCount < 2 || _refInstance == nullptr)
error("PC98AudioCoreInternal::addNewRef(): Internal reference management failure");
else if (!_refInstance->assignPluginDriver(owner, driver))
error("PC98AudioCoreInternal::addNewRef(): Plugin driver conflict");
return _refInstance;
}
void PC98AudioCoreInternal::releaseRef(PC98AudioCore *owner) {
if (!_refCount)
return;
_refCount--;
if (_refCount) {
if (_refInstance)
_refInstance->removePluginDriver(owner);
} else {
delete _refInstance;
_refInstance = nullptr;
}
}
bool PC98AudioCoreInternal::init() {
if (_ready)
return true;
if (!TownsPC98_FmSynth::init())
return false;
reset();
writeReg(0, 0x26, 0xDD);
writeReg(0, 0x25, 0x01);
writeReg(0, 0x24, 0x00);
writeReg(0, 0x27, 0x30);
setVolumeChannelMasks(-1, 0);
ssgSetVolume(0x60);
_ready = true;
return true;
}
void PC98AudioCoreInternal::writePort(uint16 port, uint8 value) {
Common::StackLock lock(_mutex);
if (port == _port1)
_address[0] = value;
else if (port == _port2 && _address[0] < 0xc0) {
writeReg(0, _address[0], value);
_address[0] = 0xFF;
} else if (port == _port3)
_address[1] = value;
else if (port == _port4 && _address[1] < 0xc0) {
writeReg(1, _address[1], value);
_address[1] = 0xFF;
}
}
uint8 PC98AudioCoreInternal::readPort(uint16 port) {
Common::StackLock lock(_mutex);
uint8 val = 0;
if (port == _port2 && _address[0] < 0xc0) {
val = readReg(0, _address[0]);
_address[0] = 0xFF;
} else if (port == _port4 && _address[1] < 0xc0) {
val = readReg(1, _address[1]);
_address[1] = 0xFF;
}
return val;
}
void PC98AudioCoreInternal::setMusicVolume(int volume) {
Common::StackLock lock(_mutex);
_musicVolume = CLIP<uint16>(volume, 0, Audio::Mixer::kMaxMixerVolume);
setVolumeIntern(_musicVolume, _sfxVolume);
}
void PC98AudioCoreInternal::setSoundEffectVolume(int volume) {
Common::StackLock lock(_mutex);
_sfxVolume = CLIP<uint16>(volume, 0, Audio::Mixer::kMaxMixerVolume);
setVolumeIntern(_musicVolume, _sfxVolume);
}
void PC98AudioCoreInternal::setSoundEffectChanMask(int mask) {
Common::StackLock lock(_mutex);
setVolumeChannelMasks(~mask, mask);
}
void PC98AudioCoreInternal::ssgSetVolume(int volume) {
Common::StackLock lock(_mutex);
setLevelSSG(volume);
}
Common::Mutex &PC98AudioCoreInternal::mutex() {
return _mutex;
}
int PC98AudioCoreInternal::mixerThreadLockCounter() const {
return _mixerThreadLockCounter;
}
bool PC98AudioCoreInternal::assignPluginDriver(PC98AudioCore *owner, PC98AudioPluginDriver *driver, bool externalMutexHandling) {
Common::StackLock lock(_mutex);
if (_refCount <= 1)
return true;
if (_drv) {
if (driver && driver != _drv)
return false;
} else {
_drv = driver;
_drvOwner = owner;
}
return true;
}
void PC98AudioCoreInternal::removePluginDriver(PC98AudioCore *owner) {
Common::StackLock lock(_mutex);
if (_drvOwner == owner)
_drv = nullptr;
}
void PC98AudioCoreInternal::timerCallbackA() {
if (_drv && _ready)
_drv->timerCallbackA();
}
void PC98AudioCoreInternal::timerCallbackB() {
if (_drv && _ready)
_drv->timerCallbackB();
}
PC98AudioCoreInternal *PC98AudioCoreInternal::_refInstance = nullptr;
int PC98AudioCoreInternal::_refCount = 0;
PC98AudioCore::PC98AudioCore(Audio::Mixer *mixer, PC98AudioPluginDriver *driver, PC98AudioPluginDriver::EmuType type) {
_internal = PC98AudioCoreInternal::addNewRef(mixer, this, driver, type);
}
PC98AudioCore::~PC98AudioCore() {
PC98AudioCoreInternal::releaseRef(this);
_internal = nullptr;
}
bool PC98AudioCore::init() {
return _internal->init();
}
void PC98AudioCore::reset() {
_internal->reset();
}
void PC98AudioCore::writeReg(uint8 part, uint8 regAddress, uint8 value) {
_internal->writeReg(part, regAddress, value);
}
uint8 PC98AudioCore::readReg(uint8 part, uint8 regAddress) {
return _internal->readReg(part, regAddress);
}
void PC98AudioCore::writePort(uint16 port, uint8 value) {
_internal->writePort(port, value);
}
uint8 PC98AudioCore::readPort(uint16 port) {
return _internal->readPort(port);
}
void PC98AudioCore::setMusicVolume(int volume) {
_internal->setMusicVolume(volume);
}
void PC98AudioCore::setSoundEffectVolume(int volume) {
_internal->setSoundEffectVolume(volume);
}
void PC98AudioCore::setSoundEffectChanMask(int mask) {
_internal->setSoundEffectChanMask(mask);
}
void PC98AudioCore::ssgSetVolume(int volume) {
_internal->ssgSetVolume(volume);
}
PC98AudioCore::MutexLock PC98AudioCore::stackLockMutex() {
return MutexLock(_internal);
}
PC98AudioCore::MutexLock PC98AudioCore::stackUnlockMutex() {
return MutexLock(_internal, _internal->mixerThreadLockCounter());
}
PC98AudioCore::MutexLock::MutexLock(PC98AudioCoreInternal *pc98int, int reverse) : _pc98int(pc98int), _count(reverse) {
if (!_pc98int)
return;
if (!reverse) {
_pc98int->mutex().lock();
return;
}
while (reverse--)
_pc98int->mutex().unlock();
}
PC98AudioCore::MutexLock::~MutexLock() {
if (!_pc98int)
return;
if (!_count)
_pc98int->mutex().unlock();
while (_count--)
_pc98int->mutex().lock();
}

View File

@@ -0,0 +1,85 @@
/* 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 PC98_AUDIO_H
#define PC98_AUDIO_H
#include "common/scummsys.h"
namespace Audio {
class Mixer;
}
class PC98AudioCoreInternal;
class PC98AudioPluginDriver {
public:
enum EmuType {
kTypeTowns = 0,
kType26 = 1,
kType86 = 2
};
virtual ~PC98AudioPluginDriver() {}
virtual void timerCallbackA() {}
virtual void timerCallbackB() {}
};
class PC98AudioCore {
public:
PC98AudioCore(Audio::Mixer *mixer, PC98AudioPluginDriver *driver, PC98AudioPluginDriver::EmuType type);
~PC98AudioCore();
bool init();
void reset();
void writeReg(uint8 part, uint8 regAddress, uint8 value);
uint8 readReg(uint8 part, uint8 regAddress);
void writePort(uint16 port, uint8 value);
uint8 readPort(uint16 port);
void setMusicVolume(int volume);
void setSoundEffectVolume(int volume);
// Defines the channels used as sound effect channels for the purpose of ScummVM GUI volume control.
// The first 6 bits are the 6 fm channels. The next 3 bits are ssg channels. The next bit is the rhythm channel.
void setSoundEffectChanMask(int mask);
void ssgSetVolume(int volume);
class MutexLock {
friend class PC98AudioCore;
public:
~MutexLock();
private:
MutexLock(PC98AudioCoreInternal *pc98int, int reverse = 0);
PC98AudioCoreInternal *_pc98int;
int _count;
};
MutexLock stackLockMutex();
MutexLock stackUnlockMutex();
private:
PC98AudioCoreInternal *_internal;
};
#endif

View File

@@ -0,0 +1,167 @@
/* 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/pcm_common.h"
#include "audio/mixer.h"
PCMChannel_Base::PCMChannel_Base() : _vol(0), _data(nullptr), _dataEnd(0), _loopLen(0), _pos(0), _loopStart(0), _step(0), _panLeft(7), _panRight(7), _activeOutput(false) {
}
PCMChannel_Base::~PCMChannel_Base() {
clear();
}
void PCMChannel_Base::clear() {
_vol = 0;
_data = nullptr;
_dataEnd = 0;
_loopLen = 0;
_pos = 0;
_loopStart = 0;
_step = 0;
_panLeft = _panRight = 7;
_activeOutput = false;
}
void PCMChannel_Base::updateOutput() {
if (!isPlaying())
return;
_pos += _step;
if (_pos >= _dataEnd) {
if (_loopStart != _dataEnd) {
_pos = _loopStart;
_dataEnd = _loopStart + _loopLen;
} else {
_pos = 0;
stopInternal();
}
}
}
int32 PCMChannel_Base::currentSampleLeft() {
return (isActive() && _panLeft) ? (((_data[_pos >> 11] * _vol) * _panLeft) >> 3) : 0;
}
int32 PCMChannel_Base::currentSampleRight() {
return (isActive() && _panRight) ? (((_data[_pos >> 11] * _vol) * _panRight) >> 3) : 0;
}
bool PCMChannel_Base::isActive() const {
return _activeOutput;
}
void PCMChannel_Base::activate() {
_activeOutput = true;
}
void PCMChannel_Base::deactivate() {
_activeOutput = false;
}
void PCMChannel_Base::setData(const int8 *data, uint32 dataEnd, uint32 dataStart) {
_data = data;
_dataEnd = dataEnd;
_pos = dataStart;
}
void PCMChannel_Base::setVolume(uint8 vol) {
_vol = vol;
}
void PCMChannel_Base::setPanPos(uint8 pan) {
_panLeft = pan & 0x0f;
_panRight = pan >> 4;
}
void PCMChannel_Base::setupLoop(uint32 loopStart, uint32 loopLen) {
_loopStart = loopStart << 11;
_loopLen = loopLen << 11;
}
void PCMChannel_Base::setRate(uint16 rate) {
_step = rate;
}
PCMDevice_Base::PCMDevice_Base(int samplingRate, int deviceVolume, int numChannels) : _numChannels(numChannels), _deviceVolume(deviceVolume), _intRate((7670454 << 8) / samplingRate),
_extRate(72 << 8), _timer(0), _musicVolume(Audio::Mixer::kMaxMixerVolume), _sfxVolume(Audio::Mixer::kMaxMixerVolume), _pcmSfxChanMask(0) {
_channels = new PCMChannel_Base*[numChannels];
}
PCMDevice_Base::~PCMDevice_Base() {
delete[] _channels;
}
void PCMDevice_Base::assignChannel(uint8 id, PCMChannel_Base *const chan) {
assert(id < _numChannels);
_channels[id] = chan;
}
void PCMDevice_Base::setMusicVolume(uint16 vol) {
_musicVolume = vol;
}
void PCMDevice_Base::setSfxVolume(uint16 vol) {
_sfxVolume = vol;
}
void PCMDevice_Base::setSfxChanMask(int mask) {
_pcmSfxChanMask = mask;
}
void PCMDevice_Base::readBuffer(int32 *buffer, uint32 bufferSize) {
for (uint32 i = 0; i < bufferSize; i++) {
_timer += _extRate;
while (_timer >= _intRate) {
_timer -= _intRate;
for (int ii = 0; ii < 8; ii++)
_channels[ii]->updateOutput();
}
int32 finOutL = 0;
int32 finOutR = 0;
for (int ii = 0; ii < _numChannels; ii++) {
if (_channels[ii]->isActive()) {
int32 oL = _channels[ii]->currentSampleLeft();
int32 oR = _channels[ii]->currentSampleRight();
if ((1 << ii) & (~_pcmSfxChanMask)) {
oL = (oL * _musicVolume) / Audio::Mixer::kMaxMixerVolume;
oR = (oR * _musicVolume) / Audio::Mixer::kMaxMixerVolume;
}
if ((1 << ii) & _pcmSfxChanMask) {
oL = (oL * _sfxVolume) / Audio::Mixer::kMaxMixerVolume;
oR = (oR * _sfxVolume) / Audio::Mixer::kMaxMixerVolume;
}
finOutL += oL;
finOutR += oR;
if (!_channels[ii]->isPlaying())
_channels[ii]->deactivate();
}
}
buffer[i << 1] += ((finOutL * _deviceVolume) >> 7);
buffer[(i << 1) + 1] += ((finOutR * _deviceVolume) >> 7);
}
}

View File

@@ -0,0 +1,95 @@
/* 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 PCM_COMMON_H
#define PCM_COMMON_H
#include "common/scummsys.h"
// The SegaCD and the FM-Towns have (almost) the same PCM sound chip. And while each platform has low-level driver
// stuff going on top of that it still makes sense to identify and abstract some commmon code.
class PCMChannel_Base {
public:
PCMChannel_Base();
virtual ~PCMChannel_Base();
virtual void clear();
void updateOutput();
int32 currentSampleLeft();
int32 currentSampleRight();
virtual bool isPlaying() const = 0;
bool isActive() const;
void activate();
void deactivate();
protected:
void setData(const int8 *data, uint32 dataEnd, uint32 dataStart = 0);
void setVolume(uint8 vol);
void setPanPos(uint8 setPanPos);
void setupLoop(uint32 loopStart, uint32 loopLen);
void setRate(uint16 rate);
private:
virtual void stopInternal() = 0;
uint8 _panLeft;
uint8 _panRight;
uint8 _vol;
bool _activeOutput;
uint32 _loopStart;
uint32 _loopLen;
uint32 _dataEnd;
uint32 _pos;
uint16 _step;
const int8 *_data;
};
class PCMDevice_Base {
public:
PCMDevice_Base(int samplingRate, int deviceVolume, int numChannels);
~PCMDevice_Base();
void assignChannel(uint8 id, PCMChannel_Base *const chan);
void setMusicVolume(uint16 vol);
void setSfxVolume(uint16 vol);
void setSfxChanMask(int mask);
void readBuffer(int32 *buffer, uint32 bufferSize);
private:
const uint32 _intRate;
const uint32 _extRate;
const int _deviceVolume;
uint32 _timer;
uint16 _musicVolume;
uint16 _sfxVolume;
int _pcmSfxChanMask;
PCMChannel_Base **_channels;
const int _numChannels;
};
#endif

View File

@@ -0,0 +1,511 @@
/* 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/sega_audio.h"
#include "audio/softsynth/fmtowns_pc98/towns_pc98_fmsynth.h"
#include "audio/softsynth/fmtowns_pc98/pcm_common.h"
#include "common/mutex.h"
class SegaPCMChannel final : public PCMChannel_Base {
public:
SegaPCMChannel() : PCMChannel_Base(), _playing(false) {}
~SegaPCMChannel() override {}
void play(const int8 *data, uint16 dataSize, uint16 startAddress, uint16 loopStart, uint16 loopLen, uint16 pitch, uint8 pan, uint8 vol);
void stop();
bool isPlaying() const override;
private:
void stopInternal() override;
bool _playing;
};
class SegaPSG {
public:
SegaPSG(int samplingRate, int deviceVolume);
~SegaPSG() {}
void write(uint8 val);
void setMusicVolume(uint16 vol);
void setSfxVolume(uint16 vol);
void setSfxChanMask(int mask);
void readBuffer(int32 *buffer, uint32 bufferSize);
private:
struct Channel {
Channel() : freq(0), curSample(0), counter(0), out(0) {}
uint16 freq;
int16 curSample;
int16 out;
uint16 counter;
};
Channel _channels[3];
uint8 _nfb;
uint8 _nfs;
uint8 _nat;
int _cr;
int16 _attnTable[16];
uint16 _musicVolume;
uint16 _sfxVolume;
int _sfxChanMask;
const uint32 _extRate;
const uint32 _intRate;
uint32 _timer;
const uint16 _deviceVolume;
const uint8 _numChannels;
};
class SegaAudioInterfaceInternal final : public TownsPC98_FmSynth {
private:
SegaAudioInterfaceInternal(Audio::Mixer *mixer, SegaAudioInterface *owner, SegaAudioPluginDriver *driver);
public:
~SegaAudioInterfaceInternal();
static SegaAudioInterfaceInternal *addNewRef(Audio::Mixer *mixer, SegaAudioInterface *owner, SegaAudioPluginDriver *driver);
static void releaseRef(SegaAudioInterface *owner);
bool init() override;
void loadPCMData(uint16 address, const uint8 *data, uint16 dataLen);
void playPCMChannel(uint8 channel, uint8 dataStart, uint16 loopStart, uint16 pitch, uint8 pan, uint8 vol);
void stopPCMChannel(uint8 channel);
void psgWrite(uint8 data);
void setMusicVolume(int volume);
void setSoundEffectVolume(int volume);
// Defines the channels used as sound effect channels for the purpose of ScummVM GUI volume control.
// The first 6 bits are 6 fm channels. The next 3 bits are psg channels. The bits that follow represent pcm channels.
void setSoundEffectChanMask(int mask);
Common::Mutex &mutex();
int mixerThreadLockCounter() const;
private:
bool assignPluginDriver(SegaAudioInterface *owner, SegaAudioPluginDriver *driver, bool externalMutexHandling = false);
void removePluginDriver(SegaAudioInterface *owner);
void nextTickEx(int32 *buffer, uint32 bufferSize) override;
void timerCallbackA() override;
void timerCallbackB() override;
uint16 pcmCountSamples(uint16 address) const;
int8 *_pcmBanks;
uint16 _musicVolume;
uint16 _sfxVolume;
SegaPCMChannel **_pcmChan;
SegaPSG *_psgDev;
PCMDevice_Base *_pcmDev;
SegaAudioPluginDriver *_drv;
void *_drvOwner;
bool _ready;
static SegaAudioInterfaceInternal *_refInstance;
static int _refCount;
};
SegaAudioInterfaceInternal *SegaAudioInterfaceInternal::_refInstance = nullptr;
int SegaAudioInterfaceInternal::_refCount = 0;
SegaAudioInterfaceInternal::SegaAudioInterfaceInternal(Audio::Mixer *mixer, SegaAudioInterface *owner, SegaAudioPluginDriver *driver) :TownsPC98_FmSynth(mixer, TownsPC98_FmSynth::kTypeTowns),
_drv(driver), _drvOwner(owner), _musicVolume(Audio::Mixer::kMaxMixerVolume), _sfxVolume(Audio::Mixer::kMaxMixerVolume), _pcmBanks(nullptr), _pcmDev(nullptr), _psgDev(nullptr), _pcmChan(nullptr), _ready(false) {
}
SegaAudioInterfaceInternal::~SegaAudioInterfaceInternal() {
deinit();
Common::StackLock lock(_mutex);
_ready = false;
if (_pcmChan) {
for (int i = 0; i < 8; ++i)
delete _pcmChan[i];
delete[] _pcmChan;
}
delete _pcmDev;
delete _psgDev;
delete[] _pcmBanks;
}
SegaAudioInterfaceInternal *SegaAudioInterfaceInternal::addNewRef(Audio::Mixer *mixer, SegaAudioInterface *owner, SegaAudioPluginDriver *driver) {
_refCount++;
if (_refCount == 1 && _refInstance == nullptr)
_refInstance = new SegaAudioInterfaceInternal(mixer, owner, driver);
else if (_refCount < 2 || _refInstance == nullptr)
error("SegaAudioInterfaceInternal::addNewRef(): Internal reference management failure");
else if (!_refInstance->assignPluginDriver(owner, driver))
error("SegaAudioInterfaceInternal::addNewRef(): Plugin driver conflict");
return _refInstance;
}
void SegaAudioInterfaceInternal::releaseRef(SegaAudioInterface *owner) {
if (!_refCount)
return;
_refCount--;
if (_refCount) {
if (_refInstance)
_refInstance->removePluginDriver(owner);
} else {
delete _refInstance;
_refInstance = nullptr;
}
}
bool SegaAudioInterfaceInternal::init() {
if (_ready)
return true;
if (!TownsPC98_FmSynth::init())
return false;
_pcmBanks = new int8[0x10000]();
_pcmChan = new SegaPCMChannel*[8];
_psgDev = new SegaPSG(7670454 / 72, 16);
_pcmDev = new PCMDevice_Base(33300, 16, 8);
for (int i = 0; i < 8; ++i) {
_pcmChan[i] = new SegaPCMChannel();
_pcmDev->assignChannel(i, _pcmChan[i]);
}
reset();
writeReg(0, 0x26, 0xC6);
writeReg(0, 0x25, 0x62);
writeReg(0, 0x24, 0x00);
writeReg(0, 0x27, 0x30);
// Declare FM channels as music channels and PCM channels as sound effect channels.
setSoundEffectChanMask(~0x1FF);
_ready = true;
return true;
}
void SegaAudioInterfaceInternal::loadPCMData(uint16 address, const uint8 *data, uint16 dataSize) {
if (!_ready)
return;
Common::StackLock lock(_mutex);
while (dataSize--)
_pcmBanks[address++] = (*data & 0x80) ? (*data++ & 0x7F) : -*data++;
}
void SegaAudioInterfaceInternal::playPCMChannel(uint8 channel, uint8 dataStart, uint16 loopStart, uint16 rate, uint8 pan, uint8 vol) {
if (!_ready)
return;
Common::StackLock lock(_mutex);
assert(channel < 8);
_pcmChan[channel]->play(_pcmBanks, pcmCountSamples(dataStart << 8), dataStart << 8, loopStart, pcmCountSamples(loopStart), rate, pan, vol);
}
void SegaAudioInterfaceInternal::stopPCMChannel(uint8 channel) {
if (!_ready)
return;
Common::StackLock lock(_mutex);
assert(channel < 8);
_pcmChan[channel]->stop();
}
void SegaAudioInterfaceInternal::psgWrite(uint8 data) {
if (!_ready)
return;
Common::StackLock lock(_mutex);
_psgDev->write(data);
}
void SegaAudioInterfaceInternal::setMusicVolume(int volume) {
if (!_ready)
return;
Common::StackLock lock(_mutex);
_musicVolume = CLIP<uint16>(volume, 0, Audio::Mixer::kMaxMixerVolume);
_pcmDev->setMusicVolume(_musicVolume);
setVolumeIntern(_musicVolume, _sfxVolume);
}
void SegaAudioInterfaceInternal::setSoundEffectVolume(int volume) {
if (!_ready)
return;
Common::StackLock lock(_mutex);
_sfxVolume = CLIP<uint16>(volume, 0, Audio::Mixer::kMaxMixerVolume);
_pcmDev->setSfxVolume(_sfxVolume);
setVolumeIntern(_musicVolume, _sfxVolume);
}
void SegaAudioInterfaceInternal::setSoundEffectChanMask(int mask) {
if (!_ready)
return;
Common::StackLock lock(_mutex);
_psgDev->setSfxChanMask((mask >> 6) & 7);
_pcmDev->setSfxChanMask(mask >> 9);
mask &= 0x3f;
setVolumeChannelMasks(~mask, mask);
}
Common::Mutex &SegaAudioInterfaceInternal::mutex() {
return _mutex;
}
int SegaAudioInterfaceInternal::mixerThreadLockCounter() const {
return _mixerThreadLockCounter;
}
bool SegaAudioInterfaceInternal::assignPluginDriver(SegaAudioInterface *owner, SegaAudioPluginDriver *driver, bool externalMutexHandling) {
Common::StackLock lock(_mutex);
if (_refCount <= 1)
return true;
if (_drv) {
if (driver && driver != _drv)
return false;
} else {
_drv = driver;
_drvOwner = owner;
}
return true;
}
void SegaAudioInterfaceInternal::removePluginDriver(SegaAudioInterface *owner) {
Common::StackLock lock(_mutex);
if (_drvOwner == owner)
_drv = nullptr;
}
void SegaAudioInterfaceInternal::nextTickEx(int32 *buffer, uint32 bufferSize) {
Common::StackLock lock(_mutex);
if (!_ready)
return;
_pcmDev->readBuffer(buffer, bufferSize);
_psgDev->readBuffer(buffer, bufferSize);
}
void SegaAudioInterfaceInternal::timerCallbackA() {
if (_drv && _ready)
_drv->timerCallbackA();
}
void SegaAudioInterfaceInternal::timerCallbackB() {
if (_drv && _ready)
_drv->timerCallbackB();
}
uint16 SegaAudioInterfaceInternal::pcmCountSamples(uint16 address) const {
const int8 *start = &_pcmBanks[address];
const int8 *end = &_pcmBanks[0xFFFF];
const int8 *pos = start;
for (; pos <= end; ++pos) {
if (*pos == 0x7F)
break;
}
return pos - start;
}
void SegaPCMChannel::play(const int8 *data, uint16 dataSize, uint16 startAddress, uint16 loopStart, uint16 loopLen, uint16 rate, uint8 pan, uint8 vol) {
setData(data, (startAddress + dataSize) << 11, startAddress << 11);
setupLoop(loopLen ? loopStart : (startAddress + dataSize), loopLen);
setRate(rate);
setPanPos(pan);
setVolume(vol);
activate();
_playing = true;
}
void SegaPCMChannel::stop() {
stopInternal();
}
bool SegaPCMChannel::isPlaying() const {
return _playing;
}
void SegaPCMChannel::stopInternal() {
_playing = false;
}
SegaPSG::SegaPSG(int samplingRate, int deviceVolume) : _intRate(3579545), _extRate(samplingRate), _deviceVolume(deviceVolume), _numChannels(3), _cr(-1),
_musicVolume(Audio::Mixer::kMaxMixerVolume), _sfxVolume(Audio::Mixer::kMaxMixerVolume), _sfxChanMask(0), _nfb(0), _nfs(0), _nat(0), _timer(0) {
memset(_attnTable, 0, sizeof(_attnTable));
for (int i = 0; i < 15; ++i)
_attnTable[i] = (32767.0 / (double)(_numChannels + 1)) / pow(2.0, (double)(i << 1) / 6.0);
}
void SegaPSG::write(uint8 val) {
if (val & 0x80) {
uint8 reg = (val >> 4) & 7;
val &= 0x0F;
_cr = -1;
// The noise generator is not implemented, since we don't have a single test case for it.
if (reg == 7) {
_nat = val;
} else if (reg & 1) {
_channels[reg >> 1].curSample = _attnTable[val];
} else if (reg == 6) {
_nfb = val >> 2;
_nfs = val & 3;
} else {
_channels[reg >> 1].freq = (_channels[reg >> 1].freq & 0x3F0) | val;
_cr = reg >> 1;
}
} else if (_cr != -1) {
_channels[_cr].freq = (_channels[_cr].freq & 0x0F) | (val << 4);
}
}
void SegaPSG::setMusicVolume(uint16 vol) {
_musicVolume = vol;
}
void SegaPSG::setSfxVolume(uint16 vol) {
_sfxVolume = vol;
}
void SegaPSG::setSfxChanMask(int mask) {
_sfxChanMask = mask;
}
void SegaPSG::readBuffer(int32 *buffer, uint32 bufferSize) {
while (bufferSize--) {
_timer += _intRate;
while (_timer >= _extRate) {
_timer -= _extRate;
// The noise generator is not implemented, since we don't have a single test case for it.
for (int i = 0; i < _numChannels; ++i) {
Channel *c = &_channels[i];
if (c->counter)
c->counter--;
if (!c->counter) {
c->counter = c->freq << 4;
c->out = c->curSample;
c->curSample = ~c->curSample;
if (c->curSample < 0)
c->curSample++;
}
}
}
int32 smp = 0;
for (int i = 0; i < _numChannels; ++i)
smp += ((_channels[i].out * (((1 << i) & _sfxChanMask) ? _sfxVolume : _musicVolume)) / Audio::Mixer::kMaxMixerVolume);
smp = (smp * _deviceVolume) >> 7;
*buffer++ += smp;
*buffer++ += smp;
}
}
SegaAudioInterface::SegaAudioInterface(Audio::Mixer *mixer, SegaAudioPluginDriver *driver) {
_internal = SegaAudioInterfaceInternal::addNewRef(mixer, this, driver);
}
SegaAudioInterface::~SegaAudioInterface() {
SegaAudioInterfaceInternal::releaseRef(this);
_internal = nullptr;
}
bool SegaAudioInterface::init() {
return _internal->init();
}
void SegaAudioInterface::reset() {
_internal->reset();
}
void SegaAudioInterface::loadPCMData(uint16 address, const uint8 *data, uint16 dataSize) {
_internal->loadPCMData(address, data, dataSize);
}
void SegaAudioInterface::playPCMChannel(uint8 channel, uint8 dataStart, uint16 loopStart, uint16 rate, uint8 pan, uint8 vol) {
_internal->playPCMChannel(channel, dataStart, loopStart, rate, pan, vol);
}
void SegaAudioInterface::stopPCMChannel(uint8 channel) {
_internal->stopPCMChannel(channel);
}
void SegaAudioInterface::writeReg(uint8 part, uint8 regAddress, uint8 value) {
_internal->writeReg(part, regAddress, value);
}
uint8 SegaAudioInterface::readReg(uint8 part, uint8 regAddress) {
return _internal->readReg(part, regAddress);
}
void SegaAudioInterface::psgWrite(uint8 data) {
_internal->psgWrite(data);
}
void SegaAudioInterface::setMusicVolume(int volume) {
_internal->setMusicVolume(volume);
}
void SegaAudioInterface::setSoundEffectVolume(int volume) {
_internal->setSoundEffectVolume(volume);
}
void SegaAudioInterface::setSoundEffectChanMask(int mask) {
_internal->setSoundEffectChanMask(mask);
}
SegaAudioInterface::MutexLock SegaAudioInterface::stackLockMutex() {
return MutexLock(_internal);
}
SegaAudioInterface::MutexLock SegaAudioInterface::stackUnlockMutex() {
return MutexLock(_internal, _internal->mixerThreadLockCounter());
}
SegaAudioInterface::MutexLock::MutexLock(SegaAudioInterfaceInternal *saii, int reverse) : _saii(saii), _count(reverse) {
if (!_saii)
return;
if (!reverse) {
_saii->mutex().lock();
return;
}
while (reverse--)
_saii->mutex().unlock();
}
SegaAudioInterface::MutexLock::~MutexLock() {
if (!_saii)
return;
if (!_count)
_saii->mutex().unlock();
while (_count--)
_saii->mutex().lock();
}

View File

@@ -0,0 +1,81 @@
/* 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 SEGA_AUDIO_H
#define SEGA_AUDIO_H
#include "common/scummsys.h"
namespace Audio {
class Mixer;
}
class SegaAudioInterfaceInternal;
class SegaAudioPluginDriver {
public:
virtual ~SegaAudioPluginDriver() {}
virtual void timerCallback60Hz() {}
virtual void timerCallbackA() {}
virtual void timerCallbackB() {}
};
class SegaAudioInterface {
public:
SegaAudioInterface(Audio::Mixer *mixer, SegaAudioPluginDriver *driver);
~SegaAudioInterface();
bool init();
void reset();
void loadPCMData(uint16 address, const uint8 *data, uint16 dataSize);
void playPCMChannel(uint8 channel, uint8 dataStart, uint16 loopStart, uint16 rate, uint8 pan, uint8 env);
void stopPCMChannel(uint8 channel);
void writeReg(uint8 part, uint8 regAddress, uint8 value);
uint8 readReg(uint8 part, uint8 regAddress);
void psgWrite(uint8 data);
void setMusicVolume(int volume);
void setSoundEffectVolume(int volume);
// Defines the channels used as sound effect channels for the purpose of ScummVM GUI volume control.
// The first 6 bits are 6 fm channels. The next 3 bits are psg channels. The bits that follow represent pcm channels.
void setSoundEffectChanMask(int mask);
class MutexLock {
friend class SegaAudioInterface;
public:
~MutexLock();
private:
MutexLock(SegaAudioInterfaceInternal *saii, int reverse = 0);
SegaAudioInterfaceInternal *_saii;
int _count;
};
MutexLock stackLockMutex();
MutexLock stackUnlockMutex();
private:
SegaAudioInterfaceInternal *_internal;
};
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,70 @@
/* 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 TOWNS_AUDIO_H
#define TOWNS_AUDIO_H
namespace Audio {
class Mixer;
}
class TownsAudioInterfaceInternal;
class TownsAudioInterfacePluginDriver {
public:
virtual ~TownsAudioInterfacePluginDriver() {}
virtual void timerCallback(int timerId) = 0;
};
class TownsAudioInterface {
public:
TownsAudioInterface(Audio::Mixer *mixer, TownsAudioInterfacePluginDriver *driver, bool externalMutex = false);
~TownsAudioInterface();
enum ErrorCode {
kSuccess = 0,
kInvalidChannel,
kUnavailable,
kArgumentOutOfRange,
kNotImplemented,
kOutOfWaveMemory,
kInvalidWaveTable,
kChannelNotReserved,
kNoteOutOfRangeForInstrument,
kNoMatchingWaveTable,
kDuplicateWaveTable
};
bool init();
ErrorCode callback(int command, ...);
void setMusicVolume(int volume);
void setSoundEffectVolume(int volume);
// Defines the channels used as sound effect channels for the purpose of ScummVM GUI volume control.
// The first 6 bits are the 6 fm channels. The next 8 bits are pcm channels.
void setSoundEffectChanMask(int mask);
private:
TownsAudioInterfaceInternal *_intf;
};
#endif

View File

@@ -0,0 +1,988 @@
/* 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

View File

@@ -0,0 +1,238 @@
/* 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 TOWNS_EUP_H
#define TOWNS_EUP_H
#define EUP_USE_MEMPOOL
#include "audio/softsynth/fmtowns_pc98/towns_audio.h"
#include "common/array.h"
#include "common/func.h"
#ifdef EUP_USE_MEMPOOL
#include "common/memorypool.h"
#endif
class EuphonyBaseDriver {
public:
EuphonyBaseDriver() {}
virtual ~EuphonyBaseDriver() {}
virtual bool init() { return true; }
virtual void send(uint8 command) = 0;
};
class EuphonyPlayer;
class EuphonyDriver : public EuphonyBaseDriver {
public:
EuphonyDriver(Audio::Mixer *mixer, EuphonyPlayer *pl);
~EuphonyDriver();
bool init();
void reset();
int assignPartToChannel(int chan, int part);
void send(uint8 command);
void setTimerA(bool enable, int tempo);
void setTimerB(bool enable, int tempo);
void loadInstrument(int chanType, int id, const uint8 *data);
void setInstrument(int chan, int instrID);
void loadWaveTable(const uint8 *data);
void unloadWaveTable(int id);
void reserveSoundEffectChannels(int num);
void playSoundEffect(int chan, int note, int velo, const uint8 *data);
void stopSoundEffect(int chan);
bool soundEffectIsPlaying(int chan);
void channelPan(int chan, int mode);
void channelPitch(int chan, int pitch);
void channelVolume(int chan, int vol);
void setOutputVolume(int chanType, int volLeft, int volRight);
void cdaToggle(int a);
void setMusicVolume(int volume);
void setSoundEffectVolume(int volume);
private:
void noteOff();
void noteOn();
void controlChange_volume();
void controlChange_panPos();
void controlChange_allNotesOff();
void programChange();
void pitchWheel();
Common::Array<uint8> _currentEvent;
int8 *_partToChanMapping;
int8 *_sustainChannels;
struct Channel {
int8 part;
int8 next;
uint8 note;
uint8 pri;
} *_channels;
TownsAudioInterface *_intf;
};
class Type0Driver : public EuphonyBaseDriver {
public:
Type0Driver(EuphonyPlayer *pl);
~Type0Driver();
bool init();
void send(uint8 command);
};
class EuphonyPlayer : public TownsAudioInterfacePluginDriver {
public:
EuphonyPlayer(Audio::Mixer *mixer);
virtual ~EuphonyPlayer();
bool init();
int startTrack(const uint8 *data, int trackSize, int barLen);
void stop();
void pause();
void resume();
int setTempo(int tempo);
void setLoopStatus(bool loop);
bool isPlaying() {return _playing; }
int configPart_enable(int part, int val);
int configPart_setType(int part, int val);
int configPart_remap(int part, int val);
int configPart_adjustVolume(int part, int val);
int configPart_setTranspose(int part, int val);
void timerCallback(int timerId);
EuphonyDriver *driver() { return _eupDriver; }
private:
void reset();
void resetPartConfig();
void resetTempo();
void updatePulseCounters();
void updateBeat();
void updateParser();
void updateCheckEot();
bool parseEvent();
void proceedToNextEvent();
void updateHangingNotes();
void clearHangingNotes();
void resetAllControls();
void allPartsOff();
uint8 appendEvent(uint8 evt, uint8 chan);
bool event_notImpl();
bool event_noteOn();
bool event_polyphonicAftertouch();
bool event_controlChange_pitchWheel();
bool event_programChange_channelAftertouch();
bool event_sysex();
bool event_advanceBar();
bool event_setTempo();
bool event_typeOrdrChange();
uint8 applyTranspose(uint8 in);
uint8 applyVolumeAdjust(uint8 in);
void sendByte(uint8 type, uint8 command);
void sendPendingEvent(int type, int evt, int note, int velo);
void sendControllerReset(int type, int part);
void sendAllNotesOff(int type, int part);
void sendTempo(int tempo);
uint8 *_partConfig_enable;
uint8 *_partConfig_type;
uint8 *_partConfig_ordr;
int8 *_partConfig_volume;
int8 *_partConfig_transpose;
struct PendingEvent {
PendingEvent(int ev, int tp, int nt, int vl, int ln, PendingEvent *chain) : evt(ev), type(tp), note(nt), velo(vl), len(ln), next(chain) {}
uint8 evt;
uint8 type;
uint8 note;
uint8 velo;
uint16 len;
PendingEvent *next;
};
#ifdef EUP_USE_MEMPOOL
Common::ObjectPool<PendingEvent> _pendingEventsPool;
#endif
PendingEvent *_pendingEventsChain;
typedef Common::Functor0Mem<bool, EuphonyPlayer> EuphonyEvent;
typedef Common::Array<const EuphonyEvent*> EuphonyEventsArray;
EuphonyEventsArray _euphonyEvents;
uint8 _defaultBarLength;
uint8 _barLength;
int _playerUpdatesLeft;
int _tempoControlMode;
int _updatesPerPulseRemainder;
int _updatesPerPulse;
int _timerSetting;
int8 _tempoMode1PulseCounter;
int _tempoModifier;
uint32 _bar;
uint32 _parseToBar;
int8 _tempoMode1UpdateF8;
uint8 _deltaTicks;
uint32 _beat;
uint8 _defaultTempo;
int _trackTempo;
bool _loop;
bool _playing;
bool _endOfTrack;
bool _paused;
const uint8 *_musicStart;
const uint8 *_musicPos;
uint32 _musicTrackSize;
EuphonyDriver *_eupDriver;
EuphonyBaseDriver *_drivers[3];
};
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,121 @@
/* 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 TOWNS_PC98_AUDIODRIVER_H
#define TOWNS_PC98_AUDIODRIVER_H
#include "audio/softsynth/fmtowns_pc98/pc98_audio.h"
class TownsPC98_MusicChannel;
class TownsPC98_MusicChannelSSG;
class TownsPC98_SfxChannel;
#ifndef DISABLE_PC98_RHYTHM_CHANNEL
class TownsPC98_MusicChannelPCM;
#endif
class TownsPC98_AudioDriver : public PC98AudioPluginDriver {
friend class TownsPC98_MusicChannel;
friend class TownsPC98_MusicChannelSSG;
friend class TownsPC98_SfxChannel;
#ifndef DISABLE_PC98_RHYTHM_CHANNEL
friend class TownsPC98_MusicChannelPCM;
#endif
public:
TownsPC98_AudioDriver(Audio::Mixer *mixer, EmuType type);
~TownsPC98_AudioDriver();
void loadMusicData(uint8 *data, bool loadPaused = false);
void loadSoundEffectData(uint8 *data, uint8 trackNum);
bool init();
void reset();
void fadeStep();
void pause();
void cont();
bool looping() const;
bool musicPlaying() const;
void setMusicVolume(int volume);
void setSoundEffectVolume(int volume);
private:
uint8 readReg(uint8 part, uint8 reg);
void writeReg(uint8 part, uint8 reg, uint8 val);
void preventRegisterWrite(bool prevent);
void timerCallbackA();
void timerCallbackB();
void startSoundEffect();
void setMusicTempo(uint8 tempo);
void setSfxTempo(uint16 tempo);
TownsPC98_MusicChannel **_channels;
TownsPC98_MusicChannelSSG **_ssgChannels;
TownsPC98_SfxChannel **_sfxChannels;
#ifndef DISABLE_PC98_RHYTHM_CHANNEL
TownsPC98_MusicChannelPCM *_rhythmChannel;
#endif
uint8 *_musicBuffer;
uint8 *_sfxBuffer;
const uint8 *_patchData;
uint8 _updateChannelsFlag;
uint8 _updateSSGFlag;
uint8 _updateRhythmFlag;
uint8 _updateSfxFlag;
uint8 _finishedChannelsFlag;
uint8 _finishedSSGFlag;
uint8 _finishedRhythmFlag;
uint8 _finishedSfxFlag;
bool _musicPlaying;
bool _sfxPlaying;
uint8 _fading;
uint8 _looping;
uint32 _musicTickCounter;
int _sfxOffs;
uint8 *_sfxData;
uint16 _sfxOffsets[2];
uint8 *_trackPtr;
bool _regWriteProtect;
PC98AudioCore *_pc98a;
const int _numChanFM;
const int _numChanSSG;
const int _numChanRHY;
static const uint8 _channelPreset[36];
static const uint8 _levelPresetFMTOWNS[24];
static const uint8 _levelPresetPC98[24];
const uint8 *_levelPresets;
bool _ready;
};
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,230 @@
/* 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 TOWNS_PC98_FMSYNTH_H
#define TOWNS_PC98_FMSYNTH_H
#include "audio/audiostream.h"
#include "audio/mixer.h"
#include "common/mutex.h"
#include "common/func.h"
#include "common/array.h"
#ifdef __DS__
/* This disables the rhythm channel when emulating the PC-98 type 86 sound card.
* The only purpose is code size reduction for certain backends.
* At the moment the only games which make use of the rhythm channel are the
* (very rare) PC-98 versions of Legend of Kyrandia 2 and Lands of Lore. Music will
* still be okay, just missing a couple of rhythm instruments.
*/
#define DISABLE_PC98_RHYTHM_CHANNEL
#endif
/* Experimental code for emulation of the chip's busy flag wait cycle.
* Explanation:
* Before attempting a port write a client application would usually read the chip's
* busy flag and remain in a loop until the flag is cleared. This does not work with
* an emulator that is on the same thread as the client code (the busy flag will never
* clear). Instead, I emulate a wait cycle by withholding (enqueueing) incoming register
* writes for the duration of the wait cycle.
* For now I have disabled this with an #ifdef since I haven't seen any impact on the
* sound.
*/
//#define ENABLE_SNDTOWNS98_WAITCYCLES
class TownsPC98_FmSynthOperator;
class TownsPC98_FmSynthSquareWaveSource;
#ifndef DISABLE_PC98_RHYTHM_CHANNEL
class TownsPC98_FmSynthPercussionSource;
#endif
enum EnvelopeState {
kEnvReady = 0,
kEnvAttacking,
kEnvDecaying,
kEnvSustaining,
kEnvReleasing
};
class TownsPC98_FmSynth : public Audio::AudioStream {
public:
enum EmuType {
kTypeTowns = 0,
kType26 = 1,
kType86 = 2
};
TownsPC98_FmSynth(Audio::Mixer *mixer, EmuType type);
virtual ~TownsPC98_FmSynth();
virtual bool init();
virtual void reset();
void writeReg(uint8 part, uint8 regAddress, uint8 value);
uint8 readReg(uint8 part, uint8 regAddress);
// AudioStream interface
int readBuffer(int16 *buffer, const int numSamples);
bool isStereo() const;
bool endOfData() const;
int getRate() const;
protected:
void deinit();
// Implement this in your inherited class if your driver generates
// additional output that has to be inserted into the buffer.
virtual void nextTickEx(int32 *buffer, uint32 bufferSize) {}
virtual void timerCallbackA() = 0;
virtual void timerCallbackB() = 0;
// The audio driver can store and apply two different volume settings
// (usually for music and sound effects). The channel mask will determine
// which channels get effected by which setting. The first bits will be
// the normal fm channels, the next bits the ssg channels and the final
// bit the rhythm channel.
void setVolumeIntern(int volA, int volB);
void setVolumeChannelMasks(int channelMaskA, int channelMaskB);
// This allows to balance out the fm/ssg levels.
void setLevelSSG(int vol);
const int _numChan;
const int _numSSG;
const bool _hasPercussion;
Common::Mutex &_mutex;
int _mixerThreadLockCounter;
private:
void generateTables();
void writeRegInternal(uint8 part, uint8 regAddress, uint8 value);
void nextTick(int32 *buffer, uint32 bufferSize);
#ifdef ENABLE_SNDTOWNS98_WAITCYCLES
void startWaitCycle();
#endif
struct ChanInternal {
ChanInternal();
~ChanInternal();
void ampModSensitivity(uint32 value) {
ampModSvty = (1 << (3 - value)) - (((value >> 1) & 1) | (value & 1));
}
void frqModSensitivity(uint32 value) {
frqModSvty = value << 5;
}
void fbClear() {
feedbuf[0] = feedbuf[1] = feedbuf[2] = 0;
}
bool enableLeft;
bool enableRight;
bool updateEnvelopeParameters;
int32 feedbuf[3];
uint8 algorithm;
uint32 ampModSvty;
uint32 frqModSvty;
TownsPC98_FmSynthOperator *opr[4];
};
TownsPC98_FmSynthSquareWaveSource *_ssg;
#ifndef DISABLE_PC98_RHYTHM_CHANNEL
TownsPC98_FmSynthPercussionSource *_prc;
#endif
ChanInternal *_chanInternal;
uint8 *_oprRates;
uint8 *_oprRateshift;
uint8 *_oprAttackDecay;
uint32 *_oprFrq;
uint32 *_oprSinTbl;
int32 *_oprLevelOut;
int32 *_oprDetune;
typedef Common::Functor0Mem<void, TownsPC98_FmSynth> ChipTimerProc;
ChipTimerProc *_timerProcIdle;
ChipTimerProc *_timerProcA;
ChipTimerProc *_timerProcB;
void idleTimerCallback() {}
struct ChipTimer {
bool enabled;
uint16 value;
int32 smpTillCb;
uint32 smpTillCbRem;
int32 smpPerCb;
uint32 smpPerCbRem;
ChipTimerProc *cb;
};
ChipTimer _timers[2];
int _volMaskA, _volMaskB;
uint16 _volumeA, _volumeB;
int32 *_renderBuffer;
int _renderBufferSize;
int _numPending;
int _offsPending;
int _rateScale;
int _outRateMult;
int _rateConvCnt;
float _predSmpCount;
const int _internalRate;
const int _outputRate;
#ifdef ENABLE_SNDTOWNS98_WAITCYCLES
int _waitCycleRemainder;
const int _samplesPerWaitCycle;
struct RegEntry {
RegEntry(uint8 p, uint8 r, uint8 v) : part(p), reg(r), val(v) {}
uint8 part;
uint8 reg;
uint8 val;
};
Common::Array<RegEntry> _waitCycleElapsedWrites;
#endif
uint8 _registers[255][2];
Audio::Mixer *_mixer;
Audio::SoundHandle _soundHandle;
#ifndef DISABLE_PC98_RHYTHM_CHANNEL
static const uint8 _percussionData[];
#endif
static const uint32 _adtStat[];
static const uint8 _detSrc[];
static const int _ssgTables[];
bool _ready;
};
#endif

View File

@@ -0,0 +1,110 @@
/* 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/musicplugin.h"
#include "common/translation.h"
#include "common/error.h"
#include "common/system.h"
class TownsEmuMusicPlugin : public MusicPluginObject {
public:
const char *getName() const override {
return _s("FM-Towns Audio");
}
const char *getId() const override {
return "towns";
}
MusicDevices getDevices() const override;
Common::Error createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle = 0) const override;
};
MusicDevices TownsEmuMusicPlugin::getDevices() const {
MusicDevices devices;
devices.push_back(MusicDevice(this, "", MT_TOWNS));
return devices;
}
Common::Error TownsEmuMusicPlugin::createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle) const {
*mididriver = nullptr;
return Common::kUnknownError;
}
class PC98EmuMusicPlugin : public MusicPluginObject {
public:
const char *getName() const override {
return _s("PC-98 Audio");
}
const char *getId() const override {
return "pc98";
}
MusicDevices getDevices() const override;
Common::Error createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle = 0) const override;
};
MusicDevices PC98EmuMusicPlugin::getDevices() const {
MusicDevices devices;
devices.push_back(MusicDevice(this, "", MT_PC98));
return devices;
}
Common::Error PC98EmuMusicPlugin::createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle) const {
*mididriver = nullptr;
return Common::kUnknownError;
}
class SegaCDSoundPlugin : public MusicPluginObject {
public:
const char *getName() const override {
return _s("SegaCD Audio");
}
const char *getId() const override {
return "segacd";
}
MusicDevices getDevices() const override;
Common::Error createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle = 0) const override;
};
MusicDevices SegaCDSoundPlugin::getDevices() const {
MusicDevices devices;
devices.push_back(MusicDevice(this, "", MT_SEGACD));
return devices;
}
Common::Error SegaCDSoundPlugin::createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle) const {
*mididriver = nullptr;
return Common::kUnknownError;
}
//#if PLUGIN_ENABLED_DYNAMIC(TOWNS)
//REGISTER_PLUGIN_DYNAMIC(TOWNS, PLUGIN_TYPE_MUSIC, TownsEmuMusicPlugin);
//REGISTER_PLUGIN_DYNAMIC(PC98, PLUGIN_TYPE_MUSIC, PC98EmuMusicPlugin);
//#else
REGISTER_PLUGIN_STATIC(TOWNS, PLUGIN_TYPE_MUSIC, TownsEmuMusicPlugin);
REGISTER_PLUGIN_STATIC(PC98, PLUGIN_TYPE_MUSIC, PC98EmuMusicPlugin);
REGISTER_PLUGIN_STATIC(SEGACD, PLUGIN_TYPE_MUSIC, SegaCDSoundPlugin);
//#endif