Initial commit
This commit is contained in:
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();
|
||||
}
|
||||
Reference in New Issue
Block a user