aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJamieson Christian2003-09-16 11:44:48 +0000
committerJamieson Christian2003-09-16 11:44:48 +0000
commit091b41a2787cf7eeb36732cb02aec68c66ab570c (patch)
treefa0ae6d9588b41eb2e770b69a12d2741d60f8dee
parent00c1fdce3aa67fae7d314ba48dfbb774879cf221 (diff)
downloadscummvm-rg350-091b41a2787cf7eeb36732cb02aec68c66ab570c.tar.gz
scummvm-rg350-091b41a2787cf7eeb36732cb02aec68c66ab570c.tar.bz2
scummvm-rg350-091b41a2787cf7eeb36732cb02aec68c66ab570c.zip
Partial fix for Bug [636985] ZAK256: No kazoo tune
Implemented a parser for Euphony music. No FM instrument support yet, as the FM chip used by FM Towns is not being emulated yet. In the meantime, a stock FM-emulated GM instrument is being used instead. This at least makes the Zak Towns kazoo tune and the Loom Towns distaff audible. Emulation of the FM Towns synth chip, or suitable emulation using the OPL2 synth, is still under investigation. svn-id: r10265
-rw-r--r--scumm.dsp4
-rw-r--r--scumm.vcproj3
-rw-r--r--scumm/imuse.cpp13
-rw-r--r--scumm/imuse_player.cpp4
-rw-r--r--scumm/midiparser_eup.cpp193
-rw-r--r--scumm/module.mk1
-rw-r--r--scumm/scummvm.cpp6
-rw-r--r--scumm/sound.cpp19
8 files changed, 221 insertions, 22 deletions
diff --git a/scumm.dsp b/scumm.dsp
index d9c5f5a5b0..e3911f1c00 100644
--- a/scumm.dsp
+++ b/scumm.dsp
@@ -343,6 +343,10 @@ SOURCE=.\scumm\intern.h
# End Source File
# Begin Source File
+SOURCE=.\scumm\midiparser_eup.cpp
+# End Source File
+# Begin Source File
+
SOURCE=.\scumm\midiparser_ro.cpp
# End Source File
# Begin Source File
diff --git a/scumm.vcproj b/scumm.vcproj
index 8b719fe09c..e55a02a8a3 100644
--- a/scumm.vcproj
+++ b/scumm.vcproj
@@ -311,6 +311,9 @@
RelativePath=".\scumm\midiparser_ro.cpp">
</File>
<File
+ RelativePath=".\scumm\midiparser_eup.cpp">
+ </File>
+ <File
RelativePath="scumm\music.h">
</File>
<File
diff --git a/scumm/imuse.cpp b/scumm/imuse.cpp
index 57cf12b1e4..a02acd538c 100644
--- a/scumm/imuse.cpp
+++ b/scumm/imuse.cpp
@@ -88,6 +88,8 @@ byte *IMuseInternal::findStartOfSound(int sound) {
// Check for old-style headers first, like 'RO'
if (ptr[4] == 'R' && ptr[5] == 'O'&& ptr[6] != 'L')
return ptr + 4;
+ if (ptr[8] == 'S' && ptr[9] == 'O')
+ return ptr + 8;
ptr += 8;
size = READ_BE_UINT32(ptr);
@@ -134,9 +136,12 @@ bool IMuseInternal::isMT32(int sound) {
return false;
}
- // Check old style headers, like 'RO'
+ // Old style 'RO' has equivalent properties to 'ROL'
if (ptr[4] == 'R' && ptr[5] == 'O')
return true;
+ // Euphony tracks show as 'SO' and have equivalent properties to 'ADL'
+ if (ptr[8] == 'S' && ptr[9] == 'O')
+ return false;
return false;
}
@@ -170,9 +175,13 @@ bool IMuseInternal::isGM(int sound) {
return false;
}
- // Check old style headers, like 'RO'
+ // Old style 'RO' has equivalent properties to 'ROL'
if (ptr[4] == 'R' && ptr[5] == 'O')
return true;
+ // Euphony tracks show as 'SO' and have equivalent properties to 'ADL'
+ // FIXME: Right now we're pretending it's GM.
+ if (ptr[8] == 'S' && ptr[9] == 'O')
+ return true;
return false;
}
diff --git a/scumm/imuse_player.cpp b/scumm/imuse_player.cpp
index 51315e0a6c..986dfb6340 100644
--- a/scumm/imuse_player.cpp
+++ b/scumm/imuse_player.cpp
@@ -39,6 +39,7 @@
////////////////////////////////////////
extern MidiParser *MidiParser_createRO();
+extern MidiParser *MidiParser_createEUP();
static uint read_word(byte *a) {
return (a[0] << 8) + a[1];
@@ -186,6 +187,9 @@ int Player::start_seq_sound(int sound, bool reset_vars) {
if (!memcmp (ptr, "RO", 2)) {
// Old style 'RO' resource
_parser = MidiParser_createRO();
+ } else if (!memcmp (ptr, "SO", 2)) {
+ // Euphony (FM Towns) resource
+ _parser = MidiParser_createEUP();
} else if (!memcmp(ptr, "FORM", 4)) {
// Humongous Games XMIDI resource
_parser = MidiParser::createParser_XMIDI();
diff --git a/scumm/midiparser_eup.cpp b/scumm/midiparser_eup.cpp
new file mode 100644
index 0000000000..ba0dd0ff93
--- /dev/null
+++ b/scumm/midiparser_eup.cpp
@@ -0,0 +1,193 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2001-2003 The ScummVM project
+ *
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header$
+ *
+ */
+
+#include "stdafx.h"
+#include "sound/midiparser.h"
+#include "sound/mididrv.h"
+#include "common/util.h"
+
+
+//////////////////////////////////////////////////
+//
+// The FM Towns Euphony version of MidiParser
+//
+//////////////////////////////////////////////////
+
+class MidiParser_EUP : public MidiParser {
+protected:
+ struct {
+ bool mute;
+ uint8 channel;
+ int8 volume;
+ int8 transpose;
+ } _presets[32];
+ bool _loop;
+ byte _presend; // Tracks which startup implied events have been sent.
+
+protected:
+ void parseNextEvent (EventInfo &info);
+ void resetTracking();
+
+public:
+ bool loadMusic (byte *data, uint32 size);
+};
+
+
+
+//////////////////////////////////////////////////
+//
+// MidiParser_EUP implementation
+//
+//////////////////////////////////////////////////
+
+void MidiParser_EUP::parseNextEvent (EventInfo &info) {
+ byte *pos = _position._play_pos;
+
+ // FIXME: The presend is for sending init events
+ // that aren't actually in the stream. This would
+ // be for, e.g., instrument setup. Right now, we
+ // don't actually use the instruments specified
+ // in the music header. We're sending fixed GM
+ // program changes to get a reasonable "one-size-
+ // fits-all" sound until we actually support the
+ // FM synthesis capabilities of FM Towns.
+ if (_presend) {
+ --_presend;
+ info.start = pos;
+ info.delta = 0;
+ info.event = ((_presend & 1) ? 0xB0 : 0xC0) | (_presend >> 1);
+ info.basic.param1 = ((_presend & 1) ? 7 : 0x38);
+ info.basic.param2 = ((_presend & 1) ? 127 : 0);
+ _presend = (_presend + 2) % 32;
+ return;
+ }
+
+ while (true) {
+ byte cmd = *pos;
+ if ((cmd & 0xF0) == 0x90) {
+ byte preset = pos[1];
+ byte channel = _presets[preset].channel;
+ if (channel >= 16)
+ channel = cmd & 0x0F;
+ uint16 tick = pos[2] | ((uint16) pos[3] << 7);
+ int note = (int) pos[4] + _presets[preset].transpose;
+ int volume = (int) pos[5] + _presets[preset].volume;
+ pos += 6;
+ if ((*pos & 0xF0) == 0x80) {
+ if (!_presets[preset].mute) {
+ uint16 duration = pos[1] | (pos[2] << 4) | (pos[3] << 8) | (pos[4] << 12);
+ info.start = pos;
+ uint32 last = _position._last_event_tick;
+ info.delta = (tick < last) ? 0 : (tick - last);
+ info.event = 0x90 | channel;
+ info.length = duration;
+ info.basic.param1 = note;
+ info.basic.param2 = volume;
+ pos += 6;
+ break;
+ }
+ pos += 6;
+ }
+ } else if (cmd == 0xF2) {
+ // This is basically a "rest".
+ uint16 tick = pos[2] | (pos[3] << 7);
+ uint32 last = _position._last_event_tick;
+ info.start = pos;
+ info.delta = (tick < last) ? 0 : (tick - last);
+ info.event = 0xFF;
+ info.length = 0;
+ info.ext.type = 0x7F; // Bogus META event
+ info.ext.data = pos;
+ pos += 6;
+ break;
+ } else if (cmd == 0xF8) {
+ // TODO: Implement this.
+ pos += 6;
+ } else if (cmd == 0xFD || cmd == 0xFE) {
+ // End of track.
+ if (_loop && false) {
+ // TODO: Implement this.
+ } else {
+ info.start = pos;
+ info.delta = 0;
+ info.event = 0xFF;
+ info.length = 0;
+ info.ext.type = 0x2F;
+ info.ext.data = pos;
+ pos = 0;
+ break;
+ }
+ } else {
+ printf ("Unknown Euphony music event 0x%02X\n", (int) cmd);
+ memset (&info, 0, sizeof(info));
+ pos = 0;
+ break;
+ }
+ }
+ _position._play_pos = pos;
+}
+
+bool MidiParser_EUP::loadMusic (byte *data, uint32 size) {
+ unloadMusic();
+ byte *pos = data;
+
+ if (memcmp (pos, "SO", 2)) {
+ printf ("Warning: 'SO' header expected but found '%c%c' instead.\n", pos[0], pos[1]);
+ return false;
+ }
+
+ byte numInstruments = pos[16];
+ pos += (16 + 2 + numInstruments * 48);
+
+ for (int i = 0; i < 32; ++i) {
+ _presets[i].mute = ((int8) pos[i] == 0);
+ _presets[i].channel = pos[i+32];
+ _presets[i].volume = (int8) pos[i+64];
+ _presets[i].transpose = (int8) pos[i+96];
+ }
+ pos += 32 * 4; // Jump past presets
+ pos += 8; // Unknown bytes
+ pos += 6; // Instrument-to-channel mapping (not supported yet)
+ pos += 4; // Skip the music size for now.
+ pos++; // Unknown byte
+ byte tempo = *pos++;
+ _loop = (*pos++ != 1);
+ pos++; // Unknown byte
+
+ _num_tracks = 1;
+ _ppqn = 120;
+ _tracks[0] = pos;
+
+ // Note that we assume the original data passed in
+ // will persist beyond this call, i.e. we do NOT
+ // copy the data to our own buffer. Take warning....
+ resetTracking();
+ setTempo (1000000 * 60 / tempo);
+ setTrack (0);
+ return true;
+}
+
+void MidiParser_EUP::resetTracking() {
+ MidiParser::resetTracking();
+ _presend = 1;
+}
+
+MidiParser *MidiParser_createEUP() { return new MidiParser_EUP; }
diff --git a/scumm/module.mk b/scumm/module.mk
index 755b8a5a43..1d5a606e78 100644
--- a/scumm/module.mk
+++ b/scumm/module.mk
@@ -19,6 +19,7 @@ SCUMM_OBJS = \
scumm/instrument.o \
scumm/help.o \
scumm/midiparser_ro.o \
+ scumm/midiparser_eup.o \
scumm/nut_renderer.o \
scumm/object.o \
scumm/player_v1.o\
diff --git a/scumm/scummvm.cpp b/scumm/scummvm.cpp
index 9751856a70..408e3f9dae 100644
--- a/scumm/scummvm.cpp
+++ b/scumm/scummvm.cpp
@@ -91,11 +91,11 @@ static const TargetSettings scumm_settings[] = {
GF_SMALL_HEADER | GF_SMALL_NAMES | GF_NO_SCALING | GF_OLD256 | GF_FEW_LOCALS | GF_FMTOWNS | GF_AUDIOTRACKS, "00.LFL"},
{"indy3", "Indiana Jones and the Last Crusade (256)", GID_INDY3, 3, MDT_PCSPK | MDT_ADLIB,
GF_SMALL_HEADER | GF_SMALL_NAMES | GF_NO_SCALING | GF_OLD256 | GF_FEW_LOCALS, "00.LFL"},
- {"zak256", "Zak McKracken and the Alien Mindbenders (256)", GID_ZAK256, 3, MDT_PCSPK,
+ {"zak256", "Zak McKracken and the Alien Mindbenders (256)", GID_ZAK256, 3, MDT_ADLIB,
GF_SMALL_HEADER | GF_SMALL_NAMES | GF_NO_SCALING | GF_OLD256 | GF_FMTOWNS | GF_AUDIOTRACKS, "00.LFL"},
{"loom", "Loom", GID_LOOM, 3, MDT_PCSPK | MDT_ADLIB | MDT_NATIVE,
GF_SMALL_HEADER | GF_SMALL_NAMES | GF_NO_SCALING | GF_USE_KEY | GF_16COLOR | GF_OLD_BUNDLE, "00.LFL"},
- {"loomTowns", "Loom (FM Towns)", GID_LOOM, 3, MDT_NONE,
+ {"loomTowns", "Loom (FM Towns)", GID_LOOM, 3, MDT_ADLIB,
GF_SMALL_HEADER | GF_SMALL_NAMES | GF_NO_SCALING | GF_OLD256 | GF_FMTOWNS | GF_AUDIOTRACKS, "00.LFL"},
/* Scumm Version 4 */
@@ -723,7 +723,7 @@ Scumm::Scumm (GameDetector *detector, OSystem *syst)
_imuse->property(IMuse::PROP_OLD_ADLIB_INSTRUMENTS, (_features & GF_SMALL_HEADER) ? 1 : 0);
_imuse->property(IMuse::PROP_MULTI_MIDI, detector->_multi_midi && _midiDriver != MD_NULL);
_imuse->property(IMuse::PROP_NATIVE_MT32, detector->_native_mt32);
- if (_features & GF_HUMONGOUS) {
+ if (_features & GF_HUMONGOUS || _features & GF_FMTOWNS) {
_imuse->property(IMuse::PROP_LIMIT_PLAYERS, 1);
_imuse->property(IMuse::PROP_RECYCLE_PLAYERS, 1);
}
diff --git a/scumm/sound.cpp b/scumm/sound.cpp
index 76374c414c..9138ce1a99 100644
--- a/scumm/sound.cpp
+++ b/scumm/sound.cpp
@@ -384,23 +384,8 @@ void Sound::playSound(int soundID) {
}
case 1: { // Music (Euphony format)
- int numInstruments = *(ptr + 0x14);
- int tuneSize = 0, tempo = 0;
-
- ptr += (0x16 + (numInstruments * 48)); // Skip instrument definitions
- ptr += (32*4); // Skip preset values (mute, channel, volume, transpose)
- ptr += 8; // (Unknown)
-
- ptr += 6; // Instrument channel's. Always 6 bytes according to the disassembly.
-
- tuneSize = READ_LE_UINT32(ptr);
- ptr += 5;
-
- tempo = *ptr++;
- ptr += 2;
- // Music data begins here
-
- warning("Euphony tune #%d unsupported", soundID);
+ if (_scumm->_musicEngine)
+ _scumm->_musicEngine->startSound (soundID);
break;
}