/* 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. * */ #ifndef SCI_AUDIO32_H #define SCI_AUDIO32_H #include "audio/audiostream.h" // for AudioStream, SeekableAudioStream (... #include "audio/mixer.h" // for Mixer, SoundHandle #include "audio/rate.h" // for Audio::st_volume_t, RateConverter #include "common/array.h" // for Array #include "common/mutex.h" // for StackLock, Mutex #include "common/scummsys.h" // for int16, uint8, uint32, uint16 #include "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 { class Console; bool detectSolAudio(Common::SeekableReadStream &stream); bool detectWaveAudio(Common::SeekableReadStream &stream); #pragma mark AudioChannel /** * An audio channel used by the software SCI mixer. */ struct AudioChannel { /** * The ID of the resource loaded into this channel. */ ResourceId id; /** * The resource loaded into this channel. The resource is owned by * ResourceManager. */ Resource *resource; /** * The audio stream loaded into this channel. Can cast to * `SeekableAudioStream` for normal channels and `RobotAudioStream` for * robot channels. */ Common::ScopedPtr stream; /** * The converter used to transform and merge the input stream into the * mixer's output buffer. */ Common::ScopedPtr converter; /** * Duration of the channel, in ticks. */ uint32 duration; /** * The tick when the channel was started. */ uint32 startedAtTick; /** * The tick when the channel was paused. */ uint32 pausedAtTick; /** * The time, in ticks, that the channel fade began. If 0, the channel is not * being faded. */ uint32 fadeStartTick; /** * The start volume of a fade. */ int fadeStartVolume; /** * The total length of the fade, in ticks. */ uint32 fadeDuration; /** * The end volume of a fade. */ int fadeTargetVolume; /** * Whether or not the channel should be stopped and freed when the fade is * complete. */ bool stopChannelOnFade; /** * Whether or not this channel contains a Robot audio block. */ bool robot; /** * For digital sound effects, the related VM Sound::nodePtr object for the * sound. */ reg_t soundNode; /** * The playback volume, from 1 to 127 inclusive. */ int volume; /** * The amount to pan to the right, from 0 to 100. 50 is centered, -1 is not * panned. */ int pan; AudioChannel &operator=(AudioChannel &other) { id = other.id; resource = other.resource; stream.reset(other.stream.release()); converter.reset(other.converter.release()); duration = other.duration; startedAtTick = other.startedAtTick; pausedAtTick = other.pausedAtTick; fadeStartTick = other.fadeStartTick; fadeStartVolume = other.fadeStartVolume; fadeDuration = other.fadeDuration; fadeTargetVolume = other.fadeTargetVolume; stopChannelOnFade = other.stopChannelOnFade; robot = other.robot; soundNode = other.soundNode; volume = other.volume; pan = other.pan; return *this; } }; #pragma mark - /** * Special audio channel indexes used to select a channel for digital audio * playback. */ enum AudioChannelIndex { kRobotChannel = -3, kNoExistingChannel = -2, kAllChannels = -1 }; /** * Audio32 acts as a permanent audio stream into the system mixer and provides * digital audio services for the SCI32 engine, since the system mixer does not * support all the features of SCI. */ class Audio32 : public Audio::AudioStream, public Common::Serializable { public: Audio32(ResourceManager *resMan); ~Audio32(); virtual void saveLoadWithSerializer(Common::Serializer &s); enum { /** * The maximum channel volume. */ kMaxVolume = 127, kMonitorAudioFlagSci3 = 0x80 }; private: ResourceManager *_resMan; Audio::Mixer *_mixer; Audio::SoundHandle _handle; Common::Mutex _mutex; #pragma mark - #pragma mark AudioStream implementation public: int readBuffer(Audio::st_sample_t *buffer, const int numSamples); bool isStereo() const { return true; } int getRate() const { return _mixer->getOutputRate(); } bool endOfData() const { return _numActiveChannels == 0; } bool endOfStream() const { return false; } private: /** * Determines the number of channels that will be mixed together during a * call to readBuffer. */ int16 getNumChannelsToMix() const; /** * Determines whether or not the given audio channel will be mixed into the * output stream. */ bool channelShouldMix(const AudioChannel &channel) const; /** * Mixes audio from the given source stream into the target buffer using the * given rate converter. */ int writeAudioInternal(Audio::AudioStream &sourceStream, Audio::RateConverter &converter, Audio::st_sample_t *targetBuffer, const int numSamples, const Audio::st_volume_t leftVolume, const Audio::st_volume_t rightVolume); #pragma mark - #pragma mark Channel management public: /** * Gets the number of currently active channels. */ inline uint8 getNumActiveChannels() const { Common::StackLock lock(_mutex); return _numActiveChannels; } /** * Gets the number of currently active channels that are playing from * unlocked resources. * * @note In SSCI, this function would actually return the number of channels * whose audio data were not loaded into memory. In practice, the signal for * placing audio data into memory was a call to kLock, so since we do not * follow how SSCI works when it comes to resource management, the lock * state is used as an (apparently) successful proxy for this information * instead. */ uint8 getNumUnlockedChannels() const; /** * Finds a channel that is already configured for the given audio sample. * * @param startIndex The location of the audio resource information in the * arguments list. */ int16 findChannelByArgs(int argc, const reg_t *argv, const int startIndex, const reg_t soundNode) const; /** * Finds a channel that is already configured for the given audio sample. */ int16 findChannelById(const ResourceId resourceId, const reg_t soundNode = NULL_REG) const; /** * Sets or clears a lock on the given resource ID. */ void lockResource(const ResourceId resourceId, const bool lock); private: typedef Common::Array LockList; typedef Common::Array UnlockList; /** * The audio channels. */ Common::Array _channels; /** * The number of active audio channels in the mixer. Being active is not the * same as playing; active channels may be paused. */ uint8 _numActiveChannels; /** * Whether or not we are in the audio thread. * * This flag is used instead of passing a parameter to `freeUnusedChannels` * because a parameter would require forwarding through the public method * `stop`, and there is not currently any reason for this implementation * detail to be exposed. */ bool _inAudioThread; /** * The list of resources from freed channels that need to be unlocked from * the main thread. */ UnlockList _resourcesToUnlock; /** * The list of resource IDs that have been locked by game scripts. */ LockList _lockedResourceIds; /** * Gets the audio channel at the given index. */ inline AudioChannel &getChannel(const int16 channelIndex) { Common::StackLock lock(_mutex); assert(channelIndex >= 0 && channelIndex < _numActiveChannels); return _channels[channelIndex]; } /** * Gets the audio channel at the given index. */ inline const AudioChannel &getChannel(const int16 channelIndex) const { Common::StackLock lock(_mutex); assert(channelIndex >= 0 && channelIndex < _numActiveChannels); return _channels[channelIndex]; } /** * Frees all non-looping channels that have reached the end of their stream. */ void freeUnusedChannels(); /** * Frees resources allocated to the given channel. */ void freeChannel(const int16 channelIndex); /** * Unlocks all resources that were freed by the audio thread. */ void unlockResources(); #pragma mark - #pragma mark Script compatibility public: /** * Gets the (fake) sample rate of the hardware DAC. For script compatibility * only. */ inline uint16 getSampleRate() const { return _globalSampleRate; } /** * Sets the (fake) sample rate of the hardware DAC. For script compatibility * only. */ void setSampleRate(uint16 rate); /** * Gets the (fake) bit depth of the hardware DAC. For script compatibility * only. */ inline uint8 getBitDepth() const { return _globalBitDepth; } /** * Sets the (fake) sample rate of the hardware DAC. For script compatibility * only. */ void setBitDepth(uint8 depth); /** * Gets the (fake) number of output (speaker) channels of the hardware DAC. * For script compatibility only. */ inline uint8 getNumOutputChannels() const { return _globalNumOutputChannels; } /** * Sets the (fake) number of output (speaker) channels of the hardware DAC. * For script compatibility only. */ void setNumOutputChannels(int16 numChannels); /** * Gets the (fake) number of preloaded channels. For script compatibility * only. */ inline uint8 getPreload() const { return _preload; } /** * Sets the (fake) number of preloaded channels. For script compatibility * only. */ inline void setPreload(uint8 preload) { _preload = preload; } private: /** * The hardware DAC sample rate. Stored only for script compatibility. */ uint16 _globalSampleRate; /** * The maximum allowed sample rate of the system mixer. Stored only for * script compatibility. */ uint16 _maxAllowedSampleRate; /** * The hardware DAC bit depth. Stored only for script compatibility. */ uint8 _globalBitDepth; /** * The maximum allowed bit depth of the system mixer. Stored only for script * compatibility. */ uint8 _maxAllowedBitDepth; /** * The hardware DAC output (speaker) channel configuration. Stored only for * script compatibility. */ uint8 _globalNumOutputChannels; /** * The maximum allowed number of output (speaker) channels of the system * mixer. Stored only for script compatibility. */ uint8 _maxAllowedOutputChannels; /** * The number of audio channels that should have their data preloaded into * memory instead of streaming from disk. 1 = all channels, 2 = 2nd active * channel and above, etc. Stored only for script compatibility. */ uint8 _preload; #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. */ bool _robotAudioPaused; #pragma mark - #pragma mark Playback public: /** * Starts or resumes playback of an audio channel. */ uint16 play(int16 channelIndex, const ResourceId resourceId, const bool autoPlay, const bool loop, const int16 volume, const reg_t soundNode, const bool monitor); /** * Resumes playback of a paused audio channel, or of the entire audio * player. */ bool resume(const int16 channelIndex); bool resume(const ResourceId resourceId, const reg_t soundNode = NULL_REG) { Common::StackLock lock(_mutex); return resume(findChannelById(resourceId, soundNode)); } /** * Pauses an audio channel, or the entire audio player. */ bool pause(const int16 channelIndex); bool pause(const ResourceId resourceId, const reg_t soundNode = NULL_REG) { Common::StackLock lock(_mutex); return pause(findChannelById(resourceId, soundNode)); } /** * Stops and unloads an audio channel, or the entire audio player. */ int16 stop(const int16 channelIndex); int16 stop(const ResourceId resourceId, const reg_t soundNode = NULL_REG) { Common::StackLock lock(_mutex); return stop(findChannelById(resourceId, soundNode)); } /** * Restarts playback of the given audio resource. */ uint16 restart(const ResourceId resourceId, const bool autoPlay, const bool loop, const int16 volume, const reg_t soundNode, const bool monitor); /** * Returns the playback position for the given channel number, in ticks. */ int16 getPosition(const int16 channelIndex) const; int16 getPosition(const ResourceId resourceId, const reg_t soundNode = NULL_REG) { Common::StackLock lock(_mutex); return getPosition(findChannelById(resourceId, soundNode)); } /** * Sets whether or not the given channel should loop. */ void setLoop(const int16 channelIndex, const bool loop); void setLoop(const ResourceId resourceId, const reg_t soundNode, const bool loop) { Common::StackLock lock(_mutex); setLoop(findChannelById(resourceId, soundNode), loop); } /** * Sets the stereo panning for the given channel. */ void setPan(const int16 channelIndex, const int16 pan) { Common::StackLock lock(_mutex); getChannel(channelIndex).pan = pan; } private: /** * The tick when audio was globally paused. */ uint32 _pausedAtTick; /** * The tick when audio was globally started. */ uint32 _startedAtTick; #pragma mark - #pragma mark Effects public: /** * Gets the volume for a given channel. Passing `kAllChannels` will get the * global volume. */ int16 getVolume(const int16 channelIndex) const; int16 getVolume(const ResourceId resourceId, const reg_t soundNode) const { Common::StackLock lock(_mutex); return getVolume(findChannelById(resourceId, soundNode)); } /** * Sets the volume of an audio channel. Passing `kAllChannels` will set the * global volume. */ void setVolume(const int16 channelIndex, int16 volume); void setVolume(const ResourceId resourceId, const reg_t soundNode, const int16 volume) { Common::StackLock lock(_mutex); setVolume(findChannelById(resourceId, soundNode), volume); } /** * Sets the master volume for digital audio playback. */ void setMasterVolume(const int16 volume) { _mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, volume * Audio::Mixer::kMaxChannelVolume / kMaxVolume); } /** * Initiate an immediate fade of the given channel. */ bool fadeChannel(const int16 channelIndex, const int16 targetVolume, const int16 speed, const int16 steps, const bool stopAfterFade); bool fadeChannel(const ResourceId resourceId, const reg_t soundNode, const int16 targetVolume, const int16 speed, const int16 steps, const bool stopAfterFade) { Common::StackLock lock(_mutex); return fadeChannel(findChannelById(resourceId, soundNode), targetVolume, speed, steps, stopAfterFade); } /** * Gets whether attenuated mixing mode is active. */ inline bool getAttenuatedMixing() const { return _attenuatedMixing; } /** * Sets the attenuated mixing mode. */ void setAttenuatedMixing(bool attenuated) { Common::StackLock lock(_mutex); _attenuatedMixing = attenuated; } private: /** * If true, audio will be mixed by reducing the target buffer by half every * time a new channel is mixed in. The final channel is not attenuated. */ bool _attenuatedMixing; /** * When true, a modified attenuation algorithm is used (`A/4 + B`) instead * of standard linear attenuation (`A/2 + B/2`). */ bool _useModifiedAttenuation; /** * Processes an audio fade for the given channel. * * @returns true if the fade was completed and the channel was stopped. */ bool processFade(const int16 channelIndex); #pragma mark - #pragma mark Signal monitoring public: /** * Returns whether the currently monitored audio channel contains any signal * within the next audio frame. */ bool hasSignal() const; private: /** * The index of the channel being monitored for signal, or -1 if no channel * is monitored. When a channel is monitored, it also causes the engine to * play only the monitored channel. */ int16 _monitoredChannelIndex; /** * The data buffer holding decompressed audio data for the channel that will * be monitored for an audio signal. */ Common::Array _monitoredBuffer; /** * The number of valid audio samples in the signal monitoring buffer. */ int _numMonitoredSamples; #pragma mark - #pragma mark Kernel public: reg_t kernelPlay(const bool autoPlay, const int argc, const reg_t *const argv); reg_t kernelStop(const int argc, const reg_t *const argv); reg_t kernelPause(const int argc, const reg_t *const argv); reg_t kernelResume(const int argc, const reg_t *const argv); reg_t kernelPosition(const int argc, const reg_t *const argv); reg_t kernelVolume(const int argc, const reg_t *const argv); reg_t kernelMixing(const int argc, const reg_t *const argv); reg_t kernelFade(const int argc, const reg_t *const argv); void kernelLoop(const int argc, const reg_t *const argv); void kernelPan(const int argc, const reg_t *const argv); void kernelPanOff(const int argc, const reg_t *const argv); #pragma mark - #pragma mark Debugging public: void printAudioList(Console *con) const; }; } // End of namespace Sci #endif