From 0f2748b15a630f9d12ff0511d4e4cde79b8e915f Mon Sep 17 00:00:00 2001 From: Colin Snover Date: Sun, 3 Jul 2016 17:57:58 -0500 Subject: SCI32: Implement kRobot --- engines/sci/sound/audio32.cpp | 164 ++++++++++++++++++++++++++++++++----- engines/sci/sound/audio32.h | 24 ++++-- engines/sci/sound/decoders/sol.cpp | 19 ++++- 3 files changed, 174 insertions(+), 33 deletions(-) (limited to 'engines/sci/sound') diff --git a/engines/sci/sound/audio32.cpp b/engines/sci/sound/audio32.cpp index 288b7c00f5..4af474b918 100644 --- a/engines/sci/sound/audio32.cpp +++ b/engines/sci/sound/audio32.cpp @@ -164,7 +164,7 @@ Audio32::~Audio32() { #pragma mark - #pragma mark AudioStream implementation -int Audio32::writeAudioInternal(Audio::RewindableAudioStream *const sourceStream, Audio::RateConverter *const converter, Audio::st_sample_t *targetBuffer, const int numSamples, const Audio::st_volume_t leftVolume, const Audio::st_volume_t rightVolume, const bool loop) { +int Audio32::writeAudioInternal(Audio::AudioStream *const sourceStream, Audio::RateConverter *const converter, Audio::st_sample_t *targetBuffer, const int numSamples, const Audio::st_volume_t leftVolume, const Audio::st_volume_t rightVolume, const bool loop) { int samplesToRead = numSamples; // The parent rate converter will request N * 2 @@ -182,7 +182,8 @@ int Audio32::writeAudioInternal(Audio::RewindableAudioStream *const sourceStream do { if (loop && sourceStream->endOfStream()) { - sourceStream->rewind(); + Audio::RewindableAudioStream *rewindableStream = dynamic_cast(sourceStream); + rewindableStream->rewind(); } const int loopSamplesWritten = converter->flow(*sourceStream, targetBuffer, samplesToRead, leftVolume, rightVolume); @@ -305,7 +306,14 @@ int Audio32::readBuffer(Audio::st_sample_t *buffer, const int numSamples) { } if (channel.robot) { - // TODO: Robot audio into output buffer + if (channel.stream->endOfStream()) { + stop(channelIndex--); + } else { + const int channelSamplesWritten = writeAudioInternal(channel.stream, channel.converter, buffer, numSamples, kMaxVolume, kMaxVolume, channel.loop); + if (channelSamplesWritten > maxSamplesWritten) { + maxSamplesWritten = channelSamplesWritten; + } + } continue; } @@ -443,9 +451,9 @@ void Audio32::freeUnusedChannels() { Common::StackLock lock(_mutex); for (int channelIndex = 0; channelIndex < _numActiveChannels; ++channelIndex) { const AudioChannel &channel = getChannel(channelIndex); - if (channel.stream->endOfStream()) { + if (!channel.robot && channel.stream->endOfStream()) { if (channel.loop) { - channel.stream->rewind(); + dynamic_cast(channel.stream)->rewind(); } else { stop(channelIndex--); } @@ -466,21 +474,29 @@ void Audio32::freeChannel(const int16 channelIndex) { Common::StackLock lock(_mutex); AudioChannel &channel = getChannel(channelIndex); - // We cannot unlock resources from the audio thread - // because ResourceManager is not thread-safe; instead, - // we just record that the resource needs unlocking and - // unlock it whenever we are on the main thread again - if (_inAudioThread) { - _resourcesToUnlock.push_back(channel.resource); + // Robots have no corresponding resource to free + if (channel.robot) { + delete channel.stream; + channel.stream = nullptr; + channel.robot = false; } else { - _resMan->unlockResource(channel.resource); + // We cannot unlock resources from the audio thread + // because ResourceManager is not thread-safe; instead, + // we just record that the resource needs unlocking and + // unlock it whenever we are on the main thread again + if (_inAudioThread) { + _resourcesToUnlock.push_back(channel.resource); + } else { + _resMan->unlockResource(channel.resource); + } + + channel.resource = nullptr; + delete channel.stream; + channel.stream = nullptr; + delete channel.resourceStream; + channel.resourceStream = nullptr; } - channel.resource = nullptr; - delete channel.stream; - channel.stream = nullptr; - delete channel.resourceStream; - channel.resourceStream = nullptr; delete channel.converter; channel.converter = nullptr; @@ -526,6 +542,111 @@ void Audio32::setNumOutputChannels(int16 numChannels) { _globalNumOutputChannels = numChannels; } +#pragma mark - +#pragma mark Robot + +int16 Audio32::findRobotChannel() const { + Common::StackLock lock(_mutex); + for (int16 i = 0; i < _numActiveChannels; ++i) { + if (_channels[i].robot) { + return i; + } + } + + return kNoExistingChannel; +} + +bool Audio32::playRobotAudio(const RobotAudioStream::RobotAudioPacket &packet) { + // Stop immediately + if (packet.dataSize == 0) { + warning("Stopping robot stream by zero-length packet"); + return stopRobotAudio(); + } + + // Flush and then stop + if (packet.dataSize == -1) { + warning("Stopping robot stream by negative-length packet"); + return finishRobotAudio(); + } + + Common::StackLock lock(_mutex); + int16 channelIndex = findRobotChannel(); + + bool isNewChannel = false; + if (channelIndex == kNoExistingChannel) { + if (_numActiveChannels == _channels.size()) { + return false; + } + + channelIndex = _numActiveChannels++; + isNewChannel = true; + } + + AudioChannel &channel = getChannel(channelIndex); + + if (isNewChannel) { + channel.id = ResourceId(); + channel.resource = nullptr; + channel.loop = false; + channel.robot = true; + channel.fadeStartTick = 0; + channel.pausedAtTick = 0; + channel.soundNode = NULL_REG; + channel.volume = kMaxVolume; + // TODO: SCI3 introduces stereo audio + channel.pan = -1; + channel.converter = Audio::makeRateConverter(RobotAudioStream::kRobotSampleRate, getRate(), false); + // The RobotAudioStream buffer size is + // ((bytesPerSample * channels * sampleRate * 2000ms) / 1000ms) & ~3 + // where bytesPerSample = 2, channels = 1, and sampleRate = 22050 + channel.stream = new RobotAudioStream(88200); + _robotAudioPaused = false; + + if (_numActiveChannels == 1) { + _startedAtTick = g_sci->getTickCount(); + } + } + + return static_cast(channel.stream)->addPacket(packet); +} + +bool Audio32::queryRobotAudio(RobotAudioStream::StreamState &status) const { + Common::StackLock lock(_mutex); + + const int16 channelIndex = findRobotChannel(); + if (channelIndex == kNoExistingChannel) { + status.bytesPlaying = 0; + return false; + } + + status = static_cast(getChannel(channelIndex).stream)->getStatus(); + return true; +} + +bool Audio32::finishRobotAudio() { + Common::StackLock lock(_mutex); + + const int16 channelIndex = findRobotChannel(); + if (channelIndex == kNoExistingChannel) { + return false; + } + + static_cast(getChannel(channelIndex).stream)->finish(); + return true; +} + +bool Audio32::stopRobotAudio() { + Common::StackLock lock(_mutex); + + const int16 channelIndex = findRobotChannel(); + if (channelIndex == kNoExistingChannel) { + return false; + } + + stop(channelIndex); + return true; +} + #pragma mark - #pragma mark Playback @@ -536,14 +657,15 @@ uint16 Audio32::play(int16 channelIndex, const ResourceId resourceId, const bool if (channelIndex != kNoExistingChannel) { AudioChannel &channel = getChannel(channelIndex); + Audio::SeekableAudioStream *stream = dynamic_cast(channel.stream); if (channel.pausedAtTick) { resume(channelIndex); - return MIN(65534, 1 + channel.stream->getLength().msecs() * 60 / 1000); + return MIN(65534, 1 + stream->getLength().msecs() * 60 / 1000); } warning("Tried to resume channel %s that was not paused", channel.id.toString().c_str()); - return MIN(65534, 1 + channel.stream->getLength().msecs() * 60 / 1000); + return MIN(65534, 1 + stream->getLength().msecs() * 60 / 1000); } if (_numActiveChannels == _channels.size()) { @@ -642,7 +764,7 @@ uint16 Audio32::play(int16 channelIndex, const ResourceId resourceId, const bool // use audio streams, and allocate and fill the monitoring buffer // when reading audio data from the stream. - channel.duration = /* round up */ 1 + (channel.stream->getLength().msecs() * 60 / 1000); + channel.duration = /* round up */ 1 + (dynamic_cast(channel.stream)->getLength().msecs() * 60 / 1000); const uint32 now = g_sci->getTickCount(); channel.pausedAtTick = autoPlay ? 0 : now; @@ -687,8 +809,6 @@ bool Audio32::resume(const int16 channelIndex) { if (channel.robot) { channel.startedAtTick += now - channel.pausedAtTick; channel.pausedAtTick = 0; - // TODO: Robot - // StartRobot(); return true; } } diff --git a/engines/sci/sound/audio32.h b/engines/sci/sound/audio32.h index ac3176cc5a..a9905ab6bf 100644 --- a/engines/sci/sound/audio32.h +++ b/engines/sci/sound/audio32.h @@ -30,8 +30,10 @@ #include "common/scummsys.h" // for int16, uint8, uint32, uint16 #include "engines/sci/resource.h" // for ResourceId #include "sci/engine/vm_types.h" // for reg_t, NULL_REG +#include "sci/video/robot_decoder.h" // for RobotAudioStream namespace Sci { +#pragma mark AudioChannel /** * An audio channel used by the software SCI mixer. @@ -53,14 +55,11 @@ struct AudioChannel { Common::SeekableReadStream *resourceStream; /** - * The audio stream loaded into this channel. - * `SeekableAudioStream` is used here instead of - * `RewindableAudioStream` because - * `RewindableAudioStream` does not include the - * `getLength` function, which is needed to tell the - * game engine the duration of audio streams. + * The audio stream loaded into this channel. Can cast + * to `SeekableAudioStream` for normal channels and + * `RobotAudioStream` for robot channels. */ - Audio::SeekableAudioStream *stream; + Audio::AudioStream *stream; /** * The converter used to transform and merge the input @@ -188,7 +187,7 @@ private: * Mixes audio from the given source stream into the * target buffer using the given rate converter. */ - int writeAudioInternal(Audio::RewindableAudioStream *const sourceStream, Audio::RateConverter *const converter, Audio::st_sample_t *targetBuffer, const int numSamples, const Audio::st_volume_t leftVolume, const Audio::st_volume_t rightVolume, const bool loop); + int writeAudioInternal(Audio::AudioStream *const sourceStream, Audio::RateConverter *const converter, Audio::st_sample_t *targetBuffer, const int numSamples, const Audio::st_volume_t leftVolume, const Audio::st_volume_t rightVolume, const bool loop); #pragma mark - #pragma mark Channel management @@ -395,8 +394,17 @@ private: #pragma mark - #pragma mark Robot public: + bool playRobotAudio(const RobotAudioStream::RobotAudioPacket &packet); + bool queryRobotAudio(RobotAudioStream::StreamState &outStatus) const; + bool finishRobotAudio(); + bool stopRobotAudio(); private: + /** + * Finds a channel that is configured for robot playback. + */ + int16 findRobotChannel() const; + /** * When true, channels marked as robot audio will not be * played. diff --git a/engines/sci/sound/decoders/sol.cpp b/engines/sci/sound/decoders/sol.cpp index e445403120..ee1ba35406 100644 --- a/engines/sci/sound/decoders/sol.cpp +++ b/engines/sci/sound/decoders/sol.cpp @@ -21,6 +21,7 @@ */ #include "audio/audiostream.h" +#include "audio/rate.h" #include "audio/decoders/raw.h" #include "common/substream.h" #include "common/util.h" @@ -52,7 +53,7 @@ static const byte tableDPCM8[8] = { 0, 1, 2, 3, 6, 10, 15, 21 }; * Decompresses 16-bit DPCM compressed audio. Each byte read * outputs one sample into the decompression buffer. */ -static void deDPCM16(int16 *out, Common::ReadStream &audioStream, uint32 numBytes, int16 &sample) { +static void deDPCM16(int16 *out, Common::ReadStream &audioStream, const uint32 numBytes, int16 &sample) { for (uint32 i = 0; i < numBytes; ++i) { const uint8 delta = audioStream.readByte(); if (delta & 0x80) { @@ -65,6 +66,19 @@ static void deDPCM16(int16 *out, Common::ReadStream &audioStream, uint32 numByte } } +void deDPCM16(int16 *out, const byte *in, const uint32 numBytes, int16 &sample) { + for (uint32 i = 0; i < numBytes; ++i) { + const uint8 delta = *in++; + if (delta & 0x80) { + sample -= tableDPCM16[delta & 0x7f]; + } else { + sample += tableDPCM16[delta]; + } + sample = CLIP(sample, -32768, 32767); + *out++ = TO_LE_16(sample); + } +} + /** * Decompresses one half of an 8-bit DPCM compressed audio * byte. @@ -178,7 +192,7 @@ int SOLStream::getRate() const { template bool SOLStream::endOfData() const { - return _stream->eos() || _stream->pos() >= _dataOffset + _rawDataSize; + return _stream->eos() || _stream->pos() >= _rawDataSize; } template @@ -269,5 +283,4 @@ Audio::SeekableAudioStream *makeSOLStream(Common::SeekableReadStream *headerStre return Audio::makeRawStream(dataStream, sampleRate, rawFlags, disposeAfterUse); } - } -- cgit v1.2.3