0x47 stub
[scummvm-innocent.git] / backends / midi / quicktime.cpp
blob5f0ee37361289a6fa257c3fec02f78faf1fa699a
1 /* ScummVM - Graphic Adventure Engine
3 * ScummVM is the legal property of its developers, whose names
4 * are too numerous to list here. Please refer to the COPYRIGHT
5 * file distributed with this source distribution.
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 * $URL$
22 * $Id$
25 #if defined(MACOSX) || defined(macintosh)
27 // HACK to disable deprecated warnings under Mac OS X 10.5.
28 // Apple depracted the complete QuickTime Music/MIDI API.
29 // Apps are supposed to use CoreAudio & CoreMIDI. We do support
30 // those, but while QT Midi support is still around, there is no
31 // reason to disable this driver. If they really ditch the API in 10.6,
32 // we can still release binaries with this driver disabled/removed.
33 #include <AvailabilityMacros.h>
34 #undef DEPRECATED_ATTRIBUTE
35 #define DEPRECATED_ATTRIBUTE
39 #include "common/endian.h"
40 #include "common/util.h"
41 #include "sound/musicplugin.h"
42 #include "sound/mpu401.h"
44 #if defined(MACOSX)
45 #include <QuickTime/QuickTimeComponents.h>
46 #include <QuickTime/QuickTimeMusic.h>
47 #else
48 #include <QuickTimeComponents.h>
49 #include <QuickTimeMusic.h>
50 #endif
53 // FIXME: the following disables reverb support in the QuickTime / CoreAudio
54 // midi backends. For some reasons, reverb will suck away a *lot* of CPU time.
55 // Until we know for sure what is causing this and if there is a better way to
56 // fix the problem, we just disable all reverb for these backends.
57 #define COREAUDIO_REVERB_HACK
60 /* QuickTime MIDI driver
61 * Original QuickTime support by Florent Boudet <flobo@ifrance.com>
62 * Modified by Max Horn
64 class MidiDriver_QT : public MidiDriver_MPU401 {
65 public:
66 MidiDriver_QT();
68 int open();
69 void close();
70 void send(uint32 b);
71 void setPitchBendRange (byte channel, uint range);
73 private:
74 NoteAllocator qtNoteAllocator;
75 NoteChannel qtNoteChannel[16];
76 NoteRequest simpleNoteRequest;
78 // Pitch bend tracking. Necessary since QTMA handles
79 // pitch bending so differently from MPU401.
80 uint16 _pitchbend [16];
81 byte _pitchbend_range [16];
83 void dispose();
86 MidiDriver_QT::MidiDriver_QT() {
87 qtNoteAllocator = 0;
88 for (int i = 0; i < 16; i++)
89 qtNoteChannel[i] = 0;
92 int MidiDriver_QT::open() {
93 ComponentResult qtErr = noErr;
95 if (qtNoteAllocator != 0)
96 return MERR_ALREADY_OPEN;
98 qtNoteAllocator = OpenDefaultComponent(kNoteAllocatorComponentType, 0);
99 if (qtNoteAllocator == 0)
100 goto bail;
102 simpleNoteRequest.info.flags = 0;
103 WRITE_BE_UINT16(& simpleNoteRequest.info.polyphony, 11); // simultaneous tones
104 WRITE_BE_UINT32(& simpleNoteRequest.info.typicalPolyphony, 0x00010000);
106 qtErr = NAStuffToneDescription(qtNoteAllocator, 1, &simpleNoteRequest.tone);
107 if (qtErr != noErr)
108 goto bail;
110 for (int i = 0; i < 16; i++) {
111 qtErr = NANewNoteChannel(qtNoteAllocator, &simpleNoteRequest, &(qtNoteChannel[i]));
112 if ((qtErr != noErr) || (qtNoteChannel[i] == 0))
113 goto bail;
115 qtErr = NAResetNoteChannel(qtNoteAllocator, qtNoteChannel[i]);
116 if (qtErr != noErr)
117 goto bail;
119 // Channel 10 (i.e. index 9) is the drum channel. Set it up as such.
120 // All other channels default to piano.
121 qtErr = NASetInstrumentNumber(qtNoteAllocator, qtNoteChannel[i], (i == 9 ? kFirstDrumkit : kFirstGMInstrument) + 1);
122 if (qtErr != noErr)
123 goto bail;
125 return 0;
127 bail:
128 error("Init QT failed %x %x %d", (int)qtNoteAllocator, (int)qtNoteChannel, (int)qtErr);
130 dispose();
132 return MERR_DEVICE_NOT_AVAILABLE;
135 void MidiDriver_QT::close() {
136 MidiDriver_MPU401::close();
137 dispose();
140 void MidiDriver_QT::send(uint32 b) {
141 MusicMIDIPacket midPacket;
142 unsigned char *midiCmd = midPacket.data;
143 midPacket.length = 3;
144 midiCmd[3] = (b & 0xFF000000) >> 24;
145 midiCmd[2] = (b & 0x00FF0000) >> 16;
146 midiCmd[1] = (b & 0x0000FF00) >> 8;
147 midiCmd[0] = (b & 0x000000FF);
149 unsigned char chanID = midiCmd[0] & 0x0F;
150 switch (midiCmd[0] & 0xF0) {
151 case 0x80: // Note off
152 NAPlayNote(qtNoteAllocator, qtNoteChannel[chanID], midiCmd[1], 0);
153 break;
155 case 0x90: // Note on
156 NAPlayNote(qtNoteAllocator, qtNoteChannel[chanID], midiCmd[1], midiCmd[2]);
157 break;
159 case 0xB0: // Effect
160 switch (midiCmd[1]) {
161 case 0x01: // Modulation
162 NASetController(qtNoteAllocator, qtNoteChannel[chanID], kControllerModulationWheel, midiCmd[2] << 8);
163 break;
165 case 0x07: // Volume
166 NASetController(qtNoteAllocator, qtNoteChannel[chanID], kControllerVolume, midiCmd[2] << 8);
167 break;
169 case 0x0A: // Pan
170 NASetController(qtNoteAllocator, qtNoteChannel[chanID], kControllerPan, (midiCmd[2] << 1) + 256);
171 break;
173 case 0x40: // Sustain on/off
174 NASetController(qtNoteAllocator, qtNoteChannel[chanID], kControllerSustain, midiCmd[2]);
175 break;
177 case 0x5b: // ext effect depth
178 #if !defined(COREAUDIO_REVERB_HACK)
179 NASetController(qtNoteAllocator, qtNoteChannel[chanID], kControllerReverb, midiCmd[2] << 8);
180 #endif
181 break;
183 case 0x5d: // chorus depth
184 NASetController(qtNoteAllocator, qtNoteChannel[chanID], kControllerChorus, midiCmd[2] << 8);
185 break;
187 case 0x7b: // mode message all notes off
188 // FIXME: For unknown reasons, the following code causes weird
189 // troubles. In particular, in the Sam&Max intro, all channel are
190 // sent this event. As a result, not only does the music stop - it
191 // also never resumes!!! This is very odd.
192 /* for (int i = 0; i < 128; i++)
193 NAPlayNote(qtNoteAllocator, qtNoteChannel[chanID], i, 0);
195 break;
196 case 0x64:
197 case 0x65:
198 case 0x06:
199 case 0x26:
200 // pitch bend changes - ignore those for now
201 break;
203 case 0x12: // Occurs in Scumm games
204 case 0x77: // Occurs in Simon2
205 case 0x79: // Occurs in Simon1
206 // What are these ?!? Ignore it for now
207 break;
209 default:
210 warning("Unknown MIDI effect: %08x", (int)b);
211 break;
213 break;
215 case 0xC0: // Program change
216 NASetInstrumentNumber(qtNoteAllocator, qtNoteChannel[chanID], midiCmd[1] + (chanID == 9 ? kFirstDrumkit : kFirstGMInstrument));
217 break;
219 case 0xE0:{ // Pitch bend
220 // QuickTime specifies pitchbend in semitones, using 8.8 fixed point values;
221 // but iMuse sends us the pitch bend data as 0-16383. which has to be mapped
222 // to +/- 12 semitones. Based on this, we first center the input data, then
223 // multiply it by a factor. If all was right, the factor would be 3/8, but for
224 // mysterious reasons the actual factor we have to use is more like 1/32 or 3/64.
225 // Maybe the QT docs are right, and
226 _pitchbend[chanID] = ((uint16) midiCmd[1] | (uint16) (midiCmd[2] << 7));
227 long theBend = ((long) _pitchbend[chanID] - 0x2000) * _pitchbend_range[chanID] / 32;
229 NASetController(qtNoteAllocator, qtNoteChannel[chanID], kControllerPitchBend, theBend);
231 break;
233 default:
234 error("Unknown Command: %08x", (int)b);
235 NASendMIDI(qtNoteAllocator, qtNoteChannel[chanID], &midPacket);
236 break;
240 void MidiDriver_QT::setPitchBendRange (byte channel, uint range) {
241 if (_pitchbend_range[channel] == range)
242 return;
243 _pitchbend_range[channel] = range;
245 long theBend = _pitchbend[channel];
246 theBend = (theBend - 0x2000) * range / 32;
247 NASetController(qtNoteAllocator, qtNoteChannel[channel], kControllerPitchBend, theBend);
250 void MidiDriver_QT::dispose() {
251 for (int i = 0; i < 16; i++) {
252 if (qtNoteChannel[i] != 0)
253 NADisposeNoteChannel(qtNoteAllocator, qtNoteChannel[i]);
254 qtNoteChannel[i] = 0;
257 if (qtNoteAllocator != 0) {
258 CloseComponent(qtNoteAllocator);
259 qtNoteAllocator = 0;
264 // Plugin interface
266 class QuickTimeMusicPlugin : public MusicPluginObject {
267 public:
268 const char *getName() const {
269 return "QuickTime";
272 const char *getId() const {
273 return "qt";
276 MusicDevices getDevices() const;
277 Common::Error createInstance(Audio::Mixer *mixer, MidiDriver **mididriver) const;
280 MusicDevices QuickTimeMusicPlugin::getDevices() const {
281 MusicDevices devices;
282 // TODO: Return a different music type depending on the configuration
283 // TODO: List the available devices
284 devices.push_back(MusicDevice(this, "", MT_GM));
285 return devices;
288 Common::Error QuickTimeMusicPlugin::createInstance(Audio::Mixer *mixer, MidiDriver **mididriver) const {
289 *mididriver = new MidiDriver_QT();
291 return Common::kNoError;
294 MidiDriver *MidiDriver_QT_create(Audio::Mixer *mixer) {
295 MidiDriver *mididriver;
297 QuickTimeMusicPlugin p;
298 p.createInstance(mixer, &mididriver);
300 return mididriver;
303 //#if PLUGIN_ENABLED_DYNAMIC(QUICKTIME)
304 //REGISTER_PLUGIN_DYNAMIC(QUICKTIME, PLUGIN_TYPE_MUSIC, QuickTimeMusicPlugin);
305 //#else
306 REGISTER_PLUGIN_STATIC(QUICKTIME, PLUGIN_TYPE_MUSIC, QuickTimeMusicPlugin);
307 //#endif
309 #endif // MACOSX || macintosh