/* 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 . * */ #include "common/file.h" #include "audio/audiostream.h" #include "audio/decoders/raw.h" #include "freescape/freescape.h" #include "freescape/games/eclipse/eclipse.h" namespace Freescape { void FreescapeEngine::loadSpeakerFxZX(Common::SeekableReadStream *file, int sfxTable, int sfxData) { debugC(1, kFreescapeDebugParser, "Reading sound table for ZX"); int numberSounds = 25; if (isDark()) numberSounds = 34; if (isEclipse() && (_variant & GF_ZX_DEMO_MICROHOBBY)) numberSounds = 21; for (int i = 1; i < numberSounds; i++) { debugC(1, kFreescapeDebugParser, "Reading sound table entry: %d ", i); _soundsSpeakerFxZX[i] = new Common::Array(); int soundIdx = (i - 1) * 4; file->seek(sfxTable + soundIdx); byte SFXtempStruct[8] = {}; uint8 dataIndex = file->readByte(); uint16 soundValue = file->readUint16LE(); SFXtempStruct[0] = file->readByte(); file->seek(sfxData + dataIndex * 4); uint8 soundType = file->readByte(); int original_sound_ptr = sfxData + dataIndex * 4 + 1; int sound_ptr = original_sound_ptr; uint8 soundSize = 0; int16 repetitions = 0; debugC(1, kFreescapeDebugParser, "dataIndex: %x, value: %x, SFXtempStruct[0]: %x, type: %x", dataIndex, soundValue, SFXtempStruct[0], soundType); if (soundType == 0xff) break; if ((soundType & 0x80) == 0) { SFXtempStruct[6] = 0; SFXtempStruct[4] = soundType; while (true) { while (true) { file->seek(sound_ptr); //debug("start sound ptr: %x", sound_ptr); soundSize = file->readByte(); SFXtempStruct[1] = soundSize; SFXtempStruct[2] = file->readByte(); SFXtempStruct[3] = file->readByte(); for (int j = 0; j <= 7; j++) debugC(1, kFreescapeDebugParser, "SFXtempStruct[%d]: %x", j, SFXtempStruct[j]); do { uint32 var9 = 0xffffff & (SFXtempStruct[3] * 0xd0); uint32 var10 = var9 / soundValue; var9 = 0xffffff & (7 * soundValue); uint16 var5 = (0xffff & var9) - 0x1e; if ((short)var5 < 0) var5 = 1; soundUnitZX soundUnit; soundUnit.isRaw = false; soundUnit.freqTimesSeconds = (var10 & 0xffff) + 1; soundUnit.tStates = var5; soundUnit.multiplier = 10; //debug("playSFX(%x, %x)", soundUnit.freqTimesSeconds, soundUnit.tStates); _soundsSpeakerFxZX[i]->push_back(soundUnit); int16 var4 = 0; if ((SFXtempStruct[2] & 0x80) != 0) { var4 = 0xff; } //debug("var4: %d", var4); //debug("soundValue delta: %d", int16(((var4 << 8) | SFXtempStruct[2]))); soundValue = soundValue + int16(((var4 << 8) | SFXtempStruct[2])); //debug("soundValue: %x", soundValue); soundSize = soundSize - 1; } while (soundSize != 0); SFXtempStruct[5] = SFXtempStruct[5] + 1; if (SFXtempStruct[5] == SFXtempStruct[4]) break; sound_ptr = original_sound_ptr + SFXtempStruct[5] * 3; //debug("sound ptr: %x", sound_ptr); } soundSize = SFXtempStruct[0]; SFXtempStruct[0] = soundSize - 1; sound_ptr = original_sound_ptr; if ((soundSize - 1) == 0) break; SFXtempStruct[5] = 0; } } else if (soundType & 0x80) { file->seek(sound_ptr); for (int j = 1; j <= 7; j++) { SFXtempStruct[j] = file->readByte(); debugC(1, kFreescapeDebugParser, "SFXtempStruct[%d]: %x", j, SFXtempStruct[j]); } soundSize = SFXtempStruct[0]; repetitions = SFXtempStruct[1] | (SFXtempStruct[2] << 8); uint16 var5 = soundValue; //debug("Repetitions: %x", repetitions); if ((soundType & 0x7f) == 1) { do { do { soundUnitZX soundUnit; soundUnit.isRaw = false; soundUnit.tStates = var5; soundUnit.freqTimesSeconds = SFXtempStruct[3] | (SFXtempStruct[4] << 8); soundUnit.multiplier = 1.8f; //debug("playSFX(%x, %x)", soundUnit.freqTimesSeconds, soundUnit.tStates); _soundsSpeakerFxZX[i]->push_back(soundUnit); repetitions = repetitions - 1; var5 = var5 + (SFXtempStruct[5] | (SFXtempStruct[6] << 8)); } while ((byte)((byte)repetitions | (byte)((uint16)repetitions >> 8)) != 0); soundSize = soundSize - 1; repetitions = SFXtempStruct[1] | (SFXtempStruct[2] << 8); var5 = soundValue; } while (soundSize != 0); } else if ((soundType & 0x7f) == 2) { repetitions = SFXtempStruct[1] | (SFXtempStruct[0] << 8); debugC(1, kFreescapeDebugParser, "Raw sound, repetitions: %x", repetitions); uint16 sVar7 = SFXtempStruct[3]; soundType = 0; soundSize = SFXtempStruct[2]; uint16 silenceSize = SFXtempStruct[4]; bool cond1 = (SFXtempStruct[4] != 0 && SFXtempStruct[4] != 2); bool cond2 = SFXtempStruct[4] == 2; bool cond3 = SFXtempStruct[4] == 0; assert(cond1 || cond2 || cond3); do { soundUnitZX soundUnit; soundUnit.isRaw = true; int totalSize = soundSize + sVar7; soundUnit.rawFreq = 0.1f; soundUnit.rawLengthus = totalSize; _soundsSpeakerFxZX[i]->push_back(soundUnit); //debugN("%x ", silenceSize); soundUnit.rawFreq = 0; soundUnit.rawLengthus = silenceSize; _soundsSpeakerFxZX[i]->push_back(soundUnit); repetitions = repetitions + -1; soundSize = SFXtempStruct[5] + soundSize; if (cond1) silenceSize = (repetitions & 0xff) | (repetitions >> 8); else if (cond2) silenceSize = (repetitions & 0xff); else silenceSize = soundSize; //debug("soundSize: %x", soundSize); //sVar7 = (uint16)bVar9 << 8; } while (repetitions != 0); //debug("\n"); //if (i == 15) // assert(0); } else { debugC(1, kFreescapeDebugParser, "Sound type: %x", soundType); bool beep = false; do { soundType = 0; uint16 uVar2 = SFXtempStruct[1] | (SFXtempStruct[2] << 8); uint8 cVar3 = 0; do { //debug("start cycle %d:", cVar3); //ULA_PORT = bVar4; //bVar4 = bVar4 ^ 0x10; beep = !beep; repetitions = (((uint16)soundType * 0x100 + (uint16)soundType * -2) - (uint16)((uint16)soundType * 0x100 < (uint16)soundType)) + (uVar2 & 0xff); uint8 bVar9 = (byte)repetitions; uint8 bVar8 = (byte)((uint16)repetitions >> 8); uint8 bVar1 = bVar9 - bVar8; soundType = bVar1; if (bVar8 <= bVar9) { bVar1 = bVar1 - 1; soundType = bVar1; } //debug("wait %d", bVar1); assert(bVar1 > 0); soundUnitZX soundUnit; soundUnit.isRaw = false; soundUnit.freqTimesSeconds = beep ? 1000 : 0; soundUnit.tStates = beep ? 437500 / 1000 - 30.125 : 0; soundUnit.multiplier = float(bVar1) / 500; _soundsSpeakerFxZX[i]->push_back(soundUnit); // No need to wait //do { // bVar1 = bVar1 - 1; //} while (bVar1 != 0); cVar3 = (char)(uVar2 >> 8) + -1; uVar2 = (((uint16)cVar3) << 8) | (uint8)uVar2; } while (cVar3 != '\0'); soundSize = soundSize + -1; } while (soundSize != '\0'); } } } //assert(0); } void FreescapeEngine::loadSpeakerFxDOS(Common::SeekableReadStream *file, int offsetFreq, int offsetTable, int numberSounds) { debugC(1, kFreescapeDebugParser, "Reading PC speaker sound table for DOS"); for (int i = 1; i <= numberSounds; i++) { debugC(1, kFreescapeDebugParser, "Reading sound table entry: %d ", i); int soundIdx = (i - 1) * 4; file->seek(offsetFreq + soundIdx); uint16 index = file->readByte(); if (index == 0xff) continue; uint iVar = index * 5; uint16 frequencyStart = file->readUint16LE(); uint8 repetitions = file->readByte(); debugC(1, kFreescapeDebugParser, "Frequency start: %d ", frequencyStart); debugC(1, kFreescapeDebugParser, "Repetitions: %d ", repetitions); uint8 frequencyStepsNumber = 0; uint16 frequencyStep = 0; file->seek(offsetTable + iVar); uint8 lastIndex = file->readByte(); debugC(1, kFreescapeDebugParser, "0x%x %d (lastIndex)", offsetTable - 0x200, lastIndex); frequencyStepsNumber = file->readByte(); debugC(1, kFreescapeDebugParser, "0x%x %d (frequency steps)", offsetTable + 1 - 0x200, frequencyStepsNumber); int basePtr = offsetTable + iVar + 1; debugC(1, kFreescapeDebugParser, "0x%x (basePtr)", basePtr - 0x200); frequencyStep = file->readUint16LE(); debugC(1, kFreescapeDebugParser, "0x%x %d (steps number)", offsetTable + 2 - 0x200, (int16)frequencyStep); uint8 frequencyDuration = file->readByte(); debugC(1, kFreescapeDebugParser, "0x%x %d (frequency duration)", offsetTable + 4 - 0x200, frequencyDuration); soundSpeakerFx *speakerFxInfo = new soundSpeakerFx(); _soundsSpeakerFx[i] = speakerFxInfo; speakerFxInfo->frequencyStart = frequencyStart; speakerFxInfo->repetitions = repetitions; speakerFxInfo->frequencyStepsNumber = frequencyStepsNumber; speakerFxInfo->frequencyStep = frequencyStep; speakerFxInfo->frequencyDuration = frequencyDuration; for (int j = 1; j < lastIndex; j++) { soundSpeakerFx *speakerFxInfoAdditionalStep = new soundSpeakerFx(); speakerFxInfoAdditionalStep->frequencyStart = 0; speakerFxInfoAdditionalStep->repetitions = 0; file->seek(basePtr + 4 * j); debugC(1, kFreescapeDebugParser, "Reading at %x", basePtr + 4 * j - 0x200); frequencyStepsNumber = file->readByte(); debugC(1, kFreescapeDebugParser, "%d (steps number)", frequencyStepsNumber); frequencyStep = file->readUint16LE(); debugC(1, kFreescapeDebugParser, "%d (frequency step)", (int16)frequencyStep); frequencyDuration = file->readByte(); debugC(1, kFreescapeDebugParser, "%d (frequency duration)", frequencyDuration); speakerFxInfoAdditionalStep->frequencyStepsNumber = frequencyStepsNumber; speakerFxInfoAdditionalStep->frequencyStep = frequencyStep; speakerFxInfoAdditionalStep->frequencyDuration = frequencyDuration; speakerFxInfo->additionalSteps.push_back(speakerFxInfoAdditionalStep); } debugC(1, kFreescapeDebugParser, "\n"); } } void FreescapeEngine::playSound(int index, bool sync, Audio::SoundHandle &handle) { if (index < 0) { debugC(1, kFreescapeDebugMedia, "Sound not specified"); return; } if (_syncSound) waitForSounds(); _syncSound = sync; debugC(1, kFreescapeDebugMedia, "Playing sound %d with sync: %d", index, sync); if (isAmiga() || isAtariST()) { playSoundFx(index, sync); return; } if (isDOS()) { soundSpeakerFx *speakerFxInfo = _soundsSpeakerFx[index]; if (speakerFxInfo) playSoundDOS(speakerFxInfo, sync, handle); else debugC(1, kFreescapeDebugMedia, "WARNING: Sound %d is not available", index); return; } else if (isSpectrum() && !isDriller()) { playSoundZX(_soundsSpeakerFxZX[index], handle); return; } Common::Path filename; filename = Common::String::format("%s-%d.wav", _targetName.c_str(), index); debugC(1, kFreescapeDebugMedia, "Playing sound %s", filename.toString().c_str()); playWav(filename); /*switch (index) { case 1: playWav("fsDOS_laserFire.wav"); break; case 2: // Done playWav("fsDOS_WallBump.wav"); break; case 3: playWav("fsDOS_stairDown.wav"); break; case 4: playWav("fsDOS_stairUp.wav"); break; case 5: playWav("fsDOS_roomChange.wav"); break; case 6: playWav("fsDOS_configMenu.wav"); break; case 7: playWav("fsDOS_bigHit.wav"); break; case 8: playWav("fsDOS_teleporterActivated.wav"); break; case 9: playWav("fsDOS_powerUp.wav"); break; case 10: playWav("fsDOS_energyDrain.wav"); break; case 11: // ??? debugC(1, kFreescapeDebugMedia, "Playing unknown sound"); break; case 12: playWav("fsDOS_switchOff.wav"); break; case 13: // Seems to be repeated? playWav("fsDOS_laserHit.wav"); break; case 14: playWav("fsDOS_tankFall.wav"); break; case 15: playWav("fsDOS_successJingle.wav"); break; case 16: // Silence? break; case 17: playWav("fsDOS_badJingle.wav"); break; case 18: // Silence? break; case 19: debugC(1, kFreescapeDebugMedia, "Playing unknown sound"); break; case 20: playWav("fsDOS_bigHit.wav"); break; default: debugC(1, kFreescapeDebugMedia, "Unexpected sound %d", index); break; }*/ _syncSound = sync; } void FreescapeEngine::playWav(const Common::Path &filename) { Common::SeekableReadStream *s = _dataBundle->createReadStreamForMember(filename); if (!s) { debugC(1, kFreescapeDebugMedia, "WARNING: Sound %s not found", filename.toString().c_str()); return; } Audio::AudioStream *stream = Audio::makeWAVStream(s, DisposeAfterUse::YES); _mixer->playStream(Audio::Mixer::kSFXSoundType, &_soundFxHandle, stream); } void FreescapeEngine::playMusic(const Common::Path &filename) { Audio::SeekableAudioStream *stream = nullptr; stream = Audio::SeekableAudioStream::openStreamFile(filename); if (stream) { _mixer->stopHandle(_musicHandle); Audio::LoopingAudioStream *loop = new Audio::LoopingAudioStream(stream, 0); _mixer->playStream(Audio::Mixer::kMusicSoundType, &_musicHandle, loop); } } void FreescapeEngine::playSoundFx(int index, bool sync) { if (_soundsFx.size() == 0) { debugC(1, kFreescapeDebugMedia, "WARNING: Sounds are not loaded"); return; } if (index < 0 || index >= int(_soundsFx.size())) { debugC(1, kFreescapeDebugMedia, "WARNING: Sound %d not available", index); return; } int size = _soundsFx[index]->size; int sampleRate = _soundsFx[index]->sampleRate; int repetitions = _soundsFx[index]->repetitions; byte *data = _soundsFx[index]->data; if (size > 4) { Audio::SeekableAudioStream *s = Audio::makeRawStream(data, size, sampleRate, Audio::FLAG_16BITS, DisposeAfterUse::NO); Audio::AudioStream *stream = new Audio::LoopingAudioStream(s, repetitions); _mixer->playStream(Audio::Mixer::kSFXSoundType, &_soundFxHandle, stream); } else debugC(1, kFreescapeDebugMedia, "WARNING: Sound %d is empty", index); } void FreescapeEngine::stopAllSounds(Audio::SoundHandle &handle) { debugC(1, kFreescapeDebugMedia, "Stopping sound"); _mixer->stopHandle(handle); } void FreescapeEngine::waitForSounds() { if (_usePrerecordedSounds || isAmiga() || isAtariST()) while (_mixer->isSoundHandleActive(_soundFxHandle)) waitInLoop(10); else { while (!_speaker->endOfStream()) waitInLoop(10); } } bool FreescapeEngine::isPlayingSound() { if (_usePrerecordedSounds || isAmiga() || isAtariST()) return _mixer->isSoundHandleActive(_soundFxHandle); return (!_speaker->endOfStream()); } void FreescapeEngine::playSilence(int duration, bool sync) { _speaker->playQueue(Audio::PCSpeaker::kWaveFormSilence, 0, 1000 * 10 * duration); _mixer->stopHandle(_soundFxHandle); _mixer->playStream(Audio::Mixer::kSFXSoundType, &_soundFxHandle, _speaker, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO); } void FreescapeEngine::queueSoundConst(double hzFreq, int duration) { _speaker->playQueue(Audio::PCSpeaker::kWaveFormSquare, hzFreq, 1000 * 10 * duration); } uint16 FreescapeEngine::playSoundDOSSpeaker(uint16 frequencyStart, soundSpeakerFx *speakerFxInfo) { uint8 frequencyStepsNumber = speakerFxInfo->frequencyStepsNumber; int16 frequencyStep = speakerFxInfo->frequencyStep; uint8 frequencyDuration = speakerFxInfo->frequencyDuration; int16 freq = frequencyStart; int waveDurationMultipler = 1800; int waveDuration = waveDurationMultipler * (frequencyDuration + 1); while (true) { if (freq > 0) { float hzFreq = 1193180.0 / freq; debugC(1, kFreescapeDebugMedia, "raw %d, hz: %f, duration: %d", freq, hzFreq, waveDuration); _speaker->playQueue(Audio::PCSpeaker::kWaveFormSquare, hzFreq, waveDuration); } if (frequencyStepsNumber > 0) { // Ascending initial portions of cycle freq += frequencyStep; frequencyStepsNumber--; } else break; } return freq; } void FreescapeEngine::playSoundZX(Common::Array *data, Audio::SoundHandle &handle) { for (auto &it : *data) { soundUnitZX value = it; if (value.isRaw) { debugC(1, kFreescapeDebugMedia, "raw hz: %f, duration: %d", value.rawFreq, value.rawLengthus); if (value.rawFreq == 0) { _speaker->playQueue(Audio::PCSpeaker::kWaveFormSilence, 1, 5 * value.rawLengthus); continue; } _speaker->playQueue(Audio::PCSpeaker::kWaveFormSquare, value.rawFreq, 5 * value.rawLengthus); } else { if (value.freqTimesSeconds == 0 && value.tStates == 0) { _speaker->playQueue(Audio::PCSpeaker::kWaveFormSilence, 1, 1000 * value.multiplier); continue; } float hzFreq = 1 / ((value.tStates + 30.125) / 437500.0); float waveDuration = value.freqTimesSeconds / hzFreq; waveDuration = value.multiplier * 1000 * (waveDuration + 1); debugC(1, kFreescapeDebugMedia, "non raw hz: %f, duration: %f", hzFreq, waveDuration); _speaker->playQueue(Audio::PCSpeaker::kWaveFormSquare, hzFreq, waveDuration); } } _mixer->stopHandle(_soundFxHandle); _mixer->playStream(Audio::Mixer::kSFXSoundType, &handle, _speaker, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO); } void FreescapeEngine::playSoundDOS(soundSpeakerFx *speakerFxInfo, bool sync, Audio::SoundHandle &handle) { uint freq = speakerFxInfo->frequencyStart; for (int i = 0; i < speakerFxInfo->repetitions; i++) { freq = playSoundDOSSpeaker(freq, speakerFxInfo); for (auto &it : speakerFxInfo->additionalSteps) { assert(it); freq = playSoundDOSSpeaker(freq, it); } } _mixer->stopHandle(handle); _mixer->playStream(Audio::Mixer::kSFXSoundType, &handle, _speaker, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO); } void FreescapeEngine::loadSoundsFx(Common::SeekableReadStream *file, int offset, int number) { file->seek(offset); soundFx *sound = nullptr; _soundsFx[0] = sound; for (int i = 1; i < number + 1; i++) { sound = (soundFx *)malloc(sizeof(soundFx)); int zero = file->readUint16BE(); assert(zero == 0); int size = file->readUint16BE(); float sampleRate = float(file->readUint16BE()) / 2; debugC(1, kFreescapeDebugParser, "Loading sound: %d (size: %d, sample rate: %f) at %" PRIx64, i, size, sampleRate, file->pos()); byte *data = (byte *)malloc(size * sizeof(byte)); file->read(data, size); sound->sampleRate = sampleRate; sound->size = size; sound->data = (byte *)data; sound->repetitions = 1; _soundsFx[i] = sound; } } } // namespace Freescape