scummvm/backends/midi/quicktime.cpp
Max Horn 47280d9433 Updated copyright
svn-id: r16398
2005-01-01 16:09:25 +00:00

253 lines
7.3 KiB
C++

/* ScummVM - Scumm Interpreter
* Copyright (C) 2001-2005 The ScummVM project
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* $Header$
*/
#if defined(MACOSX) || defined(macintosh)
#include "sound/mpu401.h"
#include "common/util.h"
#if defined(MACOSX)
#include <QuickTime/QuickTimeComponents.h>
#include <QuickTime/QuickTimeMusic.h>
#else
#include <QuickTimeComponents.h>
#include <QuickTimeMusic.h>
#endif
// FIXME - the following disables reverb support in the QuickTime / CoreAudio
// midi backends. For some reasons, reverb will suck away a *lot* of CPU time.
// Until we know for sure what is causing this and if there is a better way to
// fix the problem, we just disable all reverb for these backends.
#define COREAUDIO_REVERB_HACK
/* QuickTime MIDI driver
* Original QuickTime support by Florent Boudet <flobo@ifrance.com>
* Modified by Max Horn
*/
class MidiDriver_QT : public MidiDriver_MPU401 {
public:
MidiDriver_QT();
int open();
void close();
void send(uint32 b);
void setPitchBendRange (byte channel, uint range);
private:
NoteAllocator qtNoteAllocator;
NoteChannel qtNoteChannel[16];
NoteRequest simpleNoteRequest;
// Pitch bend tracking. Necessary since QTMA handles
// pitch bending so differently from MPU401.
uint16 _pitchbend [16];
byte _pitchbend_range [16];
void dispose();
};
MidiDriver_QT::MidiDriver_QT() {
qtNoteAllocator = NULL;
for (int i = 0; i < 16; i++)
qtNoteChannel[i] = NULL;
}
int MidiDriver_QT::open() {
ComponentResult qtErr = noErr;
if (qtNoteAllocator != NULL)
return MERR_ALREADY_OPEN;
qtNoteAllocator = OpenDefaultComponent(kNoteAllocatorComponentType, 0);
if (qtNoteAllocator == NULL)
goto bail;
simpleNoteRequest.info.flags = 0;
simpleNoteRequest.info.polyphony = 11; // simultaneous tones
simpleNoteRequest.info.typicalPolyphony = 0x00010000;
qtErr = NAStuffToneDescription(qtNoteAllocator, 1, &simpleNoteRequest.tone);
if (qtErr != noErr)
goto bail;
for (int i = 0; i < 16; i++) {
qtErr = NANewNoteChannel(qtNoteAllocator, &simpleNoteRequest, &(qtNoteChannel[i]));
if ((qtErr != noErr) || (qtNoteChannel[i] == NULL))
goto bail;
qtErr = NAResetNoteChannel(qtNoteAllocator, qtNoteChannel[i]);
if (qtErr != noErr)
goto bail;
// Channel 10 (i.e. index 9) is the drum channel. Set it up as such.
// All other channels default to piano.
qtErr = NASetInstrumentNumber(qtNoteAllocator, qtNoteChannel[i], (i == 9 ? kFirstDrumkit : kFirstGMInstrument) + 1);
if (qtErr != noErr)
goto bail;
}
return 0;
bail:
error("Init QT failed %x %x %d\n", (int)qtNoteAllocator, (int)qtNoteChannel, (int)qtErr);
dispose();
return MERR_DEVICE_NOT_AVAILABLE;
}
void MidiDriver_QT::close()
{
MidiDriver_MPU401::close();
dispose();
}
void MidiDriver_QT::send(uint32 b) {
MusicMIDIPacket midPacket;
unsigned char *midiCmd = midPacket.data;
midPacket.length = 3;
midiCmd[3] = (b & 0xFF000000) >> 24;
midiCmd[2] = (b & 0x00FF0000) >> 16;
midiCmd[1] = (b & 0x0000FF00) >> 8;
midiCmd[0] = (b & 0x000000FF);
unsigned char chanID = midiCmd[0] & 0x0F;
switch (midiCmd[0] & 0xF0) {
case 0x80: // Note off
NAPlayNote(qtNoteAllocator, qtNoteChannel[chanID], midiCmd[1], 0);
break;
case 0x90: // Note on
NAPlayNote(qtNoteAllocator, qtNoteChannel[chanID], midiCmd[1], midiCmd[2]);
break;
case 0xB0: // Effect
switch (midiCmd[1]) {
case 0x01: // Modulation
NASetController(qtNoteAllocator, qtNoteChannel[chanID], kControllerModulationWheel, midiCmd[2] << 8);
break;
case 0x07: // Volume
NASetController(qtNoteAllocator, qtNoteChannel[chanID], kControllerVolume, midiCmd[2] << 8);
break;
case 0x0A: // Pan
NASetController(qtNoteAllocator, qtNoteChannel[chanID], kControllerPan, (midiCmd[2] << 1) + 256);
break;
case 0x40: // Sustain on/off
NASetController(qtNoteAllocator, qtNoteChannel[chanID], kControllerSustain, midiCmd[2]);
break;
case 0x5b: // ext effect depth
#if !defined(COREAUDIO_REVERB_HACK)
NASetController(qtNoteAllocator, qtNoteChannel[chanID], kControllerReverb, midiCmd[2] << 8);
#endif
break;
case 0x5d: // chorus depth
NASetController(qtNoteAllocator, qtNoteChannel[chanID], kControllerChorus, midiCmd[2] << 8);
break;
case 0x7b: // mode message all notes off
// FIXME: For unknown reasons, the following code causes weird
// troubles. In particular, in the Sam&Max intro, all channel are
// sent this event. As a result, not only does the music stop - it
// also never resumes!!! This is very odd.
/* for (int i = 0; i < 128; i++)
NAPlayNote(qtNoteAllocator, qtNoteChannel[chanID], i, 0);
*/
break;
case 0x64:
case 0x65:
case 0x06:
case 0x26:
// pitch bend changes - ignore those for now
break;
case 0x12: // Occurs in Scumm games
case 0x77: // Occurs in Simon2
case 0x79: // Occurs in Simon1
// What are these ?!? Ignore it for now
break;
default:
warning("Unknown MIDI effect: %08x", (int)b);
break;
}
break;
case 0xC0: // Program change
NASetInstrumentNumber(qtNoteAllocator, qtNoteChannel[chanID], midiCmd[1] + (chanID == 9 ? kFirstDrumkit : kFirstGMInstrument));
break;
case 0xE0:{ // Pitch bend
// QuickTime specifies pitchbend in semitones, using 8.8 fixed point values;
// but iMuse sends us the pitch bend data as 0-16383. which has to be mapped
// to +/- 12 semitones. Based on this, we first center the input data, then
// multiply it by a factor. If all was right, the factor would be 3/8, but for
// mysterious reasons the actual factor we have to use is more like 1/32 or 3/64.
// Maybe the QT docs are right, and
_pitchbend[chanID] = ((uint16) midiCmd[1] | (uint16) (midiCmd[2] << 7));
long theBend = ((long) _pitchbend[chanID] - 0x2000) * _pitchbend_range[chanID] / 32;
NASetController(qtNoteAllocator, qtNoteChannel[chanID], kControllerPitchBend, theBend);
}
break;
default:
error("Unknown Command: %08x", (int)b);
NASendMIDI(qtNoteAllocator, qtNoteChannel[chanID], &midPacket);
break;
}
}
void MidiDriver_QT::setPitchBendRange (byte channel, uint range) {
if (_pitchbend_range[channel] == range)
return;
_pitchbend_range[channel] = range;
long theBend = _pitchbend[channel];
theBend = (theBend - 0x2000) * range / 32;
NASetController(qtNoteAllocator, qtNoteChannel[channel], kControllerPitchBend, theBend);
}
void MidiDriver_QT::dispose()
{
for (int i = 0; i < 16; i++) {
if (qtNoteChannel[i] != NULL)
NADisposeNoteChannel(qtNoteAllocator, qtNoteChannel[i]);
qtNoteChannel[i] = NULL;
}
if (qtNoteAllocator != NULL) {
CloseComponent(qtNoteAllocator);
qtNoteAllocator = NULL;
}
}
MidiDriver *MidiDriver_QT_create() {
return new MidiDriver_QT();
}
#endif // MACOSX || macintosh