scummvm/engines/scumm/he/mixer_he.cpp
AndywinXp 29337ef0d4 SCUMM: HE/SOUND: Fix bug #13102
This was also happening in the original. It is caused by the
presence of a SRFS header block which gets played as audio
data.
2023-11-19 23:02:15 +01:00

1516 lines
51 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/>.
*
*/
#include "scumm/he/mixer_he.h"
namespace Scumm {
HEMixer::HEMixer(Audio::Mixer *mixer, ScummEngine_v60he *vm, bool useMilesSoundSystem) {
_mixer = mixer;
_vm = vm;
_useMilesSoundSystem = useMilesSoundSystem;
initSoftMixerSubSystem();
}
HEMixer::~HEMixer() {
deinitSoftMixerSubSystem();
}
bool HEMixer::isMilesActive() {
return _useMilesSoundSystem;
}
uint32 calculateDeflatedADPCMBlockSize(uint32 numBlocks, uint32 blockAlign, uint32 numChannels, uint32 bitsPerSample) {
uint32 samplesPerBlock = (blockAlign - (4 * numChannels)) * (numChannels ^ 3) + 1;
uint32 totalSize = numBlocks * samplesPerBlock * (bitsPerSample / 8);
return totalSize;
}
bool HEMixer::initSoftMixerSubSystem() {
if (!isMilesActive()) {
return mixerInitMyMixerSubSystem();
}
return true;
}
void HEMixer::deinitSoftMixerSubSystem() {
if (isMilesActive()) {
milesStopAllSounds();
} else {
mixerStopAllSounds();
}
}
bool HEMixer::stopChannel(int channel) {
if (isMilesActive()) {
return milesStopChannel(channel);
} else {
return mixerStopChannel(channel);
}
return true;
}
void HEMixer::stopAllChannels() {
for (int i = 0; i < HSND_MAX_CHANNELS; i++) {
stopChannel(i);
}
}
bool HEMixer::pauseMixerSubSystem(bool paused) {
if (isMilesActive()) {
return milesPauseMixerSubSystem(paused);
} else {
return mixerPauseMixerSubSystem(paused);
}
}
void HEMixer::feedMixer() {
if (isMilesActive()) {
milesFeedMixer();
} else {
mixerFeedMixer();
}
}
bool HEMixer::changeChannelVolume(int channel, int volume, bool soft) {
if (!isMilesActive()) {
return mixerChangeChannelVolume(channel, volume, soft);
}
return true;
}
bool HEMixer::startChannelNew(
int channel, int globType, int globNum, uint32 soundData, uint32 offset,
int sampleLen, int frequency, int bitsPerSample, int sampleChannels,
const HESoundModifiers &modifiers, int callbackID, int32 flags, ...) {
va_list params;
bool retValue;
if (!isMilesActive()) {
if (bitsPerSample != 8) {
debug(5, "HEMixer::startChannelNew(): Glob(%d, %d) is %d bits per channel, must be 8 for software mixer", globType, globNum, bitsPerSample);
return false;
}
if (flags & CHANNEL_CALLBACK_EARLY) {
va_start(params, flags);
retValue = mixerStartChannel(
channel, globType, globNum, soundData + offset, sampleLen,
frequency, modifiers.volume, callbackID, flags, va_arg(params, int));
va_end(params);
return retValue;
} else {
return mixerStartChannel(
channel, globType, globNum, soundData + offset, sampleLen,
frequency, modifiers.volume, callbackID, flags);
}
} else {
flags &= ~CHANNEL_CALLBACK_EARLY;
return milesStartChannel(
channel, globType, globNum, soundData, offset, sampleLen,
bitsPerSample, sampleChannels, frequency, modifiers, callbackID, flags);
}
return true;
}
bool HEMixer::startChannel(int channel, int globType, int globNum,
uint32 sampleDataOffset, int sampleLen, int frequency, int volume, int callbackId, int32 flags, ...) {
va_list params;
bool retValue;
if (flags & CHANNEL_CALLBACK_EARLY) {
va_start(params, flags);
retValue = mixerStartChannel(
channel, globType, globNum, sampleDataOffset, sampleLen,
frequency, volume, callbackId, flags, va_arg(params, int));
va_end(params);
return retValue;
} else {
return mixerStartChannel(
channel, globType, globNum, sampleDataOffset, sampleLen,
frequency, volume, callbackId, flags);
}
}
bool HEMixer::startSpoolingChannel(int channel, int song, Common::File &sampleFileIOHandle,
int sampleLen, int frequency, int volume, int callbackID, int32 flags) {
if (!isMilesActive()) {
return mixerStartSpoolingChannel(channel, song, sampleFileIOHandle, sampleLen,
frequency, volume, callbackID, flags);
}
return false;
}
bool HEMixer::audioOverrideExists(int soundId, bool justGetInfo, int *duration, Audio::SeekableAudioStream **outStream) {
if (!_vm->_enableAudioOverride) {
return false;
}
const char *formats[] = {
#ifdef USE_FLAC
"flac",
#endif
"wav",
#ifdef USE_VORBIS
"ogg",
#endif
#ifdef USE_MAD
"mp3",
#endif
};
Audio::SeekableAudioStream *(*formatDecoders[])(Common::SeekableReadStream *, DisposeAfterUse::Flag) = {
#ifdef USE_FLAC
Audio::makeFLACStream,
#endif
Audio::makeWAVStream,
#ifdef USE_VORBIS
Audio::makeVorbisStream,
#endif
#ifdef USE_MAD
Audio::makeMP3Stream,
#endif
};
STATIC_ASSERT(
ARRAYSIZE(formats) == ARRAYSIZE(formatDecoders),
formats_formatDecoders_must_have_same_size);
const char *type;
if (soundId == HSND_TALKIE_SLOT) {
// Speech audio doesn't have a unique ID,
// so we use the file offset instead.
// _heTalkOffset is set at playVoice.
type = "speech";
soundId = ((SoundHE *)(_vm->_sound))->getCurrentSpeechOffset();
} else {
// Music and sfx share the same prefix.
type = "sound";
}
for (int i = 0; i < ARRAYSIZE(formats); i++) {
Common::Path pathDir(Common::String::format("%s%d.%s", type, soundId, formats[i]));
Common::Path pathSub(Common::String::format("%s/%d.%s", type, soundId, formats[i]));
debug(5, "HEMixer::audioOverrideExists(): %s or %s", pathSub.toString().c_str(), pathDir.toString().c_str());
// First check if the file exists before opening it to
// reduce the amount of "opening %s failed" in the console.
// Prefer files in subdirectory.
Common::File soundFileOverride;
bool foundFile = (soundFileOverride.exists(pathSub) && soundFileOverride.open(pathSub)) ||
(soundFileOverride.exists(pathDir) && soundFileOverride.open(pathDir));
if (foundFile) {
soundFileOverride.seek(0, SEEK_SET);
Common::SeekableReadStream *oStr = soundFileOverride.readStream(soundFileOverride.size());
soundFileOverride.close();
Audio::SeekableAudioStream *seekStream = formatDecoders[i](oStr, DisposeAfterUse::YES);
if (duration != nullptr) {
*duration = seekStream->getLength().msecs();
}
if (justGetInfo) {
delete seekStream;
return true;
}
debug(5, "HEMixer::audioOverrideExists(): %s loaded from %s", formats[i], soundFileOverride.getName());
*outStream = seekStream;
return true;
}
}
debug(5, "HEMixer::audioOverrideExists(): file not found");
return false;
}
void HEMixer::setSpoolingSongsTable(HESpoolingMusicItem *heSpoolingMusicTable, int32 tableSize) {
for (int i = 0; i < tableSize; i++) {
_offsetsToSongId.setVal(heSpoolingMusicTable[i].offset, heSpoolingMusicTable[i].song);
}
}
int32 HEMixer::matchOffsetToSongId(int32 offset) {
return _offsetsToSongId.getValOrDefault(offset, 0);
}
/* --- SOFTWARE MIXER --- */
bool HEMixer::mixerInitMyMixerSubSystem() {
for (int i = 0; i < MIXER_MAX_CHANNELS; i++) {
_mixerChannels[i].stream = Audio::makeQueuingAudioStream(MIXER_DEFAULT_SAMPLE_RATE, false);
_mixer->playStream(
Audio::Mixer::kPlainSoundType,
&_mixerChannels[i].handle,
_mixerChannels[i].stream,
-1,
Audio::Mixer::kMaxChannelVolume);
}
for (int i = 0; i < MIXER_MAX_CHANNELS; i++) {
mixerStartChannel(i, 0, 0, 0, 0, 0, 0, 0, CHANNEL_EMPTY_FLAGS);
}
return true;
}
bool HEMixer::mixerStopAllSounds() {
for (int i = 0; i < MIXER_MAX_CHANNELS; i++) {
mixerStopChannel(i);
}
return true;
}
void HEMixer::mixerFeedMixer() {
if (_mixerPaused) {
return;
}
// Unqueue any callback scripts, if available...
if (!_vm->_insideCreateResource && _vm->_game.heversion >= 80) {
((SoundHE *)_vm->_sound)->unqueueSoundCallbackScripts();
}
// Then check if any sound has ended; if so, issue a callback...
for (int i = 0; i < MIXER_MAX_CHANNELS; i++) {
if (!(_mixerChannels[i].flags & CHANNEL_LOOPING)) {
// This is the check for audio overrides, it is sufficient to check if the stream is empty
if ((_mixerChannels[i].flags & CHANNEL_ACTIVE) && _mixerChannels[i].isUsingStreamOverride) {
if (_mixerChannels[i].stream->endOfData()) {
_mixerChannels[i].flags &= ~CHANNEL_ACTIVE;
_mixerChannels[i].flags |= CHANNEL_FINISHED;
_mixerChannels[i].stream->finish();
_mixerChannels[i].stream = nullptr;
((SoundHE *)_vm->_sound)->digitalSoundCallback(HSND_SOUND_ENDED, _mixerChannels[i].callbackID, false);
}
continue;
}
if (!(_mixerChannels[i].flags & CHANNEL_SPOOLING)) {
// Early callback! Play just one more audio frame from the residual data...
if (_mixerChannels[i].flags & CHANNEL_CALLBACK_EARLY) {
bool validEarlyCallback =
(_mixerChannels[i].flags & CHANNEL_ACTIVE) &&
!(_mixerChannels[i].flags & CHANNEL_LAST_CHUNK) &&
_mixerChannels[i].stream->numQueuedStreams() == 1 &&
_mixerChannels[i].residualData;
if (validEarlyCallback) {
_mixerChannels[i].flags |= CHANNEL_LAST_CHUNK;
}
}
if (((_mixerChannels[i].flags & CHANNEL_ACTIVE) && _mixerChannels[i].stream->endOfData()) ||
((_mixerChannels[i].flags & CHANNEL_ACTIVE) && (_mixerChannels[i].flags & CHANNEL_LAST_CHUNK))) {
bool isEarlyCallback = (_mixerChannels[i].flags & CHANNEL_LAST_CHUNK);
// For early callbacks...
if (_mixerChannels[i].flags & CHANNEL_LAST_CHUNK)
_mixerChannels[i].flags &= ~CHANNEL_LAST_CHUNK;
_mixerChannels[i].flags |= CHANNEL_FINISHED;
_mixerChannels[i].flags &= ~CHANNEL_ACTIVE;
_mixerChannels[i].stream->finish();
_mixerChannels[i].stream = nullptr;
((SoundHE *)_vm->_sound)->digitalSoundCallback(HSND_SOUND_ENDED, _mixerChannels[i].callbackID, isEarlyCallback);
}
}
if (_mixerChannels[i].flags & CHANNEL_SPOOLING) {
// Callback, assuming the stream has finished playing the final chunk of the song...
if (_mixerChannels[i].callbackOnNextFrame && _mixerChannels[i].stream->endOfData()) {
_mixerChannels[i].callbackOnNextFrame = false;
_mixerChannels[i].stream->finish();
_mixerChannels[i].stream = nullptr;
((SoundHE *)_vm->_sound)->digitalSoundCallback(HSND_SOUND_ENDED, _mixerChannels[i].callbackID);
}
// Last chunk fetched! Signal a callback for the next frame...
if ((_mixerChannels[i].flags & CHANNEL_ACTIVE) && (_mixerChannels[i].flags & CHANNEL_LAST_CHUNK)) {
_mixerChannels[i].flags &= ~CHANNEL_LAST_CHUNK;
_mixerChannels[i].flags &= ~CHANNEL_ACTIVE;
_mixerChannels[i].flags |= CHANNEL_FINISHED;
_mixerChannels[i].callbackOnNextFrame = true;
}
}
}
}
// Finally, feed the streams...
for (int i = 0; i < MIXER_MAX_CHANNELS; i++) {
// Do not feed the streams for audio overrides!
if (_mixerChannels[i].isUsingStreamOverride)
continue;
if (_mixerChannels[i].flags & CHANNEL_ACTIVE) {
if (_mixerChannels[i].flags & CHANNEL_SPOOLING) {
bool looping = (_mixerChannels[i].flags & CHANNEL_LOOPING);
if (_mixerChannels[i].stream->numQueuedStreams() > 1)
continue;
_mixerChannels[i].fileHandle->seek(_mixerChannels[i].initialSpoolingFileOffset, SEEK_SET);
uint32 curOffset = _mixerChannels[i].lastReadPosition;
_mixerChannels[i].fileHandle->seek(curOffset, SEEK_CUR);
int length = MIXER_SPOOL_CHUNK_SIZE;
if (_mixerChannels[i].lastReadPosition + MIXER_SPOOL_CHUNK_SIZE >= _mixerChannels[i].sampleLen) {
length = _mixerChannels[i].sampleLen % MIXER_SPOOL_CHUNK_SIZE;
_mixerChannels[i].flags |= CHANNEL_LAST_CHUNK;
}
// The file size is an exact multiple of SPOOL_CHUNK_SIZE, and we've already read the
// last chunk, so signal it this is not a looping sample, otherwise rewind the file.
// Should never happen, but... you know how it goes :-)
if (length == 0) {
if (looping) {
curOffset = _mixerChannels[i].lastReadPosition = 0;
_mixerChannels[i].fileHandle->seek(curOffset, SEEK_CUR);
} else {
_mixerChannels[i].flags |= CHANNEL_LAST_CHUNK;
continue;
}
}
byte *data = (byte *)malloc(length);
if (data) {
_mixerChannels[i].fileHandle->read(data, length);
_mixerChannels[i].lastReadPosition += length;
// If we've reached the end of the sample after a residual read,
// and if we have to loop the sample, rewind the read position
if (looping && _mixerChannels[i].lastReadPosition == _mixerChannels[i].sampleLen)
_mixerChannels[i].lastReadPosition = 0;
_mixerChannels[i].stream->queueBuffer(
data,
length,
DisposeAfterUse::YES,
mixerGetOutputFlags());
}
}
}
}
}
bool HEMixer::mixerIsMixerDisabled() {
return false;
}
bool HEMixer::mixerStopChannel(int channel) {
if ((channel >= 0) && (channel < MIXER_MAX_CHANNELS))
return mixerStartChannel(channel, 0, 0, 0, 0, 0, 0, 0, CHANNEL_EMPTY_FLAGS);
return false;
}
bool HEMixer::mixerChangeChannelVolume(int channel, int volume, bool soft) {
byte newVolume = (byte)MAX<int>(0, MIN<int>(255, volume));
_mixerChannels[channel].volume = newVolume;
_mixer->setChannelVolume(_mixerChannels[channel].handle, newVolume);
return true;
}
bool HEMixer::mixerPauseMixerSubSystem(bool paused) {
_mixerPaused = paused;
_mixer->pauseAll(_mixerPaused);
return true;
}
bool HEMixer::mixerStartChannel(
int channel, int globType, int globNum, uint32 sampleDataOffset,
int sampleLen, int frequency, int volume, int callbackID, uint32 flags, ...) {
va_list params;
bool is3DOMusic = false;
if ((channel < 0) || (channel >= MIXER_MAX_CHANNELS))
return false;
if (_mixerChannels[channel].flags != CHANNEL_EMPTY_FLAGS) {
if (!(_mixerChannels[channel].flags & CHANNEL_FINISHED)) {
_mixerChannels[channel].flags |= CHANNEL_FINISHED;
_mixerChannels[channel].flags &= ~CHANNEL_ACTIVE;
_mixer->stopHandle(_mixerChannels[channel].handle);
_mixerChannels[channel].stream = nullptr;
// Signal the sound engine that a sound has finished playing
((SoundHE *)_vm->_sound)->digitalSoundCallback(HSND_SOUND_STOPPED, _mixerChannels[channel].callbackID);
}
}
if (flags != CHANNEL_EMPTY_FLAGS) {
if (sampleLen <= 0) {
error("HEMixer::mixerStartChannel(): Sample invalid size %d", sampleLen);
}
}
_mixerChannels[channel].flags = flags;
_mixerChannels[channel].volume = MIN<int>(MAX<int>(0, volume), 255);
_mixerChannels[channel].number = channel;
_mixerChannels[channel].frequency = frequency;
_mixerChannels[channel].callbackID = callbackID;
_mixerChannels[channel].lastReadPosition = 0;
_mixerChannels[channel].dataOffset = sampleDataOffset;
_mixerChannels[channel].sampleLen = sampleLen;
_mixerChannels[channel].globType = globType;
_mixerChannels[channel].globNum = globNum;
_mixerChannels[channel].residualData = nullptr;
_mixerChannels[channel].isUsingStreamOverride = false;
Audio::SeekableAudioStream *audioOverride = nullptr;
if (audioOverrideExists(globNum, false, nullptr, &audioOverride)) {
_mixerChannels[channel].isUsingStreamOverride = true;
bool shouldLoop = (_mixerChannels[channel].flags & CHANNEL_LOOPING);
_mixerChannels[channel].stream = Audio::makeQueuingAudioStream(
audioOverride->getRate(),
audioOverride->isStereo());
_mixer->playStream(
Audio::Mixer::kMusicSoundType,
&_mixerChannels[channel].handle,
_mixerChannels[channel].stream,
-1,
volume);
_mixerChannels[channel].stream->queueAudioStream(
Audio::makeLoopingAudioStream(audioOverride, shouldLoop ? 0 : 1),
DisposeAfterUse::YES);
return true;
}
bool hasCallbackData = false;
if (flags & CHANNEL_CALLBACK_EARLY) {
va_start(params, flags);
_mixerChannels[channel].endSampleAdjustment = va_arg(params, int);
va_end(params);
// The original has a very convoluted way of making sure that the sample
// both callbacks earlier and stops earlier. We just set a shorter sample length
// as this should ensure the same effect on both ends. Hopefully it's still accurate.
if (_mixerChannels[channel].endSampleAdjustment != 0) {
if ((int)_mixerChannels[channel].sampleLen >= _mixerChannels[channel].endSampleAdjustment) {
_mixerChannels[channel].sampleLen -= _mixerChannels[channel].endSampleAdjustment;
_mixerChannels[channel].residualData = (byte *)malloc(_mixerChannels[channel].endSampleAdjustment);
hasCallbackData = _mixerChannels[channel].residualData != nullptr;
} else {
// Sometimes a game can give an end sample adjustment which is bigger than the sample itself
// (looking at you, Backyard Baseball '97). In this case we should at least give one frame
// for the sound to play for a while.
_mixerChannels[channel].flags |= CHANNEL_LAST_CHUNK;
}
}
} else {
_mixerChannels[channel].endSampleAdjustment = 0;
}
if (flags != CHANNEL_EMPTY_FLAGS) {
byte *ptr = _vm->getResourceAddress((ResType)globType, globNum);
if (READ_BE_UINT32(ptr) == MKTAG('M', 'R', 'A', 'W')) {
is3DOMusic = true;
}
if (READ_BE_UINT32(ptr) == MKTAG('W', 'S', 'O', 'U')) {
ptr += 8;
}
byte *data = nullptr;
ptr += sampleDataOffset;
// Some looping sounds need the original resource address to
// work properly (e.g. the ones created with createSound()),
// so we only copy the data over and create a 64 samples fade-in
// just for non-looping sounds. Also, remember that non-looping
// sounds might have early callbacks, so we still have to copy
// data over, instead of using the original buffer.
if (!(_mixerChannels[channel].flags & CHANNEL_LOOPING)) {
data = (byte *)malloc(_mixerChannels[channel].sampleLen);
if (!data)
return false;
memcpy(data, ptr, _mixerChannels[channel].sampleLen);
// Residual early callback data
if (hasCallbackData) {
memcpy(
_mixerChannels[channel].residualData,
&ptr[_mixerChannels[channel].sampleLen],
_mixerChannels[channel].endSampleAdjustment);
}
// Fade-in to avoid possible sound popping...
byte *dataTmp = data;
int rampUpSampleCount = 64;
if (!is3DOMusic) {
for (int i = 0; i < rampUpSampleCount; i++) {
*dataTmp = 128 + (((*dataTmp - 128) * i) / rampUpSampleCount);
dataTmp++;
}
} else {
// We can't just ramp volume as done above, we have to take
// into account the fact that 3DO music is 8-bit -> signed <-
rampUpSampleCount = 128;
for (int i = 0; i < rampUpSampleCount; i++) {
int8 signedSample = (int8)(*dataTmp);
signedSample = (signedSample * i) / rampUpSampleCount;
*dataTmp = (byte)signedSample;
dataTmp++;
}
}
}
_mixerChannels[channel].stream = Audio::makeQueuingAudioStream(MIXER_DEFAULT_SAMPLE_RATE, false);
Audio::Mixer::SoundType soundType =
globNum == HSND_TALKIE_SLOT ?
Audio::Mixer::kSpeechSoundType : Audio::Mixer::kSFXSoundType;
if (is3DOMusic)
soundType = Audio::Mixer::kMusicSoundType;
_mixer->playStream(
soundType,
&_mixerChannels[channel].handle,
_mixerChannels[channel].stream,
-1,
volume);
// Only HE60/61/62 games allowed for samples pitch shifting
if (_vm->_game.heversion < 70)
_mixer->setChannelRate(_mixerChannels[channel].handle, frequency);
if (_mixerChannels[channel].flags & CHANNEL_LOOPING) {
Audio::RewindableAudioStream *stream = Audio::makeRawStream(
ptr,
_mixerChannels[channel].sampleLen,
MIXER_DEFAULT_SAMPLE_RATE,
mixerGetOutputFlags(is3DOMusic),
DisposeAfterUse::NO);
_mixerChannels[channel].stream->queueAudioStream(Audio::makeLoopingAudioStream(stream, 0), DisposeAfterUse::YES);
} else {
// If we're here, data is surely a correctly allocated buffer...
_mixerChannels[channel].stream->queueBuffer(
data,
_mixerChannels[channel].sampleLen,
DisposeAfterUse::YES,
mixerGetOutputFlags(is3DOMusic));
}
if (hasCallbackData && !(_mixerChannels[channel].flags & CHANNEL_LOOPING)) {
_mixerChannels[channel].stream->queueBuffer(
_mixerChannels[channel].residualData,
_mixerChannels[channel].endSampleAdjustment,
DisposeAfterUse::YES,
mixerGetOutputFlags(is3DOMusic));
}
} else {
_mixerChannels[channel].callbackOnNextFrame = false;
_mixerChannels[channel].stream = nullptr;
}
return true;
}
bool HEMixer::mixerStartSpoolingChannel(
int channel, int song, Common::File &sampleFileIOHandle, int sampleLen, int frequency,
int volume, int callbackID, uint32 flags) {
uint32 initialReadCount;
if ((channel < 0) || (channel >= MIXER_MAX_CHANNELS))
return false;
if (_mixerChannels[channel].flags != CHANNEL_EMPTY_FLAGS) {
if (!(_mixerChannels[channel].flags & CHANNEL_FINISHED)) {
_mixerChannels[channel].flags |= CHANNEL_FINISHED;
_mixerChannels[channel].flags &= ~CHANNEL_ACTIVE;
_mixer->stopHandle(_mixerChannels[channel].handle);
_mixerChannels[channel].stream = nullptr;
// Signal the sound engine that a sound has finished playing
((SoundHE *)_vm->_sound)->digitalSoundCallback(HSND_SOUND_STOPPED, callbackID);
}
}
if (flags != CHANNEL_EMPTY_FLAGS) {
if (sampleLen <= 0) {
error("HEMixer::mixerStartSpoolingChannel(): Sample invalid size %d", sampleLen);
}
if ((flags & CHANNEL_LOOPING) && (sampleLen <= MIXER_PCM_CHUNK_SIZE)) {
error("HEMixer::mixerStartSpoolingChannel(): Sample too small to loop (%d)", sampleLen);
}
}
_mixerChannels[channel].flags = flags | CHANNEL_SPOOLING;
_mixerChannels[channel].volume = MIN<int>(MAX<int>(0, volume), 255);
_mixerChannels[channel].number = channel;
_mixerChannels[channel].frequency = frequency;
_mixerChannels[channel].callbackID = callbackID;
_mixerChannels[channel].lastReadPosition = 0;
_mixerChannels[channel].dataOffset = 0;
_mixerChannels[channel].globType = rtSpoolBuffer;
_mixerChannels[channel].globNum = channel + 1;
_mixerChannels[channel].endSampleAdjustment = 0;
_mixerChannels[channel].isUsingStreamOverride = false;
Audio::SeekableAudioStream *audioOverride = nullptr;
if (audioOverrideExists(song, false, nullptr, &audioOverride)) {
_mixerChannels[channel].isUsingStreamOverride = true;
bool shouldLoop = (_mixerChannels[channel].flags & CHANNEL_LOOPING);
_mixerChannels[channel].stream = Audio::makeQueuingAudioStream(
audioOverride->getRate(),
audioOverride->isStereo());
_mixer->playStream(
Audio::Mixer::kMusicSoundType,
&_mixerChannels[channel].handle,
_mixerChannels[channel].stream,
-1,
volume);
_mixerChannels[channel].stream->queueAudioStream(
Audio::makeLoopingAudioStream(audioOverride, shouldLoop ? 0 : 1),
DisposeAfterUse::YES);
return true;
}
if (_vm->_res->createResource(
(ResType)_mixerChannels[channel].globType,
_mixerChannels[channel].globNum, MIXER_SPOOL_CHUNK_SIZE) == nullptr) {
return false;
}
_mixerChannels[channel].fileHandle = &sampleFileIOHandle;
_mixerChannels[channel].sampleLen = sampleLen;
initialReadCount = MIN<int>(sampleLen, MIXER_SPOOL_CHUNK_SIZE);
_mixerChannels[channel].initialSpoolingFileOffset = sampleFileIOHandle.pos();
_mixerChannels[channel].stream = Audio::makeQueuingAudioStream(MIXER_DEFAULT_SAMPLE_RATE, false);
_mixer->playStream(
Audio::Mixer::kMusicSoundType,
&_mixerChannels[channel].handle,
_mixerChannels[channel].stream,
-1,
volume);
byte *data = (byte *)malloc(initialReadCount);
if (data) {
sampleFileIOHandle.read(data, initialReadCount);
_mixerChannels[channel].lastReadPosition += initialReadCount;
// Freddi Fish 4 has some unhandled headers inside its spooled
// music files which were leftovers from the development process.
// These are 32 bytes blocks, so we can just skip them... (#13102)
if (READ_BE_UINT32(data) == MKTAG('S', 'R', 'F', 'S')) {
sampleFileIOHandle.seek(_mixerChannels[channel].initialSpoolingFileOffset, SEEK_SET);
sampleFileIOHandle.seek(32, SEEK_CUR);
sampleFileIOHandle.read(data, initialReadCount);
}
byte *dataTmp = data;
int rampUpSampleCount = 64;
for (int i = 0; i < rampUpSampleCount; i++) {
*dataTmp = 128 + (((*dataTmp - 128) * i) / rampUpSampleCount);
dataTmp++;
}
_mixerChannels[channel].stream->queueBuffer(
data,
initialReadCount,
DisposeAfterUse::YES,
mixerGetOutputFlags());
}
return true;
}
byte HEMixer::mixerGetOutputFlags(bool is3DOMusic) {
// Just plain mono 8-bit sound...
byte streamFlags = 0;
if (!is3DOMusic)
streamFlags |= Audio::FLAG_UNSIGNED;
#ifdef SCUMM_LITTLE_ENDIAN
streamFlags |= Audio::FLAG_LITTLE_ENDIAN;
#endif
return streamFlags;
}
/* --- MILES FUNCTIONS --- */
void HEMixer::milesServiceAllStreams() {
for (int i = 0; i < MILES_MAX_CHANNELS; i++) {
if (_milesChannels[i]._stream.streamObj != nullptr && !_milesChannels[i]._isUsingStreamOverride)
_milesChannels[i].serviceStream();
}
}
void HEMixer::milesStartSpoolingChannel(int channel, const char *filename, long offset, int flags, HESoundModifiers modifiers) {
assert(channel >= 0 && channel < ARRAYSIZE(_milesChannels));
if (_vm->_enableAudioOverride) {
int32 songId = matchOffsetToSongId(offset);
Audio::SeekableAudioStream *audioOverride = nullptr;
if (songId != 0 && audioOverrideExists(songId, false, nullptr, &audioOverride)) {
_milesChannels[channel]._playFlags = flags;
_milesChannels[channel]._bitsPerSample = 16;
_milesChannels[channel]._numChannels = audioOverride->isStereo() ? 2 : 1;
_milesChannels[channel]._dataOffset = offset;
_milesChannels[channel]._lastPlayPosition = 0;
_milesChannels[channel]._globType = 0;
_milesChannels[channel]._globNum = songId;
_milesChannels[channel]._modifiers.frequencyShift = modifiers.frequencyShift;
_milesChannels[channel]._modifiers.volume = modifiers.volume;
_milesChannels[channel]._modifiers.pan = modifiers.pan;
_milesChannels[channel]._baseFrequency = audioOverride->getRate();
_milesChannels[channel]._audioHandleActive = true; // Treat it as a sound effect since we're not streaming it in chunks
_milesChannels[channel]._isUsingStreamOverride = true;
bool shouldLoop = (_milesChannels[channel]._playFlags & CHANNEL_LOOPING);
if (shouldLoop) {
// Looping sounds don't care for modifiers!
_mixer->playStream(
Audio::Mixer::kMusicSoundType,
&_milesChannels[channel]._audioHandle,
Audio::makeLoopingAudioStream(audioOverride, 0),
channel,
255,
0,
DisposeAfterUse::NO);
} else {
int scaledPan = (modifiers.pan != 64) ? 2 * modifiers.pan - 127 : 0;
int newFrequency = (_milesChannels[channel]._baseFrequency * modifiers.frequencyShift) / HSND_SOUND_FREQ_BASE;
_mixer->playStream(
Audio::Mixer::kMusicSoundType,
&_milesChannels[channel]._audioHandle,
audioOverride,
-1,
modifiers.volume,
scaledPan,
DisposeAfterUse::NO);
_mixer->setChannelRate(_milesChannels[channel]._audioHandle, newFrequency);
}
return;
}
}
if (channel >= 0 && channel < ARRAYSIZE(_milesChannels))
_milesChannels[channel].startSpoolingChannel(filename, offset, flags, modifiers, _mixer);
}
bool HEMixer::milesStartChannel(int channel, int globType, int globNum, uint32 soundData, uint32 offset,
int sampleLen, int bitsPerSample, int sampleChannels, int frequency, HESoundModifiers modifiers, int callbackID, uint32 flags, ...) {
// This function executes either a one-shot or a looping sfx/voice file
// which will entirely fit in RAM (as opposed to spooling sounds)
debug(5, "HEMixer::milesStartChannel(): Starting sound with resource %d in channel %d, modifiers v %d p %d f %d",
globNum, channel, modifiers.volume, modifiers.pan, modifiers.frequencyShift);
// Stop any running sound on the target channel; this
// is also going to trigger the appropriate callbacks
milesStopChannel(channel);
// Are there any audio overrides? If so, use a different codepath...
Audio::SeekableAudioStream *audioOverride = nullptr;
if (audioOverrideExists(globNum, false, nullptr, &audioOverride)) {
_milesChannels[channel]._playFlags = flags;
_milesChannels[channel]._bitsPerSample = 16;
_milesChannels[channel]._numChannels = audioOverride->isStereo() ? 2 : 1;
_milesChannels[channel]._dataOffset = offset;
_milesChannels[channel]._lastPlayPosition = 0;
_milesChannels[channel]._globType = globType;
_milesChannels[channel]._globNum = globNum;
_milesChannels[channel]._modifiers.frequencyShift = modifiers.frequencyShift;
_milesChannels[channel]._modifiers.volume = modifiers.volume;
_milesChannels[channel]._modifiers.pan = modifiers.pan;
_milesChannels[channel]._baseFrequency = audioOverride->getRate();
_milesChannels[channel]._audioHandleActive = true;
_mixerChannels[channel].isUsingStreamOverride = true;
Audio::Mixer::SoundType soundType =
globNum == HSND_TALKIE_SLOT ? Audio::Mixer::kSpeechSoundType : Audio::Mixer::kSFXSoundType;
bool shouldLoop = (_mixerChannels[channel].flags & CHANNEL_LOOPING);
if (shouldLoop) {
// Looping sounds don't care for modifiers!
_mixer->playStream(
soundType,
&_milesChannels[channel]._audioHandle,
Audio::makeLoopingAudioStream(audioOverride, 0),
channel,
255,
0,
DisposeAfterUse::NO);
} else {
int scaledPan = (modifiers.pan != 64) ? 2 * modifiers.pan - 127 : 0;
int newFrequency = (_milesChannels[channel]._baseFrequency * modifiers.frequencyShift) / HSND_SOUND_FREQ_BASE;
int msOffset = (offset * 1000) / newFrequency;
audioOverride->seek(msOffset);
_mixer->playStream(
soundType,
&_milesChannels[channel]._audioHandle,
audioOverride,
-1,
modifiers.volume,
scaledPan,
DisposeAfterUse::NO);
_mixer->setChannelRate(_milesChannels[channel]._audioHandle, newFrequency);
}
return true;
}
uint32 actualDataSize = 0;
// Fetch the audio format for the target sound, and fill out
// the format fields on our milesChannel
byte *audioData = milesGetAudioDataFromResource(globType, globNum, soundData,
_milesChannels[channel]._dataFormat, _milesChannels[channel]._blockAlign, actualDataSize);
uint32 audioDataLen = 0;
if (_milesChannels[channel]._dataFormat == WAVE_FORMAT_IMA_ADPCM)
audioDataLen = actualDataSize;
else
audioDataLen = sampleLen * _milesChannels[channel]._blockAlign;
_milesChannels[channel]._bitsPerSample = bitsPerSample;
_milesChannels[channel]._numChannels = sampleChannels;
if (audioData) {
_vm->_res->lock((ResType)globType, globNum);
// Fill out some other information about the sound
_milesChannels[channel]._dataOffset = soundData + offset;
_milesChannels[channel]._lastPlayPosition = 0;
_milesChannels[channel]._globType = globType;
_milesChannels[channel]._globNum = globNum;
// This flag signals that there's an active sound in our channel!
_milesChannels[channel]._audioHandleActive = true;
Audio::Mixer::SoundType soundType =
globNum == HSND_TALKIE_SLOT ?
Audio::Mixer::kSpeechSoundType : Audio::Mixer::kSFXSoundType;
// Play the sound, whether in one-shot fashion or as a loop
if (flags & CHANNEL_LOOPING) {
_milesChannels[channel]._playFlags = CHANNEL_LOOPING;
// Looping sounds don't care for modifiers!
Audio::RewindableAudioStream *stream = nullptr;
if (_milesChannels[channel]._dataFormat == WAVE_FORMAT_PCM) {
stream = Audio::makeRawStream(audioData, audioDataLen, frequency, _milesChannels[channel].getOutputFlags());
} else if (_milesChannels[channel]._dataFormat == WAVE_FORMAT_IMA_ADPCM) {
Common::MemoryReadStream memStream(audioData, audioDataLen);
Audio::AudioStream *adpcmStream = Audio::makeADPCMStream(&memStream, DisposeAfterUse::NO, audioDataLen, Audio::kADPCMMSIma,
frequency, _milesChannels[channel]._numChannels, _milesChannels[channel]._blockAlign);
byte *adpcmData = (byte *)malloc(audioDataLen * 4);
uint32 adpcmSize = adpcmStream->readBuffer((int16 *)(void *)adpcmData, audioDataLen * 2);
delete adpcmStream;
adpcmSize *= 2;
stream = Audio::makeRawStream(adpcmData, adpcmSize, frequency, _milesChannels[channel].getOutputFlags());
}
if (stream) {
_mixer->playStream(soundType, &_milesChannels[channel]._audioHandle,
Audio::makeLoopingAudioStream(stream, 0), channel, 255, 0, DisposeAfterUse::NO);
}
} else {
_milesChannels[channel]._playFlags = CHANNEL_EMPTY_FLAGS;
// Fill out the modifiers
int newFrequency = (frequency * modifiers.frequencyShift) / HSND_SOUND_FREQ_BASE;
int msOffset = (offset * 1000) / newFrequency;
_milesChannels[channel]._modifiers.frequencyShift = modifiers.frequencyShift;
_milesChannels[channel]._modifiers.volume = modifiers.volume;
_milesChannels[channel]._modifiers.pan = modifiers.pan;
// Set the target sample rate for the playback
_milesChannels[channel]._baseFrequency = frequency;
// Transform the range of the pan value from [0, 127] to [-127, 127]
int scaledPan = (_milesChannels[channel]._modifiers.pan != 64) ? 2 * _milesChannels[channel]._modifiers.pan - 127 : 0;
// Play the one-shot sound!
Audio::SeekableAudioStream *stream = nullptr;
if (_milesChannels[channel]._dataFormat == WAVE_FORMAT_PCM) {
stream = Audio::makeRawStream(audioData, audioDataLen, newFrequency, _milesChannels[channel].getOutputFlags());
} else if (_milesChannels[channel]._dataFormat == WAVE_FORMAT_IMA_ADPCM) {
Common::MemoryReadStream memStream(audioData, audioDataLen);
Audio::AudioStream *adpcmStream = Audio::makeADPCMStream(&memStream, DisposeAfterUse::NO, audioDataLen, Audio::kADPCMMSIma,
_milesChannels[channel]._baseFrequency, _milesChannels[channel]._numChannels, _milesChannels[channel]._blockAlign);
byte *adpcmData = (byte *)malloc(audioDataLen * 4);
uint32 adpcmSize = adpcmStream->readBuffer((int16 *)(void *)adpcmData, audioDataLen * 2);
delete adpcmStream;
adpcmSize *= 2;
stream = Audio::makeRawStream(adpcmData, adpcmSize, newFrequency, _milesChannels[channel].getOutputFlags());
}
if (stream) {
stream->seek(msOffset);
_mixer->playStream(soundType, &_milesChannels[channel]._audioHandle,
stream, channel, _milesChannels[channel]._modifiers.volume, scaledPan, DisposeAfterUse::NO);
}
}
}
return true;
}
bool HEMixer::milesStopChannel(int channel) {
milesStopAndCallback(channel, HSND_SOUND_STOPPED);
return true;
}
void HEMixer::milesStopAllSounds() {
for (int i = 0; i < MILES_MAX_CHANNELS; i++) {
milesStopChannel(i);
}
}
void HEMixer::milesModifySound(int channel, int offset, HESoundModifiers modifiers, int flags) {
// This function usually serves as a parameter fade handler (e.g. fading out volume in Moonbase)
debug(5, "HEMixer::milesModifySound(): modifying sound in channel %d, flags %d, vol %d, pan %d, freq %d",
channel, flags, modifiers.volume, modifiers.pan, modifiers.frequencyShift);
// Handling for non-streamed sound effects
if (_milesChannels[channel]._audioHandleActive) {
if (flags & ScummEngine_v70he::HESndFlags::HE_SND_VOL)
_milesChannels[channel]._modifiers.volume = modifiers.volume;
if (flags & ScummEngine_v70he::HESndFlags::HE_SND_PAN)
_milesChannels[channel]._modifiers.pan = modifiers.pan;
if ((flags & ScummEngine_v70he::HESndFlags::HE_SND_VOL) || (flags & ScummEngine_v70he::HESndFlags::HE_SND_PAN)) {
// Transform the range of the pan value from [0, 127] to [-127, 127]
int scaledPan = (_milesChannels[channel]._modifiers.pan != 64) ? 2 * _milesChannels[channel]._modifiers.pan - 127 : 0;
_mixer->setChannelVolume(_milesChannels[channel]._audioHandle, _milesChannels[channel]._modifiers.volume);
_mixer->setChannelBalance(_milesChannels[channel]._audioHandle, scaledPan);
}
if (flags & ScummEngine_v70he::HESndFlags::HE_SND_FREQUENCY) {
_milesChannels[channel]._modifiers.frequencyShift = modifiers.frequencyShift;
int newFrequency = (_milesChannels[channel]._baseFrequency * modifiers.frequencyShift) / HSND_SOUND_FREQ_BASE;
if (newFrequency)
_mixer->setChannelRate(_milesChannels[channel]._audioHandle, newFrequency);
}
}
// Handling for streamed music
if (_milesChannels[channel]._stream.streamObj) {
if (flags & ScummEngine_v70he::HESndFlags::HE_SND_VOL) {
_milesChannels[channel]._modifiers.volume = modifiers.volume;
_mixer->setChannelVolume(_milesChannels[channel]._stream.streamHandle, _milesChannels[channel]._modifiers.volume);
}
if (flags & ScummEngine_v70he::HESndFlags::HE_SND_PAN) {
_milesChannels[channel]._modifiers.pan = modifiers.pan;
// Transform the range of the pan value from [0, 127] to [-127, 127]
int scaledPan = (_milesChannels[channel]._modifiers.pan != 64) ? 2 * _milesChannels[channel]._modifiers.pan - 127 : 0;
_mixer->setChannelBalance(_milesChannels[channel]._stream.streamHandle, scaledPan);
}
if (flags & ScummEngine_v70he::HESndFlags::HE_SND_FREQUENCY) {
_milesChannels[channel]._modifiers.frequencyShift = modifiers.frequencyShift;
int newFrequency = (_milesChannels[channel]._baseFrequency * modifiers.frequencyShift) / HSND_SOUND_FREQ_BASE;
if (newFrequency)
_mixer->setChannelRate(_milesChannels[channel]._stream.streamHandle, newFrequency);
}
}
}
void HEMixer::milesStopAndCallback(int channel, int messageId) {
// In here we check if we can actually stop the target channel,
// and if so, we call the script callback.
if (!_milesChannels[channel]._audioHandleActive && !_milesChannels[channel]._stream.streamObj) {
return;
}
if (_milesChannels[channel]._audioHandleActive) { // Non streamed sounds
// Stop the sound, and then unload it...
_mixer->stopHandle(_milesChannels[channel]._audioHandle);
int globType = _milesChannels[channel]._globType;
int globNum = _milesChannels[channel]._globNum;
if (!_milesChannels[channel]._isUsingStreamOverride && !_vm->_res->isOffHeap((ResType)globType, globNum)) {
_vm->_res->unlock((ResType)globType, globNum);
if (globType == rtSound && globNum == HSND_TALKIE_SLOT) {
// Voice files have to be manually purged
_vm->_res->nukeResource((ResType)globType, globNum);
}
}
} else { // Streamed music
_milesChannels[channel]._stream.streamObj->finish();
_mixer->stopHandle(_milesChannels[channel]._stream.streamHandle);
if (_milesChannels[channel]._stream.fileHandle)
_milesChannels[channel]._stream.fileHandle->close();
}
// Clean up the channel
_milesChannels[channel].clearChannelData();
// Signal the sound engine that we've stopped a sound
((SoundHE *)_vm->_sound)->digitalSoundCallback(messageId, channel);
}
void HEMixer::milesFeedMixer() {
if (_mixerPaused) {
return;
}
// Feed the audio streams
milesServiceAllStreams();
// Check for any sound which has finished playing and call the milesStopAndCallback function
for (int i = 0; i < MILES_MAX_CHANNELS; i++) {
bool soundDone = false;
if (_milesChannels[i]._audioHandleActive) {
soundDone = !_mixer->isSoundHandleActive(_milesChannels[i]._audioHandle);
}
if (_milesChannels[i]._stream.streamObj && !_milesChannels[i]._isUsingStreamOverride) {
soundDone |= _milesChannels[i]._stream.streamObj->endOfStream();
soundDone |= !_mixer->isSoundHandleActive(_milesChannels[i]._stream.streamHandle);
}
if (soundDone) {
milesStopAndCallback(i, HSND_SOUND_ENDED);
}
}
// Finally, check the callback queue and execute any pending callback script
if (!_vm->_insideCreateResource) {
((SoundHE *)_vm->_sound)->unqueueSoundCallbackScripts();
}
}
bool HEMixer::milesPauseMixerSubSystem(bool paused) {
_mixerPaused = paused;
_mixer->pauseAll(_mixerPaused);
return true;
}
byte *HEMixer::milesGetAudioDataFromResource(int globType, int globNum, uint32 dataOffset, uint16 &compType, uint16 &blockAlign, uint32 &dataSize) {
// This function is used for non streamed sound effects and voice files,
// and fetches metadata for the target sound resource
byte *globPtr = _vm->getResourceAddress((ResType)globType, globNum);
if (globPtr == nullptr) {
error("HEMixer::milesGetAudioDataFromResource(): Glob(%d,%d) missing from heap", globType, globNum);
}
uint32 globId = READ_BE_UINT32(globPtr);
if (globId != MKTAG('W', 'S', 'O', 'U')) {
debug(5, "HEMixer::milesGetAudioDataFromResource(): Glob(%d,%d) - type '%s' - is not a WSOU (wrapped .wav) file", globType, globNum, tag2str(globId));
return nullptr;
}
// WSOU tag found, skip to the RIFF header...
globPtr += 8;
globId = READ_BE_UINT32(globPtr);
if (globId != MKTAG('R', 'I', 'F', 'F')) {
debug(5, "HEMixer::milesGetAudioDataFromResource(): Glob(%d,%d) - '%s' - is not a .wav file", globType, globNum, tag2str(globId));
return nullptr;
}
compType = READ_LE_UINT16(globPtr + 20); // Format type from the 'fmt ' block
blockAlign = READ_LE_UINT16(globPtr + 32); // Block align field
if (compType != WAVE_FORMAT_PCM && compType != WAVE_FORMAT_IMA_ADPCM) {
debug("HEMixer::milesGetAudioDataFromResource(): .wav files must be PCM or IMA ADPCM. Unsupported .wav sound type %d.", compType);
return nullptr;
}
// Check for the 'data' chunk
if (READ_BE_UINT32(globPtr + (dataOffset - 8)) != MKTAG('d', 'a', 't', 'a')) {
debug("HEMixer::milesGetAudioDataFromResource(): Did not find 'data' chunk in .wav file");
return nullptr;
}
// The 'data' chunk size is immediately after the 'data' chunk ID
dataSize = READ_LE_UINT32(globPtr + (dataOffset - 4));
return globPtr + dataOffset;
}
void HEMilesChannel::startSpoolingChannel(const char *filename, long offset, int flags, HESoundModifiers modifiers, Audio::Mixer *mixer) {
// This function sets up a stream for the target spooling music file.
// applies modifiers, and begins playing said stream
// We use this auxiliary struct representing a RIFF header for two things:
// - Tidier code;
// - File read consistency checks
//
// I certainly could have done without it, but I feel the code is
// more readable and comprehensible this way...
struct WaveHeader {
uint32 riffTag;
uint32 riffSize;
uint32 waveTag;
uint32 fmtTag;
uint32 fmtSize;
// Format subchunk
uint16 wFormatTag;
uint16 wChannels;
uint32 dwSamplesPerSec;
uint32 dwAvgBytesPerSec;
uint16 wBlockAlign;
uint16 wBitsPerSample;
};
WaveHeader waveHeader;
// Open the music bundle file and then seek to the target sound
_stream.fileHandle = new Common::File();
if (!_stream.fileHandle->open(filename)) {
debug(5, "HEMilesChannel::startSpoolingChannel(): Couldn't open spooling file '%s'", filename);
return;
}
_stream.fileHandle->seek(offset, SEEK_CUR);
if (_stream.fileHandle->pos() != offset) {
debug(5, "HEMilesChannel::startSpoolingChannel(): Couldn't seek file %s to offset %ld", filename, offset);
_stream.fileHandle->close();
return;
}
// Extract the usual metadata information from the header, for later usage
int beginningPos = _stream.fileHandle->pos();
waveHeader.riffTag = _stream.fileHandle->readUint32BE();
waveHeader.riffSize = _stream.fileHandle->readUint32LE();
waveHeader.waveTag = _stream.fileHandle->readUint32BE();
waveHeader.fmtTag = _stream.fileHandle->readUint32BE();
waveHeader.fmtSize = _stream.fileHandle->readUint32LE();
int fmtPos = _stream.fileHandle->pos();
waveHeader.wFormatTag = _stream.fileHandle->readUint16LE();
waveHeader.wChannels = _stream.fileHandle->readUint16LE();
waveHeader.dwSamplesPerSec = _stream.fileHandle->readUint32LE();
waveHeader.dwAvgBytesPerSec = _stream.fileHandle->readUint32LE();
waveHeader.wBlockAlign = _stream.fileHandle->readUint16LE();
waveHeader.wBitsPerSample = _stream.fileHandle->readUint16LE();
// Some safety checks...
if (_stream.fileHandle->pos() - beginningPos != sizeof(waveHeader)) {
debug(5, "HEMilesChannel::startSpoolingChannel(): Couldn't read RIFF header correctly");
_stream.fileHandle->close();
return;
}
if (waveHeader.riffTag != MKTAG('R', 'I', 'F', 'F')) {
debug(5, "HEMilesChannel::startSpoolingChannel(): Expected RIFF tag, found %s instead", tag2str(waveHeader.riffTag));
return;
}
if (waveHeader.waveTag != MKTAG('W', 'A', 'V', 'E')) {
debug(5, "HEMilesChannel::startSpoolingChannel(): Expected WAVE tag, found %s instead", tag2str(waveHeader.waveTag));
return;
}
if (waveHeader.fmtTag != MKTAG('f', 'm', 't', ' ')) {
debug(5, "HEMilesChannel::startSpoolingChannel(): Expected fmt tag, found %s instead", tag2str(waveHeader.fmtTag));
return;
}
// Fill out the relevant metadata for our channel
_modifiers.volume = modifiers.volume;
_modifiers.pan = modifiers.pan;
_modifiers.frequencyShift = modifiers.frequencyShift;
_dataFormat = waveHeader.wFormatTag;
_blockAlign = waveHeader.wBlockAlign;
_numChannels = waveHeader.wChannels;
_bitsPerSample = waveHeader.wBitsPerSample;
_baseFrequency = waveHeader.dwSamplesPerSec;
// Transform the range of the pan value from [0, 127] to [-127, 127]
int scaledPan = (_modifiers.pan != 64) ? 2 * _modifiers.pan - 127 : 0;
int newFrequency = (_baseFrequency * modifiers.frequencyShift) / HSND_SOUND_FREQ_BASE;
// Create our stream and start it
_stream.streamObj = Audio::makeQueuingAudioStream(_baseFrequency, (waveHeader.wChannels > 1));
mixer->playStream(Audio::Mixer::kMusicSoundType, &_stream.streamHandle, _stream.streamObj, -1, 255, 0, DisposeAfterUse::NO);
if (_dataFormat == WAVE_FORMAT_PCM) {
// Apply the modifiers and the loop flag, if available
mixer->setChannelVolume(_stream.streamHandle, _modifiers.volume);
mixer->setChannelBalance(_stream.streamHandle, scaledPan);
if (newFrequency)
mixer->setChannelRate(_stream.streamHandle, newFrequency);
_stream.loopFlag = flags & ScummEngine_v70he::HESndFlags::HE_SND_LOOP;
// And now we help out the stream by feeding the first bytes of data to it
_stream.fileHandle->readUint32BE(); // Skip 'data' tag
_stream.dataLength = _stream.fileHandle->readUint32LE();
_stream.curDataPos = 0;
_stream.dataOffset = _stream.fileHandle->pos();
} else if (_dataFormat == WAVE_FORMAT_IMA_ADPCM) {
// IMA ADPCM might have a longer header, so use the previously obtained
// information to jump through blocks and find the 'data' tag
_stream.fileHandle->seek(fmtPos, SEEK_SET);
_stream.fileHandle->seek(waveHeader.fmtSize, SEEK_CUR);
uint32 curTag = 0;
while ((curTag = _stream.fileHandle->readUint32BE()) != MKTAG('d', 'a', 't', 'a')) {
uint32 blockSize = _stream.fileHandle->readUint32LE();
_stream.fileHandle->seek(blockSize, SEEK_CUR);
debug(5, "HEMixer::milesStartChannel(): APDCM spooling sound, searching for 'data' tag, now on '%s' tag...",
tag2str(curTag));
if (_stream.fileHandle->eos()) {
debug(5, "HEMixer::milesStartChannel(): APDCM spooling sound, couldn't find 'data' block, bailing out...");
return;
}
}
_stream.dataLength = _stream.fileHandle->readUint32LE();
_stream.curDataPos = 0;
_stream.dataOffset = _stream.fileHandle->pos();
} else {
debug(5, "HEMixer::milesStartChannel(): Unexpected sound format %d in sound file '%s' at offset %ld",
_dataFormat, filename, offset);
}
// Saturate the stream queue with the beginning of the audio data
for (int i = 0; i < MILES_MAX_QUEUED_STREAMS; i++)
serviceStream();
}
void HEMilesChannel::clearChannelData() {
_audioHandleActive = false;
_lastPlayPosition = 0;
_playFlags = 0;
_dataOffset = 0;
_globType = 0;
_globNum = 0;
_baseFrequency = 0;
_modifiers.volume = HSND_MAX_VOLUME;
_modifiers.pan = HSND_SOUND_PAN_CENTER;
_modifiers.frequencyShift = HSND_BASE_FREQ_FACTOR;
closeFileHandle();
_stream.fileHandle = nullptr;
_stream.streamObj = nullptr;
_stream.loopFlag = false;
_stream.dataLength = 0;
_stream.curDataPos = 0;
_stream.dataOffset = 0;
_bitsPerSample = 8;
_numChannels = 1;
_dataFormat = WAVE_FORMAT_PCM;
_isUsingStreamOverride = false;
}
void HEMilesChannel::closeFileHandle() {
if (_stream.fileHandle && _stream.fileHandle->isOpen())
_stream.fileHandle->close();
}
byte HEMilesChannel::getOutputFlags() {
byte streamFlags = 0;
if (_bitsPerSample == 8) // 8 bit data is unsigned
streamFlags |= Audio::FLAG_UNSIGNED;
else if (_bitsPerSample == 16) // 16 bit data is signed little endian
streamFlags |= (Audio::FLAG_16BITS | Audio::FLAG_LITTLE_ENDIAN);
else if (_bitsPerSample == 24) // 24 bit data is signed little endian
streamFlags |= (Audio::FLAG_24BITS | Audio::FLAG_LITTLE_ENDIAN);
else if (_bitsPerSample == 4 && (_dataFormat == WAVE_FORMAT_IMA_ADPCM))
streamFlags |= Audio::FLAG_16BITS;
if (_numChannels == 2)
streamFlags |= Audio::FLAG_STEREO;
#ifdef SCUMM_LITTLE_ENDIAN
if (_dataFormat == WAVE_FORMAT_IMA_ADPCM)
streamFlags |= Audio::FLAG_LITTLE_ENDIAN;
#endif
return streamFlags;
}
void HEMilesChannel::serviceStream() {
bool reachedTheEnd = false;
uint32 sizeToRead = 0;
// This is called at each frame, to ensure that the target stream doesn't starve
if (_stream.streamObj->numQueuedStreams() < MILES_MAX_QUEUED_STREAMS) {
if (_dataFormat == WAVE_FORMAT_PCM) {
sizeToRead = MIN<uint32>(MILES_PCM_CHUNK_SIZE * _blockAlign, _stream.dataLength - _stream.curDataPos);
reachedTheEnd = sizeToRead < MILES_PCM_CHUNK_SIZE * _blockAlign;
if (sizeToRead > 0) {
byte *buffer = (byte *)malloc(sizeToRead);
if (buffer != nullptr) {
int readBytes = _stream.fileHandle->read(buffer, sizeToRead);
_stream.curDataPos += readBytes;
_stream.streamObj->queueBuffer(buffer, readBytes, DisposeAfterUse::YES, getOutputFlags());
}
}
} else if (_dataFormat == WAVE_FORMAT_IMA_ADPCM) {
// Look, I know: it's some of the ugliest code you've ever seen. Sorry.
// Unfortunately when it comes to streaming ADPCM audio from a file this is as
// clean as I can possibly make it (instead of loading and keeping the whole
// thing in memory, that is).
sizeToRead = MIN<uint32>(MILES_IMA_ADPCM_PER_FRAME_CHUNKS_NUM * _blockAlign, _stream.dataLength - _stream.curDataPos);
reachedTheEnd = sizeToRead < MILES_IMA_ADPCM_PER_FRAME_CHUNKS_NUM * _blockAlign;
// We allocate a buffer which is going to be filled with
// (MILES_IMA_ADPCM_PER_FRAME_CHUNKS_NUM) compressed blocks or less
if (sizeToRead > 0) {
byte *compressedBuffer = (byte *)malloc(sizeToRead);
if (compressedBuffer != nullptr){
int readBytes = _stream.fileHandle->read(compressedBuffer, sizeToRead);
_stream.curDataPos += readBytes;
// Now, the ugly trick: use a MemoryReadStream containing our compressed data,
// to feed an ADPCM stream, and then use the latter to read uncompressed data,
// and then queue the latter in the output stream.
// Hey, it IS ugly! ...and it works :-)
Common::MemoryReadStream memStream(compressedBuffer, readBytes);
Audio::AudioStream *adpcmStream = Audio::makeADPCMStream(&memStream, DisposeAfterUse::NO,
readBytes, Audio::kADPCMMSIma, _baseFrequency, _numChannels, _blockAlign);
uint32 uncompSize =
calculateDeflatedADPCMBlockSize(MILES_IMA_ADPCM_PER_FRAME_CHUNKS_NUM, _blockAlign, _numChannels, 16);
byte *adpcmData = (byte *)malloc(uncompSize);
uint32 adpcmSize = adpcmStream->readBuffer((int16 *)(void *)adpcmData, uncompSize * 2);
adpcmSize *= 2;
_stream.streamObj->queueBuffer(adpcmData, adpcmSize, DisposeAfterUse::YES, getOutputFlags());
delete adpcmStream;
free(compressedBuffer);
}
}
}
if (reachedTheEnd) {
if (_stream.loopFlag) {
// Rewind the stream...
_stream.curDataPos = 0;
_stream.fileHandle->seek(_stream.dataOffset, SEEK_SET);
} else {
// Mark the stream as finished...
_stream.streamObj->finish();
}
}
}
}
} // End of namespace Scumm