diff options
Diffstat (limited to 'engines/tinsel/music.cpp')
-rw-r--r-- | engines/tinsel/music.cpp | 551 |
1 files changed, 551 insertions, 0 deletions
diff --git a/engines/tinsel/music.cpp b/engines/tinsel/music.cpp new file mode 100644 index 0000000000..7d4efd8079 --- /dev/null +++ b/engines/tinsel/music.cpp @@ -0,0 +1,551 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +// FIXME: This code is taken from MADE and may need more work (e.g. setVolume). + +// MIDI and digital music class + +#include "sound/audiostream.h" +#include "sound/mididrv.h" +#include "sound/midiparser.h" +#include "sound/audiocd.h" +#include "common/config-manager.h" +#include "common/file.h" + +#include "tinsel/config.h" +#include "tinsel/sound.h" +#include "tinsel/music.h" + +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 +}; + +// get set when music driver is installed +//static MDI_DRIVER *mDriver; +//static HSEQUENCE mSeqHandle; + +// if non-zero this is the index position of the next MIDI sequence to play +static uint32 dwMidiIndex = 0; + +// MIDI buffer +static SOUND_BUFFER midiBuffer = { 0, 0 }; + +static SCNHANDLE currentMidi = 0; +static bool currentLoop = false; + +const SCNHANDLE midiOffsetsGRAVersion[] = { + 4, 4534, 14298, 18828, 23358, 38888, 54418, 57172, 59926, 62450, + 62952, 67482, 72258, 74538, 79314, 87722, 103252, 115176, 127100, 127898, + 130256, 132614, 134972, 137330, 139688, 150196, 152554, 154912, 167422, 174762, + 182102, 194612, 198880, 199536, 206128, 206380, 216372, 226364, 235676, 244988, + 249098, 249606, 251160, 252714, 263116, 268706, 274296, 283562, 297986, 304566, + 312028, 313524, 319192, 324860, 331772, 336548, 336838, 339950, 343062, 346174, + 349286, 356246, 359358, 360434, 361510, 369966, 374366, 382822, 384202, 394946, + 396022, 396730, 399524, 401020, 403814, 418364, 419466, 420568, 425132, 433540, + 434384, 441504, 452132, 462760, 472804, 486772, 491302, 497722, 501260, 507680, + 509726, 521858, 524136, 525452, 533480, 538236, 549018, 559870, 564626, 565306, + 566734, 567616, 570144, 574102, 574900, 582518, 586350, 600736, 604734, 613812, + 616566, 619626, 623460, 627294, 631128, 634188, 648738, 663288, 667864, 681832, + 682048, 683014, 688908, 689124, 698888, 708652, 718416, 728180, 737944, 747708, + 752238, 765522, 766554, 772944, 774546, 776148, 776994, 781698, 786262, 789016, + 794630, 796422, 798998 +}; + +const SCNHANDLE midiOffsetsSCNVersion[] = { + 4, 4504, 11762, 21532, 26070, 28754, 33254, 40512, 56310, 72108, + 74864, 77620, 80152, 80662, 85200, 89982, 92268, 97050, 105466, 121264, + 133194, 145124, 145928, 148294, 150660, 153026, 155392, 157758, 168272, 170638, + 173004, 185522, 192866, 200210, 212728, 217000, 217662, 224254, 224756, 234754, + 244752, 245256, 245950, 255256, 264562, 268678, 269192, 270752, 272312, 282712, + 288312, 293912, 303186, 317624, 324210, 331680, 333208, 338884, 344560, 351478, + 356262, 356552, 359670, 362788, 365906, 369024, 376014, 379132, 380214, 381296, + 389758, 394164, 402626, 404012, 414762, 415844, 416552, 419352, 420880, 423680, + 438236, 439338, 440440, 445010, 453426, 454276, 461398, 472032, 482666, 492716, + 506690, 511226, 517654, 521198, 527626, 529676, 541814, 546210, 547532, 555562, + 560316, 571104, 581962, 586716, 587402, 588836, 589718, 592246, 596212, 597016, + 604636, 608474, 622862, 626860, 635944, 638700, 641456, 645298, 649140, 652982, + 655738, 670294, 684850, 689432, 703628, 703850, 704816, 706350, 706572, 716342, + 726112, 735882, 745652, 755422, 765192, 774962, 784732, 794502, 804272, 814042, + 823812, 832996, 846286, 847324, 853714, 855324, 856934, 857786, 862496, 867066, + 869822, 875436, 877234, 879818 +}; + +// TODO: finish this (currently unmapped tracks are 0) +const int enhancedAudioSCNVersion[] = { + 0, 0, 2, 0, 0, 0, 0, 3, 3, 4, + 4, 0, 0, 0, 0, 0, 0, 10, 3, 11, + 11, 0, 13, 13, 13, 13, 13, 0, 13, 13, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 24, 0, 0, 27, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 55, 56, 56, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 4, 4, 83, 83, 83, 4, + 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 0, 0, 0, 0, 0, 0, 0, 0, 52, 4, + 0, 0, 0, 0 +}; + +int GetTrackNumber(SCNHANDLE hMidi) { + if (_vm->getFeatures() & GF_SCNFILES) { + for (int i = 0; i < ARRAYSIZE(midiOffsetsSCNVersion); i++) { + if (midiOffsetsSCNVersion[i] == hMidi) + return i; + } + } else { + for (int i = 0; i < ARRAYSIZE(midiOffsetsGRAVersion); i++) { + if (midiOffsetsGRAVersion[i] == hMidi) + return i; + } + } + + return -1; +} + +SCNHANDLE GetTrackOffset(int trackNumber) { + if (_vm->getFeatures() & GF_SCNFILES) { + assert(trackNumber < ARRAYSIZE(midiOffsetsSCNVersion)); + return midiOffsetsSCNVersion[trackNumber]; + } else { + assert(trackNumber < ARRAYSIZE(midiOffsetsGRAVersion)); + return midiOffsetsGRAVersion[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) { + currentMidi = dwFileOffset; + currentLoop = bLoop; + + if (volMidi != 0) { + SetMidiVolume(volMidi); + // Support for compressed music from the music enhancement project + AudioCD.stop(); + + int trackNumber = GetTrackNumber(dwFileOffset); + if (trackNumber >= 0) { +#if 0 + // TODO: GRA version + int track = enhancedAudioSCNVersion[trackNumber]; + if (track > 0) + AudioCD.play(track, -1, 0, 0); +#endif + } else { + warning("Unknown MIDI offset %d", dwFileOffset); + } + + if (AudioCD.isPlaying()) + return true; + } + + // set file offset for this sequence + dwMidiIndex = dwFileOffset; + + // the index and length of the last tune loaded + static uint32 dwLastMidiIndex; + static uint32 dwLastSeqLen; + + uint32 dwSeqLen = 0; // length of the sequence + + if (dwMidiIndex == 0) + return true; + + if (dwMidiIndex != dwLastMidiIndex) { + Common::File midiStream; + + // open MIDI sequence file in binary mode + if (!midiStream.open(MIDI_FILE)) + error("Cannot find file %s", MIDI_FILE); + + // update index of last tune loaded + dwLastMidiIndex = dwMidiIndex; + + // move to correct position in the file + midiStream.seek(dwMidiIndex, SEEK_SET); + + // read the length of the sequence + dwSeqLen = midiStream.readUint32LE(); + + // make sure buffer is large enough for this sequence + assert(dwSeqLen > 0 && dwSeqLen <= midiBuffer.size); + + // stop any currently playing tune + _vm->_music->stop(); + + // read the sequence + if (midiStream.read(midiBuffer.pDat, dwSeqLen) != dwSeqLen) + error("File %s is corrupt", MIDI_FILE); + + midiStream.close(); + + _vm->_music->playXMIDI(midiBuffer.pDat, dwSeqLen, bLoop); + + // Store the length + dwLastSeqLen = dwSeqLen; + } else { + // dwMidiIndex == dwLastMidiIndex + _vm->_music->stop(); + _vm->_music->playXMIDI(midiBuffer.pDat, dwSeqLen, bLoop); + } + + // allow another sequence to play + dwMidiIndex = 0; + + return true; +} + +/** + * Returns TRUE if a Midi tune is currently playing. + */ +bool MidiPlaying(void) { + if (AudioCD.isPlaying()) return true; + return _vm->_music->isPlaying(); +} + +/** + * Stops any currently playing midi. + */ +bool StopMidi(void) { + currentMidi = 0; + currentLoop = false; + + AudioCD.stop(); + _vm->_music->stop(); + return true; +} + + +/** + * Gets the volume of the MIDI music. + */ +int GetMidiVolume() { + return volMidi; +} + +/** + * Sets the volume of the MIDI music. + * @param vol New volume - 0..MAXMIDIVOL + */ +void SetMidiVolume(int vol) { + assert(vol >= 0 && vol <= MAXMIDIVOL); + + if (vol == 0 && volMidi == 0) { + // Nothing to do + } else if (vol == 0 && volMidi != 0) { + // Stop current midi sequence + AudioCD.stop(); + StopMidi(); + } else if (vol != 0 && volMidi == 0) { + // Perhaps restart last midi sequence + if (currentLoop) { + PlayMidiSequence(currentMidi, true); + _vm->_music->setVolume(vol); + } + } else if (vol != 0 && volMidi != 0) { + // Alter current volume + _vm->_music->setVolume(vol); + } + + volMidi = vol; +} + +/** + * Opens and inits all MIDI sequence files. + */ +void OpenMidiFiles(void) { + Common::File midiStream; + + // Demo version has no midi file + if (_vm->getFeatures() & GF_DEMO) + return; + + if (midiBuffer.pDat) + // already allocated + return; + + // open MIDI sequence file in binary mode + if (!midiStream.open(MIDI_FILE)) + error("Cannot find file %s", MIDI_FILE); + + // gen length of the largest sequence + midiBuffer.size = midiStream.readUint32LE(); + if (midiStream.ioFailed()) + error("File %s is corrupt", MIDI_FILE); + + if (midiBuffer.size) { + // allocate a buffer big enough for the largest MIDI sequence + if ((midiBuffer.pDat = (uint8 *)malloc(midiBuffer.size)) != NULL) { + // clear out the buffer + memset(midiBuffer.pDat, 0, midiBuffer.size); +// VMM_lock(midiBuffer.pDat, midiBuffer.size); + } else { + //mSeqHandle = NULL; + } + } + + midiStream.close(); +} + +void DeleteMidiBuffer() { + free(midiBuffer.pDat); + midiBuffer.pDat = NULL; +} + +MusicPlayer::MusicPlayer(MidiDriver *driver) : _parser(0), _driver(driver), _looping(false), _isPlaying(false) { + memset(_channel, 0, sizeof(_channel)); + _masterVolume = 0; + this->open(); + _xmidiParser = MidiParser::createParser_XMIDI(); +} + +MusicPlayer::~MusicPlayer() { + _driver->setTimerCallback(NULL, NULL); + stop(); + this->close(); + _xmidiParser->setMidiDriver(NULL); + delete _xmidiParser; +} + +void MusicPlayer::setVolume(int volume) { + Common::StackLock lock(_mutex); + + // FIXME: Could we simply change MAXMIDIVOL to match ScummVM's range? + volume = CLIP((255 * volume) / MAXMIDIVOL, 0, 255); + _vm->_mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, volume); + + if (_masterVolume == volume) + return; + + _masterVolume = volume; + + for (int i = 0; i < 16; ++i) { + if (_channel[i]) { + _channel[i]->volume(_channelVolume[i] * _masterVolume / 255); + } + } +} + +int MusicPlayer::open() { + // Don't ever call open without first setting the output driver! + if (!_driver) + return 255; + + int ret = _driver->open(); + if (ret) + return ret; + + _driver->setTimerCallback(this, &onTimer); + return 0; +} + +void MusicPlayer::close() { + stop(); + if (_driver) + _driver->close(); + _driver = 0; +} + +void MusicPlayer::send(uint32 b) { + byte channel = (byte)(b & 0x0F); + if ((b & 0xFFF0) == 0x07B0) { + // Adjust volume changes by master volume + byte volume = (byte)((b >> 16) & 0x7F); + _channelVolume[channel] = volume; + volume = volume * _masterVolume / 255; + b = (b & 0xFF00FFFF) | (volume << 16); + } else if ((b & 0xFFF0) == 0x007BB0) { + //Only respond to All Notes Off if this channel + //has currently been allocated + if (_channel[b & 0x0F]) + return; + } + + if (!_channel[channel]) + _channel[channel] = (channel == 9) ? _driver->getPercussionChannel() : _driver->allocateChannel(); + + if (_channel[channel]) { + _channel[channel]->send(b); + + 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. + _channel[channel]->send(0x000007B0 | (((_channelVolume[channel] * _masterVolume) / 255) << 16) | channel); + } + } +} + +void MusicPlayer::metaEvent(byte type, byte *data, uint16 length) { + switch (type) { + case 0x2F: // End of Track + if (_looping) + _parser->jumpToTick(0); + else + stop(); + break; + default: + //warning("Unhandled meta event: %02x", type); + break; + } +} + +void MusicPlayer::onTimer(void *refCon) { + MusicPlayer *music = (MusicPlayer *)refCon; + Common::StackLock lock(music->_mutex); + + if (music->_isPlaying) + music->_parser->onTimer(); +} + +void MusicPlayer::playXMIDI(byte *midiData, uint32 size, bool loop) { + if (_isPlaying) + return; + + stop(); + + // 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 + + if (_xmidiParser->loadMusic(midiData, size)) { + MidiParser *parser = _xmidiParser; + parser->setTrack(0); + parser->setMidiDriver(this); + parser->setTimerRate(getBaseTempo()); + parser->property(MidiParser::mpCenterPitchWheelOnUnload, 1); + + _parser = parser; + + _looping = loop; + _isPlaying = true; + } +} + +void MusicPlayer::stop() { + Common::StackLock lock(_mutex); + + _isPlaying = false; + if (_parser) { + _parser->unloadMusic(); + _parser = NULL; + } +} + +void MusicPlayer::pause() { + setVolume(-1); + _isPlaying = false; +} + +void MusicPlayer::resume() { + setVolume(GetMidiVolume()); + _isPlaying = true; +} + +void CurrentMidiFacts(SCNHANDLE *pMidi, bool *pLoop) { + *pMidi = currentMidi; + *pLoop = currentLoop; +} + +void RestoreMidiFacts(SCNHANDLE Midi, bool Loop) { + AudioCD.stop(); + StopMidi(); + + currentMidi = Midi; + currentLoop = Loop; + + if (volMidi != 0 && Loop) { + PlayMidiSequence(currentMidi, true); + SetMidiVolume(volMidi); + } +} + +#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]; + + int total = (_vm->getFeatures() & GF_SCNFILES) ? + ARRAYSIZE(midiOffsetsSCNVersion) : + ARRAYSIZE(midiOffsetsGRAVersion); + + for (int i = 0; i < total; i++) { + sprintf(outName, "track%03d.xmi", i + 1); + outFile.open(outName); + + if (_vm->getFeatures() & GF_SCNFILES) { + if (i < total - 1) + outFileSize = midiOffsetsSCNVersion[i + 1] - midiOffsetsSCNVersion[i] - 4; + else + outFileSize = midiFile.size() - midiOffsetsSCNVersion[i] - 4; + + midiFile.seek(midiOffsetsSCNVersion[i] + 4, SEEK_SET); + } else { + if (i < total - 1) + outFileSize = midiOffsetsGRAVersion[i + 1] - midiOffsetsGRAVersion[i] - 4; + else + outFileSize = midiFile.size() - midiOffsetsGRAVersion[i] - 4; + + midiFile.seek(midiOffsetsGRAVersion[i] + 4, SEEK_SET); + } + + assert(outFileSize < 20000); + midiFile.read(buffer, outFileSize); + outFile.write(buffer, outFileSize); + + outFile.close(); + } + + midiFile.close(); +} +#endif + +} // End of namespace Made |