aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorclone27272012-12-13 15:49:40 -0800
committerclone27272012-12-13 15:49:40 -0800
commit91317c3630f9bff164d8c747ef886b52a85e3a9b (patch)
treebb66e8e1acf7bd9315dc7307184b77209adb8d51
parenta0863dfb3f3998f34c591f3007efe9f9f0f7f311 (diff)
parent17f923532533c8a094e1bf0a0efa7369b20ef48a (diff)
downloadscummvm-rg350-91317c3630f9bff164d8c747ef886b52a85e3a9b.tar.gz
scummvm-rg350-91317c3630f9bff164d8c747ef886b52a85e3a9b.tar.bz2
scummvm-rg350-91317c3630f9bff164d8c747ef886b52a85e3a9b.zip
Merge pull request #293 from clone2727/qtmidi
Add support for QuickTime Music playback
-rw-r--r--audio/decoders/quicktime.cpp2
-rw-r--r--audio/decoders/quicktime_intern.h2
-rw-r--r--audio/midiparser.h1
-rw-r--r--audio/midiparser_qt.cpp496
-rw-r--r--audio/midiparser_qt.h134
-rw-r--r--audio/module.mk1
-rw-r--r--common/quicktime.cpp6
-rw-r--r--common/quicktime.h5
-rw-r--r--engines/groovie/detection.cpp99
-rw-r--r--engines/groovie/groovie.cpp19
-rw-r--r--engines/groovie/module.mk1
-rw-r--r--engines/groovie/music.cpp54
-rw-r--r--engines/groovie/music.h12
-rw-r--r--engines/groovie/stuffit.cpp537
-rw-r--r--engines/groovie/stuffit.h43
-rw-r--r--engines/saga/music.cpp59
-rw-r--r--engines/saga/music.h1
-rw-r--r--video/qt_decoder.cpp4
-rw-r--r--video/qt_decoder.h2
19 files changed, 1433 insertions, 45 deletions
diff --git a/audio/decoders/quicktime.cpp b/audio/decoders/quicktime.cpp
index 0588650ec6..787b547495 100644
--- a/audio/decoders/quicktime.cpp
+++ b/audio/decoders/quicktime.cpp
@@ -134,7 +134,7 @@ void QuickTimeAudioDecoder::init() {
_audioTracks.push_back(new QuickTimeAudioTrack(this, _tracks[i]));
}
-Common::QuickTimeParser::SampleDesc *QuickTimeAudioDecoder::readSampleDesc(Track *track, uint32 format) {
+Common::QuickTimeParser::SampleDesc *QuickTimeAudioDecoder::readSampleDesc(Track *track, uint32 format, uint32 descSize) {
if (track->codecType == CODEC_TYPE_AUDIO) {
debug(0, "Audio Codec FourCC: \'%s\'", tag2str(format));
diff --git a/audio/decoders/quicktime_intern.h b/audio/decoders/quicktime_intern.h
index f1ab037d89..bb5ff0cf5c 100644
--- a/audio/decoders/quicktime_intern.h
+++ b/audio/decoders/quicktime_intern.h
@@ -131,7 +131,7 @@ protected:
};
// Common::QuickTimeParser API
- virtual Common::QuickTimeParser::SampleDesc *readSampleDesc(Track *track, uint32 format);
+ virtual Common::QuickTimeParser::SampleDesc *readSampleDesc(Track *track, uint32 format, uint32 descSize);
void init();
diff --git a/audio/midiparser.h b/audio/midiparser.h
index a4dbf174e1..bb9749b97f 100644
--- a/audio/midiparser.h
+++ b/audio/midiparser.h
@@ -394,6 +394,7 @@ public:
static MidiParser *createParser_SMF();
static MidiParser *createParser_XMIDI(XMidiCallbackProc proc = defaultXMidiCallback, void *refCon = 0);
+ static MidiParser *createParser_QT();
static void timerCallback(void *data) { ((MidiParser *) data)->onTimer(); }
};
diff --git a/audio/midiparser_qt.cpp b/audio/midiparser_qt.cpp
new file mode 100644
index 0000000000..6214d28f95
--- /dev/null
+++ b/audio/midiparser_qt.cpp
@@ -0,0 +1,496 @@
+/* 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.
+ *
+ */
+
+#include "audio/midiparser_qt.h"
+#include "common/debug.h"
+#include "common/memstream.h"
+
+bool MidiParser_QT::loadMusic(byte *data, uint32 size) {
+ if (size < 8)
+ return false;
+
+ Common::SeekableReadStream *stream = new Common::MemoryReadStream(data, size, DisposeAfterUse::NO);
+
+ // Attempt to detect what format we have
+ bool result;
+ if (READ_BE_UINT32(data + 4) == MKTAG('m', 'u', 's', 'i'))
+ result = loadFromTune(stream);
+ else
+ result = loadFromContainerStream(stream);
+
+ if (!result) {
+ delete stream;
+ return false;
+ }
+
+ return true;
+}
+
+void MidiParser_QT::unloadMusic() {
+ MidiParser::unloadMusic();
+ close();
+
+ // Unlike those lesser formats, we *do* hold track data
+ for (uint i = 0; i < _trackInfo.size(); i++)
+ free(_trackInfo[i].data);
+
+ _trackInfo.clear();
+}
+
+bool MidiParser_QT::loadFromTune(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse) {
+ unloadMusic();
+
+ // a tune starts off with a sample description
+ stream->readUint32BE(); // header size
+
+ if (stream->readUint32BE() != MKTAG('m', 'u', 's', 'i'))
+ return false;
+
+ stream->readUint32BE(); // reserved
+ stream->readUint16BE(); // reserved
+ stream->readUint16BE(); // index
+
+ stream->readUint32BE(); // flags, ignore
+
+ MIDITrackInfo trackInfo;
+ trackInfo.size = stream->size() - stream->pos();
+ assert(trackInfo.size > 0);
+
+ trackInfo.data = (byte *)malloc(trackInfo.size);
+ stream->read(trackInfo.data, trackInfo.size);
+
+ trackInfo.timeScale = 600; // the default
+ _trackInfo.push_back(trackInfo);
+
+ initCommon();
+ return true;
+}
+
+bool MidiParser_QT::loadFromContainerStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse) {
+ unloadMusic();
+
+ if (!parseStream(stream, disposeAfterUse))
+ return false;
+
+ initFromContainerTracks();
+ return true;
+}
+
+bool MidiParser_QT::loadFromContainerFile(const Common::String &fileName) {
+ unloadMusic();
+
+ if (!parseFile(fileName))
+ return false;
+
+ initFromContainerTracks();
+ return true;
+}
+
+void MidiParser_QT::parseNextEvent(EventInfo &info) {
+ uint32 delta = 0;
+
+ while (_queuedEvents.empty())
+ delta += readNextEvent();
+
+ info = _queuedEvents.pop();
+ info.delta = delta;
+}
+
+uint32 MidiParser_QT::readNextEvent() {
+ if (_position._playPos >= _trackInfo[_activeTrack].data + _trackInfo[_activeTrack].size) {
+ // Manually insert end of track when we reach the end
+ EventInfo info;
+ info.event = 0xFF;
+ info.ext.type = 0x2F;
+ _queuedEvents.push(info);
+ return 0;
+ }
+
+ uint32 control = readUint32();
+
+ switch (control >> 28) {
+ case 0x0:
+ case 0x1:
+ // Rest
+ // We handle this by recursively adding up all the rests into the
+ // next event's delta
+ return readNextEvent() + (control & 0xFFFFFF);
+ case 0x2:
+ case 0x3:
+ // Note event
+ handleNoteEvent((control >> 24) & 0x1F, ((control >> 18) & 0x3F) + 32, (control >> 11) & 0x7F, control & 0x7FF);
+ break;
+ case 0x4:
+ case 0x5:
+ // Controller
+ handleControllerEvent((control >> 16) & 0xFF, (control >> 24) & 0x1F, (control >> 8) & 0xFF, control & 0xFF);
+ break;
+ case 0x6:
+ case 0x7:
+ // Marker
+ // Used for editing only, so we don't need to care about this
+ break;
+ case 0x9: {
+ // Extended note event
+ uint32 extra = readUint32();
+ handleNoteEvent((control >> 16) & 0xFFF, (control >> 8) & 0xFF, (extra >> 22) & 0x7F, extra & 0x3FFFFF);
+ break;
+ }
+ case 0xA: {
+ // Extended controller
+ uint32 extra = readUint32();
+ handleControllerEvent((extra >> 16) & 0x3FFF, (control >> 16) & 0xFFF, (extra >> 8) & 0xFF, extra & 0xFF);
+ break;
+ }
+ case 0xB:
+ // Knob
+ error("Encountered knob event in QuickTime MIDI");
+ break;
+ case 0x8:
+ case 0xC:
+ case 0xD:
+ case 0xE:
+ // Reserved
+ readUint32();
+ break;
+ case 0xF:
+ // General
+ handleGeneralEvent(control);
+ break;
+ }
+
+ return 0;
+}
+
+void MidiParser_QT::handleNoteEvent(uint32 part, byte pitch, byte velocity, uint32 length) {
+ byte channel = getChannel(part);
+
+ EventInfo info;
+ info.event = 0x90 | channel;
+ info.basic.param1 = pitch;
+ info.basic.param2 = velocity;
+ info.length = (velocity == 0) ? 0 : length;
+ _queuedEvents.push(info);
+}
+
+void MidiParser_QT::handleControllerEvent(uint32 control, uint32 part, byte intPart, byte fracPart) {
+ byte channel = getChannel(part);
+ EventInfo info;
+
+ if (control == 0) {
+ // "Bank select"
+ // QuickTime docs don't list this, but IHNM Mac calls this anyway
+ // We have to ignore this.
+ return;
+ } else if (control == 32) {
+ // Pitch bend
+ info.event = 0xE0 | channel;
+
+ // Actually an 8.8 fixed point number
+ int16 value = (int16)((intPart << 8) | fracPart);
+
+ if (value < -0x200 || value > 0x1FF) {
+ warning("QuickTime MIDI pitch bend value (%d) out of range, clipping", value);
+ value = CLIP<int16>(value, -0x200, 0x1FF);
+ }
+
+ // Now convert the value to 'normal' MIDI values
+ value += 0x200;
+ value *= 16;
+
+ // param1 holds the low 7 bits, param2 holds the high 7 bits
+ info.basic.param1 = value & 0x7F;
+ info.basic.param2 = value >> 7;
+
+ _partMap[part].pitchBend = value;
+ } else {
+ // Regular controller
+ info.event = 0xB0 | channel;
+ info.basic.param1 = control;
+ info.basic.param2 = intPart;
+
+ // TODO: Parse more controls to hold their status
+ switch (control) {
+ case 7:
+ _partMap[part].volume = intPart;
+ break;
+ case 10:
+ _partMap[part].pan = intPart;
+ break;
+ }
+ }
+
+ _queuedEvents.push(info);
+}
+
+void MidiParser_QT::handleGeneralEvent(uint32 control) {
+ uint32 part = (control >> 16) & 0xFFF;
+ uint32 dataSize = ((control & 0xFFFF) - 2) * 4;
+ byte subType = READ_BE_UINT16(_position._playPos + dataSize) & 0x3FFF;
+
+ switch (subType) {
+ case 1:
+ // Note Request
+ // Currently we're only using the GM number from the request
+ assert(dataSize == 84);
+
+ // We have to remap channels because GM needs percussion to be on the
+ // percussion channel but QuickTime can have that anywhere.
+ definePart(part, READ_BE_UINT32(_position._playPos + 80));
+ break;
+ case 5: // Tune Difference
+ case 8: // MIDI Channel
+ case 10: // No-op
+ case 11: // Used Notes
+ // Should be safe to skip these
+ break;
+ default:
+ warning("Unhandled general event %d", subType);
+ }
+
+ _position._playPos += dataSize + 4;
+}
+
+void MidiParser_QT::definePart(uint32 part, uint32 instrument) {
+ if (_partMap.contains(part))
+ warning("QuickTime MIDI part %d being redefined", part);
+
+ PartStatus partStatus;
+ partStatus.instrument = instrument;
+ partStatus.volume = 127;
+ partStatus.pan = 64;
+ partStatus.pitchBend = 0x2000;
+ _partMap[part] = partStatus;
+}
+
+byte MidiParser_QT::getChannel(uint32 part) {
+ // If we already mapped it, just go with it
+ if (!_channelMap.contains(part)) {
+ byte newChannel = findFreeChannel(part);
+ _channelMap[part] = newChannel;
+ setupPart(part);
+ }
+
+ return _channelMap[part];
+}
+
+byte MidiParser_QT::findFreeChannel(uint32 part) {
+ if (_partMap[part].instrument != 0x4001) {
+ // Normal Instrument -> First Free Channel
+ if (allChannelsAllocated())
+ deallocateFreeChannel();
+
+ for (int i = 0; i < 16; i++)
+ if (i != 9 && !isChannelAllocated(i)) // 9 is reserved for Percussion
+ return i;
+
+ // Can't actually get here
+ }
+
+ // Drum Kit -> Percussion Channel
+ deallocateChannel(9);
+ return 9;
+}
+
+void MidiParser_QT::deallocateFreeChannel() {
+ for (int i = 0; i < 16; i++) {
+ if (i != 9 && !_activeNotes[i]) {
+ // TODO: Improve this by looking for the channel with the longest
+ // time since the last note.
+ deallocateChannel(i);
+ return;
+ }
+ }
+
+ error("Exceeded QuickTime MIDI channel polyphony");
+}
+
+void MidiParser_QT::deallocateChannel(byte channel) {
+ for (ChannelMap::iterator it = _channelMap.begin(); it != _channelMap.end(); it++) {
+ if (it->_value == channel) {
+ _channelMap.erase(it);
+ return;
+ }
+ }
+}
+
+bool MidiParser_QT::isChannelAllocated(byte channel) const {
+ for (ChannelMap::const_iterator it = _channelMap.begin(); it != _channelMap.end(); it++)
+ if (it->_value == channel)
+ return true;
+
+ return false;
+}
+
+bool MidiParser_QT::allChannelsAllocated() const {
+ // Less than 15? We definitely have room
+ if (_channelMap.size() < 15)
+ return false;
+
+ // 15? One of the allocated channels might be the percussion one
+ if (_channelMap.size() == 15)
+ for (ChannelMap::const_iterator it = _channelMap.begin(); it != _channelMap.end(); it++)
+ if (it->_value == 9)
+ return false;
+
+ // 16 -> definitely all allocated
+ return true;
+}
+
+void MidiParser_QT::setupPart(uint32 part) {
+ PartStatus &status = _partMap[part];
+ byte channel = _channelMap[part];
+ EventInfo info;
+
+ // First, the program change
+ if (channel != 9) {
+ // 9 is always percussion
+ info.event = 0xC0 | channel;
+ info.basic.param1 = status.instrument;
+ _queuedEvents.push(info);
+ }
+
+ // Volume
+ info.event = 0xB0 | channel;
+ info.basic.param1 = 7;
+ info.basic.param2 = status.volume;
+ _queuedEvents.push(info);
+
+ // Pan
+ info.event = 0xB0 | channel;
+ info.basic.param1 = 10;
+ info.basic.param2 = status.pan;
+ _queuedEvents.push(info);
+
+ // Pitch Bend
+ info.event = 0xE0 | channel;
+ info.basic.param1 = status.pitchBend & 0x7F;
+ info.basic.param2 = status.pitchBend >> 7;
+ _queuedEvents.push(info);
+}
+
+void MidiParser_QT::resetTracking() {
+ MidiParser::resetTracking();
+ _channelMap.clear();
+ _queuedEvents.clear();
+ _partMap.clear();
+}
+
+Common::QuickTimeParser::SampleDesc *MidiParser_QT::readSampleDesc(Track *track, uint32 format, uint32 descSize) {
+ if (track->codecType == CODEC_TYPE_MIDI) {
+ debug(0, "MIDI Codec FourCC '%s'", tag2str(format));
+
+ _fd->readUint32BE(); // flags, ignore
+ descSize -= 4;
+
+ MIDISampleDesc *entry = new MIDISampleDesc(track, format);
+ entry->_requestSize = descSize;
+ entry->_requestData = (byte *)malloc(descSize);
+ _fd->read(entry->_requestData, descSize);
+ return entry;
+ }
+
+ return 0;
+}
+
+MidiParser_QT::MIDISampleDesc::MIDISampleDesc(Common::QuickTimeParser::Track *parentTrack, uint32 codecTag) :
+ Common::QuickTimeParser::SampleDesc(parentTrack, codecTag) {
+}
+
+void MidiParser_QT::initFromContainerTracks() {
+ const Common::Array<Common::QuickTimeParser::Track *> &tracks = Common::QuickTimeParser::_tracks;
+
+ for (uint32 i = 0; i < tracks.size(); i++) {
+ if (tracks[i]->codecType == CODEC_TYPE_MIDI) {
+ assert(tracks[i]->sampleDescs.size() == 1);
+
+ if (tracks[i]->editCount != 1)
+ warning("Unhandled QuickTime MIDI edit lists, things may go awry");
+
+ MIDITrackInfo trackInfo;
+ trackInfo.data = readWholeTrack(tracks[i], trackInfo.size);
+ trackInfo.timeScale = tracks[i]->timeScale;
+ _trackInfo.push_back(trackInfo);
+ }
+ }
+
+ initCommon();
+}
+
+void MidiParser_QT::initCommon() {
+ // Now we have all our info needed in _trackInfo from whatever container
+ // form, we can fill in the MidiParser tracks.
+
+ _numTracks = _trackInfo.size();
+ assert(_numTracks > 0);
+
+ for (uint32 i = 0; i < _trackInfo.size(); i++)
+ MidiParser::_tracks[i] = _trackInfo[i].data;
+
+ _ppqn = _trackInfo[0].timeScale;
+ resetTracking();
+ setTempo(1000000);
+ setTrack(0);
+}
+
+byte *MidiParser_QT::readWholeTrack(Common::QuickTimeParser::Track *track, uint32 &trackSize) {
+ // This just goes through all chunks and appends them together
+
+ Common::MemoryWriteStreamDynamic output;
+ uint32 curSample = 0;
+
+ // Read in the note request data first
+ MIDISampleDesc *entry = (MIDISampleDesc *)track->sampleDescs[0];
+ output.write(entry->_requestData, entry->_requestSize);
+
+ for (uint i = 0; i < track->chunkCount; i++) {
+ _fd->seek(track->chunkOffsets[i]);
+
+ uint32 sampleCount = 0;
+
+ for (uint32 j = 0; j < track->sampleToChunkCount; j++)
+ if (i >= track->sampleToChunk[j].first)
+ sampleCount = track->sampleToChunk[j].count;
+
+ for (uint32 j = 0; j < sampleCount; j++, curSample++) {
+ uint32 size = (track->sampleSize != 0) ? track->sampleSize : track->sampleSizes[curSample];
+
+ byte *data = new byte[size];
+ _fd->read(data, size);
+ output.write(data, size);
+ delete[] data;
+ }
+ }
+
+ trackSize = output.size();
+ return output.getData();
+}
+
+uint32 MidiParser_QT::readUint32() {
+ uint32 value = READ_BE_UINT32(_position._playPos);
+ _position._playPos += 4;
+ return value;
+}
+
+MidiParser *MidiParser::createParser_QT() {
+ return new MidiParser_QT();
+}
diff --git a/audio/midiparser_qt.h b/audio/midiparser_qt.h
new file mode 100644
index 0000000000..d6d0f40a48
--- /dev/null
+++ b/audio/midiparser_qt.h
@@ -0,0 +1,134 @@
+/* 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.
+ *
+ */
+
+#ifndef AUDIO_MIDIPARSER_QT_H
+#define AUDIO_MIDIPARSER_QT_H
+
+#include "audio/midiparser.h"
+#include "common/array.h"
+#include "common/hashmap.h"
+#include "common/queue.h"
+#include "common/quicktime.h"
+
+/**
+ * The QuickTime Music version of MidiParser.
+ *
+ * QuickTime Music is actually a superset of MIDI. It has its own custom
+ * instruments and supports more than 15 non-percussion channels. It also
+ * has custom control changes and a more advanced pitch bend (which we
+ * convert to GM pitch bend as best as possible). We then use the fallback
+ * GM instrument that each QuickTime instrument definition has to provide.
+ *
+ * Furthermore, Apple's documentation on this is terrible. You know
+ * documentation is bad when it contradicts itself three times on the same
+ * subject (like about setting the GM instrument field to percussion).
+ *
+ * This is as close to a proper QuickTime Music parser as we can currently
+ * implement using our MidiParser interface.
+ */
+class MidiParser_QT : public MidiParser, public Common::QuickTimeParser {
+public:
+ MidiParser_QT() {}
+ ~MidiParser_QT() {}
+
+ // MidiParser
+ bool loadMusic(byte *data, uint32 size);
+ void unloadMusic();
+
+ /**
+ * Load the MIDI from a 'Tune' resource
+ */
+ bool loadFromTune(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES);
+
+ /**
+ * Load the MIDI from a QuickTime stream
+ */
+ bool loadFromContainerStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES);
+
+ /**
+ * Load the MIDI from a QuickTime file
+ */
+ bool loadFromContainerFile(const Common::String &fileName);
+
+protected:
+ // MidiParser
+ void parseNextEvent(EventInfo &info);
+ void resetTracking();
+
+ // QuickTimeParser
+ SampleDesc *readSampleDesc(Track *track, uint32 format, uint32 descSize);
+
+private:
+ struct MIDITrackInfo {
+ byte *data;
+ uint32 size;
+ uint32 timeScale;
+ };
+
+ struct PartStatus {
+ uint32 instrument;
+ byte volume;
+ byte pan;
+ uint16 pitchBend;
+ };
+
+ class MIDISampleDesc : public SampleDesc {
+ public:
+ MIDISampleDesc(Common::QuickTimeParser::Track *parentTrack, uint32 codecTag);
+ ~MIDISampleDesc() {}
+
+ byte *_requestData;
+ uint32 _requestSize;
+ };
+
+ uint32 readNextEvent();
+ void handleGeneralEvent(uint32 control);
+ void handleControllerEvent(uint32 control, uint32 part, byte intPart, byte fracPart);
+ void handleNoteEvent(uint32 part, byte pitch, byte velocity, uint32 length);
+
+ void definePart(uint32 part, uint32 instrument);
+ void setupPart(uint32 part);
+
+ byte getChannel(uint32 part);
+ bool isChannelAllocated(byte channel) const;
+ byte findFreeChannel(uint32 part);
+ void deallocateFreeChannel();
+ void deallocateChannel(byte channel);
+ bool allChannelsAllocated() const;
+
+ byte *readWholeTrack(Common::QuickTimeParser::Track *track, uint32 &trackSize);
+
+ Common::Array<MIDITrackInfo> _trackInfo;
+ Common::Queue<EventInfo> _queuedEvents;
+
+ typedef Common::HashMap<uint, PartStatus> PartMap;
+ PartMap _partMap;
+
+ typedef Common::HashMap<uint, byte> ChannelMap;
+ ChannelMap _channelMap;
+
+ void initFromContainerTracks();
+ void initCommon();
+ uint32 readUint32();
+};
+
+#endif
diff --git a/audio/module.mk b/audio/module.mk
index e3aa0aaa81..4e1c031c83 100644
--- a/audio/module.mk
+++ b/audio/module.mk
@@ -4,6 +4,7 @@ MODULE_OBJS := \
audiostream.o \
fmopl.o \
mididrv.o \
+ midiparser_qt.o \
midiparser_smf.o \
midiparser_xmidi.o \
midiparser.o \
diff --git a/common/quicktime.cpp b/common/quicktime.cpp
index 173d3c6a97..5b3659b0d5 100644
--- a/common/quicktime.cpp
+++ b/common/quicktime.cpp
@@ -165,6 +165,8 @@ void QuickTimeParser::initParseTable() {
{ &QuickTimeParser::readWAVE, MKTAG('w', 'a', 'v', 'e') },
{ &QuickTimeParser::readESDS, MKTAG('e', 's', 'd', 's') },
{ &QuickTimeParser::readSMI, MKTAG('S', 'M', 'I', ' ') },
+ { &QuickTimeParser::readDefault, MKTAG('g', 'm', 'h', 'd') },
+ { &QuickTimeParser::readLeaf, MKTAG('g', 'm', 'i', 'n') },
{ 0, 0 }
};
@@ -477,6 +479,8 @@ int QuickTimeParser::readHDLR(Atom atom) {
track->codecType = CODEC_TYPE_VIDEO;
else if (type == MKTAG('s', 'o', 'u', 'n'))
track->codecType = CODEC_TYPE_AUDIO;
+ else if (type == MKTAG('m', 'u', 's', 'i'))
+ track->codecType = CODEC_TYPE_MIDI;
_fd->readUint32BE(); // component manufacture
_fd->readUint32BE(); // component flags
@@ -540,7 +544,7 @@ int QuickTimeParser::readSTSD(Atom atom) {
_fd->readUint16BE(); // reserved
_fd->readUint16BE(); // index
- track->sampleDescs[i] = readSampleDesc(track, format);
+ track->sampleDescs[i] = readSampleDesc(track, format, size - 16);
debug(0, "size=%d 4CC= %s codec_type=%d", size, tag2str(format), track->codecType);
diff --git a/common/quicktime.h b/common/quicktime.h
index 641718e13a..caa92578b1 100644
--- a/common/quicktime.h
+++ b/common/quicktime.h
@@ -120,7 +120,8 @@ protected:
enum CodecType {
CODEC_TYPE_MOV_OTHER,
CODEC_TYPE_VIDEO,
- CODEC_TYPE_AUDIO
+ CODEC_TYPE_AUDIO,
+ CODEC_TYPE_MIDI
};
struct Track {
@@ -161,7 +162,7 @@ protected:
byte objectTypeMP4;
};
- virtual SampleDesc *readSampleDesc(Track *track, uint32 format) = 0;
+ virtual SampleDesc *readSampleDesc(Track *track, uint32 format, uint32 descSize) = 0;
uint32 _timeScale;
uint32 _duration;
diff --git a/engines/groovie/detection.cpp b/engines/groovie/detection.cpp
index 895686b5e0..e06dace0d7 100644
--- a/engines/groovie/detection.cpp
+++ b/engines/groovie/detection.cpp
@@ -137,6 +137,36 @@ static const GroovieGameDescription gameDescriptions[] = {
kGroovieV2, 1
},
+ // The 11th Hour Macintosh English
+ {
+ {
+ "11h", "",
+ {
+ { "disk.1", 0, "5c0428cd3659fc7bbcd0aa16485ed5da", 227 },
+ { "The 11th Hour Installer", 0, "bcdb4040b27f15b18f39fb9e496d384a", 1002987 },
+ { 0, 0, 0, 0 }
+ },
+ Common::EN_ANY, Common::kPlatformMacintosh, ADGF_UNSTABLE,
+ GUIO4(GUIO_MIDIADLIB, GUIO_MIDIMT32, GUIO_MIDIGM, GUIO_NOASPECT)
+ },
+ kGroovieV2, 1
+ },
+
+ // The 11th Hour Macintosh English (Installed)
+ {
+ {
+ "11h", "Installed",
+ {
+ { "disk.1", 0, "5c0428cd3659fc7bbcd0aa16485ed5da", 227 },
+ { "el01.mov", 0, "70f42dfc25b1488a08011dc45bb5145d", 6039 },
+ { 0, 0, 0, 0 }
+ },
+ Common::EN_ANY, Common::kPlatformMacintosh, ADGF_UNSTABLE,
+ GUIO4(GUIO_MIDIADLIB, GUIO_MIDIMT32, GUIO_MIDIGM, GUIO_NOASPECT)
+ },
+ kGroovieV2, 1
+ },
+
// The 11th Hour DOS Demo English
{
{
@@ -159,6 +189,36 @@ static const GroovieGameDescription gameDescriptions[] = {
kGroovieV2, 2
},
+ // The Making of The 11th Hour Macintosh English
+ {
+ {
+ "11h", "Making Of",
+ {
+ { "disk.1", 0, "5c0428cd3659fc7bbcd0aa16485ed5da", 227 },
+ { "The 11th Hour Installer", 0, "bcdb4040b27f15b18f39fb9e496d384a", 1002987 },
+ { 0, 0, 0, 0 }
+ },
+ Common::EN_ANY, Common::kPlatformMacintosh, ADGF_UNSTABLE,
+ GUIO4(GUIO_MIDIADLIB, GUIO_MIDIMT32, GUIO_MIDIGM, GUIO_NOASPECT)
+ },
+ kGroovieV2, 2
+ },
+
+ // The Making of The 11th Hour Macintosh English (Installed)
+ {
+ {
+ "11h", "Making Of (Installed)",
+ {
+ { "disk.1", 0, "5c0428cd3659fc7bbcd0aa16485ed5da", 227 },
+ { "el01.mov", 0, "70f42dfc25b1488a08011dc45bb5145d", 6039 },
+ { 0, 0, 0, 0 }
+ },
+ Common::EN_ANY, Common::kPlatformMacintosh, ADGF_UNSTABLE,
+ GUIO4(GUIO_MIDIADLIB, GUIO_MIDIMT32, GUIO_MIDIGM, GUIO_NOASPECT)
+ },
+ kGroovieV2, 2
+ },
+
// Clandestiny Trailer DOS English
{
{
@@ -170,6 +230,36 @@ static const GroovieGameDescription gameDescriptions[] = {
kGroovieV2, 3
},
+ // Clandestiny Trailer Macintosh English
+ {
+ {
+ "clandestiny", "Trailer",
+ {
+ { "disk.1", 0, "5c0428cd3659fc7bbcd0aa16485ed5da", 227 },
+ { "The 11th Hour Installer", 0, "bcdb4040b27f15b18f39fb9e496d384a", 1002987 },
+ { 0, 0, 0, 0 }
+ },
+ Common::EN_ANY, Common::kPlatformMacintosh, ADGF_UNSTABLE,
+ GUIO4(GUIO_MIDIADLIB, GUIO_MIDIMT32, GUIO_MIDIGM, GUIO_NOASPECT)
+ },
+ kGroovieV2, 3
+ },
+
+ // Clandestiny Trailer Macintosh English (Installed)
+ {
+ {
+ "clandestiny", "Trailer (Installed)",
+ {
+ { "disk.1", 0, "5c0428cd3659fc7bbcd0aa16485ed5da", 227 },
+ { "el01.mov", 0, "70f42dfc25b1488a08011dc45bb5145d", 6039 },
+ { 0, 0, 0, 0 }
+ },
+ Common::EN_ANY, Common::kPlatformMacintosh, ADGF_UNSTABLE,
+ GUIO4(GUIO_MIDIADLIB, GUIO_MIDIMT32, GUIO_MIDIGM, GUIO_NOASPECT)
+ },
+ kGroovieV2, 3
+ },
+
// Clandestiny DOS English
{
{
@@ -207,6 +297,11 @@ static const GroovieGameDescription gameDescriptions[] = {
{AD_TABLE_END_MARKER, kGroovieT7G, 0}
};
+static const char *directoryGlobs[] = {
+ "MIDI",
+ 0
+};
+
class GroovieMetaEngine : public AdvancedMetaEngine {
public:
GroovieMetaEngine() : AdvancedMetaEngine(gameDescriptions, sizeof(GroovieGameDescription), groovieGames) {
@@ -222,6 +317,10 @@ public:
// replaced with an according explanation.
_flags = kADFlagUseExtraAsHint;
_guioptions = GUIO3(GUIO_NOSUBTITLES, GUIO_NOSFX, GUIO_NOASPECT);
+
+ // Need MIDI directory to detect 11H Mac Installed
+ _maxScanDepth = 2;
+ _directoryGlobs = directoryGlobs;
}
const char *getName() const {
diff --git a/engines/groovie/groovie.cpp b/engines/groovie/groovie.cpp
index 726e7cbede..16358bfa28 100644
--- a/engines/groovie/groovie.cpp
+++ b/engines/groovie/groovie.cpp
@@ -30,6 +30,7 @@
#include "groovie/music.h"
#include "groovie/resource.h"
#include "groovie/roq.h"
+#include "groovie/stuffit.h"
#include "groovie/vdx.h"
#include "common/config-manager.h"
@@ -56,6 +57,7 @@ GroovieEngine::GroovieEngine(OSystem *syst, const GroovieGameDescription *gd) :
SearchMan.addSubDirectoryMatching(gameDataDir, "groovie");
SearchMan.addSubDirectoryMatching(gameDataDir, "media");
SearchMan.addSubDirectoryMatching(gameDataDir, "system");
+ SearchMan.addSubDirectoryMatching(gameDataDir, "MIDI");
_modeSpeed = kGroovieSpeedNormal;
if (ConfMan.hasKey("t7g_speed")) {
@@ -93,6 +95,15 @@ GroovieEngine::~GroovieEngine() {
}
Common::Error GroovieEngine::run() {
+ if (_gameDescription->version == kGroovieV2 && getPlatform() == Common::kPlatformMacintosh) {
+ // Load the Mac installer with the lowest priority (in case the user has installed
+ // the game and has the MIDI folder present; faster to just load them)
+ Common::Archive *archive = createStuffItArchive("The 11th Hour Installer");
+
+ if (archive)
+ SearchMan.add("The 11th Hour Installer", archive);
+ }
+
_script = new Script(this, _gameDescription->version);
// Initialize the graphics
@@ -160,10 +171,10 @@ Common::Error GroovieEngine::run() {
// Create the music player
switch (getPlatform()) {
case Common::kPlatformMacintosh:
- // TODO: The 11th Hour Mac uses QuickTime MIDI files
- // Right now, since the XMIDI are present and it is still detected as
- // the DOS version, we don't have to do anything here.
- _musicPlayer = new MusicPlayerMac(this);
+ if (_gameDescription->version == kGroovieT7G)
+ _musicPlayer = new MusicPlayerMac_t7g(this);
+ else
+ _musicPlayer = new MusicPlayerMac_v2(this);
break;
case Common::kPlatformIOS:
_musicPlayer = new MusicPlayerIOS(this);
diff --git a/engines/groovie/module.mk b/engines/groovie/module.mk
index 1e89ff66f5..b47eed912b 100644
--- a/engines/groovie/module.mk
+++ b/engines/groovie/module.mk
@@ -15,6 +15,7 @@ MODULE_OBJS := \
roq.o \
saveload.o \
script.o \
+ stuffit.o \
vdx.o
# This module can be built as a plugin
diff --git a/engines/groovie/music.cpp b/engines/groovie/music.cpp
index af929d439b..95637fc407 100644
--- a/engines/groovie/music.cpp
+++ b/engines/groovie/music.cpp
@@ -678,9 +678,9 @@ void MusicPlayerXMI::setTimbreMT(byte channel, const Timbre &timbre) {
}
-// MusicPlayerMac
+// MusicPlayerMac_t7g
-MusicPlayerMac::MusicPlayerMac(GroovieEngine *vm) : MusicPlayerMidi(vm) {
+MusicPlayerMac_t7g::MusicPlayerMac_t7g(GroovieEngine *vm) : MusicPlayerMidi(vm) {
// Create the parser
_midiParser = MidiParser::createParser_SMF();
@@ -701,7 +701,7 @@ MusicPlayerMac::MusicPlayerMac(GroovieEngine *vm) : MusicPlayerMidi(vm) {
assert(_vm->_macResFork);
}
-bool MusicPlayerMac::load(uint32 fileref, bool loop) {
+bool MusicPlayerMac_t7g::load(uint32 fileref, bool loop) {
debugC(1, kGroovieDebugMIDI | kGroovieDebugAll, "Groovie::Music: Starting the playback of song: %04X", fileref);
// First try for compressed MIDI
@@ -722,7 +722,7 @@ bool MusicPlayerMac::load(uint32 fileref, bool loop) {
return loadParser(file, loop);
}
-Common::SeekableReadStream *MusicPlayerMac::decompressMidi(Common::SeekableReadStream *stream) {
+Common::SeekableReadStream *MusicPlayerMac_t7g::decompressMidi(Common::SeekableReadStream *stream) {
// Initialize an output buffer of the given size
uint32 size = stream->readUint32BE();
byte *output = (byte *)malloc(size);
@@ -768,6 +768,52 @@ Common::SeekableReadStream *MusicPlayerMac::decompressMidi(Common::SeekableReadS
return new Common::MemoryReadStream(output, size, DisposeAfterUse::YES);
}
+// MusicPlayerMac_v2
+
+MusicPlayerMac_v2::MusicPlayerMac_v2(GroovieEngine *vm) : MusicPlayerMidi(vm) {
+ // Create the parser
+ _midiParser = MidiParser::createParser_QT();
+
+ // Create the driver
+ MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI | MDT_ADLIB | MDT_PREFER_GM);
+ _driver = MidiDriver::createMidi(dev);
+ assert(_driver);
+
+ _driver->open(); // TODO: Handle return value != 0 (indicating an error)
+
+ // Set the parser's driver
+ _midiParser->setMidiDriver(this);
+
+ // Set the timer rate
+ _midiParser->setTimerRate(_driver->getBaseTempo());
+}
+
+bool MusicPlayerMac_v2::load(uint32 fileref, bool loop) {
+ debugC(1, kGroovieDebugMIDI | kGroovieDebugAll, "Groovie::Music: Starting the playback of song: %04X", fileref);
+
+ // Find correct filename
+ ResInfo info;
+ _vm->_resMan->getResInfo(fileref, info);
+ uint len = info.filename.size();
+ if (len < 4)
+ return false; // This shouldn't actually occur
+
+ // Remove the extension and add ".mov"
+ info.filename.deleteLastChar();
+ info.filename.deleteLastChar();
+ info.filename.deleteLastChar();
+ info.filename += "mov";
+
+ Common::SeekableReadStream *file = SearchMan.createReadStreamForMember(info.filename);
+
+ if (!file) {
+ warning("Could not find file '%s'", info.filename.c_str());
+ return false;
+ }
+
+ return loadParser(file, loop);
+}
+
MusicPlayerIOS::MusicPlayerIOS(GroovieEngine *vm) : MusicPlayer(vm) {
vm->getTimerManager()->installTimerProc(&onTimer, 50 * 1000, this, "groovieMusic");
}
diff --git a/engines/groovie/music.h b/engines/groovie/music.h
index cc852aa8dc..92e9c8b487 100644
--- a/engines/groovie/music.h
+++ b/engines/groovie/music.h
@@ -150,9 +150,9 @@ private:
void setTimbreMT(byte channel, const Timbre &timbre);
};
-class MusicPlayerMac : public MusicPlayerMidi {
+class MusicPlayerMac_t7g : public MusicPlayerMidi {
public:
- MusicPlayerMac(GroovieEngine *vm);
+ MusicPlayerMac_t7g(GroovieEngine *vm);
protected:
bool load(uint32 fileref, bool loop);
@@ -161,6 +161,14 @@ private:
Common::SeekableReadStream *decompressMidi(Common::SeekableReadStream *stream);
};
+class MusicPlayerMac_v2 : public MusicPlayerMidi {
+public:
+ MusicPlayerMac_v2(GroovieEngine *vm);
+
+protected:
+ bool load(uint32 fileref, bool loop);
+};
+
class MusicPlayerIOS : public MusicPlayer {
public:
MusicPlayerIOS(GroovieEngine *vm);
diff --git a/engines/groovie/stuffit.cpp b/engines/groovie/stuffit.cpp
new file mode 100644
index 0000000000..37f12585e7
--- /dev/null
+++ b/engines/groovie/stuffit.cpp
@@ -0,0 +1,537 @@
+/* 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.
+ *
+ */
+
+// Based on the StuffIt code in ResidualVM
+// StuffIt parsing based on http://code.google.com/p/theunarchiver/wiki/StuffItFormat
+// Compression 14 based on libxad (http://sourceforge.net/projects/libxad/)
+
+#include "groovie/stuffit.h"
+
+#include "common/archive.h"
+#include "common/bitstream.h"
+#include "common/debug.h"
+#include "common/hash-str.h"
+#include "common/hashmap.h"
+#include "common/memstream.h"
+#include "common/substream.h"
+
+namespace Groovie {
+
+struct SIT14Data;
+
+class StuffItArchive : public Common::Archive {
+public:
+ StuffItArchive();
+ ~StuffItArchive();
+
+ bool open(const Common::String &filename);
+ void close();
+ bool isOpen() const { return _stream != 0; }
+
+ // Common::Archive API implementation
+ bool hasFile(const Common::String &name) const;
+ int listMembers(Common::ArchiveMemberList &list) const;
+ const Common::ArchiveMemberPtr getMember(const Common::String &name) const;
+ Common::SeekableReadStream *createReadStreamForMember(const Common::String &name) const;
+
+private:
+ struct FileEntry {
+ byte compression;
+ uint32 uncompressedSize;
+ uint32 compressedSize;
+ uint32 offset;
+ };
+
+ Common::SeekableReadStream *_stream;
+
+ typedef Common::HashMap<Common::String, FileEntry, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> FileMap;
+ FileMap _map;
+
+ // Decompression Functions
+ Common::SeekableReadStream *decompress14(Common::SeekableReadStream *src, uint32 uncompressedSize) const;
+
+ // Decompression Helpers
+ void update14(uint16 first, uint16 last, byte *code, uint16 *freq) const;
+ void readTree14(Common::BitStream *bits, SIT14Data *dat, uint16 codesize, uint16 *result) const;
+};
+
+StuffItArchive::StuffItArchive() : Common::Archive() {
+ _stream = 0;
+}
+
+StuffItArchive::~StuffItArchive() {
+ close();
+}
+
+// Some known values of StuffIt FourCC's
+// 11H Mac in particular uses ST46
+static const uint32 s_magicNumbers[] = {
+ MKTAG('S', 'I', 'T', '!'), MKTAG('S', 'T', '6', '5'), MKTAG('S', 'T', '5', '0'),
+ MKTAG('S', 'T', '6', '0'), MKTAG('S', 'T', 'i', 'n'), MKTAG('S', 'T', 'i', '2'),
+ MKTAG('S', 'T', 'i', '3'), MKTAG('S', 'T', 'i', '4'), MKTAG('S', 'T', '4', '6')
+};
+
+bool StuffItArchive::open(const Common::String &filename) {
+ close();
+
+ _stream = SearchMan.createReadStreamForMember(filename);
+
+ if (!_stream)
+ return false;
+
+ uint32 tag = _stream->readUint32BE();
+
+ // Check all the possible FourCC's
+ bool found = false;
+ for (int i = 0; i < ARRAYSIZE(s_magicNumbers); i++) {
+ if (tag == s_magicNumbers[i]) {
+ found = true;
+ break;
+ }
+ }
+
+ // Didn't find one, let's bail out
+ if (!found) {
+ close();
+ return false;
+ }
+
+ /* uint16 fileCount = */ _stream->readUint16BE();
+ /* uint32 archiveSize = */ _stream->readUint32BE();
+
+ // Some sort of second magic number
+ if (_stream->readUint32BE() != MKTAG('r', 'L', 'a', 'u')) {
+ close();
+ return false;
+ }
+
+ /* byte version = */ _stream->readByte(); // meaning not clear
+
+ _stream->skip(7); // unknown
+
+ while (_stream->pos() < _stream->size() && !_stream->eos()) {
+ byte resForkCompression = _stream->readByte();
+ byte dataForkCompression = _stream->readByte();
+
+ byte fileNameLength = _stream->readByte();
+ Common::String name;
+
+ for (byte i = 0; i < fileNameLength; i++)
+ name += (char)_stream->readByte();
+
+ // Skip remaining bytes
+ _stream->skip(63 - fileNameLength);
+
+ /* uint32 fileType = */ _stream->readUint32BE();
+ /* uint32 fileCreator = */ _stream->readUint32BE();
+ /* uint16 finderFlags = */ _stream->readUint16BE();
+ /* uint32 creationDate = */ _stream->readUint32BE();
+ /* uint32 modificationDate = */ _stream->readUint32BE();
+ uint32 resForkUncompressedSize = _stream->readUint32BE();
+ uint32 dataForkUncompressedSize = _stream->readUint32BE();
+ uint32 resForkCompressedSize = _stream->readUint32BE();
+ uint32 dataForkCompressedSize = _stream->readUint32BE();
+ /* uint16 resForkCRC = */ _stream->readUint16BE();
+ /* uint16 dataForkCRC = */ _stream->readUint16BE();
+ _stream->skip(6); // unknown
+ /* uint16 headerCRC = */ _stream->readUint16BE();
+
+ // Ignore directories for now
+ if (dataForkCompression == 32 || dataForkCompression == 33)
+ continue;
+
+ if (dataForkUncompressedSize != 0) {
+ // We have a data fork
+
+ FileEntry entry;
+ entry.compression = dataForkCompression;
+ entry.uncompressedSize = dataForkUncompressedSize;
+ entry.compressedSize = dataForkCompressedSize;
+ entry.offset = _stream->pos() + resForkCompressedSize;
+ _map[name] = entry;
+
+ debug(0, "StuffIt file '%s', Compression = %d", name.c_str(), entry.compression);
+ }
+
+ if (resForkUncompressedSize != 0) {
+ // We have a resource fork
+
+ // Add a .rsrc extension so we know it's the resource fork
+ name += ".rsrc";
+
+ FileEntry entry;
+ entry.compression = resForkCompression;
+ entry.uncompressedSize = resForkUncompressedSize;
+ entry.compressedSize = resForkCompressedSize;
+ entry.offset = _stream->pos();
+ _map[name] = entry;
+
+ debug(0, "StuffIt file '%s', Compression = %d", name.c_str(), entry.compression);
+ }
+
+ // Go to the next entry
+ _stream->skip(dataForkCompressedSize + resForkCompressedSize);
+ }
+
+ return true;
+}
+
+void StuffItArchive::close() {
+ delete _stream; _stream = 0;
+ _map.clear();
+}
+
+bool StuffItArchive::hasFile(const Common::String &name) const {
+ return _map.contains(name);
+}
+
+int StuffItArchive::listMembers(Common::ArchiveMemberList &list) const {
+ for (FileMap::const_iterator it = _map.begin(); it != _map.end(); it++)
+ list.push_back(getMember(it->_key));
+
+ return _map.size();
+}
+
+const Common::ArchiveMemberPtr StuffItArchive::getMember(const Common::String &name) const {
+ return Common::ArchiveMemberPtr(new Common::GenericArchiveMember(name, this));
+}
+
+Common::SeekableReadStream *StuffItArchive::createReadStreamForMember(const Common::String &name) const {
+ if (!_stream || !_map.contains(name))
+ return 0;
+
+ const FileEntry &entry = _map[name];
+
+ if (entry.compression & 0xF0)
+ error("Unhandled StuffIt encryption");
+
+ Common::SeekableSubReadStream subStream(_stream, entry.offset, entry.offset + entry.compressedSize);
+
+ // We currently only support type 14 compression
+ switch (entry.compression) {
+ case 0: // Uncompressed
+ return subStream.readStream(subStream.size());
+ case 14: // Installer
+ return decompress14(&subStream, entry.uncompressedSize);
+ default:
+ error("Unhandled StuffIt compression %d", entry.compression);
+ }
+
+ return 0;
+}
+
+void StuffItArchive::update14(uint16 first, uint16 last, byte *code, uint16 *freq) const {
+ uint16 i, j;
+
+ while (last - first > 1) {
+ i = first;
+ j = last;
+
+ do {
+ while (++i < last && code[first] > code[i])
+ ;
+
+ while (--j > first && code[first] < code[j])
+ ;
+
+ if (j > i) {
+ SWAP(code[i], code[j]);
+ SWAP(freq[i], freq[j]);
+ }
+ } while (j > i);
+
+ if (first != j) {
+ SWAP(code[first], code[j]);
+ SWAP(freq[first], freq[j]);
+
+ i = j + 1;
+
+ if (last - i <= j - first) {
+ update14(i, last, code, freq);
+ last = j;
+ } else {
+ update14(first, j, code, freq);
+ first = i;
+ }
+ } else {
+ ++first;
+ }
+ }
+}
+
+struct SIT14Data {
+ byte code[308];
+ byte codecopy[308];
+ uint16 freq[308];
+ uint32 buff[308];
+
+ byte var1[52];
+ uint16 var2[52];
+ uint16 var3[75 * 2];
+
+ byte var4[76];
+ uint32 var5[75];
+ byte var6[1024];
+ uint16 var7[308 * 2];
+ byte var8[0x4000];
+
+ byte window[0x40000];
+};
+
+// Realign to a byte boundary
+#define ALIGN_BITS(b) \
+ if (b->pos() & 7) \
+ b->skip(8 - (b->pos() & 7))
+
+void StuffItArchive::readTree14(Common::BitStream *bits, SIT14Data *dat, uint16 codesize, uint16 *result) const {
+ uint32 i, l, n;
+ uint32 k = bits->getBit();
+ uint32 j = bits->getBits(2) + 2;
+ uint32 o = bits->getBits(3) + 1;
+ uint32 size = 1 << j;
+ uint32 m = size - 1;
+ k = k ? (m - 1) : 0xFFFFFFFF;
+
+ if (bits->getBits(2) & 1) { // skip 1 bit!
+ // requirements for this call: dat->buff[32], dat->code[32], dat->freq[32*2]
+ readTree14(bits, dat, size, dat->freq);
+
+ for (i = 0; i < codesize; ) {
+ l = 0;
+
+ do {
+ l = dat->freq[l + bits->getBit()];
+ n = size << 1;
+ } while (n > l);
+
+ l -= n;
+
+ if (k != l) {
+ if (l == m) {
+ l = 0;
+
+ do {
+ l = dat->freq[l + bits->getBit()];
+ n = size << 1;
+ } while (n > l);
+
+ l += 3 - n;
+
+ while (l--) {
+ dat->code[i] = dat->code[i - 1];
+ ++i;
+ }
+ } else {
+ dat->code[i++] = l + o;
+ }
+ } else {
+ dat->code[i++] = 0;
+ }
+ }
+ } else {
+ for (i = 0; i < codesize; ) {
+ l = bits->getBits(j);
+
+ if (k != l) {
+ if (l == m) {
+ l = bits->getBits(j) + 3;
+
+ while (l--) {
+ dat->code[i] = dat->code[i - 1];
+ ++i;
+ }
+ } else {
+ dat->code[i++] = l+o;
+ }
+ } else {
+ dat->code[i++] = 0;
+ }
+ }
+ }
+
+ for (i = 0; i < codesize; ++i) {
+ dat->codecopy[i] = dat->code[i];
+ dat->freq[i] = i;
+ }
+
+ update14(0, codesize, dat->codecopy, dat->freq);
+
+ for (i = 0; i < codesize && !dat->codecopy[i]; ++i)
+ ; // find first nonempty
+
+ for (j = 0; i < codesize; ++i, ++j) {
+ if (i)
+ j <<= (dat->codecopy[i] - dat->codecopy[i - 1]);
+
+ k = dat->codecopy[i]; m = 0;
+
+ for (l = j; k--; l >>= 1)
+ m = (m << 1) | (l & 1);
+
+ dat->buff[dat->freq[i]] = m;
+ }
+
+ for (i = 0; i < (uint32)codesize * 2; ++i)
+ result[i] = 0;
+
+ j = 2;
+
+ for (i = 0; i < codesize; ++i) {
+ l = 0;
+ m = dat->buff[i];
+
+ for (k = 0; k < dat->code[i]; ++k) {
+ l += (m & 1);
+
+ if (dat->code[i] - 1 <= (int32)k) {
+ result[l] = codesize * 2 + i;
+ } else {
+ if (!result[l]) {
+ result[l] = j;
+ j += 2;
+ }
+
+ l = result[l];
+ }
+
+ m >>= 1;
+ }
+ }
+
+ ALIGN_BITS(bits);
+}
+
+#define OUTPUT_VAL(x) \
+ out.writeByte(x); \
+ dat->window[j++] = x; \
+ j &= 0x3FFFF
+
+Common::SeekableReadStream *StuffItArchive::decompress14(Common::SeekableReadStream *src, uint32 uncompressedSize) const {
+ byte *dst = (byte *)malloc(uncompressedSize);
+ Common::MemoryWriteStream out(dst, uncompressedSize);
+
+ Common::BitStream *bits = new Common::BitStream8LSB(src);
+
+ uint32 i, j, k, l, m, n;
+
+ SIT14Data *dat = new SIT14Data();
+
+ // initialization
+ for (i = k = 0; i < 52; ++i) {
+ dat->var2[i] = k;
+ k += (1 << (dat->var1[i] = ((i >= 4) ? ((i - 4) >> 2) : 0)));
+ }
+
+ for (i = 0; i < 4; ++i)
+ dat->var8[i] = i;
+
+ for (m = 1, l = 4; i < 0x4000; m <<= 1) // i is 4
+ for (n = l+4; l < n; ++l)
+ for (j = 0; j < m; ++j)
+ dat->var8[i++] = l;
+
+ for (i = 0, k = 1; i < 75; ++i) {
+ dat->var5[i] = k;
+ k += (1 << (dat->var4[i] = (i >= 3 ? ((i - 3) >> 2) : 0)));
+ }
+
+ for (i = 0; i < 4; ++i)
+ dat->var6[i] = i - 1;
+
+ for (m = 1, l = 3; i < 0x400; m <<= 1) // i is 4
+ for (n = l + 4; l < n; ++l)
+ for (j = 0; j < m; ++j)
+ dat->var6[i++] = l;
+
+ m = bits->getBits(16); // number of blocks
+ j = 0; // window position
+
+ while (m-- && !bits->eos()) {
+ bits->getBits(16); // skip crunched block size
+ bits->getBits(16);
+ n = bits->getBits(16); // number of uncrunched bytes
+ n |= bits->getBits(16) << 16;
+ readTree14(bits, dat, 308, dat->var7);
+ readTree14(bits, dat, 75, dat->var3);
+
+ while (n && !bits->eos()) {
+ for (i = 0; i < 616;)
+ i = dat->var7[i + bits->getBit()];
+
+ i -= 616;
+
+ if (i < 0x100) {
+ OUTPUT_VAL(i);
+ --n;
+ } else {
+ i -= 0x100;
+ k = dat->var2[i]+4;
+ i = dat->var1[i];
+
+ if (i)
+ k += bits->getBits(i);
+
+ for (i = 0; i < 150;)
+ i = dat->var3[i + bits->getBit()];
+
+ i -= 150;
+ l = dat->var5[i];
+ i = dat->var4[i];
+
+ if (i)
+ l += bits->getBits(i);
+
+ n -= k;
+ l = j + 0x40000 - l;
+
+ while (k--) {
+ l &= 0x3FFFF;
+ OUTPUT_VAL(dat->window[l]);
+ l++;
+ }
+ }
+ }
+
+ ALIGN_BITS(bits);
+ }
+
+ delete dat;
+ delete bits;
+
+ return new Common::MemoryReadStream(dst, uncompressedSize, DisposeAfterUse::YES);
+}
+
+#undef OUTPUT_VAL
+#undef ALIGN_BITS
+
+Common::Archive *createStuffItArchive(const Common::String &fileName) {
+ StuffItArchive *archive = new StuffItArchive();
+
+ if (!archive->open(fileName)) {
+ delete archive;
+ return 0;
+ }
+
+ return archive;
+}
+
+} // End of namespace Groovie
diff --git a/engines/groovie/stuffit.h b/engines/groovie/stuffit.h
new file mode 100644
index 0000000000..44f593dbea
--- /dev/null
+++ b/engines/groovie/stuffit.h
@@ -0,0 +1,43 @@
+/* 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.
+ *
+ */
+
+#ifndef GROOVIE_STUFFIT_H
+#define GROOVIE_STUFFIT_H
+
+namespace Common {
+class Archive;
+class String;
+}
+
+namespace Groovie {
+
+/**
+ * This factory method creates an Archive instance corresponding to the content
+ * of the StuffIt compressed file.
+ *
+ * May return 0 in case of a failure.
+ */
+Common::Archive *createStuffItArchive(const Common::String &fileName);
+
+} // End of namespace Groovie
+
+#endif
diff --git a/engines/saga/music.cpp b/engines/saga/music.cpp
index 13850a0b6d..0eebf3f175 100644
--- a/engines/saga/music.cpp
+++ b/engines/saga/music.cpp
@@ -30,6 +30,7 @@
#include "audio/audiostream.h"
#include "audio/mididrv.h"
#include "audio/midiparser.h"
+#include "audio/midiparser_qt.h"
#include "audio/decoders/raw.h"
#include "common/config-manager.h"
#include "common/file.h"
@@ -76,19 +77,14 @@ void MusicDriver::play(SagaEngine *vm, ByteArray *buffer, bool loop) {
}
// Check if the game is using XMIDI or SMF music
- if (vm->getGameId() == GID_IHNM && vm->isMacResources()) {
- // Just set an XMIDI parser for Mac IHNM for now
+ if (!memcmp(buffer->getBuffer(), "FORM", 4)) {
_parser = MidiParser::createParser_XMIDI();
+ // ITE had MT32 mapped instruments
+ _isGM = (vm->getGameId() != GID_ITE);
} else {
- if (!memcmp(buffer->getBuffer(), "FORM", 4)) {
- _parser = MidiParser::createParser_XMIDI();
- // ITE had MT32 mapped instruments
- _isGM = (vm->getGameId() != GID_ITE);
- } else {
- _parser = MidiParser::createParser_SMF();
- // ITE with standalone MIDI files is General MIDI
- _isGM = (vm->getGameId() == GID_ITE);
- }
+ _parser = MidiParser::createParser_SMF();
+ // ITE with standalone MIDI files is General MIDI
+ _isGM = (vm->getGameId() == GID_ITE);
}
if (!_parser->loadMusic(buffer->getBuffer(), buffer->size()))
@@ -107,6 +103,27 @@ void MusicDriver::play(SagaEngine *vm, ByteArray *buffer, bool loop) {
_isPlaying = true;
}
+void MusicDriver::playQuickTime(const Common::String &musicName, bool loop) {
+ // IHNM Mac uses QuickTime MIDI
+ _parser = MidiParser::createParser_QT();
+ _isGM = true;
+
+ if (!((MidiParser_QT *)_parser)->loadFromContainerFile(musicName))
+ error("MusicDriver::playQuickTime(): Failed to load file '%s'", musicName.c_str());
+
+ _parser->setTrack(0);
+ _parser->setMidiDriver(this);
+ _parser->setTimerRate(_driver->getBaseTempo());
+ _parser->property(MidiParser::mpCenterPitchWheelOnUnload, 1);
+ _parser->property(MidiParser::mpSendSustainOffOnNotesOff, 1);
+
+ // Handle music looping
+ _parser->property(MidiParser::mpAutoLoop, loop);
+// _isLooping = loop;
+
+ _isPlaying = true;
+}
+
void MusicDriver::pause() {
_isPlaying = false;
}
@@ -343,31 +360,19 @@ void Music::play(uint32 resourceId, MusicFlags flags) {
// Load MIDI/XMI resource data
if (_vm->getGameId() == GID_IHNM && _vm->isMacResources()) {
- // Load the external music file for Mac IHNM
-#if 0
- Common::File musicFile;
- char musicFileName[40];
- sprintf(musicFileName, "Music/Music%02x", resourceId);
- musicFile.open(musicFileName);
- resourceSize = musicFile.size();
- resourceData = new byte[resourceSize];
- musicFile.read(resourceData, resourceSize);
- musicFile.close();
-
- // TODO: The Mac music format is unsupported (QuickTime MIDI)
- // so stop here
-#endif
- return;
+ // Load the external music file for Mac IHNM
+ _player->playQuickTime(Common::String::format("Music/Music%02x", resourceId), flags & MUSIC_LOOP);
} else {
if (_currentMusicBuffer == &_musicBuffer[1]) {
_currentMusicBuffer = &_musicBuffer[0];
} else {
_currentMusicBuffer = &_musicBuffer[1];
}
+
_vm->_resource->loadResource(_musicContext, resourceId, *_currentMusicBuffer);
+ _player->play(_vm, _currentMusicBuffer, (flags & MUSIC_LOOP));
}
- _player->play(_vm, _currentMusicBuffer, (flags & MUSIC_LOOP));
setVolume(_vm->_musicVolume);
}
diff --git a/engines/saga/music.h b/engines/saga/music.h
index 5a4e662af4..081fab21f6 100644
--- a/engines/saga/music.h
+++ b/engines/saga/music.h
@@ -46,6 +46,7 @@ public:
MusicDriver();
void play(SagaEngine *vm, ByteArray *buffer, bool loop);
+ void playQuickTime(const Common::String &musicName, bool loop);
virtual void pause();
virtual void resume();
diff --git a/video/qt_decoder.cpp b/video/qt_decoder.cpp
index c5fcab4b49..93e57f96ea 100644
--- a/video/qt_decoder.cpp
+++ b/video/qt_decoder.cpp
@@ -110,7 +110,7 @@ const Graphics::Surface *QuickTimeDecoder::decodeNextFrame() {
return frame;
}
-Common::QuickTimeParser::SampleDesc *QuickTimeDecoder::readSampleDesc(Common::QuickTimeParser::Track *track, uint32 format) {
+Common::QuickTimeParser::SampleDesc *QuickTimeDecoder::readSampleDesc(Common::QuickTimeParser::Track *track, uint32 format, uint32 descSize) {
if (track->codecType == CODEC_TYPE_VIDEO) {
debug(0, "Video Codec FourCC: \'%s\'", tag2str(format));
@@ -205,7 +205,7 @@ Common::QuickTimeParser::SampleDesc *QuickTimeDecoder::readSampleDesc(Common::Qu
}
// Pass it on up
- return Audio::QuickTimeAudioDecoder::readSampleDesc(track, format);
+ return Audio::QuickTimeAudioDecoder::readSampleDesc(track, format, descSize);
}
void QuickTimeDecoder::init() {
diff --git a/video/qt_decoder.h b/video/qt_decoder.h
index 45ab155c2c..135da91f88 100644
--- a/video/qt_decoder.h
+++ b/video/qt_decoder.h
@@ -70,7 +70,7 @@ public:
Audio::Timestamp getDuration() const { return Audio::Timestamp(0, _duration, _timeScale); }
protected:
- Common::QuickTimeParser::SampleDesc *readSampleDesc(Common::QuickTimeParser::Track *track, uint32 format);
+ Common::QuickTimeParser::SampleDesc *readSampleDesc(Common::QuickTimeParser::Track *track, uint32 format, uint32 descSize);
private:
void init();