Scott Percival e0c7bec488 DIRECTOR: Fade sounds if the channel handle exists
isChannelActive() excludes looping sounds, which are allowed to
be faded in or out.

Fixes the intro music fading in The Cute machine.
2023-05-10 22:32:21 +02:00

874 lines
26 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/>.
*
*/
// SNDDecoder based on snd2wav by Abraham Macias Paredes
// https://github.com/System25/drxtract/blob/master/snd2wav
// License: GNU GPL v2 (see COPYING file for details)
#include "audio/audiostream.h"
#include "common/file.h"
#include "common/macresman.h"
#include "common/substream.h"
#include "audio/decoders/wave.h"
#include "audio/decoders/raw.h"
#include "audio/softsynth/pcspk.h"
#include "audio/decoders/aiff.h"
#include "director/director.h"
#include "director/movie.h"
#include "director/sound.h"
#include "director/util.h"
#include "director/window.h"
#include "director/castmember/sound.h"
namespace Director {
DirectorSound::DirectorSound(Window *window) : _window(window) {
uint numChannels = 2;
if (g_director->getVersion() >= 300) {
numChannels = 4;
}
for (uint i = 1; i <= numChannels; i++) {
_channels[i] = new SoundChannel();
}
_mixer = g_system->getMixer();
_speaker = new Audio::PCSpeaker();
_mixer->playStream(Audio::Mixer::kSFXSoundType,
&_pcSpeakerHandle, _speaker, -1, 50, 0, DisposeAfterUse::NO, true);
_enable = true;
}
DirectorSound::~DirectorSound() {
this->stopSound();
unloadSampleSounds();
delete _speaker;
for (auto it : _channels)
delete it._value;
}
SoundChannel *DirectorSound::getChannel(uint8 soundChannel) {
if (!assertChannel(soundChannel))
return nullptr;
return _channels[soundChannel];
}
void DirectorSound::playFile(Common::String filename, uint8 soundChannel) {
if (!assertChannel(soundChannel))
return;
if (debugChannelSet(-1, kDebugFast))
return;
AudioFileDecoder af(filename);
Audio::AudioStream *sound = af.getAudioStream(false, false, DisposeAfterUse::YES);
cancelFade(soundChannel);
stopSound(soundChannel);
_mixer->playStream(Audio::Mixer::kSFXSoundType, &_channels[soundChannel]->handle, sound, -1, getChannelVolume(soundChannel));
// Set the last played sound so that cast member 0 in the sound channel doesn't stop this file.
setLastPlayedSound(soundChannel, SoundID(), false);
}
void DirectorSound::playMCI(Audio::AudioStream &stream, uint32 from, uint32 to) {
Audio::SeekableAudioStream *seekStream = dynamic_cast<Audio::SeekableAudioStream *>(&stream);
Audio::SubSeekableAudioStream *subSeekStream = new Audio::SubSeekableAudioStream(seekStream, Audio::Timestamp(from, seekStream->getRate()), Audio::Timestamp(to, seekStream->getRate()));
// TODO: make sound enable settings work on this one
_mixer->stopHandle(_scriptSound);
_mixer->playStream(Audio::Mixer::kSFXSoundType, &_scriptSound, subSeekStream);
}
uint8 DirectorSound::getChannelVolume(uint8 soundChannel) {
if (!assertChannel(soundChannel))
return 0;
return _enable ? _channels[soundChannel]->volume : 0;
}
void DirectorSound::playStream(Audio::AudioStream &stream, uint8 soundChannel) {
if (!assertChannel(soundChannel))
return;
cancelFade(soundChannel);
_mixer->stopHandle(_channels[soundChannel]->handle);
_mixer->playStream(Audio::Mixer::kSFXSoundType, &_channels[soundChannel]->handle, &stream, -1, getChannelVolume(soundChannel));
}
void DirectorSound::playSound(SoundID soundID, uint8 soundChannel, bool forPuppet) {
switch (soundID.type) {
case kSoundCast:
playCastMember(CastMemberID(soundID.u.cast.member, soundID.u.cast.castLib), soundChannel, forPuppet);
break;
case kSoundExternal:
playExternalSound(soundID.u.external.menu, soundID.u.external.submenu, soundChannel);
break;
}
}
void DirectorSound::playCastMember(CastMemberID memberID, uint8 soundChannel, bool forPuppet) {
if (!assertChannel(soundChannel))
return;
if (memberID.member == 0) {
// Normally cast member 0 stops the sound.
// But there are some sounds where it doesn't. Those are:
// 1. playFile
// 2. FPlay
// 3. non-puppet looping sounds
// 4. maybe more?
if (shouldStopOnZero(soundChannel)) {
stopSound(soundChannel);
// Director 4 will stop after the current loop iteration, but
// Director 3 will continue looping until the sound is replaced.
} else if (g_director->getVersion() >= 400) {
// If there is a loopable stream specified, set the loop to expire by itself
if (_channels[soundChannel]->loopPtr) {
debugC(5, kDebugSound, "DirectorSound::playCastMember(): telling loop in channel %d to stop", soundChannel);
_channels[soundChannel]->loopPtr->setRemainingIterations(1);
_channels[soundChannel]->loopPtr = nullptr;
}
// Don't stop the currently playing sound, just set the last played sound to 0.
setLastPlayedSound(soundChannel, SoundID(), false);
}
} else {
CastMember *soundCast = _window->getCurrentMovie()->getCastMember(memberID);
if (soundCast) {
if (soundCast->_type != kCastSound) {
warning("DirectorSound::playCastMember: attempted to play a non-SoundCastMember %s", memberID.asString().c_str());
} else {
bool looping = ((SoundCastMember *)soundCast)->_looping;
bool stopOnZero = true;
// For a non-puppet sound, if the sound is the same ID as the last
// played, do nothing.
if (!forPuppet && isLastPlayedSound(soundChannel, memberID))
return;
if (!forPuppet && looping) {
// We know that this is a non-puppet, looping sound.
// We don't want to stop it if this channel's cast member changes to 0.
stopOnZero = false;
}
AudioDecoder *ad = ((SoundCastMember *)soundCast)->_audio;
if (!ad) {
warning("DirectorSound::playCastMember: no audio data attached to %s", memberID.asString().c_str());
return;
}
Audio::AudioStream *as;
as = ad->getAudioStream(looping, forPuppet);
if (!as) {
warning("DirectorSound::playCastMember: audio data failed to load from cast");
return;
}
debugC(5, kDebugSound, "DirectorSound::playCastMember(): playing cast ID %s, channel %d, looping %d, stopOnZero %d, forPuppet %d", memberID.asString().c_str(), soundChannel, looping, stopOnZero, forPuppet);
// For looping sounds, keep a copy of the AudioStream so it is
// possible to gracefully stop the playback
if (looping)
_channels[soundChannel]->loopPtr = dynamic_cast<Audio::LoopableAudioStream *>(as);
else
_channels[soundChannel]->loopPtr = nullptr;
playStream(*as, soundChannel);
setLastPlayedSound(soundChannel, memberID, stopOnZero);
}
} else {
warning("DirectorSound::playCastMember: couldn't find %s", memberID.asString().c_str());
}
}
}
void DirectorSound::setSoundEnabled(bool enabled) {
if (_enable == enabled)
return;
if (!enabled)
stopSound();
_enable = enabled;
}
void SNDDecoder::loadExternalSoundStream(Common::SeekableReadStreamEndian &stream) {
_size = stream.readUint32BE();
uint16 sampleRateFlag = stream.readUint16();
/*uint16 unk2 = */ stream.readUint16();
_data = (byte *)malloc(_size);
stream.read(_data, _size);
switch (sampleRateFlag) {
case 1:
_rate = 22254;
break;
case 2:
_rate = 11127;
break;
case 3:
_rate = 7300;
break;
case 4:
_rate = 5500;
break;
default:
warning("DirectorSound::loadExternalSoundStream: Can't handle sampleRateFlag %d, using default one", sampleRateFlag);
_rate = 5500;
break;
}
// this may related to the unk2 flag
// TODO: figure out how to read audio flags
_flags = Audio::FLAG_UNSIGNED;
_channels = 1;
}
void DirectorSound::registerFade(uint8 soundChannel, bool fadeIn, int ticks) {
if (!assertChannel(soundChannel))
return;
debugC(5, kDebugSound, "DirectorSound::registerFade(): registered fading channel %d %s over %d ticks", soundChannel, fadeIn ? "in" : "out", ticks);
// sound enable is not working on fade sounds, so we just return directly when sounds are not enabling
if (!_enable)
return;
cancelFade(soundChannel);
int startVol = fadeIn ? 0 : _channels[soundChannel]->volume;
int targetVol = fadeIn ? _channels[soundChannel]->volume : 0;
_channels[soundChannel]->fade = new FadeParams(startVol, targetVol, ticks, _window->getVM()->getMacTicks(), fadeIn);
_mixer->setChannelVolume(_channels[soundChannel]->handle, startVol);
}
bool DirectorSound::fadeChannel(uint8 soundChannel) {
if (!assertChannel(soundChannel))
return false;
if (!_mixer->isSoundHandleActive(_channels[soundChannel]->handle))
return false;
FadeParams *fade = _channels[soundChannel]->fade;
if (!fade)
return false;
fade->lapsedTicks = _window->getVM()->getMacTicks() - fade->startTicks;
if (fade->lapsedTicks > fade->totalTicks) {
cancelFade(soundChannel);
return false;
}
int fadeVol;
if (fade->fadeIn) {
fadeVol = MIN(fade->lapsedTicks * ((float)fade->targetVol / fade->totalTicks), (float)Audio::Mixer::kMaxChannelVolume);
} else {
fadeVol = MAX((fade->totalTicks - fade->lapsedTicks) * ((float)fade->startVol / fade->totalTicks), (float)0);
}
debugC(5, kDebugSound, "DirectorSound::fadeChannel(): fading channel %d volume to %d", soundChannel, fadeVol);
_mixer->setChannelVolume(_channels[soundChannel]->handle, fadeVol);
return true;
}
void DirectorSound::cancelFade(uint8 soundChannel) {
if (!assertChannel(soundChannel))
return;
// NOTE: It is assumed that soundChannel has already been validated, which is
// why this method is private.
if (_channels[soundChannel]->fade) {
_mixer->setChannelVolume(_channels[soundChannel]->handle, _channels[soundChannel]->fade->targetVol);
delete _channels[soundChannel]->fade;
_channels[soundChannel]->fade = nullptr;
}
}
bool DirectorSound::isChannelActive(uint8 soundChannel) {
if (!assertChannel(soundChannel))
return false;
if (!_mixer->isSoundHandleActive(_channels[soundChannel]->handle))
return false;
// Looped sounds are considered to be inactive after the first play
// WORKAROUND HACK
if (_channels[soundChannel]->loopPtr != nullptr)
return _channels[soundChannel]->loopPtr->getCompleteIterations() < 1;
return true;
}
bool DirectorSound::assertChannel(int soundChannel) {
if (soundChannel <= 0) {
warning("DirectorSound::assertChannel(): Invalid sound channel %d", soundChannel);
return false;
}
if (!_channels.contains(soundChannel)) {
debugC(5, kDebugSound, "DirectorSound::assertChannel(): allocating sound channel %d", soundChannel);
_channels[soundChannel] = new SoundChannel();
}
return true;
}
void DirectorSound::loadSampleSounds(uint type) {
if (type < kMinSampledMenu || type > kMaxSampledMenu) {
warning("DirectorSound::loadSampleSounds: Invalid menu number %d", type);
return;
}
if (!_sampleSounds[type - kMinSampledMenu].empty())
return;
// trying to load external sample sounds
// lazy loading
uint32 tag = MKTAG('C', 'S', 'N', 'D');
uint id = 0xFF;
Archive *archive = nullptr;
for (Common::HashMap<Common::String, Archive *, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo>::iterator it = g_director->_allOpenResFiles.begin(); it != g_director->_allOpenResFiles.end(); ++it) {
Common::Array<uint16> idList = it->_value->getResourceIDList(tag);
for (uint j = 0; j < idList.size(); j++) {
if ((idList[j] & 0xFF) == type) {
id = idList[j];
archive = it->_value;
break;
}
}
}
if (id == 0xFF) {
warning("Score::loadSampleSounds: can not find CSND resource with id %d", type);
return;
}
Common::SeekableReadStreamEndian *csndData = archive->getResource(tag, id);
/*uint32 flag = */ csndData->readUint32();
// the flag should be 0x604E
// i'm not sure what's that mean, but it occurs in those csnd files
// contains how many csnd data
uint16 num = csndData->readUint16();
// read the offset first;
Common::Array<uint32> offset(num);
for (uint i = 0; i < num; i++)
offset[i] = csndData->readUint32();
for (uint i = 0; i < num; i++) {
csndData->seek(offset[i]);
SNDDecoder *ad = new SNDDecoder();
ad->loadExternalSoundStream(*csndData);
_sampleSounds[type - kMinSampledMenu].push_back(ad);
}
delete csndData;
}
void DirectorSound::unloadSampleSounds() {
for (uint i = 0; i < kNumSampledMenus; i++) {
for (uint j = 0; j < _sampleSounds[i].size(); j++) {
delete _sampleSounds[i][j];
}
_sampleSounds[i].clear();
}
}
void DirectorSound::playExternalSound(uint16 menu, uint16 submenu, uint8 soundChannel) {
if (!assertChannel(soundChannel))
return;
SoundID soundId(kSoundExternal, menu, submenu);
if (isChannelActive(soundChannel) && isLastPlayedSound(soundChannel, soundId))
return;
if (menu < kMinSampledMenu || menu > kMaxSampledMenu) {
warning("DirectorSound::playExternalSound: Invalid menu number %d", menu);
return;
}
Common::Array<AudioDecoder *> &menuSounds = _sampleSounds[menu - kMinSampledMenu];
if (menuSounds.empty())
loadSampleSounds(menu);
if (1 <= submenu && submenu <= menuSounds.size()) {
debugC(5, kDebugSound, "DirectorSound::playExternalSound(): playing menu ID %d, submenu ID %d, channel %d", menu, submenu, soundChannel);
playStream(*(menuSounds[submenu - 1]->getAudioStream()), soundChannel);
setLastPlayedSound(soundChannel, soundId);
} else {
warning("DirectorSound::playExternalSound: Could not find sound %d %d", menu, submenu);
}
}
void DirectorSound::changingMovie() {
for (auto it : _channels) {
it._value->movieChanged = true;
if (isChannelActive(it._key)) {
if (isChannelPuppet(it._key)) {
setPuppetSound(SoundID(), it._key); // disable puppet sound
}
// Don't stop this sound until there's a new, non-zero sound in this channel.
it._value->stopOnZero = false;
// If this is a looping sound, make it loop automatically until that happens.
const SoundID &lastPlayedSound = it._value->lastPlayedSound;
if (lastPlayedSound.type == kSoundCast) {
CastMemberID memberID(lastPlayedSound.u.cast.member, lastPlayedSound.u.cast.castLib);
CastMember *soundCast = _window->getCurrentMovie()->getCastMember(memberID);
if (soundCast && soundCast->_type == kCastSound && static_cast<SoundCastMember *>(soundCast)->_looping) {
_mixer->loopChannel(it._value->handle);
}
}
}
}
unloadSampleSounds(); // TODO: we can possibly keep this between movies
}
void DirectorSound::setLastPlayedSound(uint8 soundChannel, SoundID soundId, bool stopOnZero) {
_channels[soundChannel]->lastPlayedSound = soundId;
_channels[soundChannel]->stopOnZero = stopOnZero;
_channels[soundChannel]->movieChanged = false;
}
bool DirectorSound::isLastPlayedSound(uint8 soundChannel, const SoundID &soundId) {
return !_channels[soundChannel]->movieChanged && _channels[soundChannel]->lastPlayedSound == soundId;
}
bool DirectorSound::shouldStopOnZero(uint8 soundChannel) {
return _channels[soundChannel]->stopOnZero;
}
void DirectorSound::stopSound(uint8 soundChannel) {
if (!assertChannel(soundChannel))
return;
debugC(5, kDebugSound, "DirectorSound::stopSound(): stopping channel %d", soundChannel);
if (_channels[soundChannel]->loopPtr)
_channels[soundChannel]->loopPtr = nullptr;
cancelFade(soundChannel);
_mixer->stopHandle(_channels[soundChannel]->handle);
setLastPlayedSound(soundChannel, SoundID());
return;
}
void DirectorSound::stopSound() {
debugC(5, kDebugSound, "DirectorSound::stopSound(): stopping all channels");
for (auto it : _channels) {
if (it._value->loopPtr)
it._value->loopPtr = nullptr;
cancelFade(it._key);
_mixer->stopHandle(it._value->handle);
setLastPlayedSound(it._key, SoundID());
}
_mixer->stopHandle(_scriptSound);
_mixer->stopHandle(_pcSpeakerHandle);
}
void DirectorSound::systemBeep() {
debugC(5, kDebugSound, "DirectorSound::systemBeep(): beep!");
_speaker->play(Audio::PCSpeaker::kWaveFormSquare, 500, 150);
}
bool DirectorSound::isChannelPuppet(uint8 soundChannel) {
if (!assertChannel(soundChannel))
return false;
// cast member ID 0 means "not a puppet"
if (_channels[soundChannel]->puppet.type == kSoundCast && _channels[soundChannel]->puppet.u.cast.member == 0)
return false;
return true;
}
void DirectorSound::setPuppetSound(SoundID soundId, uint8 soundChannel) {
if (!assertChannel(soundChannel))
return;
_channels[soundChannel]->newPuppet = true;
_channels[soundChannel]->puppet = soundId;
_channels[soundChannel]->stopOnZero = true;
}
void DirectorSound::playPuppetSound(uint8 soundChannel) {
if (!assertChannel(soundChannel))
return;
// only play if the puppet was just set
if (!_channels[soundChannel]->newPuppet)
return;
debugC(5, kDebugSound, "DirectorSound::playPuppetSound(): playing on channel %d", soundChannel);
_channels[soundChannel]->newPuppet = false;
playSound(_channels[soundChannel]->puppet, soundChannel, true);
}
void DirectorSound::playFPlaySound() {
if (_fplayQueue.empty())
return;
// only when the previous sound is finished, shall we play next one
if (isChannelActive(1))
return;
Common::String sndName = _fplayQueue.pop();
if (sndName.equalsIgnoreCase("stop")) {
stopSound(1);
_currentSoundName = "";
if (_fplayQueue.empty())
return;
else
sndName = _fplayQueue.pop();
}
uint32 tag = MKTAG('s', 'n', 'd', ' ');
uint id = 0xFFFF;
Archive *archive = nullptr;
// iterate opened ResFiles
for (Common::HashMap<Common::String, Archive *, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo>::iterator it = g_director->_allOpenResFiles.begin(); it != g_director->_allOpenResFiles.end(); ++it) {
id = it->_value->findResourceID(tag, sndName, true);
if (id != 0xFFFF) {
archive = it->_value;
break;
}
}
if (id == 0xFFFF) {
warning("DirectorSound:playFPlaySound: can not find sound %s", sndName.c_str());
return;
}
Common::SeekableReadStreamEndian *sndData = archive->getResource(tag, id);
if (sndData != nullptr) {
SNDDecoder *ad = new SNDDecoder();
ad->loadStream(*sndData);
delete sndData;
Audio::AudioStream *as;
bool looping = false;
if (!_fplayQueue.empty() && _fplayQueue.front().equalsIgnoreCase("continuous")) {
_fplayQueue.pop();
looping = true;
}
// FPlay is controlled by Lingo, not the score, like a puppet,
// so we'll get the puppet version of the stream.
as = ad->getAudioStream(looping, true);
if (!as) {
warning("DirectorSound:playFPlaySound: failed to get audio stream");
return;
}
// update current playing sound
_currentSoundName = sndName;
playStream(*as, 1);
delete ad;
}
// Set the last played sound so that cast member 0 in the sound channel doesn't stop this file.
setLastPlayedSound(1, SoundID(), false);
}
void DirectorSound::playFPlaySound(const Common::Array<Common::String> &fplayList) {
for (uint i = 0; i < fplayList.size(); i++)
_fplayQueue.push(fplayList[i]);
// stop the previous sound, because new one is coming
if (isChannelActive(1))
stopSound(1);
playFPlaySound();
}
void DirectorSound::setSoundLevelInternal(uint8 soundChannel, uint8 soundLevel) {
// we have 8 level of sounds, and in ScummVM, we have range 0 to 255, thus 1 level represent 32
_channels[soundChannel]->volume = soundLevel * 32;
if (_enable && isChannelActive(soundChannel))
_mixer->setChannelVolume(_channels[soundChannel]->handle, _channels[soundChannel]->volume);
}
// -1 represent all the sound channel
void DirectorSound::setSoundLevel(int channel, uint8 soundLevel) {
if (soundLevel >= 8) {
warning("DirectorSound::setSoundLevel: soundLevel %d out of bounds", soundLevel);
return;
}
if (channel != -1) {
if (!assertChannel(channel))
return;
debugC(5, kDebugSound, "DirectorSound::setSoundLevel: setting channel %d to level %d", channel, soundLevel);
setSoundLevelInternal(channel, soundLevel);
} else {
debugC(5, kDebugSound, "DirectorSound::setSoundLevel: setting all channels to level %d", soundLevel);
for (uint i = 0; i < _channels.size(); i++)
setSoundLevelInternal(i + 1, soundLevel);
}
}
uint8 DirectorSound::getSoundLevel(uint8 soundChannel) {
if (!assertChannel(soundChannel))
return 0;
return _channels[soundChannel]->volume / 32;
}
SNDDecoder::SNDDecoder()
: AudioDecoder() {
_data = nullptr;
_channels = 0;
_size = 0;
_rate = 0;
_flags = 0;
_loopStart = _loopEnd = 0;
}
SNDDecoder::~SNDDecoder() {
if (_data) {
free(_data);
}
}
bool SNDDecoder::loadStream(Common::SeekableReadStreamEndian &stream) {
if (_data) {
free(_data);
_data = nullptr;
}
if (debugChannelSet(5, kDebugLoading)) {
debugC(5, kDebugLoading, "snd header:");
stream.hexdump(0x4e);
}
uint16 format = stream.readUint16();
if (format == 1) {
uint16 dataTypeCount = stream.readUint16();
for (uint16 i = 0; i < dataTypeCount; i++) {
uint16 dataType = stream.readUint16();
if (dataType == 5) {
// Sampled sound data
uint32 options = stream.readUint32();
_channels = (options & 0x80) ? 1 : 2;
if (!processCommands(stream))
return false;
} else {
warning("SNDDecoder: Unsupported data type: %d", dataType);
return false;
}
}
} else if (format == 2) {
_channels = 1;
/*uint16 refCount =*/stream.readUint16();
if (!processCommands(stream))
return false;
} else {
warning("SNDDecoder: Bad format: %d", format);
return false;
}
return true;
}
bool SNDDecoder::processCommands(Common::SeekableReadStreamEndian &stream) {
uint16 cmdCount = stream.readUint16();
for (uint16 i = 0; i < cmdCount; i++) {
uint16 cmd = stream.readUint16();
if (cmd == 0x8050 || cmd == 0x8051) {
if (!processBufferCommand(stream))
return false;
} else {
warning("SNDDecoder: Unsupported command: %d", cmd);
return false;
}
}
return true;
}
bool SNDDecoder::processBufferCommand(Common::SeekableReadStreamEndian &stream) {
if (_data) {
warning("SNDDecoder: Already read data");
return false;
}
/*uint16 unk1 =*/stream.readUint16();
int32 offset = stream.readUint32();
if (offset != stream.pos()) {
warning("SNDDecoder: Bad sound header offset. Expected: %d, read: %d", (int)stream.pos(), offset);
return false;
}
/*uint32 dataPtr =*/stream.readUint32();
uint32 param = stream.readUint32();
_rate = stream.readUint16();
/*uint16 rateExt =*/stream.readUint16();
_loopStart = stream.readUint32();
_loopEnd = stream.readUint32();
byte encoding = stream.readByte();
byte baseFrequency = stream.readByte();
if (baseFrequency != 0x3c) {
warning("SNDDecoder: Unsupported base frequency: %d", baseFrequency);
return false;
}
uint32 frameCount = 0;
uint16 bits = 8;
if (encoding == 0x00) {
// Standard sound header
frameCount = param / _channels;
} else if (encoding == 0xff) {
// Extended sound header
_channels = param;
frameCount = stream.readUint32();
for (uint32 i = 0; i < 0x0a; i++) {
// aiff sample rate
stream.readByte();
}
/*uint32 markerChunk =*/stream.readUint32();
/*uint32 instrumentsChunk =*/stream.readUint32();
/*uint32 aesRecording =*/stream.readUint32();
bits = stream.readUint16();
// future use
stream.readUint16();
stream.readUint32();
stream.readUint32();
stream.readUint32();
} else if (encoding == 0xfe) {
// Compressed sound header
warning("SNDDecoder: Compressed sound header not supported");
return false;
} else {
warning("SNDDecoder: Bad encoding: %d", encoding);
return false;
}
_flags = 0;
_flags |= (_channels == 2) ? Audio::FLAG_STEREO : 0;
_flags |= (bits == 16) ? Audio::FLAG_16BITS : 0;
_flags |= (bits == 8) ? Audio::FLAG_UNSIGNED : 0;
_size = frameCount * _channels * (bits == 16 ? 2 : 1);
_data = (byte *)malloc(_size);
assert(_data);
stream.read(_data, _size);
return true;
}
Audio::AudioStream *SNDDecoder::getAudioStream(bool looping, bool forPuppet, DisposeAfterUse::Flag disposeAfterUse) {
if (!_data)
return nullptr;
byte *buffer = (byte *)malloc(_size);
memcpy(buffer, _data, _size);
Audio::SeekableAudioStream *stream = Audio::makeRawStream(buffer, _size, _rate, _flags, disposeAfterUse);
if (looping) {
if (hasLoopBounds()) {
// FIXME: determine the correct behaviour for non-consecutive loop bounds
if (_loopEnd <= _loopStart) {
warning("SNDDecoder::getAudioStream: Looping sound has non-consecutive bounds, using entire sample");
return new Audio::LoopingAudioStream(stream, 0);
} else {
// Return an automatically looping stream.
debugC(5, kDebugSound, "DirectorSound::getAudioStream(): returning a loop at positions start: %i, end: %i", _loopStart, _loopEnd);
return new Audio::SubLoopingAudioStream(stream, 0, Audio::Timestamp(0, _loopStart, _rate), Audio::Timestamp(0, _loopEnd, _rate));
}
} else {
// Not sure if looping sounds can appear without loop bounds.
// Let's just log a warning and loop the entire sound...
warning("SNDDecoder::getAudioStream: Looping sound has no loop bounds");
return new Audio::LoopingAudioStream(stream, 0);
}
}
return stream;
}
bool SNDDecoder::hasLoopBounds() {
return _loopStart != 0 || _loopEnd != 0;
}
AudioFileDecoder::AudioFileDecoder(Common::String &path)
: AudioDecoder() {
_path = path;
}
AudioFileDecoder::~AudioFileDecoder() {
}
Audio::AudioStream *AudioFileDecoder::getAudioStream(bool looping, bool forPuppet, DisposeAfterUse::Flag disposeAfterUse) {
if (_path.empty())
return nullptr;
Common::Path filePath = Common::Path(pathMakeRelative(_path), g_director->_dirSeparator);
Common::SeekableReadStream *copiedStream = Common::MacResManager::openFileOrDataFork(filePath);
if (copiedStream == nullptr) {
warning("Failed to open %s", _path.c_str());
return nullptr;
}
uint32 magic1 = copiedStream->readUint32BE();
copiedStream->readUint32BE();
uint32 magic2 = copiedStream->readUint32BE();
copiedStream->seek(0);
Audio::RewindableAudioStream *stream = nullptr;
if (magic1 == MKTAG('R', 'I', 'F', 'F') &&
magic2 == MKTAG('W', 'A', 'V', 'E')) {
stream = Audio::makeWAVStream(copiedStream, disposeAfterUse);
} else if (magic1 == MKTAG('F', 'O', 'R', 'M') &&
(magic2 == MKTAG('A', 'I', 'F', 'F') || magic2 == MKTAG('A', 'I', 'F', 'C'))) {
stream = Audio::makeAIFFStream(copiedStream, disposeAfterUse);
} else {
warning("Unknown file type for %s", _path.c_str());
delete copiedStream;
}
if (stream) {
if (looping) {
return new Audio::LoopingAudioStream(stream, 0);
}
return stream;
}
return nullptr;
}
} // End of namespace Director