/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ // FIXME: This code is taken from MADE and may need more work (e.g. setVolume). // MIDI and digital music class #include "audio/audiostream.h" #include "audio/mididrv.h" #include "audio/midiparser.h" // Miles Audio for Discworld 1 #include "audio/miles.h" #include "backends/audiocd/audiocd.h" #include "common/config-manager.h" #include "common/file.h" #include "common/memstream.h" #include "tinsel/adpcm.h" #include "tinsel/config.h" #include "tinsel/sound.h" #include "tinsel/music.h" #include "tinsel/handle.h" #include "tinsel/sysvar.h" enum { MUSIC_JUMP = -1, MUSIC_END = -2, BLMAGIC = -3458, DIM_SPEED = 8 }; namespace Tinsel { //--------------------------- Midi data ------------------------------------- // sound buffer structure used for MIDI data and samples struct SOUND_BUFFER { uint8 *pDat; // pointer to actual buffer uint32 size; // size of the buffer }; // FIXME: Avoid non-const global vars // MIDI buffer static SOUND_BUFFER g_midiBuffer = { 0, 0 }; static SCNHANDLE g_currentMidi = 0; static bool g_currentLoop = false; // We allocate 155 entries because that's the maximum, used in the SCN version static SCNHANDLE g_midiOffsets[155]; static const int enhancedAudioGRAVersion[] = { 1, 2, 1, 1, 3, 3, 4, 4, 5, 6, // 1-10 1, 7, 8, 9, 10, 3, 11, 11, 12, 13, // 11-20 13, 13, 13, 13, 14, 13, 13, 15, 16, 17, // 21-30 15, 18, 19, 20, 338, 21, 21, 22, 22, 23, // 31-40 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, // 41-50 34, 35, 35, 36, 37, 38, 39, 39, 39, 39, // 51-60 40, 39, 41, 41, 42, 43, 42, 44, 45, 41, // 61-70 46, 48, 47, 48, 49, 50, 51, 52, 53, 54, // 71-80 55, 56, 57, 58, 59, 60, 61, 62, 63, 61, // 81-90 64, 65, 66, 67, 68, 69, 70, 68, 71, 72, // 91-100 73, 74, 75, 12, 76, 77, 78, 79, 80, 4, // 101-110 81, 82, 83, 82, 81, 84, 85, 86, 87, 88, // 111-120 89, 90, 88, 2, 2, 2, 2, 2, 2, 60, // 121-130 91, 92, 93, 94, 94, 95, 96, 52, 4, 97, // 131-140 98, 99, 99 // 141-143 }; static const int enhancedAudioSCNVersion[] = { 301, 302, 2, 1, 1, 301, 302, 3, 3, 4, // 1-10 4, 5, 6, 1, 7, 8, 9, 10, 8, 11, // 11-20 11, 12, 13, 13, 13, 13, 13, 14, 13, 13, // 21-30 15, 16, 17, 15, 18, 19, 20, 338, 21, 21, // 31-40 341, 342, 22, 22, 23, 24, 25, 26, 27, 28, // 41-50 29, 30, 31, 32, 33, 34, 35, 35, 36, 37, // 51-60 38, 39, 39, 39, 39, 40, 39, 41, 41, 42, // 61-70 43, 42, 44, 45, 41, 46, 48, 47, 48, 49, // 71-80 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, // 81-90 60, 61, 62, 63, 61, 64, 65, 66, 67, 68, // 91-100 69, 70, 68, 71, 72, 73, 74, 75, 12, 76, // 101-110 77, 78, 79, 80, 4, 4, 82, 83, 77, 4, // 111-120 84, 85, 86, 3124, 88, 89, 90, 88, 2, 2, // 121-130 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 131-140 3142, 91, 92, 93, 94, 94, 95, 96, 52, 4, // 141-150 97, 98, 99, 99 // 151-154 }; int GetTrackNumber(SCNHANDLE hMidi) { for (int i = 0; i < ARRAYSIZE(g_midiOffsets); i++) { if (g_midiOffsets[i] == hMidi) return i; } return -1; } SCNHANDLE GetTrackOffset(int trackNumber) { assert(trackNumber < ARRAYSIZE(g_midiOffsets)); return g_midiOffsets[trackNumber]; } /** * Plays the specified MIDI sequence through the sound driver. * @param dwFileOffset File offset of MIDI sequence data * @param bLoop Whether to loop the sequence */ bool PlayMidiSequence(uint32 dwFileOffset, bool bLoop) { g_currentMidi = dwFileOffset; g_currentLoop = bLoop; bool mute = false; if (ConfMan.hasKey("mute")) mute = ConfMan.getBool("mute"); SetMidiVolume(mute ? 0 : _vm->_config->_musicVolume); // the index and length of the last tune loaded uint32 dwSeqLen = 0; // length of the sequence // Support for external music from the music enhancement project if (_vm->getFeatures() & GF_ENHANCED_AUDIO_SUPPORT) { int trackNumber = GetTrackNumber(dwFileOffset); // Track 8 has been removed in the German CD re-release "Neon Edition" if ((_vm->getFeatures() & GF_ALT_MIDI) && trackNumber >= 8) trackNumber++; int track = 0; if (trackNumber >= 0) { if (_vm->getFeatures() & GF_SCNFILES) track = enhancedAudioSCNVersion[trackNumber]; else track = enhancedAudioGRAVersion[trackNumber]; if (track > 0) { StopMidi(); // StopMidi resets these fields, so set them again g_currentMidi = dwFileOffset; g_currentLoop = bLoop; // try to play track, but don't fall back to a true CD g_system->getAudioCDManager()->play(track, bLoop ? -1 : 1, 0, 0, true); // Check if an enhanced audio track is being played. // If it is, stop here and don't load a MIDI track if (g_system->getAudioCDManager()->isPlaying()) { return true; } } } else { warning("Unknown MIDI offset %d", dwFileOffset); } } if (dwFileOffset == 0) return true; Common::File midiStream; // open MIDI sequence file in binary mode if (!midiStream.open(MIDI_FILE)) error(CANNOT_FIND_FILE, MIDI_FILE); // move to correct position in the file midiStream.seek(dwFileOffset, SEEK_SET); if (TinselV1Mac) { // The Macintosh version of DW1 uses raw PCM for music dwSeqLen = midiStream.readUint32BE(); _vm->_sound->playDW1MacMusic(midiStream, dwSeqLen); } else { dwSeqLen = midiStream.readUint32LE(); // make sure buffer is large enough for this sequence assert(dwSeqLen > 0 && dwSeqLen <= g_midiBuffer.size); // stop any currently playing tune _vm->_midiMusic->stop(); // read the sequence. This needs to be read again before playSEQ() is // called even if the music is restarting, as playSEQ() reads the file // name off the buffer itself. However, that function adds SMF headers // to the buffer, thus if it's read again, the SMF headers will be read // and the filename will always be 'MThd'. if (midiStream.read(g_midiBuffer.pDat, dwSeqLen) != dwSeqLen) error(FILE_IS_CORRUPT, MIDI_FILE); // WORKAROUND for bug #2820054 "DW1: No intro music at first start on Wii", // which actually affects all ports, since it's specific to the GRA version. // // The GRA version does not seem to set the channel volume at all for the first // intro track, thus we need to do that here. We only initialize the channels // used in that sequence. And we are using 127 as default channel volume. // // Only in the GRA version dwFileOffset can be "38888", just to be sure, we // check for the SCN files feature flag not being set though. if (_vm->getGameID() == GID_DW1 && dwFileOffset == 38888 && !(_vm->getFeatures() & GF_SCNFILES)) { _vm->_midiMusic->send(0x7F07B0 | 3); _vm->_midiMusic->send(0x7F07B0 | 5); _vm->_midiMusic->send(0x7F07B0 | 8); _vm->_midiMusic->send(0x7F07B0 | 10); _vm->_midiMusic->send(0x7F07B0 | 13); } _vm->_midiMusic->playMIDI(dwSeqLen, bLoop); } midiStream.close(); return true; } /** * Returns TRUE if a Midi tune is currently playing. */ bool MidiPlaying() { if (_vm->getFeatures() & GF_ENHANCED_AUDIO_SUPPORT) { if (g_system->getAudioCDManager()->isPlaying()) return true; } return _vm->_midiMusic->isPlaying(); } /** * Stops any currently playing midi. */ bool StopMidi() { g_currentMidi = 0; g_currentLoop = false; if (_vm->getFeatures() & GF_ENHANCED_AUDIO_SUPPORT) { g_system->getAudioCDManager()->stop(); } _vm->_midiMusic->stop(); return true; } /** * Gets the volume of the MIDI music. */ int GetMidiVolume() { return _vm->_config->_musicVolume; } /** * Sets the volume of the MIDI music. * @param vol New volume - 0..MAXMIDIVOL */ void SetMidiVolume(int vol) { assert(vol >= 0 && vol <= Audio::Mixer::kMaxChannelVolume); _vm->_midiMusic->setVolume(vol); } /** * Opens and inits all MIDI sequence files. */ void OpenMidiFiles() { Common::File midiStream; if (TinselV0) { // The early demo version of DW1 doesn't have MIDI } else if (TinselV2) { // DW2 uses a different music mechanism } else if (TinselV1Mac) { // open MIDI sequence file in binary mode if (!midiStream.open(MIDI_FILE)) error(CANNOT_FIND_FILE, MIDI_FILE); uint32 curTrack = 1; uint32 songLength = 0; int32 fileSize = midiStream.size(); // Init for (int i = 0; i < ARRAYSIZE(g_midiOffsets); i++) g_midiOffsets[i] = 0; midiStream.skip(4); // skip file header while (!midiStream.eos() && !midiStream.err() && midiStream.pos() != fileSize) { assert(curTrack < ARRAYSIZE(g_midiOffsets)); g_midiOffsets[curTrack] = midiStream.pos(); //debug("%d: %d", curTrack, g_midiOffsets[curTrack]); songLength = midiStream.readUint32BE(); midiStream.skip(songLength); curTrack++; } midiStream.close(); } else { if (g_midiBuffer.pDat) // already allocated return; // open MIDI sequence file in binary mode if (!midiStream.open(MIDI_FILE)) error(CANNOT_FIND_FILE, MIDI_FILE); // get length of the largest sequence g_midiBuffer.size = midiStream.readUint32LE(); if (midiStream.eos() || midiStream.err()) error(FILE_IS_CORRUPT, MIDI_FILE); if (g_midiBuffer.size) { // allocate a buffer big enough for the largest MIDI sequence if ((g_midiBuffer.pDat = (uint8 *)malloc(g_midiBuffer.size)) != NULL) { // clear out the buffer memset(g_midiBuffer.pDat, 0, g_midiBuffer.size); } } // Now scan through the contents of the MIDI file to find the offset // of each individual track, in order to create a mapping from MIDI // offset to track number, for the enhanced MIDI soundtrack. // The first song is always at position 4. The subsequent ones are // calculated dynamically. uint32 curOffset = 4; uint32 curTrack = 0; uint32 songLength = 0; // Init for (int i = 0; i < ARRAYSIZE(g_midiOffsets); i++) g_midiOffsets[i] = 0; while (!midiStream.eos() && !midiStream.err()) { if (curOffset + (4 * curTrack) >= (uint32)midiStream.size()) break; assert(curTrack < ARRAYSIZE(g_midiOffsets)); g_midiOffsets[curTrack] = curOffset + (4 * curTrack); //debug("%d: %d", curTrack, midiOffsets[curTrack]); songLength = midiStream.readUint32LE(); curOffset += songLength; midiStream.skip(songLength); curTrack++; } midiStream.close(); } } void DeleteMidiBuffer() { free(g_midiBuffer.pDat); g_midiBuffer.pDat = NULL; } MidiMusicPlayer::MidiMusicPlayer(TinselEngine *vm) { _driver = NULL; _milesAudioMode = false; bool milesAudioEnabled = false; if (vm->getPlatform() == Common::kPlatformDOS) { // Enable Miles Audio for DOS platform only... switch (vm->getGameID()) { case GID_DW1: if (!vm->getIsADGFDemo()) { // ...for Discworld 1 milesAudioEnabled = true; } else { if (vm->isV1CD()) { // ...and for Discworld 1 CD Demo milesAudioEnabled = true; } } break; default: break; } } if (milesAudioEnabled) { // Discworld 1 (DOS) uses Miles Audio 3 // use our own Miles Audio drivers // // It seems that there are multiple versions of Discworld 1 // // Version 1: // Has SAMPLE.AD for AdLib and SAMPLE.OPL for OPL-3 // Timbre files are inside a subdirectory of the CD called "/drivers". Main game files are in // another subdirectory, which means the user has to copy those files over. // Installer script copies all drivers directly to harddrive without name changes // // Version 2: // Has FAT.OPL only (gets copied by the installer into MIDPAK.AD or MIDPAK.OPL) // Timbre file is inside subdirectory "drivers" right in the main game directory. // Installer copies FAT.OPL to MIDPAK.AD all the time, even when user selected AWE32 // // Neither have timbre data for MT32 ::MidiDriver::DeviceHandle dev = ::MidiDriver::detectDevice(MDT_MIDI | MDT_ADLIB | MDT_PREFER_GM); ::MusicType musicType = ::MidiDriver::getMusicType(dev); Common::File fileClass; switch (musicType) { case MT_ADLIB: if (fileClass.exists("FAT.OPL")) { // Version 2: fat.opl, may be in drivers-subdirectory _driver = Audio::MidiDriver_Miles_AdLib_create("", "FAT.OPL"); } else { if (fileClass.exists("MIDPAK.AD")) { // Version 2: drivers got installed and fat.opl got copied over by the user _driver = Audio::MidiDriver_Miles_AdLib_create("MIDPAK.AD", ""); } else { if ((fileClass.exists("SAMPLE.AD")) || (fileClass.exists("SAMPLE.OPL"))) { // Version 1: sample.ad / sample.opl, have to be copied over by the user for this version _driver = Audio::MidiDriver_Miles_AdLib_create("SAMPLE.AD", "SAMPLE.OPL"); } else { error("MILES-ADLIB: timbre file not found (may be called FAT.OPL, MIDPAK.AD, SAMPLE.AD or SAMPLE.OPL, may be in a subdirectory)"); } } } break; case MT_MT32: // Discworld 1 doesn't have a MT32 timbre file _driver = Audio::MidiDriver_Miles_MT32_create(""); break; case MT_GM: if (ConfMan.getBool("native_mt32")) { _driver = Audio::MidiDriver_Miles_MT32_create(""); musicType = MT_MT32; } break; default: break; } if (!_driver) { // nothing got created yet? -> create default driver MidiPlayer::createDriver(); } else { _milesAudioMode = true; } } else { MidiPlayer::createDriver(); } int ret = _driver->open(); if (ret == 0) { if (_nativeMT32) _driver->sendMT32Reset(); else _driver->sendGMReset(); _driver->setTimerCallback(this, &timerCallback); } } void MidiMusicPlayer::setVolume(int volume) { _vm->_mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, volume); Audio::MidiPlayer::setVolume(volume); } void MidiMusicPlayer::send(uint32 b) { if (_milesAudioMode) { _driver->send(b); return; } Audio::MidiPlayer::send(b); byte channel = (byte)(b & 0x0F); if (_channelsTable[channel]) { if ((b & 0xFFF0) == 0x0079B0) { // We've just Reset All Controllers, so we need to // re-adjust the volume. Otherwise, volume is reset to // default whenever the music changes. _channelsTable[channel]->send(0x000007B0 | (((_channelsVolume[channel] * _masterVolume) / 255) << 16) | channel); } } } void MidiMusicPlayer::playMIDI(uint32 size, bool loop) { Common::StackLock lock(_mutex); if (_isPlaying) return; stop(); if (TinselV1PSX) playSEQ(size, loop); else playXMIDI(size, loop); } void MidiMusicPlayer::playXMIDI(uint32 size, bool loop) { // It seems like not all music (the main menu music, for instance) set // all the instruments explicitly. That means the music will sound // different, depending on which music played before it. This appears // to be a genuine glitch in the original. For consistency, reset all // instruments to the default one (piano). for (int i = 0; i < 16; i++) { _driver->send(0xC0 | i, 0, 0); } // Load XMID resource data MidiParser *parser = MidiParser::createParser_XMIDI(); if (parser->loadMusic(g_midiBuffer.pDat, size)) { parser->setTrack(0); parser->setMidiDriver(this); parser->setTimerRate(getBaseTempo()); parser->property(MidiParser::mpCenterPitchWheelOnUnload, 1); parser->property(MidiParser::mpSendSustainOffOnNotesOff, 1); _parser = parser; _isLooping = loop; _isPlaying = true; } else { delete parser; } } void MidiMusicPlayer::playSEQ(uint32 size, bool loop) { // MIDI.DAT holds the file names in DW1 PSX Common::String baseName((char *)g_midiBuffer.pDat, size); Common::String seqName = baseName + ".SEQ"; // TODO: Load the instrument bank (.VB and .VH) Common::File seqFile; if (!seqFile.open(seqName)) error("Failed to open SEQ file '%s'", seqName.c_str()); if (seqFile.readUint32LE() != MKTAG('S', 'E', 'Q', 'p')) error("Failed to find SEQp tag"); // Make sure we don't have a SEP file (with multiple SEQ's inside) if (seqFile.readUint32BE() != 1) error("Can only play SEQ files, not SEP"); uint16 ppqn = seqFile.readUint16BE(); uint32 tempo = seqFile.readUint16BE() << 8; tempo |= seqFile.readByte(); /* uint16 beat = */ seqFile.readUint16BE(); // SEQ is directly based on SMF and we'll use that to our advantage here // and convert to SMF and then use the SMF MidiParser. // Calculate the SMF size we'll need uint32 dataSize = seqFile.size() - 15; uint32 actualSize = dataSize + 7 + 22; // Resize the buffer if necessary if (g_midiBuffer.size < actualSize) { g_midiBuffer.pDat = (byte *)realloc(g_midiBuffer.pDat, actualSize); assert(g_midiBuffer.pDat); } // Now construct the header WRITE_BE_UINT32(g_midiBuffer.pDat, MKTAG('M', 'T', 'h', 'd')); WRITE_BE_UINT32(g_midiBuffer.pDat + 4, 6); // header size WRITE_BE_UINT16(g_midiBuffer.pDat + 8, 0); // type 0 WRITE_BE_UINT16(g_midiBuffer.pDat + 10, 1); // one track WRITE_BE_UINT16(g_midiBuffer.pDat + 12, ppqn); WRITE_BE_UINT32(g_midiBuffer.pDat + 14, MKTAG('M', 'T', 'r', 'k')); WRITE_BE_UINT32(g_midiBuffer.pDat + 18, dataSize + 7); // SEQ data size + tempo change event size // Add in a fake tempo change event WRITE_BE_UINT32(g_midiBuffer.pDat + 22, 0x00FF5103); // no delta, meta event, tempo change, param size = 3 WRITE_BE_UINT16(g_midiBuffer.pDat + 26, tempo >> 8); g_midiBuffer.pDat[28] = tempo & 0xFF; // Now copy in the rest of the events seqFile.read(g_midiBuffer.pDat + 29, dataSize); seqFile.close(); MidiParser *parser = MidiParser::createParser_SMF(); if (parser->loadMusic(g_midiBuffer.pDat, actualSize)) { parser->setTrack(0); parser->setMidiDriver(this); parser->setTimerRate(getBaseTempo()); parser->property(MidiParser::mpCenterPitchWheelOnUnload, 1); parser->property(MidiParser::mpSendSustainOffOnNotesOff, 1); _parser = parser; _isLooping = loop; _isPlaying = true; } else { delete parser; } } void MidiMusicPlayer::pause() { setVolume(-1); _isPlaying = false; } void MidiMusicPlayer::resume() { setVolume(GetMidiVolume()); _isPlaying = true; } PCMMusicPlayer::PCMMusicPlayer() { _silenceSamples = 0; _curChunk = 0; _state = S_IDLE; _mState = S_IDLE; _scriptNum = -1; _scriptIndex = 0; _forcePlay = false; _volume = 255; _dimmed = false; _dimmedTinsel = false; _dimIteration = 0; _dimmedVolume = 0; _dimPosition = 0; _fadeOutVolume = 0; _fadeOutIteration = 0; _hScript = _hSegment = 0; _end = true; _vm->_mixer->playStream(Audio::Mixer::kMusicSoundType, &_handle, this, -1, _volume, 0, DisposeAfterUse::NO, true); } PCMMusicPlayer::~PCMMusicPlayer() { _vm->_mixer->stopHandle(_handle); delete _curChunk; } void PCMMusicPlayer::startPlay(int id) { if (_filename.empty()) return; debugC(DEBUG_DETAILED, kTinselDebugMusic, "Playing PCM music %s, index %d", _filename.c_str(), id); Common::StackLock slock(_mutex); stop(); _scriptNum = id; _scriptIndex = 1; _state = S_NEW; play(); } void PCMMusicPlayer::stopPlay() { Common::StackLock slock(_mutex); stop(); } int PCMMusicPlayer::readBuffer(int16 *buffer, const int numSamples) { Common::StackLock slock(_mutex); if (!_curChunk && ((_state == S_IDLE) || (_state == S_STOP))) return 0; int samplesLeft = numSamples; while (samplesLeft > 0) { if (_silenceSamples > 0) { int n = MIN(_silenceSamples, samplesLeft); memset(buffer, 0, n); buffer += n; _silenceSamples -= n; samplesLeft -= n; } else if (_curChunk && ((_state == S_MID) || (_state == S_NEXT) || (_state == S_NEW))) { int n = _curChunk->readBuffer(buffer, samplesLeft); buffer += n; samplesLeft -= n; if (_curChunk->endOfData()) { _state = S_END1; delete _curChunk; _curChunk = 0; } } else { if (!getNextChunk()) break; } } return (numSamples - samplesLeft); } bool PCMMusicPlayer::isPlaying() const { return ((_state != S_IDLE) && (_state != S_STOP)); } bool PCMMusicPlayer::isDimmed() const { return _dimmed; } void PCMMusicPlayer::getTunePlaying(void *voidPtr, int length) { Common::StackLock lock(_mutex); debugC(DEBUG_DETAILED, kTinselDebugMusic, "getTunePlaying"); assert(length == (3 * sizeof(int32))); int32 *p = (int32 *) voidPtr; _mState = _state; p[0] = (int32) _mState; p[1] = _scriptNum; p[2] = _scriptIndex; } void PCMMusicPlayer::restoreThatTune(void *voidPtr) { Common::StackLock lock(_mutex); debugC(DEBUG_DETAILED, kTinselDebugMusic, "restoreThatTune"); int32 *p = (int32 *) voidPtr; _mState = (State) p[0]; _scriptNum = p[1]; _scriptIndex = p[2]; if (_mState != S_IDLE) _state = S_NEW; delete _curChunk; _curChunk = 0; _end = false; } void PCMMusicPlayer::setMusicSceneDetails(SCNHANDLE hScript, SCNHANDLE hSegment, const char *fileName) { Common::StackLock lock(_mutex); stop(); debugC(DEBUG_INTERMEDIATE, kTinselDebugMusic, "Setting music scene details: %s", fileName); _hScript = hScript; _hSegment = hSegment; _filename = fileName; // Start scene with music not dimmed _dimmed = false; _dimmedTinsel = false; _dimIteration = 0; setVol(255); } void PCMMusicPlayer::setVolume(int volume) { assert((volume >= 0) && (volume <= 100)); _dimmed = false; setVol((volume * 255) / 100); } void PCMMusicPlayer::setVol(uint8 volume) { _volume = volume; _vm->_mixer->setChannelVolume(_handle, _volume); } bool PCMMusicPlayer::getMusicTinselDimmed() const { return _dimmedTinsel; } void PCMMusicPlayer::dim(bool bTinselDim) { if (_dimmed || (_volume == 0) || (_state == S_IDLE) || !_curChunk || (SysVar(SV_MUSICDIMFACTOR) == 0)) return; _dimmed = true; if (bTinselDim) _dimmedTinsel = true; _dimmedVolume = _volume - (_volume / SysVar(SV_MUSICDIMFACTOR)); // Iterate down, negative iteration if (!_dimIteration) _dimPosition = _volume; _dimIteration = (_dimmedVolume - _volume)/DIM_SPEED; debugC(DEBUG_DETAILED, kTinselDebugMusic, "Dimming music from %d to %d, steps %d", _dimPosition, _dimmedVolume, _dimIteration); // And SFX if (SysVar(SYS_SceneFxDimFactor)) _vm->_sound->setSFXVolumes(255 - 255/SysVar(SYS_SceneFxDimFactor)); } void PCMMusicPlayer::unDim(bool bTinselUnDim) { if (!_dimmed || (_dimmedTinsel && !bTinselUnDim)) return; // not dimmed _dimmed = _dimmedTinsel = false; if ((_volume == 0) || (_state == S_IDLE) || !_curChunk) return; // Iterate up, positive iteration if (!_dimIteration) _dimPosition = _dimmedVolume; _dimIteration = (_volume - _dimmedVolume)/DIM_SPEED; debugC(DEBUG_DETAILED, kTinselDebugMusic, "UnDimming music from %d to %d, steps %d", _dimPosition, _volume, _dimIteration); // And SFX _vm->_sound->setSFXVolumes(255); } void PCMMusicPlayer::dimIteration() { if (_dimIteration != 0) { _dimPosition += _dimIteration; if (_dimPosition >= _volume) { _dimPosition = _volume; _dimIteration = 0; } else if (_dimPosition <= _dimmedVolume) { _dimPosition = _dimmedVolume; _dimIteration = 0; } _vm->_mixer->setChannelVolume(_handle, _dimPosition); } } void PCMMusicPlayer::startFadeOut(int ticks) { if ((_volume == 0) || (_state == S_IDLE) || !_curChunk) return; debugC(DEBUG_INTERMEDIATE, kTinselDebugMusic, "Fading out music..."); if (_dimmed) { // Start from dimmed volume and go from there _dimmed = false; _fadeOutVolume = _volume - _volume/SysVar(SV_MUSICDIMFACTOR); } else _fadeOutVolume = _volume; assert(ticks != 0); _fadeOutIteration = _fadeOutVolume / ticks; fadeOutIteration(); } void PCMMusicPlayer::fadeOutIteration() { if ((_volume == 0) || (_state == S_IDLE) || !_curChunk) return; _fadeOutVolume = CLIP(_fadeOutVolume -= _fadeOutIteration, 0, 255); _vm->_mixer->setChannelVolume(_handle, _fadeOutVolume); } bool PCMMusicPlayer::getNextChunk() { MusicSegment *musicSegments; int32 *script, *scriptBuffer; int id; int snum; uint32 sampleOffset, sampleLength, sampleCLength; Common::File file; byte *buffer; Common::SeekableReadStream *sampleStream; switch (_state) { case S_NEW: case S_NEXT: _forcePlay = false; script = scriptBuffer = (int32 *)LockMem(_hScript); // Set parameters for this chunk of music id = _scriptNum; while (id--) script = scriptBuffer + READ_32(script); snum = FROM_32(script[_scriptIndex++]); if (snum == MUSIC_JUMP || snum == MUSIC_END) { // Let usual code sort it out! _scriptIndex--; // Undo increment _forcePlay = true; // Force a Play _state = S_END1; // 'Goto' S_END1 break; } musicSegments = (MusicSegment *) LockMem(_hSegment); assert(FROM_32(musicSegments[snum].numChannels) == 1); assert(FROM_32(musicSegments[snum].bitsPerSample) == 16); sampleOffset = FROM_32(musicSegments[snum].sampleOffset); sampleLength = FROM_32(musicSegments[snum].sampleLength); sampleCLength = (((sampleLength + 63) & ~63)*33)/64; if (!file.open(_filename)) error(CANNOT_FIND_FILE, _filename.c_str()); file.seek(sampleOffset); if (file.eos() || file.err() || (uint32)file.pos() != sampleOffset) error(FILE_IS_CORRUPT, _filename.c_str()); buffer = (byte *) malloc(sampleCLength); assert(buffer); // read all of the sample if (file.read(buffer, sampleCLength) != sampleCLength) error(FILE_IS_CORRUPT, _filename.c_str()); debugC(DEBUG_DETAILED, kTinselDebugMusic, "Creating ADPCM music chunk with size %d, " "offset %d (script %d.%d)", sampleCLength, sampleOffset, _scriptNum, _scriptIndex - 1); sampleStream = new Common::MemoryReadStream(buffer, sampleCLength, DisposeAfterUse::YES); delete _curChunk; _curChunk = new Tinsel8_ADPCMStream(sampleStream, DisposeAfterUse::YES, sampleCLength, 22050, 1, 32); _state = S_MID; return true; case S_END1: debugC(DEBUG_DETAILED, kTinselDebugMusic, "Music reached state S_END1 (script %d.%d)", _scriptNum, _scriptIndex); script = scriptBuffer = (int32 *) LockMem(_hScript); id = _scriptNum; while (id--) script = scriptBuffer + READ_32(script); snum = FROM_32(script[_scriptIndex]); if (snum == MUSIC_END) { _state = S_END2; } else { if (snum == MUSIC_JUMP) _scriptIndex = FROM_32(script[_scriptIndex+1]); _state = _forcePlay ? S_NEW : S_NEXT; _forcePlay = false; } return true; case S_END2: debugC(DEBUG_DETAILED, kTinselDebugMusic, "Music reached state S_END2 (script %d.%d)", _scriptNum, _scriptIndex); _silenceSamples = 11025; // Half a second of silence return true; case S_END3: debugC(DEBUG_DETAILED, kTinselDebugMusic, "Music reached state S_END3 (script %d.%d)", _scriptNum, _scriptIndex); stop(); _state = S_IDLE; return false; case S_IDLE: return false; default: break; } return true; } void PCMMusicPlayer::play() { if (_curChunk) return; if (_scriptNum == -1) return; _end = false; getNextChunk(); } void PCMMusicPlayer::stop() { delete _curChunk; _curChunk = 0; _scriptNum = -1; _state = S_IDLE; _mState = S_IDLE; _end = true; } void CurrentMidiFacts(SCNHANDLE *pMidi, bool *pLoop) { *pMidi = g_currentMidi; *pLoop = g_currentLoop; } void RestoreMidiFacts(SCNHANDLE Midi, bool Loop) { StopMidi(); g_currentMidi = Midi; g_currentLoop = Loop; bool mute = false; if (ConfMan.hasKey("mute")) mute = ConfMan.getBool("mute"); PlayMidiSequence(g_currentMidi, true); SetMidiVolume(mute ? 0 : _vm->_config->_musicVolume); } #if 0 // Dumps all of the game's music in external XMIDI *.xmi files void dumpMusic() { Common::File midiFile; Common::DumpFile outFile; char outName[20]; midiFile.open(MIDI_FILE); int outFileSize = 0; char buffer[20000]; const int total = 155; // maximum (SCN version) for (int i = 0; i < total; i++) { if (midiOffsets[i] == 0) break; sprintf(outName, "track%03d.xmi", i + 1); outFile.open(outName); if (i < total - 1) outFileSize = midiOffsets[i + 1] - midiOffsets[i] - 4; else outFileSize = midiFile.size() - midiOffsets[i] - 4; midiFile.seek(midiOffsets[i] + 4, SEEK_SET); assert(outFileSize < 20000); midiFile.read(buffer, outFileSize); outFile.write(buffer, outFileSize); outFile.close(); } midiFile.close(); } #endif } // End of namespace Tinsel