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