1 /*
2 Copyright (C) 2016 Apple Inc. All Rights Reserved.
3 See LICENSE.txt for this sample’s licensing information
4 
5 Abstract:
6 Part of Core Audio AUBase Classes
7 */
8 
9 #include "AUMIDIBase.h"
10 #include <CoreMIDI/CoreMIDI.h>
11 #include "CAXException.h"
12 
13 //temporaray location
14 enum
15 {
16 	kMidiMessage_NoteOff 			= 0x80,
17 	kMidiMessage_NoteOn 			= 0x90,
18 	kMidiMessage_PolyPressure 		= 0xA0,
19 	kMidiMessage_ControlChange 		= 0xB0,
20 	kMidiMessage_ProgramChange 		= 0xC0,
21 	kMidiMessage_ChannelPressure 	= 0xD0,
AUInstrumentBase(AudioComponentInstance inInstance,UInt32 numInputs,UInt32 numOutputs,UInt32 numGroups,UInt32 numParts)22 	kMidiMessage_PitchWheel 		= 0xE0,
23 
24 	kMidiController_AllSoundOff			= 120,
25 	kMidiController_ResetAllControllers	= 121,
26 	kMidiController_AllNotesOff			= 123
27 };
28 
29 AUMIDIBase::AUMIDIBase(AUBase* inBase)
30 	: mAUBaseInstance (*inBase)
31 {
32 #if CA_AUTO_MIDI_MAP
33 	mMapManager = new CAAUMIDIMapManager();
34 #endif
35 }
36 
37 AUMIDIBase::~AUMIDIBase()
38 {
39 #if CA_AUTO_MIDI_MAP
40 	if (mMapManager)
41 		delete mMapManager;
42 #endif
43 }
44 
45 #if TARGET_API_MAC_OSX
~AUInstrumentBase()46 OSStatus			AUMIDIBase::DelegateGetPropertyInfo(AudioUnitPropertyID				inID,
47 														AudioUnitScope					inScope,
48 														AudioUnitElement				inElement,
49 														UInt32 &						outDataSize,
50 														Boolean &						outWritable)
51 {
52 	OSStatus result = noErr;
53 
54 	switch (inID) {
55 #if !TARGET_OS_IPHONE
56 	case kMusicDeviceProperty_MIDIXMLNames:
57 		ca_require(inScope == kAudioUnitScope_Global, InvalidScope);
58 		ca_require(inElement == 0, InvalidElement);
59 		if (GetXMLNames(NULL) == noErr) {
60 			outDataSize = sizeof(CFURLRef);
61 			outWritable = false;
62 		} else
63 			result = kAudioUnitErr_InvalidProperty;
64 		break;
65 #endif
66 #if CA_AUTO_MIDI_MAP
67 	case kAudioUnitProperty_AllParameterMIDIMappings:
68 		ca_require(inScope == kAudioUnitScope_Global, InvalidScope);
69 		ca_require(inElement == 0, InvalidElement);
70 		outWritable = true;
71 		outDataSize = sizeof (AUParameterMIDIMapping)*mMapManager->NumMaps();
72 		result = noErr;
73 		break;
74 
75 	case kAudioUnitProperty_HotMapParameterMIDIMapping:
76 		ca_require(inScope == kAudioUnitScope_Global, InvalidScope);
77 		ca_require(inElement == 0, InvalidElement);
78 		outWritable = true;
79 		outDataSize = sizeof (AUParameterMIDIMapping);
80 		result = noErr;
81 		break;
82 
83 	case kAudioUnitProperty_AddParameterMIDIMapping:
84 		ca_require(inScope == kAudioUnitScope_Global, InvalidScope);
85 		ca_require(inElement == 0, InvalidElement);
86 		outWritable = true;
87 		outDataSize = sizeof (AUParameterMIDIMapping);
88 		result = noErr;
89 		break;
90 
91 	case kAudioUnitProperty_RemoveParameterMIDIMapping:
92 		ca_require(inScope == kAudioUnitScope_Global, InvalidScope);
93 		ca_require(inElement == 0, InvalidElement);
94 		outWritable = true;
95 		outDataSize = sizeof (AUParameterMIDIMapping);
96 		result = noErr;
97 		break;
98 #endif
99 
100 	default:
101 		result = kAudioUnitErr_InvalidProperty;
102 		break;
103 	}
104 	return result;
105 
106 #if CA_AUTO_MIDI_MAP || (!TARGET_OS_IPHONE)
107 InvalidScope:
108 	return kAudioUnitErr_InvalidScope;
109 InvalidElement:
110 	return kAudioUnitErr_InvalidElement;
111 #endif
112 }
113 
114 OSStatus			AUMIDIBase::DelegateGetProperty(	AudioUnitPropertyID 			inID,
115 														AudioUnitScope 					inScope,
116 														AudioUnitElement			 	inElement,
117 														void *							outData)
118 {
119 	OSStatus result;
120 
121 	switch (inID) {
122 #if !TARGET_OS_IPHONE
123 	case kMusicDeviceProperty_MIDIXMLNames:
Initialize()124 		ca_require(inScope == kAudioUnitScope_Global, InvalidScope);
125 		ca_require(inElement == 0, InvalidElement);
126 		result = GetXMLNames((CFURLRef *)outData);
127 		break;
128 #endif
129 #if CA_AUTO_MIDI_MAP
130 	case kAudioUnitProperty_AllParameterMIDIMappings:{
131 		ca_require(inScope == kAudioUnitScope_Global, InvalidScope);
132 		ca_require(inElement == 0, InvalidElement);
133 		AUParameterMIDIMapping*  maps =  (static_cast<AUParameterMIDIMapping*>(outData));
134 		mMapManager->GetMaps(maps);
135 //		printf ("GETTING MAPS\n");
136 //		mMapManager->Print();
137 		result = noErr;
138 		break;
139 	}
140 
141 	case kAudioUnitProperty_HotMapParameterMIDIMapping:{
142 		ca_require(inScope == kAudioUnitScope_Global, InvalidScope);
143 		ca_require(inElement == 0, InvalidElement);
144 		AUParameterMIDIMapping *  map =  (static_cast<AUParameterMIDIMapping*>(outData));
Cleanup()145 		mMapManager->GetHotParameterMap (*map);
146 		result = noErr;
147 		break;
148 	}
149 #endif
150 
Reset(AudioUnitScope inScope,AudioUnitElement inElement)151 	default:
152 		result = kAudioUnitErr_InvalidProperty;
153 		break;
154 	}
155 	return result;
156 
157 #if CA_AUTO_MIDI_MAP || (!TARGET_OS_IPHONE)
158 InvalidScope:
159 	return kAudioUnitErr_InvalidScope;
160 InvalidElement:
161 	return kAudioUnitErr_InvalidElement;
162 #endif
163 }
164 
165 OSStatus			AUMIDIBase::DelegateSetProperty(	AudioUnitPropertyID 			inID,
166 														AudioUnitScope 					inScope,
167 														AudioUnitElement			 	inElement,
168 														const void *					inData,
169 														UInt32							inDataSize)
170 {
171 	OSStatus result;
172 
173 	switch (inID) {
174 #if CA_AUTO_MIDI_MAP
175 		case kAudioUnitProperty_AddParameterMIDIMapping:{
176 			ca_require(inScope == kAudioUnitScope_Global, InvalidScope);
177 			ca_require(inElement == 0, InvalidElement);
178 			AUParameterMIDIMapping * maps = (AUParameterMIDIMapping*)inData;
179 			mMapManager->SortedInsertToParamaterMaps (maps, (inDataSize / sizeof(AUParameterMIDIMapping)), mAUBaseInstance);
180 			mAUBaseInstance.PropertyChanged (kAudioUnitProperty_AllParameterMIDIMappings, kAudioUnitScope_Global, 0);
181 			result = noErr;
182 			break;
PerformEvents(const AudioTimeStamp & inTimeStamp)183 		}
184 
185 		case kAudioUnitProperty_RemoveParameterMIDIMapping:{
186 			ca_require(inScope == kAudioUnitScope_Global, InvalidScope);
187 			ca_require(inElement == 0, InvalidElement);
188 			AUParameterMIDIMapping * maps = (AUParameterMIDIMapping*)inData;
189 			bool didChange;
190 			mMapManager->SortedRemoveFromParameterMaps(maps, (inDataSize / sizeof(AUParameterMIDIMapping)), didChange);
191 			if (didChange)
192 				mAUBaseInstance.PropertyChanged (kAudioUnitProperty_AllParameterMIDIMappings, kAudioUnitScope_Global, 0);
193 			result = noErr;
194 			break;
195 		}
196 
197 		case kAudioUnitProperty_HotMapParameterMIDIMapping:{
198 			ca_require(inScope == kAudioUnitScope_Global, InvalidScope);
199 			ca_require(inElement == 0, InvalidElement);
200 			AUParameterMIDIMapping & map = *((AUParameterMIDIMapping*)inData);
201 			mMapManager->SetHotMapping (map);
202 			result = noErr;
203 			break;
204 		}
205 		case kAudioUnitProperty_AllParameterMIDIMappings:{
206 			ca_require(inScope == kAudioUnitScope_Global, InvalidScope);
207 			ca_require(inElement == 0, InvalidElement);
208 			AUParameterMIDIMapping * mappings = (AUParameterMIDIMapping*)inData;
209 			mMapManager->ReplaceAllMaps (mappings, (inDataSize / sizeof(AUParameterMIDIMapping)), mAUBaseInstance);
210 			result = noErr;
211 			break;
212 		}
213 #endif
214 
215 	default:
216 		result = kAudioUnitErr_InvalidProperty;
217 		break;
218 	}
219 	return result;
220 #if CA_AUTO_MIDI_MAP
221 	InvalidScope:
222 		return kAudioUnitErr_InvalidScope;
223 	InvalidElement:
224 		return kAudioUnitErr_InvalidElement;
225 #endif
226 }
227 
228 
229 
230 #endif //TARGET_API_MAC_OSX
231 
232 
233 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
234 #pragma mark ____MidiDispatch
235 
236 
237 inline const Byte *	NextMIDIEvent(const Byte *event, const Byte *end)
238 {
239 	Byte c = *event;
240 	switch (c >> 4) {
Render(AudioUnitRenderActionFlags & ioActionFlags,const AudioTimeStamp & inTimeStamp,UInt32 inNumberFrames)241 	default:	// data byte -- assume in sysex
242 		while ((*++event & 0x80) == 0 && event < end)
243 			;
244 		break;
245 	case 0x8:
246 	case 0x9:
247 	case 0xA:
248 	case 0xB:
249 	case 0xE:
250 		event += 3;
251 		break;
252 	case 0xC:
253 	case 0xD:
254 		event += 2;
255 		break;
256 	case 0xF:
257 		switch (c) {
258 		case 0xF0:
259 			while ((*++event & 0x80) == 0 && event < end)
260 				;
261 			break;
262 		case 0xF1:
263 		case 0xF3:
264 			event += 2;
265 			break;
266 		case 0xF2:
267 			event += 3;
268 			break;
269 		default:
270 			++event;
271 			break;
272 		}
ValidFormat(AudioUnitScope inScope,AudioUnitElement inElement,const CAStreamBasicDescription & inNewFormat)273 	}
274 	return (event >= end) ? end : event;
275 }
276 
277 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
278 //	AUMIDIBase::HandleMIDIPacketList
279 //
280 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
281 OSStatus			AUMIDIBase::HandleMIDIPacketList(const MIDIPacketList *pktlist)
282 {
283 	if (!mAUBaseInstance.IsInitialized()) return kAudioUnitErr_Uninitialized;
284 
285 	int nPackets = pktlist->numPackets;
286 	const MIDIPacket *pkt = pktlist->packet;
287 
288 	while (nPackets-- > 0) {
289 		const Byte *event = pkt->data, *packetEnd = event + pkt->length;
290 		long startFrame = (long)pkt->timeStamp;
StreamFormatWritable(AudioUnitScope scope,AudioUnitElement element)291 		while (event < packetEnd) {
292 			Byte status = event[0];
293 			if (status & 0x80) {
294 				// really a status byte (not sysex continuation)
295 				HandleMidiEvent(status & 0xF0, status & 0x0F, event[1], event[2], static_cast<UInt32>(startFrame));
296 					// note that we're generating a bogus channel number for system messages (0xF0-FF)
297 			}
298 			event = NextMIDIEvent(event, packetEnd);
299 		}
300 		pkt = reinterpret_cast<const MIDIPacket *>(packetEnd);
301 	}
302 	return noErr;
303 }
304 
GetPartElement(AudioUnitElement inPartElement)305 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
306 //	AUMIDIBase::HandleMidiEvent
307 //
308 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
309 OSStatus 	AUMIDIBase::HandleMidiEvent(UInt8 status, UInt8 channel, UInt8 data1, UInt8 data2, UInt32 inStartFrame)
310 {
311 	if (!mAUBaseInstance.IsInitialized()) return kAudioUnitErr_Uninitialized;
312 
313 #if CA_AUTO_MIDI_MAP
314 // you potentially have a choice to make here - if a param mapping matches, do you still want to process the
315 // MIDI event or not. The default behaviour is to continue on with the MIDI event.
316 	if (mMapManager->HandleHotMapping (status, channel, data1, mAUBaseInstance)) {
317 		mAUBaseInstance.PropertyChanged (kAudioUnitProperty_HotMapParameterMIDIMapping, kAudioUnitScope_Global, 0);
318 	}
319 	else {
320 		mMapManager->FindParameterMapEventMatch(status, channel, data1, data2, inStartFrame, mAUBaseInstance);
321 	}
322 #endif
323 
324 	OSStatus result = noErr;
325 
326 	switch(status)
327 	{
328 		case kMidiMessage_NoteOn:
329 			if(data2)
330 			{
331 				result = HandleNoteOn(channel, data1, data2, inStartFrame);
332 			}
333 			else
334 			{
335 				// zero velocity translates to note off
336 				result = HandleNoteOff(channel, data1, data2, inStartFrame);
337 			}
338 			break;
339 
340 		case kMidiMessage_NoteOff:
341 			result = HandleNoteOff(channel, data1, data2, inStartFrame);
342 			break;
343 
344 		default:
345 			result = HandleNonNoteEvent (status, channel, data1, data2, inStartFrame);
346 			break;
347 	}
348 
349 	return result;
350 }
351 
352 OSStatus	AUMIDIBase::HandleNonNoteEvent (UInt8 status, UInt8 channel, UInt8 data1, UInt8 data2, UInt32 inStartFrame)
353 {
354 	OSStatus result = noErr;
355 
356 	switch (status)
357 	{
358 		case kMidiMessage_PitchWheel:
359 			result = HandlePitchWheel(channel, data1, data2, inStartFrame);
GetElForNoteID(NoteInstanceID inNoteID)360 			break;
361 
362 		case kMidiMessage_ProgramChange:
363 			result = HandleProgramChange(channel, data1);
364 			break;
365 
366 		case kMidiMessage_ChannelPressure:
367 			result = HandleChannelPressure(channel, data1, inStartFrame);
368 			break;
369 
370 		case kMidiMessage_ControlChange:
371 		{
372 			switch (data1) {
373 				case kMidiController_AllNotesOff:
374 					result = HandleAllNotesOff(channel);
375 					break;
StartNote(MusicDeviceInstrumentID inInstrument,MusicDeviceGroupID inGroupID,NoteInstanceID * outNoteInstanceID,UInt32 inOffsetSampleFrame,const MusicDeviceNoteParams & inParams)376 
377 				case kMidiController_ResetAllControllers:
378 					result = HandleResetAllControllers(channel);
379 					break;
380 
381 				case kMidiController_AllSoundOff:
382 					result = HandleAllSoundOff(channel);
383 					break;
384 
385 				default:
386 					result = HandleControlChange(channel, data1, data2, inStartFrame);
387 					break;
388 			}
389 			break;
390 		}
391 		case kMidiMessage_PolyPressure:
392 			result = HandlePolyPressure (channel, data1, data2, inStartFrame);
393 			break;
394 	}
395 	return result;
396 }
397 
398 OSStatus 	AUMIDIBase::SysEx (const UInt8 *	inData,
399 										UInt32			inLength)
400 {
401 	if (!mAUBaseInstance.IsInitialized()) return kAudioUnitErr_Uninitialized;
402 
403 	return HandleSysEx(inData, inLength );
404 }
405 
406 
407 
408 #if TARGET_OS_MAC
409 	#if __LP64__
410 		// comp instance, parameters in forward order
411 		#define PARAM(_typ, _name, _index, _nparams) \
412 			_typ _name = *(_typ *)&params->params[_index + 1];
413 	#else
414 		// parameters in reverse order, then comp instance
415 		#define PARAM(_typ, _name, _index, _nparams) \
416 			_typ _name = *(_typ *)&params->params[_nparams - 1 - _index];
417 	#endif
418 #elif TARGET_OS_WIN32
419 		// (no comp instance), parameters in forward order
420 		#define PARAM(_typ, _name, _index, _nparams) \
StopNote(MusicDeviceGroupID inGroupID,NoteInstanceID inNoteInstanceID,UInt32 inOffsetSampleFrame)421 			_typ _name = *(_typ *)&params->params[_index];
422 #endif
423 
424 #if !CA_USE_AUDIO_PLUGIN_ONLY
425 OSStatus			AUMIDIBase::ComponentEntryDispatch(	ComponentParameters *			params,
426 															AUMIDIBase *				This)
427 {
428 	if (This == NULL) return kAudio_ParamError;
429 
430 	OSStatus result;
431 
432 	switch (params->what) {
433 	case kMusicDeviceMIDIEventSelect:
434 		{
435 			PARAM(UInt32, pbinStatus, 0, 4);
436 			PARAM(UInt32, pbinData1, 1, 4);
437 			PARAM(UInt32, pbinData2, 2, 4);
438 			PARAM(UInt32, pbinOffsetSampleFrame, 3, 4);
439 			result = This->MIDIEvent(pbinStatus, pbinData1, pbinData2, pbinOffsetSampleFrame);
440 		}
441 		break;
442 	case kMusicDeviceSysExSelect:
443 		{
444 			PARAM(const UInt8 *, pbinData, 0, 2);
445 			PARAM(UInt32, pbinLength, 1, 2);
446 			result = This->SysEx(pbinData, pbinLength);
447 		}
448 		break;
449 
450 	default:
451 		result = badComponentSelector;
452 		break;
453 	}
454 
SendPedalEvent(MusicDeviceGroupID inGroupID,UInt32 inEventType,UInt32 inOffsetSampleFrame)455 	return result;
456 }
457 #endif
458