From ac4c8cd335cf1118481453961c8705a01983ebc6 Mon Sep 17 00:00:00 2001 From: Matthew Hoops Date: Wed, 5 Sep 2012 23:04:29 -0400 Subject: AUDIO: Add parsing of the QuickTime MIDI sample description --- audio/midiparser_qt.cpp | 127 ++++++++++++++++++++++++++++++++++++++++++++++++ audio/module.mk | 1 + 2 files changed, 128 insertions(+) create mode 100644 audio/midiparser_qt.cpp (limited to 'audio') diff --git a/audio/midiparser_qt.cpp b/audio/midiparser_qt.cpp new file mode 100644 index 0000000000..ca0bb85534 --- /dev/null +++ b/audio/midiparser_qt.cpp @@ -0,0 +1,127 @@ +/* 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.h" +#include "common/debug.h" +#include "common/quicktime.h" + +class MidiParser_QT : /* public MidiParser, */ public Common::QuickTimeParser { +public: + MidiParser_QT() {} + ~MidiParser_QT() {} + +protected: + SampleDesc *readSampleDesc(Track *track, uint32 format); + +private: + struct NoteRequestInfo { + byte flags; + byte reserved; + uint16 polyphony; + Common::Rational typicalPolyphony; + }; + + struct ToneDescription { + uint32 synthesizerType; + Common::String synthesizerName; + Common::String instrumentName; + uint32 instrumentNumber; + uint32 gmNumber; + }; + + struct NoteRequest { + uint16 part; + NoteRequestInfo info; + ToneDescription tone; + }; + + class MIDISampleDesc : public SampleDesc { + public: + MIDISampleDesc(Common::QuickTimeParser::Track *parentTrack, uint32 codecTag); + ~MIDISampleDesc() {} + + Common::Array _noteRequests; + }; + + Common::String readString31(); + Common::Rational readFixed(); +}; + +Common::QuickTimeParser::SampleDesc *MidiParser_QT::readSampleDesc(Track *track, uint32 format) { + if (track->codecType == CODEC_TYPE_MIDI) { + debug(0, "MIDI Codec FourCC '%s'", tag2str(format)); + + /* uint32 flags = */ _fd->readUint32BE(); // always 0 + + MIDISampleDesc *entry = new MIDISampleDesc(track, format); + + for (;;) { + uint32 event = _fd->readUint32BE(); + + if ((event & 0xF000FFFF) != 0xF0000017) // note request event + break; + + NoteRequest request; + request.part = (event >> 16) & 0xFFF; + request.info.flags = _fd->readByte(); + request.info.reserved = _fd->readByte(); + request.info.polyphony = _fd->readUint16BE(); + request.info.typicalPolyphony = readFixed(); + request.tone.synthesizerType = _fd->readUint32BE(); + request.tone.synthesizerName = readString31(); + request.tone.instrumentName = readString31(); + request.tone.instrumentNumber = _fd->readUint32BE(); + request.tone.gmNumber = _fd->readUint32BE(); + + if (_fd->readUint32BE() != 0xC0010017) // general event note request + error("Invalid instrument end event"); + + entry->_noteRequests.push_back(request); + } + + return entry; + } + + return 0; +} + +MidiParser_QT::MIDISampleDesc::MIDISampleDesc(Common::QuickTimeParser::Track *parentTrack, uint32 codecTag) : + Common::QuickTimeParser::SampleDesc(parentTrack, codecTag) { +} + +Common::String MidiParser_QT::readString31() { + byte size = _fd->readByte(); + assert(size < 32); + + Common::String string; + for (byte i = 0; i < size; i++) + string += (char)_fd->readByte(); + + _fd->skip(31 - size); + return string; +} + +Common::Rational MidiParser_QT::readFixed() { + int16 integerPart = _fd->readSint16BE(); + uint16 fractionalPart = _fd->readUint16BE(); + return integerPart + Common::Rational(fractionalPart, 0x10000); +} 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 \ -- cgit v1.2.3 From c5ab2fc95f61d7cce1d3d255ed9c991a020d72ac Mon Sep 17 00:00:00 2001 From: Matthew Hoops Date: Wed, 5 Sep 2012 23:20:12 -0400 Subject: AUDIO: Clean up note request list reading --- audio/midiparser_qt.cpp | 67 ++++++++++++++++++++++++++++--------------------- 1 file changed, 39 insertions(+), 28 deletions(-) (limited to 'audio') diff --git a/audio/midiparser_qt.cpp b/audio/midiparser_qt.cpp index ca0bb85534..0d42e6c1f9 100644 --- a/audio/midiparser_qt.cpp +++ b/audio/midiparser_qt.cpp @@ -54,50 +54,27 @@ private: ToneDescription tone; }; + typedef Common::Array NoteRequestList; + class MIDISampleDesc : public SampleDesc { public: MIDISampleDesc(Common::QuickTimeParser::Track *parentTrack, uint32 codecTag); ~MIDISampleDesc() {} - Common::Array _noteRequests; + NoteRequestList _noteRequests; }; Common::String readString31(); Common::Rational readFixed(); + NoteRequestList readNoteRequestList(); }; Common::QuickTimeParser::SampleDesc *MidiParser_QT::readSampleDesc(Track *track, uint32 format) { if (track->codecType == CODEC_TYPE_MIDI) { debug(0, "MIDI Codec FourCC '%s'", tag2str(format)); - /* uint32 flags = */ _fd->readUint32BE(); // always 0 - MIDISampleDesc *entry = new MIDISampleDesc(track, format); - - for (;;) { - uint32 event = _fd->readUint32BE(); - - if ((event & 0xF000FFFF) != 0xF0000017) // note request event - break; - - NoteRequest request; - request.part = (event >> 16) & 0xFFF; - request.info.flags = _fd->readByte(); - request.info.reserved = _fd->readByte(); - request.info.polyphony = _fd->readUint16BE(); - request.info.typicalPolyphony = readFixed(); - request.tone.synthesizerType = _fd->readUint32BE(); - request.tone.synthesizerName = readString31(); - request.tone.instrumentName = readString31(); - request.tone.instrumentNumber = _fd->readUint32BE(); - request.tone.gmNumber = _fd->readUint32BE(); - - if (_fd->readUint32BE() != 0xC0010017) // general event note request - error("Invalid instrument end event"); - - entry->_noteRequests.push_back(request); - } - + entry->_noteRequests = readNoteRequestList(); return entry; } @@ -125,3 +102,37 @@ Common::Rational MidiParser_QT::readFixed() { uint16 fractionalPart = _fd->readUint16BE(); return integerPart + Common::Rational(fractionalPart, 0x10000); } + +MidiParser_QT::NoteRequestList MidiParser_QT::readNoteRequestList() { + NoteRequestList requests; + + /* uint32 flags = */ _fd->readUint32BE(); // always 0 + + for (;;) { + uint32 event = _fd->readUint32BE(); + + if (event == 0x60000000) // marker event + break; + else if ((event & 0xF000FFFF) != 0xF0000017) // note request event + error("Invalid note request event"); + + NoteRequest request; + request.part = (event >> 16) & 0xFFF; + request.info.flags = _fd->readByte(); + request.info.reserved = _fd->readByte(); + request.info.polyphony = _fd->readUint16BE(); + request.info.typicalPolyphony = readFixed(); + request.tone.synthesizerType = _fd->readUint32BE(); + request.tone.synthesizerName = readString31(); + request.tone.instrumentName = readString31(); + request.tone.instrumentNumber = _fd->readUint32BE(); + request.tone.gmNumber = _fd->readUint32BE(); + + if (_fd->readUint32BE() != 0xC0010017) // general event note request + error("Invalid instrument end event"); + + requests.push_back(request); + } + + return requests; +} -- cgit v1.2.3 From 834ca0e04561c42d82f7bd1e5c645ded2a13b038 Mon Sep 17 00:00:00 2001 From: Matthew Hoops Date: Thu, 6 Sep 2012 09:18:42 -0400 Subject: AUDIO: Allow for parsing QuickTime 'Tune' files --- audio/midiparser.h | 1 + audio/midiparser_qt.cpp | 161 +++++++++++++++++++++++++++--------------------- audio/midiparser_qt.h | 90 +++++++++++++++++++++++++++ 3 files changed, 183 insertions(+), 69 deletions(-) create mode 100644 audio/midiparser_qt.h (limited to 'audio') diff --git a/audio/midiparser.h b/audio/midiparser.h index c935969e72..f26da456b6 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 index 0d42e6c1f9..ed4bb22242 100644 --- a/audio/midiparser_qt.cpp +++ b/audio/midiparser_qt.cpp @@ -20,61 +20,76 @@ * */ -#include "audio/midiparser.h" +#include "audio/midiparser_qt.h" #include "common/debug.h" -#include "common/quicktime.h" - -class MidiParser_QT : /* public MidiParser, */ public Common::QuickTimeParser { -public: - MidiParser_QT() {} - ~MidiParser_QT() {} - -protected: - SampleDesc *readSampleDesc(Track *track, uint32 format); - -private: - struct NoteRequestInfo { - byte flags; - byte reserved; - uint16 polyphony; - Common::Rational typicalPolyphony; - }; - - struct ToneDescription { - uint32 synthesizerType; - Common::String synthesizerName; - Common::String instrumentName; - uint32 instrumentNumber; - uint32 gmNumber; - }; - - struct NoteRequest { - uint16 part; - NoteRequestInfo info; - ToneDescription tone; - }; - - typedef Common::Array NoteRequestList; - - class MIDISampleDesc : public SampleDesc { - public: - MIDISampleDesc(Common::QuickTimeParser::Track *parentTrack, uint32 codecTag); - ~MIDISampleDesc() {} - - NoteRequestList _noteRequests; - }; - - Common::String readString31(); - Common::Rational readFixed(); - NoteRequestList readNoteRequestList(); -}; +#include "common/memstream.h" + +bool MidiParser_QT::loadMusic(byte *data, uint32 size) { + // Assume that this is a Tune and not a QuickTime container + Common::SeekableReadStream *stream = new Common::MemoryReadStream(data, size, DisposeAfterUse::NO); + + if (!loadFromTune(stream)) { + delete stream; + return false; + } + + return true; +} + +void MidiParser_QT::unloadMusic() { + MidiParser::unloadMusic(); + close(); + // TODO +} + +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 + + // TODO + readNoteRequestList(stream); + 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) { + // TODO +} Common::QuickTimeParser::SampleDesc *MidiParser_QT::readSampleDesc(Track *track, uint32 format) { if (track->codecType == CODEC_TYPE_MIDI) { debug(0, "MIDI Codec FourCC '%s'", tag2str(format)); MIDISampleDesc *entry = new MIDISampleDesc(track, format); - entry->_noteRequests = readNoteRequestList(); + entry->_noteRequests = readNoteRequestList(_fd); return entry; } @@ -85,31 +100,31 @@ MidiParser_QT::MIDISampleDesc::MIDISampleDesc(Common::QuickTimeParser::Track *pa Common::QuickTimeParser::SampleDesc(parentTrack, codecTag) { } -Common::String MidiParser_QT::readString31() { - byte size = _fd->readByte(); +Common::String MidiParser_QT::readString31(Common::SeekableReadStream *stream) { + byte size = stream->readByte(); assert(size < 32); Common::String string; for (byte i = 0; i < size; i++) - string += (char)_fd->readByte(); + string += (char)stream->readByte(); - _fd->skip(31 - size); + stream->skip(31 - size); return string; } -Common::Rational MidiParser_QT::readFixed() { - int16 integerPart = _fd->readSint16BE(); - uint16 fractionalPart = _fd->readUint16BE(); +Common::Rational MidiParser_QT::readFixed(Common::SeekableReadStream *stream) { + int16 integerPart = stream->readSint16BE(); + uint16 fractionalPart = stream->readUint16BE(); return integerPart + Common::Rational(fractionalPart, 0x10000); } -MidiParser_QT::NoteRequestList MidiParser_QT::readNoteRequestList() { +MidiParser_QT::NoteRequestList MidiParser_QT::readNoteRequestList(Common::SeekableReadStream *stream) { NoteRequestList requests; - /* uint32 flags = */ _fd->readUint32BE(); // always 0 + /* uint32 flags = */ stream->readUint32BE(); // always 0 for (;;) { - uint32 event = _fd->readUint32BE(); + uint32 event = stream->readUint32BE(); if (event == 0x60000000) // marker event break; @@ -118,17 +133,17 @@ MidiParser_QT::NoteRequestList MidiParser_QT::readNoteRequestList() { NoteRequest request; request.part = (event >> 16) & 0xFFF; - request.info.flags = _fd->readByte(); - request.info.reserved = _fd->readByte(); - request.info.polyphony = _fd->readUint16BE(); - request.info.typicalPolyphony = readFixed(); - request.tone.synthesizerType = _fd->readUint32BE(); - request.tone.synthesizerName = readString31(); - request.tone.instrumentName = readString31(); - request.tone.instrumentNumber = _fd->readUint32BE(); - request.tone.gmNumber = _fd->readUint32BE(); - - if (_fd->readUint32BE() != 0xC0010017) // general event note request + request.info.flags = stream->readByte(); + request.info.reserved = stream->readByte(); + request.info.polyphony = stream->readUint16BE(); + request.info.typicalPolyphony = readFixed(stream); + request.tone.synthesizerType = stream->readUint32BE(); + request.tone.synthesizerName = readString31(stream); + request.tone.instrumentName = readString31(stream); + request.tone.instrumentNumber = stream->readUint32BE(); + request.tone.gmNumber = stream->readUint32BE(); + + if (stream->readUint32BE() != 0xC0010017) // general event note request error("Invalid instrument end event"); requests.push_back(request); @@ -136,3 +151,11 @@ MidiParser_QT::NoteRequestList MidiParser_QT::readNoteRequestList() { return requests; } + +void MidiParser_QT::initFromContainerTracks() { + // TODO +} + +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..34abe4ced7 --- /dev/null +++ b/audio/midiparser_qt.h @@ -0,0 +1,90 @@ +/* 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/quicktime.h" + +class MidiParser_QT : public MidiParser, public Common::QuickTimeParser { +public: + MidiParser_QT() {} + ~MidiParser_QT() {} + + // MidiParser + bool loadMusic(byte *data, uint32 size); + void unloadMusic(); + + // Custom + bool loadFromTune(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES); + bool loadFromContainerStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES); + bool loadFromContainerFile(const Common::String &fileName); + +protected: + // MidiParser + void parseNextEvent(EventInfo &info); + + // QuickTimeParser + SampleDesc *readSampleDesc(Track *track, uint32 format); + +private: + struct NoteRequestInfo { + byte flags; + byte reserved; + uint16 polyphony; + Common::Rational typicalPolyphony; + }; + + struct ToneDescription { + uint32 synthesizerType; + Common::String synthesizerName; + Common::String instrumentName; + uint32 instrumentNumber; + uint32 gmNumber; + }; + + struct NoteRequest { + uint16 part; + NoteRequestInfo info; + ToneDescription tone; + }; + + typedef Common::Array NoteRequestList; + + class MIDISampleDesc : public SampleDesc { + public: + MIDISampleDesc(Common::QuickTimeParser::Track *parentTrack, uint32 codecTag); + ~MIDISampleDesc() {} + + NoteRequestList _noteRequests; + }; + + Common::String readString31(Common::SeekableReadStream *stream); + Common::Rational readFixed(Common::SeekableReadStream *stream); + NoteRequestList readNoteRequestList(Common::SeekableReadStream *stream); + + void initFromContainerTracks(); +}; + +#endif -- cgit v1.2.3 From cc309f4d166ec74e06541145550cc86125f0247c Mon Sep 17 00:00:00 2001 From: Matthew Hoops Date: Thu, 6 Sep 2012 14:18:20 -0400 Subject: AUDIO: Read in all QuickTime MIDI track data --- audio/midiparser_qt.cpp | 74 +++++++++++++++++++++++++++++++++++++++++++++++-- audio/midiparser_qt.h | 10 +++++++ 2 files changed, 81 insertions(+), 3 deletions(-) (limited to 'audio') diff --git a/audio/midiparser_qt.cpp b/audio/midiparser_qt.cpp index ed4bb22242..0701f7e919 100644 --- a/audio/midiparser_qt.cpp +++ b/audio/midiparser_qt.cpp @@ -39,7 +39,12 @@ bool MidiParser_QT::loadMusic(byte *data, uint32 size) { void MidiParser_QT::unloadMusic() { MidiParser::unloadMusic(); close(); - // TODO + + // 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) { @@ -55,8 +60,18 @@ bool MidiParser_QT::loadFromTune(Common::SeekableReadStream *stream, DisposeAfte stream->readUint16BE(); // reserved stream->readUint16BE(); // index - // TODO - readNoteRequestList(stream); + MIDITrackInfo trackInfo; + trackInfo.noteRequests = readNoteRequestList(stream); + + uint32 trackSize = stream->size() - stream->pos(); + assert(trackSize > 0); + + trackInfo.data = (byte *)malloc(trackSize); + stream->read(trackInfo.data, trackSize); + + _trackInfo.push_back(trackInfo); + + initCommon(); return true; } @@ -153,9 +168,62 @@ MidiParser_QT::NoteRequestList MidiParser_QT::readNoteRequestList(Common::Seekab } void MidiParser_QT::initFromContainerTracks() { + const Common::Array &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"); + + MIDISampleDesc *entry = (MIDISampleDesc *)tracks[i]->sampleDescs[0]; + + MIDITrackInfo trackInfo; + trackInfo.noteRequests = entry->_noteRequests; + trackInfo.data = readWholeTrack(tracks[i]); + _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. + // TODO } +byte *MidiParser_QT::readWholeTrack(Common::QuickTimeParser::Track *track) { + // This just goes through all chunks and + + Common::MemoryWriteStreamDynamic output; + uint32 curSample = 0; + + 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; + } + } + + return output.getData(); +} + MidiParser *MidiParser::createParser_QT() { return new MidiParser_QT(); } diff --git a/audio/midiparser_qt.h b/audio/midiparser_qt.h index 34abe4ced7..0047803fc4 100644 --- a/audio/midiparser_qt.h +++ b/audio/midiparser_qt.h @@ -72,6 +72,11 @@ private: typedef Common::Array NoteRequestList; + struct MIDITrackInfo { + NoteRequestList noteRequests; + byte *data; + }; + class MIDISampleDesc : public SampleDesc { public: MIDISampleDesc(Common::QuickTimeParser::Track *parentTrack, uint32 codecTag); @@ -84,7 +89,12 @@ private: Common::Rational readFixed(Common::SeekableReadStream *stream); NoteRequestList readNoteRequestList(Common::SeekableReadStream *stream); + byte *readWholeTrack(Common::QuickTimeParser::Track *track); + + Common::Array _trackInfo; + void initFromContainerTracks(); + void initCommon(); }; #endif -- cgit v1.2.3 From c22f76dbbc487b5a5e89d327bec2c550ce47378c Mon Sep 17 00:00:00 2001 From: Matthew Hoops Date: Thu, 6 Sep 2012 16:27:06 -0400 Subject: AUDIO: Fill in _tracks from MidiParser_QT --- audio/midiparser_qt.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'audio') diff --git a/audio/midiparser_qt.cpp b/audio/midiparser_qt.cpp index 0701f7e919..8ddf6d66e7 100644 --- a/audio/midiparser_qt.cpp +++ b/audio/midiparser_qt.cpp @@ -193,7 +193,10 @@ void MidiParser_QT::initCommon() { // Now we have all our info needed in _trackInfo from whatever container // form, we can fill in the MidiParser tracks. - // TODO + _num_tracks = _trackInfo.size(); + + for (uint32 i = 0; i < _trackInfo.size(); i++) + MidiParser::_tracks[i] = _trackInfo[i].data; } byte *MidiParser_QT::readWholeTrack(Common::QuickTimeParser::Track *track) { -- cgit v1.2.3 From 7e2f7099c892c212409f1c7b69426cd1a1f00552 Mon Sep 17 00:00:00 2001 From: Matthew Hoops Date: Fri, 7 Sep 2012 09:49:33 -0400 Subject: AUDIO: Keep track of the QuickTime MIDI time scale --- audio/midiparser_qt.cpp | 2 ++ audio/midiparser_qt.h | 1 + 2 files changed, 3 insertions(+) (limited to 'audio') diff --git a/audio/midiparser_qt.cpp b/audio/midiparser_qt.cpp index 8ddf6d66e7..7b20f62cb7 100644 --- a/audio/midiparser_qt.cpp +++ b/audio/midiparser_qt.cpp @@ -69,6 +69,7 @@ bool MidiParser_QT::loadFromTune(Common::SeekableReadStream *stream, DisposeAfte trackInfo.data = (byte *)malloc(trackSize); stream->read(trackInfo.data, trackSize); + trackInfo.timeScale = 600; // the default _trackInfo.push_back(trackInfo); initCommon(); @@ -182,6 +183,7 @@ void MidiParser_QT::initFromContainerTracks() { MIDITrackInfo trackInfo; trackInfo.noteRequests = entry->_noteRequests; trackInfo.data = readWholeTrack(tracks[i]); + trackInfo.timeScale = tracks[i]->timeScale; _trackInfo.push_back(trackInfo); } } diff --git a/audio/midiparser_qt.h b/audio/midiparser_qt.h index 0047803fc4..6e4ded8e74 100644 --- a/audio/midiparser_qt.h +++ b/audio/midiparser_qt.h @@ -75,6 +75,7 @@ private: struct MIDITrackInfo { NoteRequestList noteRequests; byte *data; + uint32 timeScale; }; class MIDISampleDesc : public SampleDesc { -- cgit v1.2.3 From c01dfba093f0a2506c912f644dbb44df7b877d92 Mon Sep 17 00:00:00 2001 From: Matthew Hoops Date: Fri, 7 Sep 2012 11:45:28 -0400 Subject: AUDIO: Begin basic playback of QuickTime MIDI files Sounds almost correct, but too fast --- audio/midiparser_qt.cpp | 109 +++++++++++++++++++++++++++++++++++++++++++++++- audio/midiparser_qt.h | 5 +++ 2 files changed, 113 insertions(+), 1 deletion(-) (limited to 'audio') diff --git a/audio/midiparser_qt.cpp b/audio/midiparser_qt.cpp index 7b20f62cb7..b7a201d801 100644 --- a/audio/midiparser_qt.cpp +++ b/audio/midiparser_qt.cpp @@ -96,8 +96,103 @@ bool MidiParser_QT::loadFromContainerFile(const Common::String &fileName) { return true; } +void MidiParser_QT::resetTracking() { + _loadedInstruments = 0; +} + void MidiParser_QT::parseNextEvent(EventInfo &info) { - // TODO + if (_loadedInstruments < _trackInfo[_active_track].noteRequests.size()) { + // Load instruments first + info.event = 0xC0 | _loadedInstruments; + info.basic.param1 = _trackInfo[_active_track].noteRequests[_loadedInstruments].tone.gmNumber; + _loadedInstruments++; + return; + } + + info.delta = readNextEvent(info); +} + +uint32 MidiParser_QT::readNextEvent(EventInfo &info) { + 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(info) + (control & 0xFFFFFF); + case 0x2: + case 0x3: + // Note event + info.event = 0x90 | ((control >> 24) & 0x1F); + info.basic.param1 = ((control >> 18) & 0x3F) + 32; + info.basic.param2 = (control >> 11) & 0x7F; + info.length = (info.basic.param2 == 0) ? 0 : (control & 0x7FF); + break; + case 0x4: + case 0x5: + // Controller + info.event = 0xB0 | ((control >> 24) & 0x1F); + info.basic.param1 = (control >> 16) & 0xFF; + info.basic.param2 = (control >> 8) & 0xFF; + break; + case 0x6: + case 0x7: + // Marker + switch ((control >> 16) & 0xFF) { + case 0: + // End + info.event = 0xFF; + info.ext.type = 0x2F; + break; + case 1: + // Beat + warning("Encountered beat marker"); + break; + case 2: + // Tempo + warning("Encountered tempo marker"); + break; + default: + warning("Unknown marker"); + } + break; + case 0x9: { + // Extended note event + uint32 extra = readUint32(); + info.event = 0x90 | ((control >> 16) & 0xFFF); + info.basic.param1 = (control >> 8) & 0xFF; + info.basic.param2 = (extra >> 22) & 0x7F; + info.length = (info.basic.param2 == 0) ? 0 : (extra & 0x3FFFFF); + break; + } + case 0xA: { + // Extended controller + uint32 extra = readUint32(); + info.event = 0xB0 | ((control >> 16) & 0xFFF); + info.basic.param1 = (extra >> 16) & 0x3FFF; + info.basic.param2 = (extra >> 8) & 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 + error("Encountered general event in QuickTime MIDI"); + break; + } + + return 0; } Common::QuickTimeParser::SampleDesc *MidiParser_QT::readSampleDesc(Track *track, uint32 format) { @@ -196,9 +291,15 @@ void MidiParser_QT::initCommon() { // form, we can fill in the MidiParser tracks. _num_tracks = _trackInfo.size(); + assert(_num_tracks > 0); for (uint32 i = 0; i < _trackInfo.size(); i++) MidiParser::_tracks[i] = _trackInfo[i].data; + + _ppqn = _trackInfo[0].timeScale; + resetTracking(); + setTempo(500000); + setTrack(0); } byte *MidiParser_QT::readWholeTrack(Common::QuickTimeParser::Track *track) { @@ -229,6 +330,12 @@ byte *MidiParser_QT::readWholeTrack(Common::QuickTimeParser::Track *track) { return output.getData(); } +uint32 MidiParser_QT::readUint32() { + uint32 value = READ_BE_UINT32(_position._play_pos); + _position._play_pos += 4; + return value; +} + MidiParser *MidiParser::createParser_QT() { return new MidiParser_QT(); } diff --git a/audio/midiparser_qt.h b/audio/midiparser_qt.h index 6e4ded8e74..2dcd61b9f9 100644 --- a/audio/midiparser_qt.h +++ b/audio/midiparser_qt.h @@ -43,6 +43,7 @@ public: protected: // MidiParser + void resetTracking(); void parseNextEvent(EventInfo &info); // QuickTimeParser @@ -86,6 +87,8 @@ private: NoteRequestList _noteRequests; }; + uint32 readNextEvent(EventInfo &info); + Common::String readString31(Common::SeekableReadStream *stream); Common::Rational readFixed(Common::SeekableReadStream *stream); NoteRequestList readNoteRequestList(Common::SeekableReadStream *stream); @@ -93,9 +96,11 @@ private: byte *readWholeTrack(Common::QuickTimeParser::Track *track); Common::Array _trackInfo; + uint32 _loadedInstruments; void initFromContainerTracks(); void initCommon(); + uint32 readUint32(); }; #endif -- cgit v1.2.3 From f333e633977a3dd17ce272edb5eb697e62c1af19 Mon Sep 17 00:00:00 2001 From: Matthew Hoops Date: Fri, 7 Sep 2012 14:33:39 -0400 Subject: AUDIO: Fix QuickTime MIDI end of track The end marker is used for editing only --- audio/midiparser_qt.cpp | 38 +++++++++++++++----------------------- audio/midiparser_qt.h | 3 ++- 2 files changed, 17 insertions(+), 24 deletions(-) (limited to 'audio') diff --git a/audio/midiparser_qt.cpp b/audio/midiparser_qt.cpp index b7a201d801..f8113aff2d 100644 --- a/audio/midiparser_qt.cpp +++ b/audio/midiparser_qt.cpp @@ -63,11 +63,11 @@ bool MidiParser_QT::loadFromTune(Common::SeekableReadStream *stream, DisposeAfte MIDITrackInfo trackInfo; trackInfo.noteRequests = readNoteRequestList(stream); - uint32 trackSize = stream->size() - stream->pos(); - assert(trackSize > 0); + trackInfo.size = stream->size() - stream->pos(); + assert(trackInfo.size > 0); - trackInfo.data = (byte *)malloc(trackSize); - stream->read(trackInfo.data, trackSize); + trackInfo.data = (byte *)malloc(trackInfo.size); + stream->read(trackInfo.data, trackInfo.size); trackInfo.timeScale = 600; // the default _trackInfo.push_back(trackInfo); @@ -101,6 +101,13 @@ void MidiParser_QT::resetTracking() { } void MidiParser_QT::parseNextEvent(EventInfo &info) { + if (_position._play_pos >= _trackInfo[_active_track].data + _trackInfo[_active_track].size) { + // Manually insert end of track when we reach the end + info.event = 0xFF; + info.ext.type = 0x2F; + return; + } + if (_loadedInstruments < _trackInfo[_active_track].noteRequests.size()) { // Load instruments first info.event = 0xC0 | _loadedInstruments; @@ -140,23 +147,7 @@ uint32 MidiParser_QT::readNextEvent(EventInfo &info) { case 0x6: case 0x7: // Marker - switch ((control >> 16) & 0xFF) { - case 0: - // End - info.event = 0xFF; - info.ext.type = 0x2F; - break; - case 1: - // Beat - warning("Encountered beat marker"); - break; - case 2: - // Tempo - warning("Encountered tempo marker"); - break; - default: - warning("Unknown marker"); - } + // Used for editing only, so we don't need to care about this break; case 0x9: { // Extended note event @@ -277,7 +268,7 @@ void MidiParser_QT::initFromContainerTracks() { MIDITrackInfo trackInfo; trackInfo.noteRequests = entry->_noteRequests; - trackInfo.data = readWholeTrack(tracks[i]); + trackInfo.data = readWholeTrack(tracks[i], trackInfo.size); trackInfo.timeScale = tracks[i]->timeScale; _trackInfo.push_back(trackInfo); } @@ -302,7 +293,7 @@ void MidiParser_QT::initCommon() { setTrack(0); } -byte *MidiParser_QT::readWholeTrack(Common::QuickTimeParser::Track *track) { +byte *MidiParser_QT::readWholeTrack(Common::QuickTimeParser::Track *track, uint32 &trackSize) { // This just goes through all chunks and Common::MemoryWriteStreamDynamic output; @@ -327,6 +318,7 @@ byte *MidiParser_QT::readWholeTrack(Common::QuickTimeParser::Track *track) { } } + trackSize = output.size(); return output.getData(); } diff --git a/audio/midiparser_qt.h b/audio/midiparser_qt.h index 2dcd61b9f9..50234020a0 100644 --- a/audio/midiparser_qt.h +++ b/audio/midiparser_qt.h @@ -76,6 +76,7 @@ private: struct MIDITrackInfo { NoteRequestList noteRequests; byte *data; + uint32 size; uint32 timeScale; }; @@ -93,7 +94,7 @@ private: Common::Rational readFixed(Common::SeekableReadStream *stream); NoteRequestList readNoteRequestList(Common::SeekableReadStream *stream); - byte *readWholeTrack(Common::QuickTimeParser::Track *track); + byte *readWholeTrack(Common::QuickTimeParser::Track *track, uint32 &trackSize); Common::Array _trackInfo; uint32 _loadedInstruments; -- cgit v1.2.3 From 342cc027c4d360590753a129f0c622fc1757cc0e Mon Sep 17 00:00:00 2001 From: Matthew Hoops Date: Fri, 7 Sep 2012 14:41:54 -0400 Subject: AUDIO: Fix QuickTime MIDI tempo --- audio/midiparser_qt.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'audio') diff --git a/audio/midiparser_qt.cpp b/audio/midiparser_qt.cpp index f8113aff2d..5500c28e44 100644 --- a/audio/midiparser_qt.cpp +++ b/audio/midiparser_qt.cpp @@ -289,7 +289,7 @@ void MidiParser_QT::initCommon() { _ppqn = _trackInfo[0].timeScale; resetTracking(); - setTempo(500000); + setTempo(1000000); setTrack(0); } -- cgit v1.2.3 From c882ef9dabbb69e569bd5861712cf4117794a9ae Mon Sep 17 00:00:00 2001 From: Matthew Hoops Date: Fri, 7 Sep 2012 14:47:30 -0400 Subject: AUDIO: Make MidiParser_QT::loadMusic() detect the file type --- audio/midiparser_qt.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) (limited to 'audio') diff --git a/audio/midiparser_qt.cpp b/audio/midiparser_qt.cpp index 5500c28e44..d6440508df 100644 --- a/audio/midiparser_qt.cpp +++ b/audio/midiparser_qt.cpp @@ -25,10 +25,19 @@ #include "common/memstream.h" bool MidiParser_QT::loadMusic(byte *data, uint32 size) { - // Assume that this is a Tune and not a QuickTime container + if (size < 8) + return false; + Common::SeekableReadStream *stream = new Common::MemoryReadStream(data, size, DisposeAfterUse::NO); - if (!loadFromTune(stream)) { + // 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; } -- cgit v1.2.3 From c50d40b7bf0914c5a1a5da221cee146ab4aa2f63 Mon Sep 17 00:00:00 2001 From: Matthew Hoops Date: Fri, 7 Sep 2012 14:50:21 -0400 Subject: AUDIO: Add some documentation to MidiParser_QT --- audio/midiparser_qt.h | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) (limited to 'audio') diff --git a/audio/midiparser_qt.h b/audio/midiparser_qt.h index 50234020a0..ac5f45ae34 100644 --- a/audio/midiparser_qt.h +++ b/audio/midiparser_qt.h @@ -27,6 +27,9 @@ #include "common/array.h" #include "common/quicktime.h" +/** + * The QuickTime MIDI version of MidiParser. + */ class MidiParser_QT : public MidiParser, public Common::QuickTimeParser { public: MidiParser_QT() {} @@ -36,9 +39,19 @@ public: bool loadMusic(byte *data, uint32 size); void unloadMusic(); - // Custom + /** + * 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: -- cgit v1.2.3 From 2cb301337a32b09449fe64d43fa1031af3d09454 Mon Sep 17 00:00:00 2001 From: Matthew Hoops Date: Sat, 8 Sep 2012 11:05:47 -0400 Subject: AUDIO: Fix QuickTime MIDI pitch bend --- audio/midiparser_qt.cpp | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) (limited to 'audio') diff --git a/audio/midiparser_qt.cpp b/audio/midiparser_qt.cpp index 2b8a02ba5e..e16c73bfec 100644 --- a/audio/midiparser_qt.cpp +++ b/audio/midiparser_qt.cpp @@ -149,9 +149,31 @@ uint32 MidiParser_QT::readNextEvent(EventInfo &info) { case 0x4: case 0x5: // Controller - info.event = 0xB0 | ((control >> 24) & 0x1F); - info.basic.param1 = (control >> 16) & 0xFF; - info.basic.param2 = (control >> 8) & 0xFF; + if (((control >> 16) & 0xFF) == 32) { + // Pitch bend + info.event = 0xE0 | ((control >> 24) & 0x1F); + + // Actually an 8.8 fixed point number + int16 value = (int16)(control & 0xFFFF); + + if (value < -0x200 || value > 0x1FF) { + warning("QuickTime MIDI pitch bend value (%d) out of range, clipping", value); + value = CLIP(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; + } else { + // Regular controller + info.event = 0xB0 | ((control >> 24) & 0x1F); + info.basic.param1 = (control >> 16) & 0xFF; + info.basic.param2 = (control >> 8) & 0xFF; + } break; case 0x6: case 0x7: -- cgit v1.2.3 From 4a458236f625d28706cfe4560690770a395ee6e3 Mon Sep 17 00:00:00 2001 From: Matthew Hoops Date: Sun, 9 Sep 2012 13:47:40 -0400 Subject: COMMON: Make QuickTimeParser::readSampleDesc take the desc size --- audio/decoders/quicktime.cpp | 2 +- audio/decoders/quicktime_intern.h | 2 +- audio/midiparser_qt.cpp | 2 +- audio/midiparser_qt.h | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) (limited to 'audio') diff --git a/audio/decoders/quicktime.cpp b/audio/decoders/quicktime.cpp index 5276cfc530..3ec90078db 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 efc97cbd13..7e2b0d18d1 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_qt.cpp b/audio/midiparser_qt.cpp index e16c73bfec..65ca64a7f4 100644 --- a/audio/midiparser_qt.cpp +++ b/audio/midiparser_qt.cpp @@ -217,7 +217,7 @@ uint32 MidiParser_QT::readNextEvent(EventInfo &info) { return 0; } -Common::QuickTimeParser::SampleDesc *MidiParser_QT::readSampleDesc(Track *track, uint32 format) { +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)); diff --git a/audio/midiparser_qt.h b/audio/midiparser_qt.h index ac5f45ae34..f9378f9c3d 100644 --- a/audio/midiparser_qt.h +++ b/audio/midiparser_qt.h @@ -60,7 +60,7 @@ protected: void parseNextEvent(EventInfo &info); // QuickTimeParser - SampleDesc *readSampleDesc(Track *track, uint32 format); + SampleDesc *readSampleDesc(Track *track, uint32 format, uint32 descSize); private: struct NoteRequestInfo { -- cgit v1.2.3 From cfe6a2b640e91cf11a32017e79966121bf357cc7 Mon Sep 17 00:00:00 2001 From: Matthew Hoops Date: Sun, 9 Sep 2012 14:59:05 -0400 Subject: AUDIO: Fix QuickTime MIDI with extra info in the header The 11th Hour Mac MIDI's now play --- audio/midiparser_qt.cpp | 126 +++++++++++++++++++----------------------------- audio/midiparser_qt.h | 34 ++----------- 2 files changed, 52 insertions(+), 108 deletions(-) (limited to 'audio') diff --git a/audio/midiparser_qt.cpp b/audio/midiparser_qt.cpp index 65ca64a7f4..5148ed74b1 100644 --- a/audio/midiparser_qt.cpp +++ b/audio/midiparser_qt.cpp @@ -69,9 +69,9 @@ bool MidiParser_QT::loadFromTune(Common::SeekableReadStream *stream, DisposeAfte stream->readUint16BE(); // reserved stream->readUint16BE(); // index - MIDITrackInfo trackInfo; - trackInfo.noteRequests = readNoteRequestList(stream); + stream->readUint32BE(); // flags, ignore + MIDITrackInfo trackInfo; trackInfo.size = stream->size() - stream->pos(); assert(trackInfo.size > 0); @@ -105,27 +105,19 @@ bool MidiParser_QT::loadFromContainerFile(const Common::String &fileName) { return true; } -void MidiParser_QT::resetTracking() { - _loadedInstruments = 0; -} - void MidiParser_QT::parseNextEvent(EventInfo &info) { - if (_position._playPos >= _trackInfo[_activeTrack].data + _trackInfo[_activeTrack].size) { - // Manually insert end of track when we reach the end - info.event = 0xFF; - info.ext.type = 0x2F; - return; - } + info.event = 0; + + while (info.event == 0) { + if (_position._playPos >= _trackInfo[_activeTrack].data + _trackInfo[_activeTrack].size) { + // Manually insert end of track when we reach the end + info.event = 0xFF; + info.ext.type = 0x2F; + return; + } - if (_loadedInstruments < _trackInfo[_activeTrack].noteRequests.size()) { - // Load instruments first - info.event = 0xC0 | _loadedInstruments; - info.basic.param1 = _trackInfo[_activeTrack].noteRequests[_loadedInstruments].tone.gmNumber; - _loadedInstruments++; - return; + info.delta = readNextEvent(info); } - - info.delta = readNextEvent(info); } uint32 MidiParser_QT::readNextEvent(EventInfo &info) { @@ -210,19 +202,50 @@ uint32 MidiParser_QT::readNextEvent(EventInfo &info) { break; case 0xF: // General - error("Encountered general event in QuickTime MIDI"); + handleGeneralEvent(info, control); break; } return 0; } +void MidiParser_QT::handleGeneralEvent(EventInfo &info, 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); + info.event = 0xC0 | part; + info.basic.param1 = 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; +} + 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->_noteRequests = readNoteRequestList(_fd); + entry->_requestSize = descSize; + entry->_requestData = (byte *)malloc(descSize); + _fd->read(entry->_requestData, descSize); return entry; } @@ -233,58 +256,6 @@ MidiParser_QT::MIDISampleDesc::MIDISampleDesc(Common::QuickTimeParser::Track *pa Common::QuickTimeParser::SampleDesc(parentTrack, codecTag) { } -Common::String MidiParser_QT::readString31(Common::SeekableReadStream *stream) { - byte size = stream->readByte(); - assert(size < 32); - - Common::String string; - for (byte i = 0; i < size; i++) - string += (char)stream->readByte(); - - stream->skip(31 - size); - return string; -} - -Common::Rational MidiParser_QT::readFixed(Common::SeekableReadStream *stream) { - int16 integerPart = stream->readSint16BE(); - uint16 fractionalPart = stream->readUint16BE(); - return integerPart + Common::Rational(fractionalPart, 0x10000); -} - -MidiParser_QT::NoteRequestList MidiParser_QT::readNoteRequestList(Common::SeekableReadStream *stream) { - NoteRequestList requests; - - /* uint32 flags = */ stream->readUint32BE(); // always 0 - - for (;;) { - uint32 event = stream->readUint32BE(); - - if (event == 0x60000000) // marker event - break; - else if ((event & 0xF000FFFF) != 0xF0000017) // note request event - error("Invalid note request event"); - - NoteRequest request; - request.part = (event >> 16) & 0xFFF; - request.info.flags = stream->readByte(); - request.info.reserved = stream->readByte(); - request.info.polyphony = stream->readUint16BE(); - request.info.typicalPolyphony = readFixed(stream); - request.tone.synthesizerType = stream->readUint32BE(); - request.tone.synthesizerName = readString31(stream); - request.tone.instrumentName = readString31(stream); - request.tone.instrumentNumber = stream->readUint32BE(); - request.tone.gmNumber = stream->readUint32BE(); - - if (stream->readUint32BE() != 0xC0010017) // general event note request - error("Invalid instrument end event"); - - requests.push_back(request); - } - - return requests; -} - void MidiParser_QT::initFromContainerTracks() { const Common::Array &tracks = Common::QuickTimeParser::_tracks; @@ -295,10 +266,7 @@ void MidiParser_QT::initFromContainerTracks() { if (tracks[i]->editCount != 1) warning("Unhandled QuickTime MIDI edit lists, things may go awry"); - MIDISampleDesc *entry = (MIDISampleDesc *)tracks[i]->sampleDescs[0]; - MIDITrackInfo trackInfo; - trackInfo.noteRequests = entry->_noteRequests; trackInfo.data = readWholeTrack(tracks[i], trackInfo.size); trackInfo.timeScale = tracks[i]->timeScale; _trackInfo.push_back(trackInfo); @@ -330,6 +298,10 @@ byte *MidiParser_QT::readWholeTrack(Common::QuickTimeParser::Track *track, uint3 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]); diff --git a/audio/midiparser_qt.h b/audio/midiparser_qt.h index f9378f9c3d..5ab89bd4e7 100644 --- a/audio/midiparser_qt.h +++ b/audio/midiparser_qt.h @@ -56,38 +56,13 @@ public: protected: // MidiParser - void resetTracking(); void parseNextEvent(EventInfo &info); // QuickTimeParser SampleDesc *readSampleDesc(Track *track, uint32 format, uint32 descSize); private: - struct NoteRequestInfo { - byte flags; - byte reserved; - uint16 polyphony; - Common::Rational typicalPolyphony; - }; - - struct ToneDescription { - uint32 synthesizerType; - Common::String synthesizerName; - Common::String instrumentName; - uint32 instrumentNumber; - uint32 gmNumber; - }; - - struct NoteRequest { - uint16 part; - NoteRequestInfo info; - ToneDescription tone; - }; - - typedef Common::Array NoteRequestList; - struct MIDITrackInfo { - NoteRequestList noteRequests; byte *data; uint32 size; uint32 timeScale; @@ -98,19 +73,16 @@ private: MIDISampleDesc(Common::QuickTimeParser::Track *parentTrack, uint32 codecTag); ~MIDISampleDesc() {} - NoteRequestList _noteRequests; + byte *_requestData; + uint32 _requestSize; }; uint32 readNextEvent(EventInfo &info); - - Common::String readString31(Common::SeekableReadStream *stream); - Common::Rational readFixed(Common::SeekableReadStream *stream); - NoteRequestList readNoteRequestList(Common::SeekableReadStream *stream); + void handleGeneralEvent(EventInfo &info, uint32 control); byte *readWholeTrack(Common::QuickTimeParser::Track *track, uint32 &trackSize); Common::Array _trackInfo; - uint32 _loadedInstruments; void initFromContainerTracks(); void initCommon(); -- cgit v1.2.3 From c023651cb37752a7dd5e53a7bb27f20dbc9fd41e Mon Sep 17 00:00:00 2001 From: Matthew Hoops Date: Mon, 10 Sep 2012 08:22:00 -0400 Subject: AUDIO: Implement QuickTime MIDI channel remapping Needed to support channels with a drum kit --- audio/midiparser_qt.cpp | 55 +++++++++++++++++++++++++++++++++++++++++++------ audio/midiparser_qt.h | 9 ++++++++ 2 files changed, 58 insertions(+), 6 deletions(-) (limited to 'audio') diff --git a/audio/midiparser_qt.cpp b/audio/midiparser_qt.cpp index 5148ed74b1..5ea94a4afe 100644 --- a/audio/midiparser_qt.cpp +++ b/audio/midiparser_qt.cpp @@ -133,7 +133,7 @@ uint32 MidiParser_QT::readNextEvent(EventInfo &info) { case 0x2: case 0x3: // Note event - info.event = 0x90 | ((control >> 24) & 0x1F); + info.event = 0x90 | getChannel((control >> 24) & 0x1F); info.basic.param1 = ((control >> 18) & 0x3F) + 32; info.basic.param2 = (control >> 11) & 0x7F; info.length = (info.basic.param2 == 0) ? 0 : (control & 0x7FF); @@ -143,7 +143,7 @@ uint32 MidiParser_QT::readNextEvent(EventInfo &info) { // Controller if (((control >> 16) & 0xFF) == 32) { // Pitch bend - info.event = 0xE0 | ((control >> 24) & 0x1F); + info.event = 0xE0 | getChannel((control >> 24) & 0x1F); // Actually an 8.8 fixed point number int16 value = (int16)(control & 0xFFFF); @@ -162,7 +162,7 @@ uint32 MidiParser_QT::readNextEvent(EventInfo &info) { info.basic.param2 = value >> 7; } else { // Regular controller - info.event = 0xB0 | ((control >> 24) & 0x1F); + info.event = 0xB0 | getChannel((control >> 24) & 0x1F); info.basic.param1 = (control >> 16) & 0xFF; info.basic.param2 = (control >> 8) & 0xFF; } @@ -175,7 +175,7 @@ uint32 MidiParser_QT::readNextEvent(EventInfo &info) { case 0x9: { // Extended note event uint32 extra = readUint32(); - info.event = 0x90 | ((control >> 16) & 0xFFF); + info.event = 0x90 | getChannel((control >> 16) & 0xFFF); info.basic.param1 = (control >> 8) & 0xFF; info.basic.param2 = (extra >> 22) & 0x7F; info.length = (info.basic.param2 == 0) ? 0 : (extra & 0x3FFFFF); @@ -184,7 +184,7 @@ uint32 MidiParser_QT::readNextEvent(EventInfo &info) { case 0xA: { // Extended controller uint32 extra = readUint32(); - info.event = 0xB0 | ((control >> 16) & 0xFFF); + info.event = 0xB0 | getChannel((control >> 16) & 0xFFF); info.basic.param1 = (extra >> 16) & 0x3FFF; info.basic.param2 = (extra >> 8) & 0xFF; // ??? break; @@ -219,7 +219,12 @@ void MidiParser_QT::handleGeneralEvent(EventInfo &info, uint32 control) { // Note Request // Currently we're only using the GM number from the request assert(dataSize == 84); - info.event = 0xC0 | part; + + // We have to remap channels because GM needs percussion to be on the + // percussion channel but QuickTime can have that anywhere. + allocateChannel(part, READ_BE_UINT32(_position._playPos + 80)); + + info.event = 0xC0 | getChannel(part); info.basic.param1 = READ_BE_UINT32(_position._playPos + 80); break; case 5: // Tune Difference @@ -235,6 +240,44 @@ void MidiParser_QT::handleGeneralEvent(EventInfo &info, uint32 control) { _position._playPos += dataSize + 4; } +void MidiParser_QT::allocateChannel(uint32 part, uint32 instrument) { + if (instrument == 0x4001) { + // Drum Kit -> Percussion Channel + if (isChannelAllocated(9)) + warning("Multiple QuickTime MIDI percussion channels"); + + _channelMap[part] = 9; + } else { + // Normal Instrument -> First Free Channel + for (uint32 i = 0; i < 16; i++) { + // 9 is reserved for Percussion + if (i == 9 || isChannelAllocated(i)) + continue; + + _channelMap[part] = i; + return; + } + + error("Failed to allocate channel for QuickTime MIDI"); + } +} + +byte MidiParser_QT::getChannel(uint32 part) const { + return _channelMap[part]; +} + +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; +} + +void MidiParser_QT::resetTracking() { + _channelMap.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)); diff --git a/audio/midiparser_qt.h b/audio/midiparser_qt.h index 5ab89bd4e7..25c2ae99fb 100644 --- a/audio/midiparser_qt.h +++ b/audio/midiparser_qt.h @@ -25,6 +25,7 @@ #include "audio/midiparser.h" #include "common/array.h" +#include "common/hashmap.h" #include "common/quicktime.h" /** @@ -57,6 +58,7 @@ public: protected: // MidiParser void parseNextEvent(EventInfo &info); + void resetTracking(); // QuickTimeParser SampleDesc *readSampleDesc(Track *track, uint32 format, uint32 descSize); @@ -80,10 +82,17 @@ private: uint32 readNextEvent(EventInfo &info); void handleGeneralEvent(EventInfo &info, uint32 control); + void allocateChannel(uint32 part, uint32 instrument); + byte getChannel(uint32 part) const; + bool isChannelAllocated(byte channel) const; + byte *readWholeTrack(Common::QuickTimeParser::Track *track, uint32 &trackSize); Common::Array _trackInfo; + typedef Common::HashMap ChannelMap; + ChannelMap _channelMap; + void initFromContainerTracks(); void initCommon(); uint32 readUint32(); -- cgit v1.2.3 From bb45b24f8877865e47f793d254371728aa399849 Mon Sep 17 00:00:00 2001 From: Matthew Hoops Date: Wed, 12 Sep 2012 13:22:33 -0400 Subject: AUDIO: Implement simple dynamic QuickTime MIDI channel remapping Needed for IHNM Mac sounds --- audio/midiparser_qt.cpp | 251 ++++++++++++++++++++++++++++++++++-------------- audio/midiparser_qt.h | 30 +++++- 2 files changed, 206 insertions(+), 75 deletions(-) (limited to 'audio') diff --git a/audio/midiparser_qt.cpp b/audio/midiparser_qt.cpp index 5ea94a4afe..226495bee3 100644 --- a/audio/midiparser_qt.cpp +++ b/audio/midiparser_qt.cpp @@ -106,21 +106,25 @@ bool MidiParser_QT::loadFromContainerFile(const Common::String &fileName) { } void MidiParser_QT::parseNextEvent(EventInfo &info) { - info.event = 0; + uint32 delta = 0; - while (info.event == 0) { - if (_position._playPos >= _trackInfo[_activeTrack].data + _trackInfo[_activeTrack].size) { - // Manually insert end of track when we reach the end - info.event = 0xFF; - info.ext.type = 0x2F; - return; - } + while (_queuedEvents.empty()) + delta = readNextEvent(); - info.delta = readNextEvent(info); - } + info = _queuedEvents.pop(); + info.delta = delta; } -uint32 MidiParser_QT::readNextEvent(EventInfo &info) { +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) { @@ -129,43 +133,16 @@ uint32 MidiParser_QT::readNextEvent(EventInfo &info) { // Rest // We handle this by recursively adding up all the rests into the // next event's delta - return readNextEvent(info) + (control & 0xFFFFFF); + return readNextEvent() + (control & 0xFFFFFF); case 0x2: case 0x3: // Note event - info.event = 0x90 | getChannel((control >> 24) & 0x1F); - info.basic.param1 = ((control >> 18) & 0x3F) + 32; - info.basic.param2 = (control >> 11) & 0x7F; - info.length = (info.basic.param2 == 0) ? 0 : (control & 0x7FF); + handleNoteEvent((control >> 24) & 0x1F, ((control >> 18) & 0x3F) + 32, (control >> 11) & 0x7F, control & 0x7FF); break; case 0x4: case 0x5: // Controller - if (((control >> 16) & 0xFF) == 32) { - // Pitch bend - info.event = 0xE0 | getChannel((control >> 24) & 0x1F); - - // Actually an 8.8 fixed point number - int16 value = (int16)(control & 0xFFFF); - - if (value < -0x200 || value > 0x1FF) { - warning("QuickTime MIDI pitch bend value (%d) out of range, clipping", value); - value = CLIP(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; - } else { - // Regular controller - info.event = 0xB0 | getChannel((control >> 24) & 0x1F); - info.basic.param1 = (control >> 16) & 0xFF; - info.basic.param2 = (control >> 8) & 0xFF; - } + handleControllerEvent((control >> 16) & 0xFF, (control >> 24) & 0x1F, (control >> 8) & 0xFF, control & 0xFF); break; case 0x6: case 0x7: @@ -175,18 +152,13 @@ uint32 MidiParser_QT::readNextEvent(EventInfo &info) { case 0x9: { // Extended note event uint32 extra = readUint32(); - info.event = 0x90 | getChannel((control >> 16) & 0xFFF); - info.basic.param1 = (control >> 8) & 0xFF; - info.basic.param2 = (extra >> 22) & 0x7F; - info.length = (info.basic.param2 == 0) ? 0 : (extra & 0x3FFFFF); + handleNoteEvent((control >> 16) & 0xFFF, (control >> 8) & 0xFF, (extra >> 22) & 0x7F, extra & 0x3FFFFF); break; } case 0xA: { // Extended controller uint32 extra = readUint32(); - info.event = 0xB0 | getChannel((control >> 16) & 0xFFF); - info.basic.param1 = (extra >> 16) & 0x3FFF; - info.basic.param2 = (extra >> 8) & 0xFF; // ??? + handleControllerEvent((extra >> 16) & 0x3FFF, (control >> 16) & 0xFFF, (extra >> 8) & 0xFF, extra & 0xFF); break; } case 0xB: @@ -202,14 +174,70 @@ uint32 MidiParser_QT::readNextEvent(EventInfo &info) { break; case 0xF: // General - handleGeneralEvent(info, control); + handleGeneralEvent(control); break; } return 0; } -void MidiParser_QT::handleGeneralEvent(EventInfo &info, uint32 control) { +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 == 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(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; @@ -222,10 +250,7 @@ void MidiParser_QT::handleGeneralEvent(EventInfo &info, uint32 control) { // We have to remap channels because GM needs percussion to be on the // percussion channel but QuickTime can have that anywhere. - allocateChannel(part, READ_BE_UINT32(_position._playPos + 80)); - - info.event = 0xC0 | getChannel(part); - info.basic.param1 = READ_BE_UINT32(_position._playPos + 80); + definePart(part, READ_BE_UINT32(_position._playPos + 80)); break; case 5: // Tune Difference case 8: // MIDI Channel @@ -240,30 +265,67 @@ void MidiParser_QT::handleGeneralEvent(EventInfo &info, uint32 control) { _position._playPos += dataSize + 4; } -void MidiParser_QT::allocateChannel(uint32 part, uint32 instrument) { - if (instrument == 0x4001) { - // Drum Kit -> Percussion Channel - if (isChannelAllocated(9)) - warning("Multiple QuickTime MIDI percussion channels"); +void MidiParser_QT::definePart(uint32 part, uint32 instrument) { + if (_partMap.contains(part)) + warning("QuickTime MIDI part %d being redefined", part); - _channelMap[part] = 9; - } else { + 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 - for (uint32 i = 0; i < 16; i++) { - // 9 is reserved for Percussion - if (i == 9 || isChannelAllocated(i)) - continue; + if (allChannelsAllocated()) + deallocateFreeChannel(); + + for (int i = 0; i < 16; i++) + if (i != 9 && !isChannelAllocated(i)) // 9 is reserved for Percussion + return i; - _channelMap[part] = 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("Failed to allocate channel for QuickTime MIDI"); } + + error("Exceeded QuickTime MIDI channel polyphony"); } -byte MidiParser_QT::getChannel(uint32 part) const { - return _channelMap[part]; +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 { @@ -274,8 +336,57 @@ bool MidiParser_QT::isChannelAllocated(byte channel) const { return false; } +bool MidiParser_QT::allChannelsAllocated() const { + // Less than 16? Have room + if (_channelMap.size() < 15) + return false; + + // 16? See if one of those + if (_channelMap.size() == 15) + for (ChannelMap::const_iterator it = _channelMap.begin(); it != _channelMap.end(); it++) + if (it->_value == 9) + return false; + + 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) { diff --git a/audio/midiparser_qt.h b/audio/midiparser_qt.h index 25c2ae99fb..c2403f31c5 100644 --- a/audio/midiparser_qt.h +++ b/audio/midiparser_qt.h @@ -26,6 +26,7 @@ #include "audio/midiparser.h" #include "common/array.h" #include "common/hashmap.h" +#include "common/queue.h" #include "common/quicktime.h" /** @@ -70,6 +71,13 @@ private: uint32 timeScale; }; + struct PartStatus { + uint32 instrument; + byte volume; + byte pan; + uint16 pitchBend; + }; + class MIDISampleDesc : public SampleDesc { public: MIDISampleDesc(Common::QuickTimeParser::Track *parentTrack, uint32 codecTag); @@ -79,18 +87,30 @@ private: uint32 _requestSize; }; - uint32 readNextEvent(EventInfo &info); - void handleGeneralEvent(EventInfo &info, uint32 control); + 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 allocateChannel(uint32 part, uint32 instrument); - byte getChannel(uint32 part) const; + 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 _trackInfo; + Common::Queue _queuedEvents; + + typedef Common::HashMap PartMap; + PartMap _partMap; - typedef Common::HashMap ChannelMap; + typedef Common::HashMap ChannelMap; ChannelMap _channelMap; void initFromContainerTracks(); -- cgit v1.2.3 From ca6fdb0807bf9ee46b6341685c129e27451db0f6 Mon Sep 17 00:00:00 2001 From: Matthew Hoops Date: Sun, 18 Nov 2012 14:20:24 -0500 Subject: AUDIO: Ignore QT MIDI control change 0 QuickTime docs don't list it, and we shouldn't treat it as a bank select --- audio/midiparser_qt.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'audio') diff --git a/audio/midiparser_qt.cpp b/audio/midiparser_qt.cpp index 226495bee3..3e95703728 100644 --- a/audio/midiparser_qt.cpp +++ b/audio/midiparser_qt.cpp @@ -196,7 +196,12 @@ void MidiParser_QT::handleControllerEvent(uint32 control, uint32 part, byte intP byte channel = getChannel(part); EventInfo info; - if (control == 32) { + 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; -- cgit v1.2.3 From b285db4db33943e0355ea33b1504d237ede0aa2a Mon Sep 17 00:00:00 2001 From: Matthew Hoops Date: Sun, 18 Nov 2012 14:43:35 -0500 Subject: AUDIO: Cleanup MidiParser_QT a bit --- audio/midiparser_qt.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'audio') diff --git a/audio/midiparser_qt.cpp b/audio/midiparser_qt.cpp index 3e95703728..2b7ef63e37 100644 --- a/audio/midiparser_qt.cpp +++ b/audio/midiparser_qt.cpp @@ -109,7 +109,7 @@ void MidiParser_QT::parseNextEvent(EventInfo &info) { uint32 delta = 0; while (_queuedEvents.empty()) - delta = readNextEvent(); + delta += readNextEvent(); info = _queuedEvents.pop(); info.delta = delta; @@ -342,16 +342,17 @@ bool MidiParser_QT::isChannelAllocated(byte channel) const { } bool MidiParser_QT::allChannelsAllocated() const { - // Less than 16? Have room + // Less than 15? We definitely have room if (_channelMap.size() < 15) return false; - // 16? See if one of those + // 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; } -- cgit v1.2.3 From a396e180975c0e2c8fbea44af94cdcaa576a4191 Mon Sep 17 00:00:00 2001 From: Matthew Hoops Date: Mon, 3 Dec 2012 09:07:33 -0500 Subject: AUDIO: Finish comment --- audio/midiparser_qt.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'audio') diff --git a/audio/midiparser_qt.cpp b/audio/midiparser_qt.cpp index 2b7ef63e37..6214d28f95 100644 --- a/audio/midiparser_qt.cpp +++ b/audio/midiparser_qt.cpp @@ -453,7 +453,7 @@ void MidiParser_QT::initCommon() { } byte *MidiParser_QT::readWholeTrack(Common::QuickTimeParser::Track *track, uint32 &trackSize) { - // This just goes through all chunks and + // This just goes through all chunks and appends them together Common::MemoryWriteStreamDynamic output; uint32 curSample = 0; -- cgit v1.2.3 From 17f923532533c8a094e1bf0a0efa7369b20ef48a Mon Sep 17 00:00:00 2001 From: Matthew Hoops Date: Thu, 13 Dec 2012 18:30:05 -0500 Subject: AUDIO: Add some general documentation on MidiParser_QT --- audio/midiparser_qt.h | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) (limited to 'audio') diff --git a/audio/midiparser_qt.h b/audio/midiparser_qt.h index c2403f31c5..d6d0f40a48 100644 --- a/audio/midiparser_qt.h +++ b/audio/midiparser_qt.h @@ -30,7 +30,20 @@ #include "common/quicktime.h" /** - * The QuickTime MIDI version of MidiParser. + * 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: -- cgit v1.2.3