aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTorbjörn Andersson2004-10-12 11:45:53 +0000
committerTorbjörn Andersson2004-10-12 11:45:53 +0000
commitec413cc8f9d49ca3c93092e955032a92857c3960 (patch)
tree07770306efb3b3aa9989d834eac28f7dc9dafd67
parenteaccebd7fcc39043d4a245a1347af7ef9d8e6c9f (diff)
downloadscummvm-rg350-ec413cc8f9d49ca3c93092e955032a92857c3960.tar.gz
scummvm-rg350-ec413cc8f9d49ca3c93092e955032a92857c3960.tar.bz2
scummvm-rg350-ec413cc8f9d49ca3c93092e955032a92857c3960.zip
Added support for digitized music, plus some other music-related fixes.
Note that I've only tried this with the music.rsc file from the full Linux version. It seems likely that the Mac version uses the same file format, but I have no way of verifying this. I'm told the demos use compressed audio, so the music files from them probably won't work yet. svn-id: r15527
-rw-r--r--saga/ite_introproc.cpp1
-rw-r--r--saga/music.cpp289
-rw-r--r--saga/music.h15
-rw-r--r--saga/saga.cpp2
-rw-r--r--saga/scene.cpp1
-rw-r--r--saga/sfuncs.cpp2
6 files changed, 261 insertions, 49 deletions
diff --git a/saga/ite_introproc.cpp b/saga/ite_introproc.cpp
index 25cd24a975..99a8971c96 100644
--- a/saga/ite_introproc.cpp
+++ b/saga/ite_introproc.cpp
@@ -664,6 +664,7 @@ int Scene::ITEIntroValleyProc(int param, R_SCENE_INFO *scene_info) {
event.type = R_ONESHOT_EVENT;
event.code = R_MUSIC_EVENT;
event.param = MUSIC_2;
+ event.param2 = 0;
event.op = EVENT_PLAY;
event.time = 0;
diff --git a/saga/music.cpp b/saga/music.cpp
index c57bfd77d0..5f7d8d626a 100644
--- a/saga/music.cpp
+++ b/saga/music.cpp
@@ -25,6 +25,7 @@
#include "saga/music.h"
#include "saga/rscfile_mod.h"
#include "saga/game_mod.h"
+#include "sound/audiostream.h"
#include "sound/mididrv.h"
#include "sound/midiparser.h"
#include "common/config-manager.h"
@@ -45,6 +46,139 @@ static const byte mt32_to_gm[128] = {
47, 117, 127, 118, 118, 116, 115, 119, 115, 112, 55, 124, 123, 0, 14, 117 // 7x
};
+#define BUFFER_SIZE 4096
+
+// 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:
+ File *_file;
+ uint32 _file_pos;
+ uint32 _start_pos;
+ uint32 _end_pos;
+ bool _finished;
+ bool _looping;
+ int16 _buf[BUFFER_SIZE];
+ const int16 *_bufferEnd;
+ const int16 *_pos;
+
+ void refill();
+ inline bool eosIntern() const;
+
+public:
+ RAWInputStream(File *file, int size, bool looping);
+ ~RAWInputStream();
+
+ int readBuffer(int16 *buffer, const int numSamples);
+
+ int16 read();
+ bool endOfData() const { return eosIntern(); }
+ bool isStereo() const { return true; }
+ int getRate() const { return 11025; }
+};
+
+RAWInputStream::RAWInputStream(File *file, int size, bool looping)
+ : _file(file), _finished(false), _looping(looping),
+ _bufferEnd(_buf + BUFFER_SIZE) {
+
+ _file->incRef();
+
+ // Determine the end position
+ _file_pos = _file->pos();
+ _start_pos = _file_pos;
+ _end_pos = _file_pos + size;
+
+ // Read in initial data
+ refill();
+}
+
+RAWInputStream::~RAWInputStream() {
+ _file->decRef();
+}
+
+inline int16 RAWInputStream::read() {
+ assert(!eosIntern());
+
+ int16 sample = *_pos++;
+ if (_pos >= _bufferEnd) {
+ refill();
+ }
+ return sample;
+}
+
+inline bool RAWInputStream::eosIntern() const {
+ return _pos >= _bufferEnd;
+}
+
+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 len_left;
+ byte *ptr = (byte *) _buf;
+
+ _file->seek(_file_pos, SEEK_SET);
+
+ if (_looping)
+ len_left = 2 * BUFFER_SIZE;
+ else
+ len_left = MIN((uint32) (2 * BUFFER_SIZE), _end_pos - _file_pos);
+
+ while (len_left > 0) {
+ uint32 len = _file->read(ptr, MIN(len_left, _end_pos - _file->pos()));
+
+ if (len & 1)
+ len--;
+
+ len_left -= len;
+ ptr += len;
+
+ if (len_left > 0)
+ _file->seek(_start_pos);
+ }
+
+ _file_pos = _file->pos();
+ _pos = _buf;
+ _bufferEnd = (int16 *)ptr;
+
+ if (!_looping && _file_pos >= _end_pos)
+ _finished = true;
+}
+
+AudioStream *makeRAWStream(const char *filename, uint32 pos, int size, bool looping) {
+ File *file = new File();
+
+ if (!file->open(filename)) {
+ delete file;
+ return NULL;
+ }
+
+ file->seek(pos);
+
+ AudioStream *audioStream = new RAWInputStream(file, size, looping);
+
+ file->decRef();
+ return audioStream;
+}
+
MusicPlayer::MusicPlayer(MidiDriver *driver) : _parser(0), _driver(driver), _looping(false), _isPlaying(false) {
memset(_channel, 0, sizeof(_channel));
_masterVolume = 0;
@@ -121,14 +255,21 @@ void MusicPlayer::send(uint32 b) {
}
void MusicPlayer::metaEvent(byte type, byte *data, uint16 length) {
- //Only thing we care about is End of Track.
- if (type != 0x2F)
- return;
-
- if (_looping)
- _parser->jumpToTick(0);
- else
- stopMusic();
+ // 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) {
@@ -150,9 +291,39 @@ void MusicPlayer::stopMusic() {
}
}
-Music::Music(MidiDriver *driver, int enabled) : _enabled(enabled) {
+Music::Music(SoundMixer *mixer, MidiDriver *driver, int enabled) : _mixer(mixer), _enabled(enabled) {
_player = new MusicPlayer(driver);
_musicInitialized = 1;
+ _mixer->setMusicVolume(ConfMan.getInt("music_volume"));
+
+ if (GAME_GetGameType() == GID_ITE) {
+ File file;
+
+ // The lookup table is stored at the end of music.rsc. I don't
+ // know why it has 27 elements, but the last one represents a
+ // very short tune. Perhaps it's a dummy?
+ //
+ // TODO: The demo uses compressed music?
+
+ if (file.open("music.rsc")) {
+ _hasDigiMusic = true;
+ file.seek(-ARRAYSIZE(_digiTableITECD) * 8, SEEK_END);
+
+ for (int i = 0; i < ARRAYSIZE(_digiTableITECD); i++) {
+ _digiTableITECD[i].start = file.readUint32LE();
+ _digiTableITECD[i].length = file.readUint32LE();
+ }
+
+ file.close();
+
+ // The "birdchrp" is just a short, high-pitched
+ // whining. Use the MIDI/XMIDI version instead.
+ _digiTableITECD[6].length = 0;
+ } else {
+ _hasDigiMusic = false;
+ memset(_digiTableITECD, 0, sizeof(_digiTableITECD));
+ }
+ }
}
Music::~Music() {
@@ -165,32 +336,32 @@ Music::~Music() {
// reset.mid seems to be unused.
const MUSIC_MIDITABLE Music::_midiTableITECD[26] = {
- {"cave.mid", R_MUSIC_LOOP}, // 9
- {"intro.mid", R_MUSIC_LOOP}, // 10
- {"fvillage.mid", R_MUSIC_LOOP}, // 11
- {"elkhall.mid", R_MUSIC_LOOP}, // 12
- {"mouse.mid", 0}, // 13
- {"darkclaw.mid", R_MUSIC_LOOP}, // 14
- {"birdchrp.mid", R_MUSIC_LOOP}, // 15
- {"orbtempl.mid", R_MUSIC_LOOP}, // 16
- {"spooky.mid", R_MUSIC_LOOP}, // 17
- {"catfest.mid", R_MUSIC_LOOP}, // 18
- {"elkfanfare.mid", 0}, // 19
- {"bcexpl.mid", R_MUSIC_LOOP}, // 20
- {"boargtnt.mid", R_MUSIC_LOOP}, // 21
- {"boarking.mid", R_MUSIC_LOOP}, // 22
- {"explorea.mid", R_MUSIC_LOOP}, // 23
- {"exploreb.mid", R_MUSIC_LOOP}, // 24
- {"explorec.mid", R_MUSIC_LOOP}, // 25
- {"sunstatm.mid", R_MUSIC_LOOP}, // 26
- {"nitstrlm.mid", R_MUSIC_LOOP}, // 27
- {"humruinm.mid", R_MUSIC_LOOP}, // 28
- {"damexplm.mid", R_MUSIC_LOOP}, // 29
- {"tychom.mid", R_MUSIC_LOOP}, // 30
- {"kitten.mid", R_MUSIC_LOOP}, // 31
- {"sweet.mid", R_MUSIC_LOOP}, // 32
- {"brutalmt.mid", R_MUSIC_LOOP}, // 33
- {"shiala.mid", R_MUSIC_LOOP} // 34
+ { "cave.mid", R_MUSIC_LOOP }, // 9
+ { "intro.mid", R_MUSIC_LOOP }, // 10
+ { "fvillage.mid", R_MUSIC_LOOP }, // 11
+ { "elkhall.mid", R_MUSIC_LOOP }, // 12
+ { "mouse.mid", 0 }, // 13
+ { "darkclaw.mid", R_MUSIC_LOOP }, // 14
+ { "birdchrp.mid", R_MUSIC_LOOP }, // 15
+ { "orbtempl.mid", R_MUSIC_LOOP }, // 16
+ { "spooky.mid", R_MUSIC_LOOP }, // 17
+ { "catfest.mid", R_MUSIC_LOOP }, // 18
+ { "elkfanfare.mid", 0 }, // 19
+ { "bcexpl.mid", R_MUSIC_LOOP }, // 20
+ { "boargtnt.mid", R_MUSIC_LOOP }, // 21
+ { "boarking.mid", R_MUSIC_LOOP }, // 22
+ { "explorea.mid", R_MUSIC_LOOP }, // 23
+ { "exploreb.mid", R_MUSIC_LOOP }, // 24
+ { "explorec.mid", R_MUSIC_LOOP }, // 25
+ { "sunstatm.mid", R_MUSIC_LOOP }, // 26
+ { "nitstrlm.mid", R_MUSIC_LOOP }, // 27
+ { "humruinm.mid", R_MUSIC_LOOP }, // 28
+ { "damexplm.mid", R_MUSIC_LOOP }, // 29
+ { "tychom.mid", R_MUSIC_LOOP }, // 30
+ { "kitten.mid", R_MUSIC_LOOP }, // 31
+ { "sweet.mid", R_MUSIC_LOOP }, // 32
+ { "brutalmt.mid", R_MUSIC_LOOP }, // 33
+ { "shiala.mid", R_MUSIC_LOOP } // 34
};
int Music::play(uint32 music_rn, uint16 flags) {
@@ -207,25 +378,54 @@ int Music::play(uint32 music_rn, uint16 flags) {
return R_SUCCESS;
}
- File f_midi;
+ _player->stopMusic();
+
+ if (_musicHandle.isActive())
+ _mixer->stopHandle(_musicHandle);
+
+ AudioStream *audioStream = NULL;
MidiParser *parser;
+ File midiFile;
if (GAME_GetGameType() == GID_ITE) {
if (music_rn >= 9 && music_rn <= 34) {
- f_midi.open(_midiTableITECD[music_rn - 9].filename);
+ if (flags == R_MUSIC_DEFAULT) {
+ flags = _midiTableITECD[music_rn - 9].flags;
+ }
+
+ if (_hasDigiMusic) {
+ uint32 start = _digiTableITECD[music_rn - 9].start;
+ uint32 length = _digiTableITECD[music_rn - 9].length;
+
+ if (length > 0) {
+ audioStream = makeRAWStream("music.rsc", start, length, flags == R_MUSIC_LOOP);
+ }
+ }
+
+ // No digitized music - try standalone MIDI.
+ if (!audioStream)
+ midiFile.open(_midiTableITECD[music_rn - 9].filename);
}
}
- _player->stopMusic();
+ if (flags == R_MUSIC_DEFAULT) {
+ flags = 0;
+ }
+
+ if (audioStream) {
+ debug(0, "Playing digitized music");
+ _mixer->playInputStream(&_musicHandle, audioStream, true);
+ return R_SUCCESS;
+ }
// FIXME: Is resource_data ever freed?
- if (f_midi.isOpen()) {
- debug(0, "Using external MIDI file: %s", f_midi.name());
- resource_size = f_midi.size();
+ if (midiFile.isOpen()) {
+ debug(0, "Using external MIDI file: %s", midiFile.name());
+ resource_size = midiFile.size();
resource_data = (byte *) malloc(resource_size);
- f_midi.read(resource_data, resource_size);
- f_midi.close();
+ midiFile.read(resource_data, resource_size);
+ midiFile.close();
_player->setGM(true);
parser = MidiParser::createParser_SMF();
@@ -233,7 +433,8 @@ int Music::play(uint32 music_rn, uint16 flags) {
// FIXME: Is this really the case or we receive correct parameter?
flags = _midiTableITECD[music_rn - 9].flags;
} else {
- /* Load XMI resource data */
+ // Load XMI resource data
+
GAME_GetFileContext(&rsc_ctxt, R_GAME_RESOURCEFILE, 0);
if (RSC_LoadResource(rsc_ctxt, music_rn, &resource_data,
diff --git a/saga/music.h b/saga/music.h
index ca7ad2b783..e5ba862667 100644
--- a/saga/music.h
+++ b/saga/music.h
@@ -34,7 +34,8 @@
namespace Saga {
enum MUSIC_FLAGS {
- R_MUSIC_LOOP = 0x01
+ R_MUSIC_LOOP = 0x0001,
+ R_MUSIC_DEFAULT = 0xffff
};
struct MUSIC_MIDITABLE {
@@ -42,6 +43,11 @@ struct MUSIC_MIDITABLE {
int flags;
};
+struct MUSIC_DIGITABLE {
+ uint32 start;
+ uint32 length;
+};
+
class MusicPlayer : public MidiDriver {
public:
MusicPlayer(MidiDriver *driver);
@@ -95,11 +101,11 @@ protected:
class Music {
public:
- Music(MidiDriver *driver, int enabled);
+ Music(SoundMixer *mixer, MidiDriver *driver, int enabled);
~Music(void);
void hasNativeMT32(bool b) { _player->hasNativeMT32(b); }
- int play(uint32 music_rn, uint16 flags);
+ int play(uint32 music_rn, uint16 flags = R_MUSIC_DEFAULT);
int pause(void);
int resume(void);
int stop(void);
@@ -109,11 +115,14 @@ private:
SoundMixer *_mixer;
MusicPlayer *_player;
+ PlayingSoundHandle _musicHandle;
static const MUSIC_MIDITABLE _midiTableITECD[26];
+ MUSIC_DIGITABLE _digiTableITECD[27];
int _musicInitialized;
int _enabled;
+ bool _hasDigiMusic;
};
} // End of namespace Saga
diff --git a/saga/saga.cpp b/saga/saga.cpp
index dec9c8d657..057762ca43 100644
--- a/saga/saga.cpp
+++ b/saga/saga.cpp
@@ -170,7 +170,7 @@ void SagaEngine::go() {
else if (ConfMan.getBool("native_mt32"))
driver->property(MidiDriver::PROP_CHANNEL_MASK, 0x03FE);
- _music = new Music(driver, _musicEnabled);
+ _music = new Music(_mixer, driver, _musicEnabled);
_music->hasNativeMT32(ConfMan.getBool("native_mt32"));
if (!_musicEnabled) {
diff --git a/saga/scene.cpp b/saga/scene.cpp
index db23de06b7..2bf0ccd110 100644
--- a/saga/scene.cpp
+++ b/saga/scene.cpp
@@ -1003,6 +1003,7 @@ int Scene::defaultScene(int param, R_SCENE_INFO *scene_info) {
event.type = R_ONESHOT_EVENT;
event.code = R_MUSIC_EVENT;
event.param = _desc.musicRN;
+ event.param2 = R_MUSIC_DEFAULT;
event.op = EVENT_PLAY;
event.time = 0;
diff --git a/saga/sfuncs.cpp b/saga/sfuncs.cpp
index 975b3f5926..fbf0ce983b 100644
--- a/saga/sfuncs.cpp
+++ b/saga/sfuncs.cpp
@@ -755,7 +755,7 @@ int Script::SF_playMusic(R_SCRIPTFUNC_PARAMS) {
param = thread->pop();
if (/* param >= 0 && */ param < ARRAYSIZE(musicTable))
- _vm->_music->play(musicTable[param], 0);
+ _vm->_music->play(musicTable[param]);
else
_vm->_music->stop();