aboutsummaryrefslogtreecommitdiff
path: root/engines/mohawk/sound.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engines/mohawk/sound.cpp')
-rw-r--r--engines/mohawk/sound.cpp451
1 files changed, 144 insertions, 307 deletions
diff --git a/engines/mohawk/sound.cpp b/engines/mohawk/sound.cpp
index 38cb0b3608..0711561068 100644
--- a/engines/mohawk/sound.cpp
+++ b/engines/mohawk/sound.cpp
@@ -37,6 +37,150 @@
namespace Mohawk {
+Audio::RewindableAudioStream *makeMohawkWaveStream(Common::SeekableReadStream *stream, CueList *cueList) {
+ uint32 tag = 0;
+ ADPCMStatus adpcmStatus;
+ DataChunk dataChunk;
+ uint32 dataSize = 0;
+
+ memset(&dataChunk, 0, sizeof(DataChunk));
+
+ if (stream->readUint32BE() != ID_MHWK) // MHWK tag again
+ error ("Could not find tag 'MHWK'");
+
+ stream->readUint32BE(); // Skip size
+
+ if (stream->readUint32BE() != ID_WAVE)
+ error ("Could not find tag 'WAVE'");
+
+ while (!dataChunk.audioData) {
+ tag = stream->readUint32BE();
+
+ switch (tag) {
+ case ID_ADPC:
+ debug(2, "Found Tag ADPC");
+ // ADPCM Sound Only
+ //
+ // This is useful for seeking in the stream, and is actually quite brilliant
+ // considering some of the other things Broderbund did with the engine.
+ // Only Riven and CSTime are known to use ADPCM audio and only CSTime
+ // actually requires this for seeking. On the other hand, it may be interesting
+ // to look at that one Riven sample that uses the cue points.
+ //
+ // Basically, the sample frame from the cue list is looked up here and then
+ // sets the starting sample and step index at the point specified. Quite
+ // an elegant/efficient system, really.
+
+ adpcmStatus.size = stream->readUint32BE();
+ adpcmStatus.itemCount = stream->readUint16BE();
+ adpcmStatus.channels = stream->readUint16BE();
+ adpcmStatus.statusItems = new ADPCMStatus::StatusItem[adpcmStatus.itemCount];
+
+ assert(adpcmStatus.channels <= 2);
+
+ for (uint16 i = 0; i < adpcmStatus.itemCount; i++) {
+ adpcmStatus.statusItems[i].sampleFrame = stream->readUint32BE();
+
+ for (uint16 j = 0; j < adpcmStatus.channels; j++) {
+ adpcmStatus.statusItems[i].channelStatus[j].last = stream->readSint16BE();
+ adpcmStatus.statusItems[i].channelStatus[j].stepIndex = stream->readUint16BE();
+ }
+ }
+
+ // TODO: Actually use this chunk. For now, just delete the status items...
+ delete[] adpcmStatus.statusItems;
+ break;
+ case ID_CUE:
+ debug(2, "Found Tag Cue#");
+ // Cues are used for animation sync. There are a couple in Myst and
+ // Riven but are not used there at all.
+
+ if (!cueList) {
+ uint32 size = stream->readUint32BE();
+ stream->skip(size);
+ break;
+ }
+
+ cueList->size = stream->readUint32BE();
+ cueList->pointCount = stream->readUint16BE();
+
+ if (cueList->pointCount == 0)
+ debug(2, "Cue# chunk found with no points!");
+ else
+ debug(2, "Cue# chunk found with %d point(s)!", cueList->pointCount);
+
+ cueList->points.resize(cueList->pointCount);
+ for (uint16 i = 0; i < cueList->pointCount; i++) {
+ cueList->points[i].sampleFrame = stream->readUint32BE();
+
+ byte nameLength = stream->readByte();
+ cueList->points[i].name.clear();
+ for (byte j = 0; j < nameLength; j++)
+ cueList->points[i].name += stream->readByte();
+
+ // Realign to an even boundary
+ if (!(nameLength & 1))
+ stream->readByte();
+
+ debug (3, "Cue# chunk point %d (frame %d): %s", i, cueList->points[i].sampleFrame, cueList->points[i].name.c_str());
+ }
+ break;
+ case ID_DATA:
+ debug(2, "Found Tag DATA");
+ // We subtract 20 from the actual chunk size, which is the total size
+ // of the chunk's header
+ dataSize = stream->readUint32BE() - 20;
+ dataChunk.sampleRate = stream->readUint16BE();
+ dataChunk.sampleCount = stream->readUint32BE();
+ dataChunk.bitsPerSample = stream->readByte();
+ dataChunk.channels = stream->readByte();
+ dataChunk.encoding = stream->readUint16BE();
+ dataChunk.loopCount = stream->readUint16BE();
+ dataChunk.loopStart = stream->readUint32BE();
+ dataChunk.loopEnd = stream->readUint32BE();
+
+ // NOTE: We currently ignore all of the loop parameters here. Myst uses the
+ // loopCount variable but the loopStart and loopEnd are always 0 and the size of
+ // the sample. Myst ME doesn't use the Mohawk Sound format and just standard WAVE
+ // files and therefore does not contain any of this metadata and we have to specify
+ // whether or not to loop elsewhere.
+
+ dataChunk.audioData = stream->readStream(dataSize);
+ break;
+ default:
+ error ("Unknown tag found in 'tWAV' chunk -- '%s'", tag2str(tag));
+ }
+ }
+
+ // makeMohawkWaveStream always takes control of the original stream
+ delete stream;
+
+ // The sound in Myst uses raw unsigned 8-bit data
+ // The sound in the CD version of Riven is encoded in Intel DVI ADPCM
+ // The sound in the DVD version of Riven is encoded in MPEG-2 Layer II or Intel DVI ADPCM
+ if (dataChunk.encoding == kCodecRaw) {
+ byte flags = Audio::FLAG_UNSIGNED;
+
+ if (dataChunk.channels == 2)
+ flags |= Audio::FLAG_STEREO;
+
+ return Audio::makeRawStream(dataChunk.audioData, dataChunk.sampleRate, flags);
+ } else if (dataChunk.encoding == kCodecADPCM) {
+ uint32 blockAlign = dataChunk.channels * dataChunk.bitsPerSample / 8;
+ return Audio::makeADPCMStream(dataChunk.audioData, DisposeAfterUse::YES, dataSize, Audio::kADPCMDVI, dataChunk.sampleRate, dataChunk.channels, blockAlign);
+ } else if (dataChunk.encoding == kCodecMPEG2) {
+#ifdef USE_MAD
+ return Audio::makeMP3Stream(dataChunk.audioData, DisposeAfterUse::YES);
+#else
+ warning ("MAD library not included - unable to play MP2 audio");
+#endif
+ } else {
+ error ("Unknown Mohawk WAVE encoding %d", dataChunk.encoding);
+ }
+
+ return nullptr;
+}
+
Sound::Sound(MohawkEngine* vm) : _vm(vm) {
_midiDriver = NULL;
_midiParser = NULL;
@@ -47,7 +191,6 @@ Sound::Sound(MohawkEngine* vm) : _vm(vm) {
Sound::~Sound() {
stopSound();
- stopAllSLST();
stopBackgroundMyst();
if (_midiParser) {
@@ -234,300 +377,6 @@ void Sound::stopMidi() {
_midiParser->unloadMusic();
}
-byte Sound::convertRivenVolume(uint16 volume) {
- return (volume == 256) ? 255 : volume;
-}
-
-void Sound::playSLST(uint16 index, uint16 card) {
- Common::SeekableReadStream *slstStream = _vm->getResource(ID_SLST, card);
- SLSTRecord slstRecord;
- uint16 recordCount = slstStream->readUint16BE();
-
- for (uint16 i = 0; i < recordCount; i++) {
- slstRecord.index = slstStream->readUint16BE();
- slstRecord.sound_count = slstStream->readUint16BE();
- slstRecord.sound_ids = new uint16[slstRecord.sound_count];
-
- for (uint16 j = 0; j < slstRecord.sound_count; j++)
- slstRecord.sound_ids[j] = slstStream->readUint16BE();
-
- slstRecord.fade_flags = slstStream->readUint16BE();
- slstRecord.loop = slstStream->readUint16BE();
- slstRecord.global_volume = slstStream->readUint16BE();
- slstRecord.u0 = slstStream->readUint16BE(); // Unknown
-
- if (slstRecord.u0 > 1)
- warning("slstRecord.u0: %d non-boolean", slstRecord.u0);
-
- slstRecord.u1 = slstStream->readUint16BE(); // Unknown
-
- if (slstRecord.u1 != 0)
- warning("slstRecord.u1: %d non-zero", slstRecord.u1);
-
- slstRecord.volumes = new uint16[slstRecord.sound_count];
- slstRecord.balances = new int16[slstRecord.sound_count];
- slstRecord.u2 = new uint16[slstRecord.sound_count];
-
- for (uint16 j = 0; j < slstRecord.sound_count; j++)
- slstRecord.volumes[j] = slstStream->readUint16BE();
-
- for (uint16 j = 0; j < slstRecord.sound_count; j++)
- slstRecord.balances[j] = slstStream->readSint16BE(); // negative = left, 0 = center, positive = right
-
- for (uint16 j = 0; j < slstRecord.sound_count; j++) {
- slstRecord.u2[j] = slstStream->readUint16BE(); // Unknown
-
- if (slstRecord.u2[j] != 255 && slstRecord.u2[j] != 256)
- warning("slstRecord.u2[%d]: %d not 255 or 256", j, slstRecord.u2[j]);
- }
-
- if (slstRecord.index == index) {
- playSLST(slstRecord);
- delete[] slstRecord.sound_ids;
- delete[] slstRecord.volumes;
- delete[] slstRecord.balances;
- delete[] slstRecord.u2;
- delete slstStream;
- return;
- }
-
- delete[] slstRecord.sound_ids;
- delete[] slstRecord.volumes;
- delete[] slstRecord.balances;
- delete[] slstRecord.u2;
- }
-
- delete slstStream;
-
- // If we have no matching entries, we do nothing and just let
- // the previous ambient sounds continue.
-}
-
-void Sound::playSLST(SLSTRecord slstRecord) {
- // End old sounds
- for (uint16 i = 0; i < _currentSLSTSounds.size(); i++) {
- bool noLongerPlay = true;
- for (uint16 j = 0; j < slstRecord.sound_count; j++)
- if (_currentSLSTSounds[i].id == slstRecord.sound_ids[j])
- noLongerPlay = false;
- if (noLongerPlay)
- stopSLSTSound(i, (slstRecord.fade_flags & 1) != 0);
- }
-
- // Start new sounds
- for (uint16 i = 0; i < slstRecord.sound_count; i++) {
- bool alreadyPlaying = false;
- for (uint16 j = 0; j < _currentSLSTSounds.size(); j++) {
- if (_currentSLSTSounds[j].id == slstRecord.sound_ids[i])
- alreadyPlaying = true;
- }
- if (!alreadyPlaying) {
- playSLSTSound(slstRecord.sound_ids[i],
- (slstRecord.fade_flags & (1 << 1)) != 0,
- slstRecord.loop != 0,
- slstRecord.volumes[i],
- slstRecord.balances[i]);
- }
- }
-}
-
-void Sound::stopAllSLST(bool fade) {
- for (uint16 i = 0; i < _currentSLSTSounds.size(); i++) {
- // TODO: Fade out, if requested
- _vm->_mixer->stopHandle(*_currentSLSTSounds[i].handle);
- delete _currentSLSTSounds[i].handle;
- }
-
- _currentSLSTSounds.clear();
-}
-
-static int8 convertBalance(int16 balance) {
- return (int8)(balance >> 8);
-}
-
-void Sound::playSLSTSound(uint16 id, bool fade, bool loop, uint16 volume, int16 balance) {
- // WORKAROUND: Some Riven SLST entries have a volume of 0, so we just ignore them.
- if (volume == 0)
- return;
-
- SLSTSndHandle sndHandle;
- sndHandle.handle = new Audio::SoundHandle();
- sndHandle.id = id;
- _currentSLSTSounds.push_back(sndHandle);
-
- Audio::RewindableAudioStream *rewindStream = makeMohawkWaveStream(_vm->getResource(ID_TWAV, id));
-
- // Loop here if necessary
- Audio::AudioStream *audStream = rewindStream;
- if (loop)
- audStream = Audio::makeLoopingAudioStream(rewindStream, 0);
-
- // TODO: Handle fading, possibly just raise the volume of the channel in increments?
-
- _vm->_mixer->playStream(Audio::Mixer::kPlainSoundType, sndHandle.handle, audStream, -1, convertRivenVolume(volume), convertBalance(balance));
-}
-
-void Sound::stopSLSTSound(uint16 index, bool fade) {
- // TODO: Fade out, if requested
- _vm->_mixer->stopHandle(*_currentSLSTSounds[index].handle);
- delete _currentSLSTSounds[index].handle;
- _currentSLSTSounds.remove_at(index);
-}
-
-void Sound::pauseSLST() {
- for (uint16 i = 0; i < _currentSLSTSounds.size(); i++)
- _vm->_mixer->pauseHandle(*_currentSLSTSounds[i].handle, true);
-}
-
-void Sound::resumeSLST() {
- for (uint16 i = 0; i < _currentSLSTSounds.size(); i++)
- _vm->_mixer->pauseHandle(*_currentSLSTSounds[i].handle, false);
-}
-
-Audio::RewindableAudioStream *Sound::makeMohawkWaveStream(Common::SeekableReadStream *stream, CueList *cueList) {
- uint32 tag = 0;
- ADPCMStatus adpcmStatus;
- DataChunk dataChunk;
- uint32 dataSize = 0;
-
- memset(&dataChunk, 0, sizeof(DataChunk));
-
- if (stream->readUint32BE() != ID_MHWK) // MHWK tag again
- error ("Could not find tag 'MHWK'");
-
- stream->readUint32BE(); // Skip size
-
- if (stream->readUint32BE() != ID_WAVE)
- error ("Could not find tag 'WAVE'");
-
- while (!dataChunk.audioData) {
- tag = stream->readUint32BE();
-
- switch (tag) {
- case ID_ADPC:
- debug(2, "Found Tag ADPC");
- // ADPCM Sound Only
- //
- // This is useful for seeking in the stream, and is actually quite brilliant
- // considering some of the other things Broderbund did with the engine.
- // Only Riven and CSTime are known to use ADPCM audio and only CSTime
- // actually requires this for seeking. On the other hand, it may be interesting
- // to look at that one Riven sample that uses the cue points.
- //
- // Basically, the sample frame from the cue list is looked up here and then
- // sets the starting sample and step index at the point specified. Quite
- // an elegant/efficient system, really.
-
- adpcmStatus.size = stream->readUint32BE();
- adpcmStatus.itemCount = stream->readUint16BE();
- adpcmStatus.channels = stream->readUint16BE();
- adpcmStatus.statusItems = new ADPCMStatus::StatusItem[adpcmStatus.itemCount];
-
- assert(adpcmStatus.channels <= 2);
-
- for (uint16 i = 0; i < adpcmStatus.itemCount; i++) {
- adpcmStatus.statusItems[i].sampleFrame = stream->readUint32BE();
-
- for (uint16 j = 0; j < adpcmStatus.channels; j++) {
- adpcmStatus.statusItems[i].channelStatus[j].last = stream->readSint16BE();
- adpcmStatus.statusItems[i].channelStatus[j].stepIndex = stream->readUint16BE();
- }
- }
-
- // TODO: Actually use this chunk. For now, just delete the status items...
- delete[] adpcmStatus.statusItems;
- break;
- case ID_CUE:
- debug(2, "Found Tag Cue#");
- // Cues are used for animation sync. There are a couple in Myst and
- // Riven but are not used there at all.
-
- if (!cueList) {
- uint32 size = stream->readUint32BE();
- stream->skip(size);
- break;
- }
-
- cueList->size = stream->readUint32BE();
- cueList->pointCount = stream->readUint16BE();
-
- if (cueList->pointCount == 0)
- debug(2, "Cue# chunk found with no points!");
- else
- debug(2, "Cue# chunk found with %d point(s)!", cueList->pointCount);
-
- cueList->points.resize(cueList->pointCount);
- for (uint16 i = 0; i < cueList->pointCount; i++) {
- cueList->points[i].sampleFrame = stream->readUint32BE();
-
- byte nameLength = stream->readByte();
- cueList->points[i].name.clear();
- for (byte j = 0; j < nameLength; j++)
- cueList->points[i].name += stream->readByte();
-
- // Realign to an even boundary
- if (!(nameLength & 1))
- stream->readByte();
-
- debug (3, "Cue# chunk point %d (frame %d): %s", i, cueList->points[i].sampleFrame, cueList->points[i].name.c_str());
- }
- break;
- case ID_DATA:
- debug(2, "Found Tag DATA");
- // We subtract 20 from the actual chunk size, which is the total size
- // of the chunk's header
- dataSize = stream->readUint32BE() - 20;
- dataChunk.sampleRate = stream->readUint16BE();
- dataChunk.sampleCount = stream->readUint32BE();
- dataChunk.bitsPerSample = stream->readByte();
- dataChunk.channels = stream->readByte();
- dataChunk.encoding = stream->readUint16BE();
- dataChunk.loopCount = stream->readUint16BE();
- dataChunk.loopStart = stream->readUint32BE();
- dataChunk.loopEnd = stream->readUint32BE();
-
- // NOTE: We currently ignore all of the loop parameters here. Myst uses the
- // loopCount variable but the loopStart and loopEnd are always 0 and the size of
- // the sample. Myst ME doesn't use the Mohawk Sound format and just standard WAVE
- // files and therefore does not contain any of this metadata and we have to specify
- // whether or not to loop elsewhere.
-
- dataChunk.audioData = stream->readStream(dataSize);
- break;
- default:
- error ("Unknown tag found in 'tWAV' chunk -- '%s'", tag2str(tag));
- }
- }
-
- // makeMohawkWaveStream always takes control of the original stream
- delete stream;
-
- // The sound in Myst uses raw unsigned 8-bit data
- // The sound in the CD version of Riven is encoded in Intel DVI ADPCM
- // The sound in the DVD version of Riven is encoded in MPEG-2 Layer II or Intel DVI ADPCM
- if (dataChunk.encoding == kCodecRaw) {
- byte flags = Audio::FLAG_UNSIGNED;
-
- if (dataChunk.channels == 2)
- flags |= Audio::FLAG_STEREO;
-
- return Audio::makeRawStream(dataChunk.audioData, dataChunk.sampleRate, flags);
- } else if (dataChunk.encoding == kCodecADPCM) {
- uint32 blockAlign = dataChunk.channels * dataChunk.bitsPerSample / 8;
- return Audio::makeADPCMStream(dataChunk.audioData, DisposeAfterUse::YES, dataSize, Audio::kADPCMDVI, dataChunk.sampleRate, dataChunk.channels, blockAlign);
- } else if (dataChunk.encoding == kCodecMPEG2) {
-#ifdef USE_MAD
- return Audio::makeMP3Stream(dataChunk.audioData, DisposeAfterUse::YES);
-#else
- warning ("MAD library not included - unable to play MP2 audio");
-#endif
- } else {
- error ("Unknown Mohawk WAVE encoding %d", dataChunk.encoding);
- }
-
- return NULL;
-}
-
Audio::RewindableAudioStream *Sound::makeLivingBooksWaveStream_v1(Common::SeekableReadStream *stream) {
uint16 header = stream->readUint16BE();
uint16 rate = 0;
@@ -591,18 +440,6 @@ void Sound::stopSound(uint16 id) {
}
}
-void Sound::pauseSound() {
- for (uint32 i = 0; i < _handles.size(); i++)
- if (_handles[i].type == kUsedHandle)
- _vm->_mixer->pauseHandle(_handles[i].handle, true);
-}
-
-void Sound::resumeSound() {
- for (uint32 i = 0; i < _handles.size(); i++)
- if (_handles[i].type == kUsedHandle)
- _vm->_mixer->pauseHandle(_handles[i].handle, false);
-}
-
bool Sound::isPlaying(uint16 id) {
for (uint32 i = 0; i < _handles.size(); i++)
if (_handles[i].type == kUsedHandle && _handles[i].id == id)