/* 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 . * */ #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(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(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(); }