Initial commit
This commit is contained in:
389
engines/kyra/sound/drivers/pcspeaker_v1.cpp
Normal file
389
engines/kyra/sound/drivers/pcspeaker_v1.cpp
Normal file
@@ -0,0 +1,389 @@
|
||||
/* 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
|
||||
Reference in New Issue
Block a user