Simple Example Project using CoreMidi and CoreAudio

I just wrote this simple test project for a customer. This script sets up an audio graph which consists of a sampler connected to a mixer which connects to an output unit. The sampler is used to convert midi note commands into sounds from a SoundFont. The mixer unit allows a number of channels to be output through the speakers and the output unit sends the sounds to the speakers. This project just plays a middle c but you could easily play a MIDI file through the sampler. The sample project can be downloaded here. The source code is below:

Setup the Audio Graph and play the note:

  1. // Set up variables for the audio graph
  2. OSStatus result = noErr;
  3. AUNode ioNode, mixerNode, samplerNode;
  4.  
  5. // Specify the common portion of an audio unit's identify, used for all audio units
  6. // in the graph.
  7. AudioComponentDescription cd = {};
  8. cd.componentManufacturer = kAudioUnitManufacturer_Apple;
  9.  
  10. // Instantiate an audio processing graph
  11. result = NewAUGraph (&_processingGraph);
  12. NSCAssert (result == noErr, @"Unable to create an AUGraph object. Error code: %d '%.4s'", (int) result, (const char *)&result);
  13. // SAMPLER UNIT
  14. //Specify the Sampler unit, to be used as the first node of the graph
  15. cd.componentType = kAudioUnitType_MusicDevice;
  16. cd.componentSubType = kAudioUnitSubType_Sampler;
  17.  
  18. // Create a new sampler note
  19. result = AUGraphAddNode (_processingGraph, &cd, &samplerNode);
  20.  
  21. // Check for any errors
  22. NSCAssert (result == noErr, @"Unable to add the Sampler unit to the audio processing graph. Error code: %d '%.4s'", (int) result, (const char *)&result);
  23.  
  24. // IO UNIT
  25. // Specify the Output unit, to be used as the second and final node of the graph
  26. cd.componentType = kAudioUnitType_Output;
  27. cd.componentSubType = kAudioUnitSubType_RemoteIO;
  28.  
  29. // Add the Output unit node to the graph
  30. result = AUGraphAddNode (_processingGraph, &cd, &ioNode);
  31. NSCAssert (result == noErr, @"Unable to add the Output unit to the audio processing graph. Error code: %d '%.4s'", (int) result, (const char *)&result);
  32.  
  33. // MIXER UNIT
  34. // Add the mixer unit to the graph
  35. cd.componentType = kAudioUnitType_Mixer;
  36. cd.componentSubType = kAudioUnitSubType_MultiChannelMixer;
  37.  
  38. result = AUGraphAddNode (_processingGraph, &cd, &mixerNode);
  39. NSCAssert (result == noErr, @"Unable to add the Output unit to the audio processing graph. Error code: %d '%.4s'", (int) result, (const char *)&result);
  40.  
  41.  
  42. // Open the graph
  43. result = AUGraphOpen (_processingGraph);
  44. NSCAssert (result == noErr, @"Unable to open the audio processing graph. Error code: %d '%.4s'", (int) result, (const char *)&result);
  45.  
  46. // Now that the graph is open get references to all the nodes and store
  47. // them as audio units
  48.  
  49. // Get a reference to the sampler node and store it in the samplerUnit variable
  50. result = AUGraphNodeInfo (_processingGraph, samplerNode, 0, &_samplerUnit);
  51. NSCAssert (result == noErr, @"Unable to obtain a reference to the Sampler unit. Error code: %d '%.4s'", (int) result, (const char *)&result);
  52.  
  53. // Load a soundfont into the mixer unit
  54. [self loadSoundFont:@"gorts_filters" withPatch:1 withBank:kAUSampler_DefaultMelodicBankMSB withSampler:_samplerUnit];
  55.  
  56. // Create a new mixer unit. This is necessary because if we want to have more than one
  57. // sampler outputting throught the speakers
  58. result = AUGraphNodeInfo (_processingGraph, mixerNode, 0, &_mixerUnit);
  59. NSCAssert (result == noErr, @"Unable to obtain a reference to the Sampler unit. Error code: %d '%.4s'", (int) result, (const char *)&result);
  60.  
  61. // Obtain a reference to the I/O unit from its node
  62. result = AUGraphNodeInfo (_processingGraph, ioNode, 0, &_ioUnit);
  63. NSCAssert (result == noErr, @"Unable to obtain a reference to the I/O unit. Error code: %d '%.4s'", (int) result, (const char *)&result);
  64.  
  65. // Define the number of input busses on the mixer unit
  66. UInt32 busCount = 1;
  67.  
  68. // Set the input channels property on the mixer unit
  69. result = AudioUnitSetProperty (
  70. _mixerUnit,
  71. kAudioUnitProperty_ElementCount,
  72. kAudioUnitScope_Input,
  73. 0,
  74. &busCount,
  75. sizeof (busCount)
  76. );
  77. NSCAssert (result == noErr, @"AudioUnitSetProperty Set mixer bus count. Error code: %d '%.4s'", (int) result, (const char *)&result);
  78.  
  79. // Connect the sampler unit to the mixer unit
  80. result = AUGraphConnectNodeInput(_processingGraph, samplerNode, 0, mixerNode, 0);
  81.  
  82. // Set the volume of the channel
  83. AudioUnitSetParameter(_mixerUnit, kMultiChannelMixerParam_Volume, kAudioUnitScope_Input, 0, 1, 0);
  84.  
  85. NSCAssert (result == noErr, @"Couldn't connect speech synth unit output (0) to mixer input (1). Error code: %d '%.4s'", (int) result, (const char *)&result);
  86.  
  87. // Connect the output of the mixer node to the input of he io node
  88. result = AUGraphConnectNodeInput (_processingGraph, mixerNode, 0, ioNode, 0);
  89. NSCAssert (result == noErr, @"Unable to interconnect the nodes in the audio processing graph. Error code: %d '%.4s'", (int) result, (const char *)&result);
  90.  
  91. // Print a graphic version of the graph
  92. CAShow(_processingGraph);
  93.  
  94. // Start the graph
  95. result = AUGraphInitialize (_processingGraph);
  96.  
  97. NSAssert (result == noErr, @"Unable to initialze AUGraph object. Error code: %d '%.4s'", (int) result, (const char *)&result);
  98.  
  99. // Start the graph
  100. result = AUGraphStart (_processingGraph);
  101. NSAssert (result == noErr, @"Unable to start audio processing graph. Error code: %d '%.4s'", (int) result, (const char *)&result);
  102.  
  103.  
  104. // Play middle c on the sampler - sampler unit to send the command to, midi command i.e. note on, note number, velocity
  105. MusicDeviceMIDIEvent(_samplerUnit, 0x90, 60, 127, 0);

