1 /* ScummVM - Graphic Adventure Engine
2  *
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.
6  *
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.
11  *
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.
16  *
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.
20  *
21  */
22 
23 // Disable symbol overrides so that we can use system headers.
24 #define FORBIDDEN_SYMBOL_ALLOW_ALL
25 
26 #include "common/scummsys.h"
27 
28 #ifdef MACOSX
29 
30 #include "common/config-manager.h"
31 #include "common/error.h"
32 #include "common/textconsole.h"
33 #include "common/util.h"
34 #include "audio/musicplugin.h"
35 #include "audio/mpu401.h"
36 
37 #include <CoreMIDI/CoreMIDI.h>
38 
39 
40 
41 /*
42 For information on how to unify the CoreMidi and MusicDevice code:
43 
44 http://lists.apple.com/archives/Coreaudio-api/2005/Jun/msg00194.html
45 http://lists.apple.com/archives/coreaudio-api/2003/Mar/msg00248.html
46 http://lists.apple.com/archives/coreaudio-api/2003/Jul/msg00137.html
47 
48 */
49 
50 
51 /* CoreMIDI MIDI driver
52  * By Max Horn
53  */
54 class MidiDriver_CoreMIDI : public MidiDriver_MPU401 {
55 public:
56 	MidiDriver_CoreMIDI(ItemCount device);
57 	~MidiDriver_CoreMIDI();
58 	int open();
isOpen() const59 	bool isOpen() const { return mOutPort != 0 && mDest != 0; }
60 	void close();
61 	void send(uint32 b);
62 	void sysEx(const byte *msg, uint16 length);
63 
64 private:
65 	ItemCount mDevice;
66 	MIDIClientRef	mClient;
67 	MIDIPortRef		mOutPort;
68 	MIDIEndpointRef	mDest;
69 };
70 
MidiDriver_CoreMIDI(ItemCount device)71 MidiDriver_CoreMIDI::MidiDriver_CoreMIDI(ItemCount device)
72 	: mDevice(device), mClient(0), mOutPort(0), mDest(0) {
73 
74 	OSStatus err;
75 	err = MIDIClientCreate(CFSTR("ScummVM MIDI Driver for OS X"), NULL, NULL, &mClient);
76 }
77 
~MidiDriver_CoreMIDI()78 MidiDriver_CoreMIDI::~MidiDriver_CoreMIDI() {
79 	if (mClient)
80 		MIDIClientDispose(mClient);
81 	mClient = 0;
82 }
83 
open()84 int MidiDriver_CoreMIDI::open() {
85 	if (isOpen())
86 		return MERR_ALREADY_OPEN;
87 
88 	OSStatus err = noErr;
89 
90 	mOutPort = 0;
91 
92 	ItemCount dests = MIDIGetNumberOfDestinations();
93 	if (mDevice < dests && mClient) {
94 		mDest = MIDIGetDestination(mDevice);
95 		err = MIDIOutputPortCreate( mClient,
96 									CFSTR("scummvm_output_port"),
97 									&mOutPort);
98 	} else {
99 		return MERR_DEVICE_NOT_AVAILABLE;
100 	}
101 
102 	if (err != noErr)
103 		return MERR_CANNOT_CONNECT;
104 
105 	return 0;
106 }
107 
close()108 void MidiDriver_CoreMIDI::close() {
109 	MidiDriver_MPU401::close();
110 
111 	if (isOpen()) {
112 		MIDIPortDispose(mOutPort);
113 		mOutPort = 0;
114 		mDest = 0;
115 	}
116 }
117 
send(uint32 b)118 void MidiDriver_CoreMIDI::send(uint32 b) {
119 	assert(isOpen());
120 
121 	// Extract the MIDI data
122 	byte status_byte = (b & 0x000000FF);
123 	byte first_byte = (b & 0x0000FF00) >> 8;
124 	byte second_byte = (b & 0x00FF0000) >> 16;
125 
126 	// Generate a single MIDI packet with that data
127 	MIDIPacketList packetList;
128 	MIDIPacket *packet = &packetList.packet[0];
129 
130 	packetList.numPackets = 1;
131 
132 	packet->timeStamp = 0;
133 	packet->data[0] = status_byte;
134 	packet->data[1] = first_byte;
135 	packet->data[2] = second_byte;
136 
137 	// Compute the correct length of the MIDI command. This is important,
138 	// else things may screw up badly...
139 	switch (status_byte & 0xF0) {
140 	case 0x80:	// Note Off
141 	case 0x90:	// Note On
142 	case 0xA0:	// Polyphonic Aftertouch
143 	case 0xB0:	// Controller Change
144 	case 0xE0:	// Pitch Bending
145 		packet->length = 3;
146 		break;
147 	case 0xC0:	// Programm Change
148 	case 0xD0:	// Monophonic Aftertouch
149 		packet->length = 2;
150 		break;
151 	default:
152 		warning("CoreMIDI driver encountered unsupported status byte: 0x%02x", status_byte);
153 		packet->length = 3;
154 		break;
155 	}
156 
157 	// Finally send it out to the synthesizer.
158 	MIDISend(mOutPort, mDest, &packetList);
159 }
160 
sysEx(const byte * msg,uint16 length)161 void MidiDriver_CoreMIDI::sysEx(const byte *msg, uint16 length) {
162 	assert(isOpen());
163 
164 	byte buf[384];
165 	MIDIPacketList *packetList = (MIDIPacketList *)buf;
166 	MIDIPacket *packet = packetList->packet;
167 
168 	assert(sizeof(buf) >= sizeof(UInt32) + sizeof(MIDITimeStamp) + sizeof(UInt16) + length + 2);
169 
170 	packetList->numPackets = 1;
171 
172 	packet->timeStamp = 0;
173 
174 	// Add SysEx frame
175 	packet->length = length + 2;
176 	packet->data[0] = 0xF0;
177 	memcpy(packet->data + 1, msg, length);
178 	packet->data[length + 1] = 0xF7;
179 
180 	// Send it
181 	MIDISend(mOutPort, mDest, packetList);
182 }
183 
184 
185 // Plugin interface
186 
187 class CoreMIDIMusicPlugin : public MusicPluginObject {
188 public:
getName() const189 	const char *getName() const {
190 		return "CoreMIDI";
191 	}
192 
getId() const193 	const char *getId() const {
194 		return "coremidi";
195 	}
196 
197 	MusicDevices getDevices() const;
198 	Common::Error createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle = 0) const;
199 
200 private:
201 	bool getDeviceName(ItemCount deviceIndex, Common::String &outName) const;
202 };
203 
getDevices() const204 MusicDevices CoreMIDIMusicPlugin::getDevices() const {
205 	// TODO: Is it possible to get the music type for each device?
206 	// Maybe look at the kMIDIPropertyModel property?
207 
208 	MusicDevices devices;
209 	ItemCount deviceCount = MIDIGetNumberOfDestinations();
210 	for (ItemCount i = 0 ; i < deviceCount ; ++i) {
211 		Common::String name;
212 		if (getDeviceName(i, name))
213 			devices.push_back(MusicDevice(this, name, MT_GM));
214 	}
215 	return devices;
216 }
217 
createInstance(MidiDriver ** mididriver,MidiDriver::DeviceHandle device) const218 Common::Error CoreMIDIMusicPlugin::createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle device) const {
219 	ItemCount deviceCount = MIDIGetNumberOfDestinations();
220 	for (ItemCount i = 0 ; i < deviceCount ; ++i) {
221 		Common::String name;
222 		if (getDeviceName(i, name)) {
223 			MusicDevice md(this, name, MT_GM);
224 			if (md.getHandle() == device) {
225 				*mididriver = new MidiDriver_CoreMIDI(i);
226 				return Common::kNoError;
227 			}
228 		}
229 	}
230 
231 	return Common::kUnknownError;
232 }
233 
getDeviceName(ItemCount deviceIndex,Common::String & outName) const234 bool CoreMIDIMusicPlugin::getDeviceName(ItemCount deviceIndex, Common::String &outName) const {
235 	MIDIEndpointRef dest = MIDIGetDestination(deviceIndex);
236 	if (!dest)
237 		return false;
238 	CFStringRef name = nil;
239 	if (MIDIObjectGetStringProperty(dest, kMIDIPropertyDisplayName, &name) == noErr) {
240 		char buffer[128];
241 		if (CFStringGetCString(name, buffer, sizeof(buffer), kCFStringEncodingASCII)) {
242 			outName = buffer;
243 			CFRelease(name);
244 			return true;
245 		}
246 		CFRelease(name);
247 	}
248 	// Rather than fail use a default name
249 	warning("Failed to get name for CoreMIDi device %lu", deviceIndex);
250 	outName = Common::String::format("Unknown Device %lu", deviceIndex);
251 	return true;
252 }
253 
254 //#if PLUGIN_ENABLED_DYNAMIC(COREMIDI)
255 	//REGISTER_PLUGIN_DYNAMIC(COREMIDI, PLUGIN_TYPE_MUSIC, CoreMIDIMusicPlugin);
256 //#else
257 	REGISTER_PLUGIN_STATIC(COREMIDI, PLUGIN_TYPE_MUSIC, CoreMIDIMusicPlugin);
258 //#endif
259 
260 #endif // MACOSX
261