diff options
Diffstat (limited to 'engines/scumm/player/v2.cpp')
-rw-r--r-- | engines/scumm/player/v2.cpp | 327 |
1 files changed, 327 insertions, 0 deletions
diff --git a/engines/scumm/player/v2.cpp b/engines/scumm/player/v2.cpp new file mode 100644 index 0000000000..e3b3c978dd --- /dev/null +++ b/engines/scumm/player/v2.cpp @@ -0,0 +1,327 @@ +/* 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "scumm/player/v2.h" +#include "scumm/scumm.h" + +namespace Scumm { + +#define SPK_DECAY 0xa000 /* Depends on sample rate */ +#define PCJR_DECAY 0xa000 /* Depends on sample rate */ + +#define NG_PRESET 0x0f35 /* noise generator preset */ +#define FB_WNOISE 0x12000 /* feedback for white noise */ +#define FB_PNOISE 0x08000 /* feedback for periodic noise */ + + +Player_V2::Player_V2(ScummEngine *scumm, Audio::Mixer *mixer, bool pcjr) + : Player_V2Base(scumm, mixer, pcjr) { + + int i; + + // Initialize square generator + _level = 0; + + _RNG = NG_PRESET; + + _pcjr = pcjr; + + if (_pcjr) { + _decay = PCJR_DECAY; + _update_step = (_sampleRate << FIXP_SHIFT) / (111860 * 2); + } else { + _decay = SPK_DECAY; + _update_step = (_sampleRate << FIXP_SHIFT) / (1193000 * 2); + } + + // Adapt _decay to sample rate. It must be squared when + // sample rate doubles. + for (i = 0; (_sampleRate << i) < 30000; i++) + _decay = _decay * _decay / 65536; + + _timer_output = 0; + for (i = 0; i < 4; i++) + _timer_count[i] = 0; + + setMusicVolume(255); + + _mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); +} + +Player_V2::~Player_V2() { + Common::StackLock lock(_mutex); + _mixer->stopHandle(_soundHandle); +} + +void Player_V2::setMusicVolume (int vol) { + if (vol > 255) + vol = 255; + + /* scale to int16, FIXME: find best value */ + double out = vol * 128 / 3; + + /* build volume table (2dB per step) */ + for (int i = 0; i < 15; i++) { + /* limit volume to avoid clipping */ + if (out > 0xffff) + _volumetable[i] = 0xffff; + else + _volumetable[i] = (int) out; + + out /= 1.258925412; /* = 10 ^ (2/20) = 2dB */ + } + _volumetable[15] = 0; +} + +void Player_V2::stopAllSounds() { + Common::StackLock lock(_mutex); + + for (int i = 0; i < 4; i++) { + clear_channel(i); + } + _next_nr = _current_nr = 0; + _next_data = _current_data = 0; +} + +void Player_V2::stopSound(int nr) { + Common::StackLock lock(_mutex); + + if (_next_nr == nr) { + _next_nr = 0; + _next_data = 0; + } + if (_current_nr == nr) { + for (int i = 0; i < 4; i++) { + clear_channel(i); + } + _current_nr = 0; + _current_data = 0; + chainNextSound(); + } +} + +void Player_V2::startSound(int nr) { + Common::StackLock lock(_mutex); + + byte *data = _vm->getResourceAddress(rtSound, nr); + assert(data); + + int cprio = _current_data ? *(_current_data + _header_len) : 0; + int prio = *(data + _header_len); + int nprio = _next_data ? *(_next_data + _header_len) : 0; + + int restartable = *(data + _header_len + 1); + + if (!_current_nr || cprio <= prio) { + int tnr = _current_nr; + int tprio = cprio; + byte *tdata = _current_data; + + chainSound(nr, data); + nr = tnr; + prio = tprio; + data = tdata; + restartable = data ? *(data + _header_len + 1) : 0; + } + + if (!_current_nr) { + nr = 0; + _next_nr = 0; + _next_data = 0; + } + + if (nr != _current_nr + && restartable + && (!_next_nr + || nprio <= prio)) { + + _next_nr = nr; + _next_data = data; + } +} + +int Player_V2::getSoundStatus(int nr) const { + return _current_nr == nr || _next_nr == nr; +} + +int Player_V2::readBuffer(int16 *data, const int numSamples) { + Common::StackLock lock(_mutex); + + uint step; + uint len = numSamples / 2; + + do { + if (!(_next_tick >> FIXP_SHIFT)) { + _next_tick += _tick_len; + nextTick(); + } + + step = len; + if (step > (_next_tick >> FIXP_SHIFT)) + step = (_next_tick >> FIXP_SHIFT); + if (_pcjr) + generatePCjrSamples(data, step); + else + generateSpkSamples(data, step); + data += 2 * step; + _next_tick -= step << FIXP_SHIFT; + } while (len -= step); + + return numSamples; +} + +void Player_V2::lowPassFilter(int16 *sample, uint len) { + for (uint i = 0; i < len; i++) { + _level = (int) (_level * _decay + + sample[0] * (0x10000 - _decay)) >> 16; + sample[0] = sample[1] = _level; + sample += 2; + } +} + +void Player_V2::squareGenerator(int channel, int freq, int vol, + int noiseFeedback, int16 *sample, uint len) { + int32 period = _update_step * freq; + int32 nsample; + if (period == 0) + period = _update_step; + + for (uint i = 0; i < len; i++) { + uint32 duration = 0; + + if (_timer_output & (1 << channel)) + duration += _timer_count[channel]; + + _timer_count[channel] -= (1 << FIXP_SHIFT); + while (_timer_count[channel] <= 0) { + + if (noiseFeedback) { + if (_RNG & 1) { + _RNG ^= noiseFeedback; + _timer_output ^= (1 << channel); + } + _RNG >>= 1; + } else { + _timer_output ^= (1 << channel); + } + + if (_timer_output & (1 << channel)) + duration += period; + + _timer_count[channel] += period; + } + + if (_timer_output & (1 << channel)) + duration -= _timer_count[channel]; + + nsample = *sample + + (((int32) (duration - (1 << (FIXP_SHIFT - 1))) + * (int32) _volumetable[vol]) >> FIXP_SHIFT); + /* overflow: clip value */ + if (nsample > 0x7fff) + nsample = 0x7fff; + if (nsample < -0x8000) + nsample = -0x8000; + *sample = nsample; + // The following write isn't necessary, because the lowPassFilter does it for us + //sample[1] = sample[0]; + sample += 2; + } +} + +void Player_V2::generateSpkSamples(int16 *data, uint len) { + int winning_channel = -1; + for (int i = 0; i < 4; i++) { + if (winning_channel == -1 + && _channels[i].d.volume + && _channels[i].d.time_left) { + winning_channel = i; + } + } + + memset(data, 0, 2 * sizeof(int16) * len); + if (winning_channel != -1) { + squareGenerator(0, _channels[winning_channel].d.freq, 0, + 0, data, len); + } else if (_level == 0) + /* shortcut: no sound is being played. */ + return; + + lowPassFilter(data, len); +} + +void Player_V2::generatePCjrSamples(int16 *data, uint len) { + int i, j; + int freq, vol; + + memset(data, 0, 2 * sizeof(int16) * len); + bool hasdata = false; + + for (i = 1; i < 3; i++) { + freq = _channels[i].d.freq >> 6; + if (_channels[i].d.volume && _channels[i].d.time_left) { + for (j = 0; j < i; j++) { + if (_channels[j].d.volume + && _channels[j].d.time_left + && freq == (_channels[j].d.freq >> 6)) { + /* HACK: this channel is playing at + * the same frequency as another. + * Synchronize it to the same phase to + * prevent interference. + */ + _timer_count[i] = _timer_count[j]; + _timer_output ^= (1 << i) & + (_timer_output ^ _timer_output << (i - j)); + } + } + } + } + + for (i = 0; i < 4; i++) { + freq = _channels[i].d.freq >> 6; + vol = (65535 - _channels[i].d.volume) >> 12; + if (!_channels[i].d.volume || !_channels[i].d.time_left) { + _timer_count[i] -= len << FIXP_SHIFT; + if (_timer_count[i] < 0) + _timer_count[i] = 0; + } else if (i < 3) { + hasdata = true; + squareGenerator(i, freq, vol, 0, data, len); + } else { + int noiseFB = (freq & 4) ? FB_WNOISE : FB_PNOISE; + int n = (freq & 3); + + freq = (n == 3) ? 2 * (_channels[2].d.freq>>6) : 1 << (5 + n); + hasdata = true; + squareGenerator(i, freq, vol, noiseFB, data, len); + } +#if 0 + debug(9, "channel[%d]: freq %d %.1f ; volume %d", + i, freq, 111860.0 / freq, vol); +#endif + } + + if (_level || hasdata) + lowPassFilter(data, len); +} + +} // End of namespace Scumm |