Initial commit
This commit is contained in:
314
audio/softsynth/fmtowns_pc98/pc98_audio.cpp
Normal file
314
audio/softsynth/fmtowns_pc98/pc98_audio.cpp
Normal 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();
|
||||
}
|
||||
85
audio/softsynth/fmtowns_pc98/pc98_audio.h
Normal file
85
audio/softsynth/fmtowns_pc98/pc98_audio.h
Normal 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
|
||||
167
audio/softsynth/fmtowns_pc98/pcm_common.cpp
Normal file
167
audio/softsynth/fmtowns_pc98/pcm_common.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
95
audio/softsynth/fmtowns_pc98/pcm_common.h
Normal file
95
audio/softsynth/fmtowns_pc98/pcm_common.h
Normal 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
|
||||
511
audio/softsynth/fmtowns_pc98/sega_audio.cpp
Normal file
511
audio/softsynth/fmtowns_pc98/sega_audio.cpp
Normal 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();
|
||||
}
|
||||
81
audio/softsynth/fmtowns_pc98/sega_audio.h
Normal file
81
audio/softsynth/fmtowns_pc98/sega_audio.h
Normal 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
|
||||
1907
audio/softsynth/fmtowns_pc98/towns_audio.cpp
Normal file
1907
audio/softsynth/fmtowns_pc98/towns_audio.cpp
Normal file
File diff suppressed because it is too large
Load Diff
70
audio/softsynth/fmtowns_pc98/towns_audio.h
Normal file
70
audio/softsynth/fmtowns_pc98/towns_audio.h
Normal 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
|
||||
988
audio/softsynth/fmtowns_pc98/towns_euphony.cpp
Normal file
988
audio/softsynth/fmtowns_pc98/towns_euphony.cpp
Normal 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
|
||||
238
audio/softsynth/fmtowns_pc98/towns_euphony.h
Normal file
238
audio/softsynth/fmtowns_pc98/towns_euphony.h
Normal 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
|
||||
1438
audio/softsynth/fmtowns_pc98/towns_pc98_driver.cpp
Normal file
1438
audio/softsynth/fmtowns_pc98/towns_pc98_driver.cpp
Normal file
File diff suppressed because it is too large
Load Diff
121
audio/softsynth/fmtowns_pc98/towns_pc98_driver.h
Normal file
121
audio/softsynth/fmtowns_pc98/towns_pc98_driver.h
Normal 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
|
||||
1837
audio/softsynth/fmtowns_pc98/towns_pc98_fmsynth.cpp
Normal file
1837
audio/softsynth/fmtowns_pc98/towns_pc98_fmsynth.cpp
Normal file
File diff suppressed because it is too large
Load Diff
230
audio/softsynth/fmtowns_pc98/towns_pc98_fmsynth.h
Normal file
230
audio/softsynth/fmtowns_pc98/towns_pc98_fmsynth.h
Normal 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
|
||||
110
audio/softsynth/fmtowns_pc98/towns_pc98_plugins.cpp
Normal file
110
audio/softsynth/fmtowns_pc98/towns_pc98_plugins.cpp
Normal 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
|
||||
Reference in New Issue
Block a user