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 Public Utility Classes
7 */
8 
9 #ifndef __CAAUMIDIMap_h_
10 #define __CAAUMIDIMap_h_
11 
12 #include <AudioUnit/AudioUnitProperties.h>
13 #include <algorithm>
14 
15 /*
16 enum {
17 	kAUParameterMIDIMapping_AnyChannelFlag		= (1L << 0),
18 		// If this flag is set and mStatus is a MIDI channel message, then the MIDI channel number
19 		// in the status byte is ignored; the mapping is from the specified MIDI message on ANY channel.
20 
21 	kAUParameterMIDIMapping_AnyNoteFlag			= (1L << 1),
22 		// If this flag is set and mStatus is a Note On, Note Off, or Polyphonic Pressure message,
23 		// the message's note number is ignored; the mapping is from ANY note number.
24 
25 	kAUParameterMIDIMapping_SubRange			= (1L << 2),
26 		// set this flag if the midi control should map only to a sub-range of the parameter's value
27 		// then specify that range in the mSubRangeMin and mSubRangeMax members
28 
29 	kAUParameterMIDIMapping_Toggle				= (1L << 3),
30 		// this is only useful for boolean typed parameters. When set, it means that the parameter's
31 		// value should be toggled (if true, become false and vice versa) when the represented MIDI message
32 		// is received
33 
34 	kAUParameterMIDIMapping_Bipolar				= (1L << 4),
35 		// this can be set to when mapping a MIDI Controller to indicate that the parameter (typically a boolean
36 		// style parameter) will only have its value changed to either the on or off state of a MIDI controller message
37 		// (0 < 64 is off, 64 < 127 is on) such as the sustain pedal. The seeting of the next flag
38 		// (kAUParameterMIDIMapping_Bipolar_On) determine whether the parameter is mapped to the on or off
39 		// state of the controller
40 	kAUParameterMIDIMapping_Bipolar_On			= (1L << 5)
41 		// only a valid flag if kAUParameterMIDIMapping_Bipolar is set
42 };
43 
44 // The reserved fields here are being used to reserve space (as well as align to 64 bit size) for future use
45 // When/If these fields are used, the names of the fields will be changed to reflect their functionality
46 // so, apps should NOT refer to these reserved fields directly by name
47 typedef struct AUParameterMIDIMapping
48 {
49 	AudioUnitScope			mScope;
50 	AudioUnitElement		mElement;
51 	AudioUnitParameterID	mParameterID;
52 	UInt32					mFlags;
53 	Float32					mSubRangeMin;
54 	Float32					mSubRangeMax;
55 	UInt8					mStatus;
56 	UInt8					mData1;
57 	UInt8					reserved1; // MUST be set to zero
58 	UInt8					reserved2; // MUST be set to zero
59 	UInt32					reserved3; // MUST be set to zero
60 } AUParameterMIDIMapping;
61 */
62 
63 /*
64 Parameter To MIDI Mapping Properties
65 These properties are used to:
66 Describe a current set of mappings between MIDI messages and Parameter value setting
67 Create a mapping between a parameter and a MIDI message through either:
68 - explicitly adding (or removing) the mapping
69 - telling the AU to hot-map the next MIDI message to a specified Parameter
70 	The same MIDI Message can map to one or more parameters
71 	One Parameter can be mapped from multiple MIDI messages
72 
73 	In general usage, these properties only apply to AU's that implement the MIDI API
74 	AU Instruments (type=='aumu') and Music Effects (type == 'aumf')
75 
76 	These properties are used in the Global scope. The scope and element members of the structure describe
77 	the scope and element of the parameter. In all usages, mScope, mElement and mParameterID must be
78 	correctly specified.
79 
80 
81 	* The AUParameterMIDIMapping Structure
82 
83 	Command				mStatus			mData1
84 	Note Off			0x8n			Note Num
85 	Note On				0x9n			Note Num
86 	Key Pressure		0xAn			Note Num
87 	Control Change		0xBn			ControllerID
88 	Patch Change		0xCn			Patch Num
89 	Channel Pressure	DxDn			0 (Unused)
90 	Pitch Bend			0xEn			0 (Unused)
91 
92 	(where n is 0-0xF to correspond to MIDI channels 1-16)
93 
94 		Details:
95 
96 	In general MIDI Commands can be mapped to either a specific channel as specified in the mStatus bit.
97 	If the kAUParameterMIDIMapping_AnyChannelFlag bit is set mStatus is a MIDI channel message, then the
98 	MIDI channel number in the status byte is ignored; the mapping is from the specified MIDI message on ANY channel.
99 
100 	For note commands (note on, note off, key pressure), the MIDI message can trigger either with just a specific
101 	note number, or any note number if the kAUParameterMIDIMapping_AnyNoteFlag bit is set. In these instances, the
102 	note number is used as the trigger value (for instance, a note message could be used to set the
103 											  cut off frequency of a filter).
104 
105  The Properties:
106 
107 	kAudioUnitProperty_AllParameterMIDIMappings							array of AUParameterMIDIMapping (read/write)
108 	This property is used to both retreive and set the current mapping state between (some/many/all of) its parameters
109 	and MIDI messages. When set, it should replace any previous mapped settings the AU had.
110 
111 	If this property is implemented by a non-MIDI capable AU (such as an 'aufx' type), then the property is
112 	read only, and recommends a suggested set of mappings for the host to perform. In this case, it is the
113 	host's responsibility to map MIDI message to the AU parameters. As described previously, there are a set
114 	of default mappings (see AudioToolbox/AUMIDIController.h) that the host can recommend to the user
115 	in this circumstance.
116 
117 	This property's size will be very dynamic, depending on the number of mappings currently in affect, so the
118 	caller should always get the size of the property first before retrieving it. The AU should return an error
119 	if the caller doesn't provide enough space to return all of the current mappings.
120 
121 	kAudioUnitProperty_AddParameterMIDIMapping							array of AUParameterMIDIMapping (write only)
122 	This property is used to Add mappings to the existing set of mappings the AU possesses. It does NOT replace
123 	any existing mappings.
124 
125 	kAudioUnitProperty_RemoveParameterMIDIMapping						array of AUParameterMIDIMapping (write only)
126 	This property is used to remove the specified mappings from the AU. If a mapping is specified that does not
127 	currently exist in the AU, then it should just be ignored.
128 
129 	kAudioUnitProperty_HotMapParameterMIDIMapping								AUParameterMIDIMapping (read/write)
130 	This property is used in two ways, determined by the value supplied by the caller.
131 	(1) If a mapping struct is provided, then that struct provides *all* of the information that the AU should
132 	use to map the parameter, *except* for the MIDI message. The AU should then listen for the next MIDI message
133 	and associate that MIDI message with the supplied AUParameter mapping. When this MIDI message is received and
134 	the mapping made, the AU should also issue a notification on this property
135 	(kAudioUnitProperty_HotMapParameterMIDIMapping) to indicate to the host that the mapping has been made. The host
136 	can then retrieve the mapping that was made by getting the value of this property.
137 
138 	To avoid possible confusion, it is recommended that once the host has retrieved this mapping (if it is
139 	presenting a UI to describe the mappings for example), that it then clears the mapping state as described next.
140 
141 	Thus, the only time this property will return a valid value is when the AU has made a mapping. If the AU's mapping
142 	state has been cleared (or it has not been asked to make a mapping), then the AU should return
143 	kAudioUnitErr_InvalidPropertyValue if the host tries to read this value.
144 
145 	(2) If the value passed in is NULL, then if the AU had a parameter that it was in the process of mapping, it
146 	should disregard that (stop listening to the MIDI messages to create a mapping) and discard the partially
147 	mapped struct. If the value is NULL and the AU is not in the process of mapping, the AU can ignore the request.
148 
149 	At all times, the _AllMappings property will completely describe the current known state of the AU's mappings
150 	of MIDI messages to parameters.
151 */
152 
153 
154 /*
155 	When mapping, it is recommended that LSB controllers are in general not mapped (ie. the controller range of 32 < 64)
156 	as many host parsers will map 14 bit control values. If you know (or can present an option) that the host deals with
157 	7 bit controllers only, then these controller ID's can be mapped of course.
158 */
159 
160 
161 struct MIDIValueTransformer {
162 	virtual double  tolinear(double) = 0;
163 	virtual double  fromlinear(double) = 0;
164 #if DEBUG
165 	// suppress warning
~MIDIValueTransformerMIDIValueTransformer166 	virtual ~MIDIValueTransformer() { }
167 #endif
168 };
169 
170 struct MIDILinearTransformer : public MIDIValueTransformer {
tolinearMIDILinearTransformer171 	virtual double  tolinear(double x) { return x; }
fromlinearMIDILinearTransformer172 	virtual double  fromlinear(double x) { return x; }
173 };
174 
175 struct MIDILogTransformer : public MIDIValueTransformer {
tolinearMIDILogTransformer176 	virtual double  tolinear(double x) { return log(std::max(x, .00001)); }
fromlinearMIDILogTransformer177 	virtual double  fromlinear(double x) { return exp(x); }
178 };
179 
180 struct MIDIExpTransformer : public MIDIValueTransformer {
tolinearMIDIExpTransformer181 	virtual double  tolinear(double x) { return exp(x); }
fromlinearMIDIExpTransformer182 	virtual double  fromlinear(double x) { return log(std::max(x, .00001)); }
183 };
184 
185 struct MIDISqrtTransformer : public MIDIValueTransformer {
tolinearMIDISqrtTransformer186 	virtual double  tolinear(double x) { return x < 0. ? -(sqrt(-x)) : sqrt(x); }
fromlinearMIDISqrtTransformer187 	virtual double  fromlinear(double x) { return x < 0. ? -(x * x) : x * x; }
188 };
189 
190 struct MIDISquareTransformer : public MIDIValueTransformer {
tolinearMIDISquareTransformer191 	virtual double  tolinear(double x) { return x < 0. ? -(x * x) : x * x; }
fromlinearMIDISquareTransformer192 	virtual double  fromlinear(double x) { return x < 0. ? -(sqrt(-x)) : sqrt(x); }
193 };
194 
195 struct MIDICubeRtTransformer : public MIDIValueTransformer {
tolinearMIDICubeRtTransformer196 	virtual double  tolinear(double x) { return x < 0. ? -(pow(-x, 1./3.)) : pow(x, 1./3.); }
fromlinearMIDICubeRtTransformer197 	virtual double  fromlinear(double x) { return x * x * x; }
198 };
199 
200 struct MIDICubeTransformer : public MIDIValueTransformer {
tolinearMIDICubeTransformer201 	virtual double  tolinear(double x) { return x * x * x; }
fromlinearMIDICubeTransformer202 	virtual double  fromlinear(double x) { return x < 0. ? -(pow(-x, 1./3.)) : pow(x, 1./3.); }
203 };
204 
205 
206 class CAAUMIDIMap : public AUParameterMIDIMapping {
207 
208 public:
209 // variables for more efficient parsing of MIDI to Param value
210 	Float32						mMinValue;
211 	Float32						mMaxValue;
212 	MIDIValueTransformer		*mTransType;
213 
214 // methods
215 	static MIDIValueTransformer *GetTransformer (UInt32 inFlags);
216 
CAAUMIDIMap()217 								CAAUMIDIMap() { memset(this, 0, sizeof(CAAUMIDIMap)); }
CAAUMIDIMap(const AUParameterMIDIMapping & inMap)218 								CAAUMIDIMap (const AUParameterMIDIMapping& inMap)
219 								{
220 									memset(this, 0, sizeof(CAAUMIDIMap));
221 									memcpy (this, &inMap, sizeof(inMap));
222 								}
CAAUMIDIMap(AudioUnitScope inScope,AudioUnitElement inElement,AudioUnitParameterID inParam)223 								CAAUMIDIMap (AudioUnitScope inScope, AudioUnitElement inElement, AudioUnitParameterID inParam)
224 								{
225 									memset(this, 0, sizeof(CAAUMIDIMap));
226 									mScope = inScope;
227 									mElement = inElement;
228 									mParameterID = inParam;
229 								}
230 
231 
IsValid()232 	bool						IsValid () const { return mStatus != 0; }
233 
234 	// returns -1 if any channel bit is set
Channel()235 	SInt32						Channel () const { return IsAnyChannel() ? -1 : (mStatus & 0xF); }
IsAnyChannel()236 	bool						IsAnyChannel () const {
237 									return mFlags & kAUParameterMIDIMapping_AnyChannelFlag;
238 								}
239 									// preserves the existing channel info in the status byte
240 									// preserves any previously set mFlags value
SetAnyChannel(bool inFlag)241 	void						SetAnyChannel (bool inFlag)
242 								{
243 									if (inFlag)
244 										mFlags |= kAUParameterMIDIMapping_AnyChannelFlag;
245 									else
246 										mFlags &= ~kAUParameterMIDIMapping_AnyChannelFlag;
247 								}
248 
IsAnyNote()249 	bool						IsAnyNote () const {
250 									return (mFlags & kAUParameterMIDIMapping_AnyNoteFlag) != 0;
251 								}
252 									// preserves the existing key num in the mData1 byte
253 									// preserves any previously set mFlags value
SetAnyNote(bool inFlag)254 	void						SetAnyNote (bool inFlag)
255 								{
256 									if (inFlag)
257 										mFlags |= kAUParameterMIDIMapping_AnyNoteFlag;
258 									else
259 										mFlags &= ~kAUParameterMIDIMapping_AnyNoteFlag;
260 								}
261 
IsToggle()262 	bool						IsToggle() const { return (mFlags & kAUParameterMIDIMapping_Toggle) != 0; }
SetToggle(bool inFlag)263 	void						SetToggle (bool inFlag)
264 								{
265 									if (inFlag)
266 										mFlags |= kAUParameterMIDIMapping_Toggle;
267 									else
268 										mFlags &= ~kAUParameterMIDIMapping_Toggle;
269 								}
270 
IsBipolar()271 	bool						IsBipolar() const { return (mFlags & kAUParameterMIDIMapping_Bipolar) != 0; }
272 									// inUseOnValue is valid ONLY if inFlag is true
273 	void						SetBipolar (bool inFlag, bool inUseOnValue = false)
274 								{
275 									if (inFlag) {
276 										mFlags |= kAUParameterMIDIMapping_Bipolar;
277 										if (inUseOnValue)
278 											mFlags |= kAUParameterMIDIMapping_Bipolar_On;
279 										else
280 											mFlags &= ~kAUParameterMIDIMapping_Bipolar_On;
281 									} else {
282 										mFlags &= ~kAUParameterMIDIMapping_Bipolar;
283 										mFlags &= ~kAUParameterMIDIMapping_Bipolar_On;
284 									}
285 								}
IsBipolar_OnValue()286 	bool						IsBipolar_OnValue () const { return (mFlags & kAUParameterMIDIMapping_Bipolar_On) != 0; }
287 
IsSubRange()288 	bool						IsSubRange () const { return (mFlags & kAUParameterMIDIMapping_SubRange) != 0; }
SetSubRange(Float32 inStartValue,Float32 inStopValue)289 	void						SetSubRange (Float32 inStartValue, Float32 inStopValue)
290 								{
291 									mFlags |= kAUParameterMIDIMapping_SubRange;
292 
293 									mSubRangeMin = inStartValue;
294 									mSubRangeMax = inStopValue;
295 								}
296 
SetParamRange(Float32 minValue,Float32 maxValue)297 	void						SetParamRange(Float32 minValue, Float32 maxValue)
298 								{
299 									mMinValue = minValue;
300 									mMaxValue = maxValue;
301 								}
302 
303 								// this will retain the subrange values previously set.
SetSubRange(bool inFlag)304 	void						SetSubRange (bool inFlag)
305 								{
306 									if (inFlag)
307 										mFlags |= kAUParameterMIDIMapping_SubRange;
308 									else
309 										mFlags &= ~kAUParameterMIDIMapping_SubRange;
310 								}
311 
IsAnyValue()312 	bool						IsAnyValue() const{return !IsBipolar();}
IsOnValue()313 	bool						IsOnValue() const{return IsBipolar_OnValue();}
IsOffValue()314 	bool						IsOffValue() const{return IsBipolar();}
315 
IsNoteOff()316 	bool						IsNoteOff () const { return ((mStatus & 0xF0) == 0x80); }
IsNoteOn()317 	bool						IsNoteOn () const { return ((mStatus & 0xF0) == 0x90); }
318 
IsKeyPressure()319 	bool						IsKeyPressure () const { return ((mStatus & 0xF0) == 0xA0); }
320 
IsKeyEvent()321 	bool						IsKeyEvent () const { return (mStatus > 0x7F) && (mStatus < 0xB0); }
322 
IsPatchChange()323 	bool						IsPatchChange () const { return ((mStatus & 0xF0) == 0xC0); }
IsChannelPressure()324 	bool						IsChannelPressure () const { return ((mStatus & 0xF0) == 0xD0); }
IsPitchBend()325 	bool						IsPitchBend () const { return ((mStatus & 0xF0) == 0xE0); }
IsControlChange()326 	bool						IsControlChange () const { return ((mStatus & 0xF0) == 0xB0); }
327 
328 
SetControllerOnValue()329 	void						SetControllerOnValue(){SetBipolar(true,true);}
SetControllerOffValue()330 	void						SetControllerOffValue(){SetBipolar(true,false);}
SetControllerAnyValue()331 	void						SetControllerAnyValue(){SetBipolar(false,false);}
332 
333 	// All of these Set calls will reset the mFlags field based on the
334 	// anyChannel param value
335 	void						SetNoteOff (UInt8 key, SInt8 channel, bool anyChannel = false)
336 								{
337 									mStatus = 0x80 | (channel & 0xF);
338 									mData1 = key;
339 									mFlags = (anyChannel ? kAUParameterMIDIMapping_AnyChannelFlag : 0);
340 
341 								}
342 
343 	void						SetNoteOn (UInt8 key, SInt8 channel, bool anyChannel = false)
344 								{
345 									mStatus = 0x90 | (channel & 0xF);
346 									mData1 = key;
347 									mFlags = (anyChannel ? kAUParameterMIDIMapping_AnyChannelFlag : 0);
348 								}
349 
350 	void						SetPolyKey (UInt8 key, SInt8 channel, bool anyChannel = false)
351 								{
352 									mStatus = 0xA0 | (channel & 0xF);
353 									mData1 = key;
354 									mFlags = (anyChannel ? kAUParameterMIDIMapping_AnyChannelFlag : 0);
355 								}
356 
357 	void						SetControlChange (UInt8 controllerID, SInt8 channel, bool anyChannel = false)
358 								{
359 									mStatus = 0xB0 | (channel & 0xF);
360 									mData1 = controllerID;
361 									mFlags = (anyChannel ? kAUParameterMIDIMapping_AnyChannelFlag : 0);
362 								}
363 
364 	void						SetPatchChange (UInt8 patchChange, SInt8 channel, bool anyChannel = false)
365 								{
366 									mStatus = 0xC0 | (channel & 0xF);
367 									mData1 = patchChange;
368 									mFlags = (anyChannel ? kAUParameterMIDIMapping_AnyChannelFlag : 0);
369 								}
370 
371 	void						SetChannelPressure (SInt8 channel, bool anyChannel = false)
372 								{
373 									mStatus = 0xD0 | (channel & 0xF);
374 									mData1 = 0;
375 									mFlags = (anyChannel ? kAUParameterMIDIMapping_AnyChannelFlag : 0);
376 								}
377 
378 	void						SetPitchBend (SInt8 channel, bool anyChannel = false)
379 								{
380 									mStatus = 0xE0 | (channel & 0xF);
381 									mData1 = 0;
382 									mFlags = (anyChannel ? kAUParameterMIDIMapping_AnyChannelFlag : 0);
383 								}
384 
385 
ParamValueFromMIDILinear(Float32 inLinearValue)386 	Float32						ParamValueFromMIDILinear (Float32		inLinearValue) const
387 	{
388 								Float32 low, high;
389 								if (IsSubRange()){
390 									low = mSubRangeMin;
391 									high = mSubRangeMax;
392 								}
393 								else {
394 									low = mMinValue;
395 									high = mMaxValue;
396 								}
397 
398 
399 								// WE ARE ASSUMING YOU HAVE SET THIS UP PROPERLY!!!!! (or this will crash cause it will be NULL)
400 								return (Float32)mTransType->fromlinear((inLinearValue * (high - low)) + low);
401 	}
402 
403 
404 		// The CALLER of this method must ensure that the status byte's MIDI Command (ignoring the channel) matches!!!
405 	bool						MIDI_Matches (UInt8 inChannel, UInt8 inData1, UInt8 inData2, Float32 &outLinear) const;
406 
407 	void						Print () const;
408 
409 	void						Save (CFPropertyListRef &outData) const;
410 	void						Restore (CFDictionaryRef inData);
411 
412 	static void					SaveAsMapPList (AudioUnit						inUnit,
413 											const AUParameterMIDIMapping		* inMappings,
414 											UInt32								inNumMappings,
415 											CFPropertyListRef					&outData,
416 											CFStringRef							inName = NULL);
417 
418 									// inNumMappings describes how much memory is allocated in outMappings
419 	static void					RestoreFromMapPList (const CFDictionaryRef			inData,
420 														AUParameterMIDIMapping		* outMappings,
421 														UInt32						inNumMappings);
422 
423 	static UInt32				NumberOfMaps (const CFDictionaryRef inData);
424 };
425 
426 
427 	// these sorting operations sort for run-time efficiency based on the MIDI messages
428 inline bool operator== (const CAAUMIDIMap &a, const CAAUMIDIMap &b)
429 {
430 		// ignore channel first
431 	return (((a.mStatus & 0xF0) == (b.mStatus & 0xF0))
432 			&& (a.mData1 == b.mData1)
433 			&& ((a.mStatus & 0xF) == (b.mStatus & 0xf))  // now compare the channel
434 			&&  (a.mParameterID == b.mParameterID)
435 			&& (a.mElement == b.mElement)
436 			&& (a.mScope == b.mScope));
437 
438 	// reserved field comparisons - ignored until/if they are used
439 }
440 
441 inline bool operator< (const CAAUMIDIMap	&a, const CAAUMIDIMap &b)
442 {
443 	if ((a.mStatus & 0xF0) != (b.mStatus & 0xF0))
444 		return ((a.mStatus & 0xF0) < (b.mStatus & 0xF0));
445 
446 	if (a.mData1 != b.mData1)
447 		return (a.mData1 < b.mData1);
448 
449 	if ((a.mStatus & 0xF) != (b.mStatus & 0xf))  // now compare the channel
450 		return ((a.mStatus & 0xF) < (b.mStatus & 0xf));
451 
452 // reserved field comparisons - ignored until/if they are used
453 
454 //		we're sorting this by MIDI, so we don't really care how the rest is sorted
455 	return	((a.mParameterID < b.mParameterID)
456 				&& (a.mElement < b.mElement)
457 				&& (a.mScope < b.mScope));
458 }
459 
460 
461 
462 class CompareMIDIMap {
compare(const CAAUMIDIMap & a,const CAAUMIDIMap & b)463 	int compare (const CAAUMIDIMap &a, const CAAUMIDIMap &b)
464 	{
465 		if ((a.mStatus & 0xF0) < (b.mStatus & 0xF0))
466 			return -1;
467 		if ((a.mStatus & 0xF0) > (b.mStatus & 0xF0))
468 			return 1;
469 
470 			// note event
471 		if (a.mStatus < 0xB0 || a.mStatus >= 0xD0)
472 			return 0;
473 		if (a.mData1 > b.mData1) return 1;
474 		if (a.mData1 < b.mData1) return -1;
475 		return 0;
476 	}
477 
478 public:
operator()479 	bool operator() (const CAAUMIDIMap &a, const CAAUMIDIMap &b) {
480 		return compare (a, b) < 0;
481 	}
Finish(const CAAUMIDIMap & a,const CAAUMIDIMap & b)482 	bool Finish (const CAAUMIDIMap &a, const CAAUMIDIMap &b) {
483 		return compare (a, b) != 0;
484 	}
485 };
486 
487 
488 /*
489 	usage: To find potential mapped events for a given status byte, where mMMapEvents is a sorted vec
490 	CompareMIDIMap comparObj;
491 	sortVecIter lower_iter = std::lower_bound(mMMapEvents.begin(), mMMapEvents.end(), inStatusByte, compareObj);
492 	for (;lower_iter < mMMapEvents.end(); ++lower_iter) {
493 		// then, see if we go out of the status byte range, using the Finish method
494 		if (compareObj.Finish(map, tempMap)) // tempMap is a CAAUMIDIMap object with the status/dataByte 1 set
495 			break;
496 	// ...
497 	}
498 
499 	in the for loop you call the MIDI_Matches call, to see if the MIDI event matches a given AUMIDIParam mapping
500 	special note: you HAVE to transform note on (with vel zero) events to the note off status byte
501 */
502 
503 #endif
504