aboutsummaryrefslogtreecommitdiff
path: root/engines/saga/music.cpp
diff options
context:
space:
mode:
authorMax Horn2006-02-11 22:45:04 +0000
committerMax Horn2006-02-11 22:45:04 +0000
commit26ee630756ebdd7c96bccede0881a8c8b98e8f2b (patch)
tree26e378d5cf990a2b81c2c96e9e683a7f333b62e8 /engines/saga/music.cpp
parent2a9a0d4211b1ea5723f1409d91cb95de8984429e (diff)
downloadscummvm-rg350-26ee630756ebdd7c96bccede0881a8c8b98e8f2b.tar.gz
scummvm-rg350-26ee630756ebdd7c96bccede0881a8c8b98e8f2b.tar.bz2
scummvm-rg350-26ee630756ebdd7c96bccede0881a8c8b98e8f2b.zip
Moved engines to the new engines/ directory
svn-id: r20582
Diffstat (limited to 'engines/saga/music.cpp')
-rw-r--r--engines/saga/music.cpp524
1 files changed, 524 insertions, 0 deletions
diff --git a/engines/saga/music.cpp b/engines/saga/music.cpp
new file mode 100644
index 0000000000..e352818ee6
--- /dev/null
+++ b/engines/saga/music.cpp
@@ -0,0 +1,524 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2004-2006 The ScummVM project
+ *
+ * The ReInherit Engine is (C)2000-2003 by Daniel Balsom.
+ *
+ * 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$
+ *
+ */
+#include "saga/saga.h"
+
+#include "saga/rscfile.h"
+#include "saga/music.h"
+#include "saga/stream.h"
+#include "sound/audiostream.h"
+#include "sound/mididrv.h"
+#include "sound/midiparser.h"
+#include "sound/mp3.h"
+#include "sound/vorbis.h"
+#include "sound/flac.h"
+#include "common/config-manager.h"
+#include "common/file.h"
+
+namespace Saga {
+
+#define BUFFER_SIZE 4096
+
+struct TrackFormat {
+ DigitalTrackInfo* (*openTrackFunction)(int);
+};
+
+static const TrackFormat TRACK_FORMATS[] = {
+#ifdef USE_FLAC
+ { getFlacTrack },
+#endif
+#ifdef USE_VORBIS
+ { getVorbisTrack },
+#endif
+#ifdef USE_MAD
+ { getMP3Track },
+#endif
+
+ { NULL } // Terminator
+};
+
+// I haven't decided yet if it's a good idea to make looping part of the audio
+// stream class, or if I should use a "wrapper" class, like I did for Broken
+// Sword 2, to make it easier to add support for compressed music... but I'll
+// worry about that later.
+
+class RAWInputStream : public AudioStream {
+private:
+ ResourceContext *_context;
+ Common::File *_file;
+ uint32 _filePos;
+ uint32 _startPos;
+ uint32 _endPos;
+ bool _finished;
+ bool _looping;
+ int16 _buf[BUFFER_SIZE];
+ const int16 *_bufferEnd;
+ const int16 *_pos;
+ const GameSoundInfo *_musicInfo;
+
+ void refill();
+ bool eosIntern() const {
+ return _pos >= _bufferEnd;
+ }
+
+public:
+ RAWInputStream(SagaEngine *vm, ResourceContext *context, uint32 resourceId, bool looping);
+
+ int readBuffer(int16 *buffer, const int numSamples);
+
+ bool endOfData() const { return eosIntern(); }
+ bool isStereo() const { return _musicInfo->stereo; }
+ int getRate() const { return _musicInfo->frequency; }
+};
+
+RAWInputStream::RAWInputStream(SagaEngine *vm, ResourceContext *context, uint32 resourceId, bool looping)
+ : _context(context), _finished(false), _looping(looping), _bufferEnd(_buf + BUFFER_SIZE) {
+
+ ResourceData * resourceData;
+
+ resourceData = vm->_resource->getResourceData(context, resourceId);
+ _file = context->getFile(resourceData);
+ _musicInfo = vm->getMusicInfo();
+
+ if (_musicInfo == NULL) {
+ error("RAWInputStream() wrong musicInfo");
+ }
+
+ // Determine the end position
+ _startPos = resourceData->offset;
+ _endPos = _startPos + resourceData->size;
+ _filePos = _startPos;
+
+ // Read in initial data
+ refill();
+}
+
+int RAWInputStream::readBuffer(int16 *buffer, const int numSamples) {
+ int samples = 0;
+ while (samples < numSamples && !eosIntern()) {
+ const int len = MIN(numSamples - samples, (int) (_bufferEnd - _pos));
+ memcpy(buffer, _pos, len * 2);
+ buffer += len;
+ _pos += len;
+ samples += len;
+ if (_pos >= _bufferEnd) {
+ refill();
+ }
+ }
+ return samples;
+}
+
+void RAWInputStream::refill() {
+ if (_finished)
+ return;
+
+ uint32 lengthLeft;
+ byte *ptr = (byte *) _buf;
+
+
+ _file->seek(_filePos, SEEK_SET);
+
+ if (_looping)
+ lengthLeft = 2 * BUFFER_SIZE;
+ else
+ lengthLeft = MIN((uint32) (2 * BUFFER_SIZE), _endPos - _filePos);
+
+ while (lengthLeft > 0) {
+ uint32 len = _file->read(ptr, MIN(lengthLeft, _endPos - _file->pos()));
+
+ if (len & 1)
+ len--;
+
+#ifdef SCUMM_BIG_ENDIAN
+ if (!_context->isBigEndian) {
+#else
+ if (_context->isBigEndian) {
+#endif
+ uint16 *ptr16 = (uint16 *)ptr;
+ for (uint32 i = 0; i < (len / 2); i++)
+ ptr16[i] = SWAP_BYTES_16(ptr16[i]);
+ }
+
+ lengthLeft -= len;
+ ptr += len;
+
+ if (lengthLeft > 0)
+ _file->seek(_startPos);
+ }
+
+ _filePos = _file->pos();
+ _pos = _buf;
+ _bufferEnd = (int16 *)ptr;
+
+ if (!_looping && _filePos >= _endPos) {
+ _finished = true;
+ }
+}
+
+
+MusicPlayer::MusicPlayer(MidiDriver *driver) : _parser(0), _driver(driver), _looping(false), _isPlaying(false), _passThrough(false), _isGM(false) {
+ memset(_channel, 0, sizeof(_channel));
+ _masterVolume = 0;
+ this->open();
+}
+
+MusicPlayer::~MusicPlayer() {
+ _driver->setTimerCallback(NULL, NULL);
+ stopMusic();
+ this->close();
+}
+
+void MusicPlayer::setVolume(int volume) {
+ if (volume < 0)
+ volume = 0;
+ else if (volume > 255)
+ volume = 255;
+
+ 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() {
+ stopMusic();
+ if (_driver)
+ _driver->close();
+ _driver = 0;
+}
+
+void MusicPlayer::send(uint32 b) {
+ if (_passThrough) {
+ _driver->send(b);
+ return;
+ }
+
+ 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 & 0xF0) == 0xC0 && !_isGM && !_nativeMT32) {
+ b = (b & 0xFFFF00FF) | MidiDriver::_mt32ToGm[(b >> 8) & 0xFF] << 8;
+ }
+ 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);
+}
+
+void MusicPlayer::metaEvent(byte type, byte *data, uint16 length) {
+ // FIXME: The "elkfanfare" is played much too quickly. There are some
+ // meta events that we don't handle. Perhaps there is a
+ // connection...?
+
+ switch (type) {
+ case 0x2F: // End of Track
+ if (_looping)
+ _parser->jumpToTick(0);
+ else
+ stopMusic();
+ break;
+ default:
+ //warning("Unhandled meta event: %02x", type);
+ break;
+ }
+}
+
+void MusicPlayer::onTimer(void *refCon) {
+ MusicPlayer *music = (MusicPlayer *)refCon;
+ if (music->_isPlaying)
+ music->_parser->onTimer();
+}
+
+void MusicPlayer::playMusic() {
+ _isPlaying = true;
+}
+
+void MusicPlayer::stopMusic() {
+ _isPlaying = false;
+ if (_parser) {
+ _parser->unloadMusic();
+ _parser = NULL;
+ }
+}
+
+Music::Music(SagaEngine *vm, Audio::Mixer *mixer, MidiDriver *driver, int enabled) : _vm(vm), _mixer(mixer), _enabled(enabled), _adlib(false) {
+ _player = new MusicPlayer(driver);
+ _currentVolume = 0;
+
+ xmidiParser = MidiParser::createParser_XMIDI();
+ smfParser = MidiParser::createParser_SMF();
+
+ _musicContext = _vm->_resource->getContext(GAME_MUSICFILE);
+
+ _songTableLen = 0;
+ _songTable = 0;
+
+ _track = NULL;
+}
+
+Music::~Music() {
+ _mixer->stopHandle(_musicHandle);
+ delete _player;
+ xmidiParser->setMidiDriver(NULL);
+ smfParser->setMidiDriver(NULL);
+ delete xmidiParser;
+ delete smfParser;
+
+ free(_songTable);
+}
+
+void Music::musicVolumeGaugeCallback(void *refCon) {
+ ((Music *)refCon)->musicVolumeGauge();
+}
+
+void Music::musicVolumeGauge() {
+ int volume;
+
+ _currentVolumePercent += 10;
+
+ if (_currentVolume - _targetVolume > 0) { // Volume decrease
+ volume = _targetVolume + (_currentVolume - _targetVolume) * (100 - _currentVolumePercent) / 100;
+ } else {
+ volume = _currentVolume + (_targetVolume - _currentVolume) * _currentVolumePercent / 100;
+ }
+
+ if (volume < 0)
+ volume = 1;
+
+ _mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, volume);
+ _player->setVolume(volume);
+
+ if (_currentVolumePercent == 100) {
+ Common::g_timer->removeTimerProc(&musicVolumeGaugeCallback);
+ _currentVolume = _targetVolume;
+ }
+}
+
+void Music::setVolume(int volume, int time) {
+ _targetVolume = volume * 2; // ScummVM has different volume scale
+ _currentVolumePercent = 0;
+
+ if (volume == -1) // Set Full volume
+ volume = 255;
+
+ if (time == 1) {
+ _mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, volume);
+ _player->setVolume(volume);
+ Common::g_timer->removeTimerProc(&musicVolumeGaugeCallback);
+ _currentVolume = volume;
+ return;
+ }
+
+ Common::g_timer->installTimerProc(&musicVolumeGaugeCallback, time * 100L, this);
+}
+
+bool Music::isPlaying() {
+ return _mixer->isSoundHandleActive(_musicHandle) || _player->isPlaying();
+}
+
+void Music::play(uint32 resourceId, MusicFlags flags) {
+ AudioStream *audioStream = NULL;
+ MidiParser *parser;
+ ResourceContext *context;
+ byte *resourceData;
+ size_t resourceSize;
+
+ debug(2, "Music::play %d, %d", resourceId, flags);
+
+ if (!_enabled) {
+ return;
+ }
+
+ if (isPlaying() && _trackNumber == resourceId) {
+ return;
+ }
+
+ _trackNumber = resourceId;
+ _player->stopMusic();
+ _mixer->stopHandle(_musicHandle);
+
+ int realTrackNumber;
+
+ if (_vm->getGameType() == GType_ITE) {
+ if (flags == MUSIC_DEFAULT) {
+ if (resourceId == 13 || resourceId == 19) {
+ flags = MUSIC_NORMAL;
+ } else {
+ flags = MUSIC_LOOP;
+ }
+ }
+ realTrackNumber = resourceId - 8;
+ } else {
+ realTrackNumber = resourceId + 1;
+ }
+
+ // Try to open standalone digital track
+ for (int i = 0; i < ARRAYSIZE(TRACK_FORMATS) - 1; ++i)
+ if ((_track = TRACK_FORMATS[i].openTrackFunction(realTrackNumber))) {
+ break;
+ }
+ if (_track) {
+ _track->play(_mixer, &_musicHandle, (flags == MUSIC_LOOP) ? -1 : 1, 10000);
+ return;
+ }
+
+ if (_vm->getGameType() == GType_ITE) {
+ if (resourceId >= 9 && resourceId <= 34) {
+ if (_musicContext != NULL) {
+ //TODO: check resource size
+ audioStream = new RAWInputStream(_vm, _musicContext, resourceId - 9, flags == MUSIC_LOOP);
+ }
+ }
+ }
+
+ if (audioStream) {
+ debug(2, "Playing digitized music");
+ _mixer->playInputStream(Audio::Mixer::kMusicSoundType, &_musicHandle, audioStream);
+ return;
+ }
+
+ if (flags == MUSIC_DEFAULT) {
+ flags = MUSIC_NORMAL;
+ }
+
+ // FIXME: Is resource_data ever freed?
+ // Load MIDI/XMI resource data
+
+ if (_vm->getGameType() == GType_ITE) {
+ context = _vm->_resource->getContext(GAME_MUSICFILE_GM);
+ if (context == NULL) {
+ context = _vm->_resource->getContext(GAME_RESOURCEFILE);
+ }
+ } else {
+ // I've listened to music from both the FM and the GM
+ // file, and I've tentatively reached the conclusion
+ // that they are both General MIDI. My guess is that
+ // the FM file has been reorchestrated to sound better
+ // on Adlib and other FM synths.
+ //
+ // Sev says the Adlib music does not sound like in the
+ // original, but I still think assuming General MIDI is
+ // the right thing to do. Some music, like the End
+ // Title (song 0) sound absolutely atrocious when piped
+ // through our MT-32 to GM mapping.
+ //
+ // It is, however, quite possible that the original
+ // used a different GM to FM mapping. If the original
+ // sounded markedly better, perhaps we should add some
+ // way of replacing our stock mapping in adlib.cpp?
+ //
+ // For the composer's own recording of the End Title,
+ // see http://www.johnottman.com/
+
+ // Oddly enough, the intro music (song 1) is very
+ // different in the two files. I have no idea why.
+
+ if (hasAdlib()) {
+ context = _vm->_resource->getContext(GAME_MUSICFILE_FM);
+ } else {
+ context = _vm->_resource->getContext(GAME_MUSICFILE_GM);
+ }
+ }
+
+ _player->setGM(true);
+
+ _vm->_resource->loadResource(context, resourceId, resourceData, resourceSize);
+
+ if (resourceSize < 4) {
+ error("Music::play() wrong music resource size");
+ }
+
+ if (xmidiParser->loadMusic(resourceData, resourceSize)) {
+ if (_vm->getGameType() == GType_ITE)
+ _player->setGM(false);
+
+ parser = xmidiParser;
+ } else {
+ if (smfParser->loadMusic(resourceData, resourceSize)) {
+ parser = smfParser;
+ } else {
+ error("Music::play() wrong music resource");
+ }
+ }
+
+ parser->setTrack(0);
+ parser->setMidiDriver(_player);
+ parser->setTimerRate(_player->getBaseTempo());
+ parser->property(MidiParser::mpCenterPitchWheelOnUnload, 1);
+
+ _player->_parser = parser;
+ _player->setVolume(_vm->_musicVolume == 10 ? 255 : _vm->_musicVolume * 25);
+
+ if (flags & MUSIC_LOOP)
+ _player->setLoop(true);
+ else
+ _player->setLoop(false);
+
+ _player->playMusic();
+}
+
+void Music::pause(void) {
+ //TODO: do it
+}
+
+void Music::resume(void) {
+ //TODO: do it}
+}
+
+void Music::stop(void) {
+ //TODO: do it
+}
+
+} // End of namespace Saga
+