Load a SoundFont into a Sampler Unit

  1. -(void) loadSoundFont: (NSString*) path withPatch: (int) patch withBank: (UInt8) bank withSampler: (AudioUnit) sampler {
  2.  
  3. NSLog(@"Sound font: %@", path);
  4.  
  5. NSURL *presetURL = [[NSURL alloc] initFileURLWithPath:[[NSBundle mainBundle] pathForResource:path ofType:@"sf2"]];
  6. [self loadFromDLSOrSoundFont: (NSURL *)presetURL withBank: bank withPatch: patch withSampler:sampler];
  7. [presetURL relativePath];
  8. [presetURL release];
  9. }
  10.  
  11. // Load a SoundFont into a sampler
  12. -(OSStatus) loadFromDLSOrSoundFont: (NSURL *)bankURL withBank: (UInt8) bank withPatch: (int)presetNumber withSampler: (AudioUnit) sampler {
  13. OSStatus result = noErr;
  14.  
  15. // fill out a bank preset data structure
  16. AUSamplerBankPresetData bpdata;
  17. bpdata.bankURL = (CFURLRef) bankURL;
  18. bpdata.bankMSB = bank;
  19. bpdata.bankLSB = kAUSampler_DefaultBankLSB;
  20. bpdata.presetID = (UInt8) presetNumber;
  21.  
  22. // set the kAUSamplerProperty_LoadPresetFromBank property
  23. result = AudioUnitSetProperty(sampler,
  24. kAUSamplerProperty_LoadPresetFromBank,
  25. kAudioUnitScope_Global,
  26. 0,
  27. &bpdata,
  28. sizeof(bpdata));
  29.  
  30. // check for errors
  31. NSCAssert (result == noErr,
  32. @"Unable to set the preset property on the Sampler. Error code:%d '%.4s'",
  33. (int) result,
  34. (const char *)&result);
  35.  
  36. return result;
  37. }