Files
scummvm-cursorfix/engines/kyra/sound/drivers/pcspeaker_v1.cpp
2026-02-02 04:50:13 +01:00

390 lines
11 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifdef ENABLE_EOB
#include "kyra/sound/drivers/pc_base.h"
#include "audio/audiostream.h"
#include "common/mutex.h"
namespace Kyra {
class PCSpeakerDriver : public PCSoundDriver, public Audio::AudioStream {
public:
PCSpeakerDriver(Audio::Mixer *mixer, bool pcJRMode);
~PCSpeakerDriver() override;
void initDriver() override;
void setSoundData(uint8 *data, uint32 size) override;
void startSound(int id, int) override;
bool isChannelPlaying(int channel) const override;
void stopAllChannels() override;
void setMusicVolume(uint8 volume) override;
void setSfxVolume(uint8) override {}
void update();
// AudioStream interface
int readBuffer(int16 *buffer, const int numSamples) override;
bool isStereo() const override { return false; }
int getRate() const override { return _outputRate; }
bool endOfData() const override { return false; }
private:
void noteOn(int chan, uint16 period);
void chanOff(int chan);
void generateSamples(int16 *buffer, int numSamples);
struct Channel {
Channel(uint8 attnDB) : curSample(32767.0 / pow(2.0, (double)attnDB / 6.0)),
dataPtr(0), timer(0), timerScale(0), repeatCounter1(0), repeatCounter2(0), period(-1), samplesLeft(0) {}
const uint8 *dataPtr;
int16 timer;
uint8 timerScale;
uint8 repeatCounter1;
uint8 repeatCounter2;
int32 period;
int32 curSample;
uint32 samplesLeft;
};
Channel **_channels;
int _numChannels;
const uint8 *_newTrackData;
const uint8 *_trackData;
Common::Mutex _mutex;
Audio::Mixer *_mixer;
Audio::SoundHandle _handle;
uint _outputRate;
int _samplesUpdateIntv;
int _samplesUpdateIntvRem;
int _samplesUpdateTmr;
int _samplesUpdateTmrRem;
int _masterVolume;
bool _ready;
const int _clock;
const int _updateRate;
const bool _pcJR;
const int _periodDiv;
const int _levelAdjust;
const uint16 * const _periodsTable;
static const uint16 _periodsPCSpk[96];
static const uint16 _periodsPCjr[96];
};
PCSpeakerDriver::PCSpeakerDriver(Audio::Mixer *mixer, bool pcJRMode) : PCSoundDriver(), _mixer(mixer), _samplesUpdateIntv(0), _samplesUpdateIntvRem(0),
_outputRate(0), _samplesUpdateTmr(0), _samplesUpdateTmrRem(0), _newTrackData(0), _trackData(0), _pcJR(pcJRMode), _numChannels(pcJRMode ? 3 : 1), _channels(0),
_clock(pcJRMode ? 111860 : 1193180), _updateRate(292), _masterVolume(63), _periodsTable(pcJRMode ? _periodsPCjr : _periodsPCSpk), _periodDiv(2),
_levelAdjust(pcJRMode ? 1 : 0), _ready(false) {
_outputRate = _mixer->getOutputRate();
_samplesUpdateIntv = _outputRate / _updateRate;
_samplesUpdateIntvRem = _outputRate % _updateRate;
_channels = new Channel*[_numChannels];
assert(_channels);
for (int i = 0; i < _numChannels; ++i) {
_channels[i] = new Channel(i * 10);
assert(_channels[i]);
}
}
PCSpeakerDriver::~PCSpeakerDriver() {
_ready = false;
_mixer->stopHandle(_handle);
if (_channels) {
for (int i = 0; i < _numChannels; ++i)
delete _channels[i];
delete[] _channels;
}
}
void PCSpeakerDriver::initDriver() {
if (_ready)
return;
_mixer->playStream(Audio::Mixer::kPlainSoundType, &_handle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
_ready = true;
}
void PCSpeakerDriver::setSoundData(uint8 *data, uint32 size) {
Common::StackLock lock(_mutex);
if (!_ready)
return;
_soundData = data;
_soundDataSize = size;
}
void PCSpeakerDriver::startSound(int id, int) {
Common::StackLock lock(_mutex);
if (!_ready)
return;
_newTrackData = getProgram(id & 0x7F);
}
bool PCSpeakerDriver::isChannelPlaying(int channel) const {
Common::StackLock lock(_mutex);
if (!_ready)
return false;
return _trackData;
}
void PCSpeakerDriver::stopAllChannels() {
Common::StackLock lock(_mutex);
if (!_ready)
return;
for (int i = 0; i < _numChannels; ++i)
chanOff(i);
_trackData = 0;
}
void PCSpeakerDriver::setMusicVolume(uint8 volume) {
Common::StackLock lock(_mutex);
_masterVolume = volume >> 2;
}
void PCSpeakerDriver::update() {
Common::StackLock lock(_mutex);
if (!_ready)
return;
if (_newTrackData) {
_trackData = _newTrackData;
_newTrackData = 0;
for (int i = _numChannels - 1; i >= 0; --i) {
_channels[i]->dataPtr = _trackData;
_channels[i]->timer = i * 35;
_channels[i]->timerScale = 1;
}
}
for (int i = _numChannels - 1; i >= 0; --i) {
const uint8 *pos = _channels[i]->dataPtr;
if (!pos)
continue;
for (bool runloop = true; runloop; ) {
if (--_channels[i]->timer > -1)
break;
_channels[i]->timer = 0;
int8 cmd = (int8)*pos++;
if (cmd >= 0) {
if (cmd > 95)
cmd = 0;
noteOn(i, _periodsTable[cmd]);
uint8 nextTimer = 1 + *pos++;
_channels[i]->timer = _channels[i]->timerScale * nextTimer;
} else {
switch (cmd) {
case -23: {
uint16 ts = _channels[i]->timerScale + *pos++;
_channels[i]->timerScale = (uint8)MIN<uint16>(ts, 0xFF);
} break;
case -24: {
int16 ts = _channels[i]->timerScale - *pos++;
_channels[i]->timerScale = (uint8)MAX<int16>(ts, 1);
} break;
case -26: {
uint16 prd = _clock / READ_LE_UINT16(pos);
if (_pcJR && prd >= 0x400)
prd = 0x3FF;
pos += 2;
noteOn(i, prd);
uint8 nextTimer = 1 + *pos++;
_channels[i]->timer = _channels[i]->timerScale * nextTimer;
} break;
case -30: {
_channels[i]->timerScale = *pos++;
if (!_channels[i]->timerScale)
_channels[i]->timerScale = 1;
} break;
case -46: {
if (--_channels[i]->repeatCounter2)
pos -= *pos;
else
pos += 2;
} break;
case -47: {
_channels[i]->repeatCounter2 = *pos++;
if (!_channels[i]->repeatCounter2)
_channels[i]->repeatCounter2 = 1;
} break;
case -50: {
if (--_channels[i]->repeatCounter1)
pos -= *pos;
else
pos += 2;
} break;
case -51: {
_channels[i]->repeatCounter1 = *pos++;
if (!_channels[i]->repeatCounter1)
_channels[i]->repeatCounter1 = 1;
} break;
default:
chanOff(i);
pos = 0;
runloop = false;
}
}
}
_channels[i]->dataPtr = pos;
}
}
int PCSpeakerDriver::readBuffer(int16 *buffer, const int numSamples) {
Common::StackLock lock(_mutex);
if (!_ready)
return 0;
int render = 0;
for (int samplesLeft = numSamples; samplesLeft; samplesLeft -= render) {
if (_samplesUpdateTmr <= 0) {
_samplesUpdateTmr += _samplesUpdateIntv;
update();
}
_samplesUpdateTmrRem += _samplesUpdateIntvRem;
while (_samplesUpdateTmrRem >= _updateRate) {
_samplesUpdateTmr++;
_samplesUpdateTmrRem -= _updateRate;
}
render = MIN<int>(_samplesUpdateTmr, samplesLeft);
_samplesUpdateTmr -= render;
generateSamples(buffer, render);
buffer += render;
}
return numSamples;
}
void PCSpeakerDriver::noteOn(int chan, uint16 period) {
if (chan >= _numChannels)
return;
if (period == 0) {
chanOff(chan);
return;
}
uint32 p = (_outputRate << 10) / ((_clock << 10) / period);
if (_channels[chan]->period == -1 || _channels[chan]->samplesLeft == 0)
_channels[chan]->samplesLeft = p / _periodDiv;
_channels[chan]->period = p & 0xFFFF;
}
void PCSpeakerDriver::chanOff(int chan) {
if (chan >= _numChannels)
return;
_channels[chan]->period = -1;
}
void PCSpeakerDriver::generateSamples(int16 *buffer, int numSamples) {
int render = 0;
for (int samplesLeft = numSamples; samplesLeft; samplesLeft -= render) {
render = samplesLeft;
for (int i = _numChannels - 1; i >= 0; --i)
if (_channels[i]->period != -1)
render = MIN<int>(render, _channels[i]->samplesLeft);
int32 smp = 0;
for (int i = _numChannels - 1; i >= 0; --i)
if (_channels[i]->period != -1)
smp += _channels[i]->curSample;
smp = (smp * _masterVolume) >> (8 + _levelAdjust);
Common::fill<int16*, int16>(buffer, &buffer[render], smp);
buffer += render;
for (int i = _numChannels - 1; i >= 0; --i) {
if (_channels[i]->period == -1)
continue;
_channels[i]->samplesLeft -= render;
if (_channels[i]->samplesLeft == 0) {
_channels[i]->samplesLeft = _channels[i]->period / _periodDiv;
_channels[i]->curSample = ~_channels[i]->curSample;
}
}
}
}
const uint16 PCSpeakerDriver::_periodsPCSpk[96] = {
0x0000, 0xfdff, 0xefa2, 0xe241, 0xd582, 0xc998, 0xbe3d, 0xb38a,
0xa97c, 0x9ff2, 0x96fc, 0x8e89, 0x8683, 0x7ef7, 0x77d9, 0x7121,
0x6ac7, 0x64c6, 0x5f1f, 0x59ca, 0x54be, 0x4ffd, 0x4b7e, 0x4742,
0x4342, 0x3f7b, 0x3bdb, 0x388f, 0x3562, 0x3263, 0x2f8f, 0x2ce4,
0x2a5f, 0x27fe, 0x25c0, 0x23a1, 0x21a1, 0x1fbe, 0x1df6, 0x1c48,
0x1ab1, 0x1932, 0x17c8, 0x1672, 0x1530, 0x13ff, 0x12e0, 0x11d1,
0x10d1, 0x0fdf, 0x0efb, 0x0e24, 0x0d59, 0x0c99, 0x0be4, 0x0b39,
0x0a98, 0x0a00, 0x0970, 0x08e8, 0x0868, 0x07f0, 0x077e, 0x0712,
0x06ac, 0x064c, 0x05f2, 0x059d, 0x054c, 0x0500, 0x04b8, 0x0474,
0x0434, 0x03f8, 0x03bf, 0x0382, 0x0356, 0x0326, 0x02f9, 0x02ce,
0x02a6, 0x0280, 0x025c, 0x023a, 0x021a, 0x01fc, 0x01df, 0x01c4,
0x01ab, 0x0193, 0x017c, 0x0167, 0x0153, 0x0140, 0x012e, 0x011d
};
const uint16 PCSpeakerDriver::_periodsPCjr[96] = {
0x0000, 0x03ff, 0x03ff, 0x03ff, 0x03ff, 0x03ff, 0x03ff, 0x03ff,
0x03ff, 0x03ff, 0x03ff, 0x03ff, 0x03ff, 0x03ff, 0x03ff, 0x03ff,
0x03ff, 0x03ff, 0x03ff, 0x03ff, 0x03ff, 0x03ff, 0x03ff, 0x03ff,
0x03ff, 0x03ff, 0x03ff, 0x03ff, 0x03ff, 0x03ff, 0x03ff, 0x03ff,
0x03f9, 0x03c0, 0x038a, 0x0357, 0x0327, 0x02fa, 0x02cf, 0x02a7,
0x0281, 0x025d, 0x023b, 0x021b, 0x01fc, 0x01e0, 0x01c5, 0x01ac,
0x0194, 0x017d, 0x0168, 0x0153, 0x0140, 0x012e, 0x011d, 0x010d,
0x00fe, 0x00f0, 0x00e2, 0x00d6, 0x00ca, 0x00be, 0x00b4, 0x00aa,
0x00a0, 0x0097, 0x008f, 0x0087, 0x007f, 0x0078, 0x0071, 0x006b,
0x0065, 0x005f, 0x005a, 0x0054, 0x0050, 0x004c, 0x0047, 0x0043,
0x0040, 0x003c, 0x0039, 0x0035, 0x0032, 0x0030, 0x002d, 0x002a,
0x0028, 0x0026, 0x0024, 0x0022, 0x0020, 0x001e, 0x001c, 0x001b
};
PCSoundDriver *PCSoundDriver::createPCSpk(Audio::Mixer *mixer, bool pcJRMode) {
return new PCSpeakerDriver(mixer, pcJRMode);
}
} // End of namespace Kyra
#endif