-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathSoundGen.m
371 lines (275 loc) · 14.2 KB
/
SoundGen.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
//
// SoundGen.m
// SoundRunner
//
// Created by Zachary Waleed Saraf on 3/1/14.
// Copyright (c) 2014 Zachary Waleed Saraf. All rights reserved.
//
#import "SoundGen.h"
#import <AssertMacros.h>
@interface SoundGen ()
// private class extensio
@property (readwrite) Float64 graphSampleRate;
@property (readwrite) AUGraph processingGraph;
@property (readwrite) AudioUnit samplerUnit;
@property (readwrite) AudioUnit ioUnit;
@property (nonatomic, strong) NSMutableSet *currentlyPlayingNotes;
@end
// some MIDI constants:
enum {
kMIDIMessage_NoteOn = 0x9,
kMIDIMessage_NoteOff = 0x8,
};
#define kLowNote 48
#define kHighNote 72
#define kMidNote 60
@implementation SoundGen
@synthesize graphSampleRate = _graphSampleRate;
@synthesize samplerUnit = _samplerUnit;
@synthesize ioUnit = _ioUnit;
@synthesize processingGraph = _processingGraph;
@synthesize patchNumber = _patchNumber;
@synthesize soundFontURL = _soundFontURL;
-(id)initWithSoundFontURL:(NSURL *)soundFontURL patchNumber:(NSInteger)patchNumber
{
if (self = [super init]) {
self.currentlyPlayingNotes = [[NSMutableSet alloc] init];
// Set up the audio session for this app, in the process obtaining the
// hardware sample rate for use in the audio processing graph.
BOOL audioSessionActivated = [self setupAudioSession];
NSAssert (audioSessionActivated == YES, @"Unable to set up audio session.");
_soundFontURL = soundFontURL;
// Create the audio processing graph; place references to the graph and to the Sampler unit
// into the processingGraph and samplerUnit instance variables.
[self createAUGraph];
[self configureAndStartAudioProcessingGraph: self.processingGraph];
[self loadFromDLSOrSoundFont:soundFontURL withPatch:patchNumber];
}
return self;
}
-(void)setSoundFontURL:(NSURL *)soundFontURL
{
_soundFontURL = soundFontURL;
[self loadFromDLSOrSoundFont:soundFontURL withPatch:self.patchNumber];
}
-(void)setPatchNumber:(NSInteger)patchNumber
{
_patchNumber = patchNumber;
[self loadFromDLSOrSoundFont:self.soundFontURL withPatch:patchNumber];
}
// Set up the audio session for this app.
- (BOOL) setupAudioSession {
AVAudioSession *mySession = [AVAudioSession sharedInstance];
// Specify that this object is the delegate of the audio session, so that
// this object's endInterruption method will be invoked when needed.
[mySession setDelegate: self];
// Assign the Playback category to the audio session. This category supports
// audio output with the Ring/Silent switch in the Silent position.
NSError *audioSessionError = nil;
[mySession setCategory: AVAudioSessionCategoryPlayback error: &audioSessionError];
if (audioSessionError != nil) {NSLog (@"Error setting audio session category."); return NO;}
// Request a desired hardware sample rate.
self.graphSampleRate = 44100.0; // Hertz
[mySession setPreferredSampleRate: self.graphSampleRate error: &audioSessionError];
if (audioSessionError != nil) {NSLog (@"Error setting preferred hardware sample rate."); return NO;}
// Activate the audio session
[mySession setActive: YES error: &audioSessionError];
if (audioSessionError != nil) {NSLog (@"Error activating the audio session."); return NO;}
// Obtain the actual hardware sample rate and store it for later use in the audio processing graph.
self.graphSampleRate = [mySession sampleRate];
return YES;
}
// Create an audio processing graph.
- (BOOL) createAUGraph {
OSStatus result = noErr;
AUNode samplerNode, ioNode;
// Specify the common portion of an audio unit's identify, used for both audio units
// in the graph.
AudioComponentDescription cd = {};
cd.componentManufacturer = kAudioUnitManufacturer_Apple;
cd.componentFlags = 0;
cd.componentFlagsMask = 0;
// Instantiate an audio processing graph
result = NewAUGraph (&_processingGraph);
NSCAssert (result == noErr, @"Unable to create an AUGraph object. Error code: %d '%.4s'", (int) result, (const char *)&result);
//Specify the Sampler unit, to be used as the first node of the graph
cd.componentType = kAudioUnitType_MusicDevice;
cd.componentSubType = kAudioUnitSubType_Sampler;
// Add the Sampler unit node to the graph
result = AUGraphAddNode (self.processingGraph, &cd, &samplerNode);
NSCAssert (result == noErr, @"Unable to add the Sampler unit to the audio processing graph. Error code: %d '%.4s'", (int) result, (const char *)&result);
// Specify the Output unit, to be used as the second and final node of the graph
cd.componentType = kAudioUnitType_Output;
cd.componentSubType = kAudioUnitSubType_RemoteIO;
// Add the Output unit node to the graph
result = AUGraphAddNode (self.processingGraph, &cd, &ioNode);
NSCAssert (result == noErr, @"Unable to add the Output unit to the audio processing graph. Error code: %d '%.4s'", (int) result, (const char *)&result);
// Open the graph
result = AUGraphOpen (self.processingGraph);
NSCAssert (result == noErr, @"Unable to open the audio processing graph. Error code: %d '%.4s'", (int) result, (const char *)&result);
// Connect the Sampler unit to the output unit
result = AUGraphConnectNodeInput (self.processingGraph, samplerNode, 0, ioNode, 0);
NSCAssert (result == noErr, @"Unable to interconnect the nodes in the audio processing graph. Error code: %d '%.4s'", (int) result, (const char *)&result);
// Obtain a reference to the Sampler unit from its node
result = AUGraphNodeInfo (self.processingGraph, samplerNode, 0, &_samplerUnit);
NSCAssert (result == noErr, @"Unable to obtain a reference to the Sampler unit. Error code: %d '%.4s'", (int) result, (const char *)&result);
// Obtain a reference to the I/O unit from its node
result = AUGraphNodeInfo (self.processingGraph, ioNode, 0, &_ioUnit);
NSCAssert (result == noErr, @"Unable to obtain a reference to the I/O unit. Error code: %d '%.4s'", (int) result, (const char *)&result);
return YES;
}
// Starting with instantiated audio processing graph, configure its
// audio units, initialize it, and start it.
- (void) configureAndStartAudioProcessingGraph: (AUGraph) graph {
OSStatus result = noErr;
UInt32 framesPerSlice = 0;
UInt32 framesPerSlicePropertySize = sizeof (framesPerSlice);
UInt32 sampleRatePropertySize = sizeof (self.graphSampleRate);
result = AudioUnitInitialize (self.ioUnit);
NSCAssert (result == noErr, @"Unable to initialize the I/O unit. Error code: %d '%.4s'", (int) result, (const char *)&result);
// Set the I/O unit's output sample rate.
result = AudioUnitSetProperty (
self.ioUnit,
kAudioUnitProperty_SampleRate,
kAudioUnitScope_Output,
0,
&_graphSampleRate,
sampleRatePropertySize
);
NSAssert (result == noErr, @"AudioUnitSetProperty (set Sampler unit output stream sample rate). Error code: %d '%.4s'", (int) result, (const char *)&result);
// Obtain the value of the maximum-frames-per-slice from the I/O unit.
result = AudioUnitGetProperty (
self.ioUnit,
kAudioUnitProperty_MaximumFramesPerSlice,
kAudioUnitScope_Global,
0,
&framesPerSlice,
&framesPerSlicePropertySize
);
NSCAssert (result == noErr, @"Unable to retrieve the maximum frames per slice property from the I/O unit. Error code: %d '%.4s'", (int) result, (const char *)&result);
// Set the Sampler unit's output sample rate.
result = AudioUnitSetProperty (
self.samplerUnit,
kAudioUnitProperty_SampleRate,
kAudioUnitScope_Output,
0,
&_graphSampleRate,
sampleRatePropertySize
);
NSAssert (result == noErr, @"AudioUnitSetProperty (set Sampler unit output stream sample rate). Error code: %d '%.4s'", (int) result, (const char *)&result);
// Set the Sampler unit's maximum frames-per-slice.
result = AudioUnitSetProperty (
self.samplerUnit,
kAudioUnitProperty_MaximumFramesPerSlice,
kAudioUnitScope_Global,
0,
&framesPerSlice,
framesPerSlicePropertySize
);
NSAssert( result == noErr, @"AudioUnitSetProperty (set Sampler unit maximum frames per slice). Error code: %d '%.4s'", (int) result, (const char *)&result);
if (graph) {
// Initialize the audio processing graph.
result = AUGraphInitialize (graph);
NSAssert (result == noErr, @"Unable to initialze AUGraph object. Error code: %d '%.4s'", (int) result, (const char *)&result);
// Start the graph
result = AUGraphStart (graph);
NSAssert (result == noErr, @"Unable to start audio processing graph. Error code: %d '%.4s'", (int) result, (const char *)&result);
// Print out the graph to the console
CAShow (graph);
}
}
// this method assumes the class has a member called mySamplerUnit
// which is an instance of an AUSampler
-(OSStatus) loadFromDLSOrSoundFont: (NSURL *)bankURL withPatch: (int)presetNumber
{
OSStatus result = noErr;
// fill out a bank preset data structure
AUSamplerBankPresetData bpdata;
bpdata.bankURL = (__bridge CFURLRef) bankURL;
bpdata.bankMSB = kAUSampler_DefaultMelodicBankMSB;
bpdata.bankLSB = kAUSampler_DefaultBankLSB;
bpdata.presetID = (UInt8) presetNumber;
// set the kAUSamplerProperty_LoadPresetFromBank property
result = AudioUnitSetProperty(self.samplerUnit,
kAUSamplerProperty_LoadPresetFromBank,
kAudioUnitScope_Global,
0,
&bpdata,
sizeof(bpdata));
// check for errors
NSCAssert (result == noErr,
@"Unable to set the preset property on the Sampler. Error code:%d '%.4s'",
(int) result,
(const char *)&result);
return result;
}
-(void)playMidiNote:(NSInteger)note velocity:(NSInteger)velocity
{
[self.currentlyPlayingNotes addObject:[NSNumber numberWithInteger:note]];
// UInt32 onVelocity = 127;
UInt32 noteCommand = kMIDIMessage_NoteOn << 4 | 0;
OSStatus result = noErr;
require_noerr (result = MusicDeviceMIDIEvent (self.samplerUnit, noteCommand, note, velocity, 0), logTheError);
logTheError:
if (result != noErr) NSLog (@"Unable to start playing the low note. Error code: %d '%.4s'\n", (int) result, (const char *)&result);
}
-(void)stopPlayingMidiNote:(NSInteger)note
{
[self.currentlyPlayingNotes removeObject:[NSNumber numberWithInteger:note]];
UInt32 noteCommand = kMIDIMessage_NoteOff << 4 | 0;
OSStatus result = noErr;
require_noerr (result = MusicDeviceMIDIEvent (self.samplerUnit, noteCommand, note, 0, 0), logTheError);
logTheError:
if (result != noErr) NSLog (@"Unable to stop playing the low note. Error code: %d '%.4s'\n", (int) result, (const char *)&result);
}
-(void)stopPlayingAllNotes
{
for (NSNumber *num in [self.currentlyPlayingNotes copy]) {
[self stopPlayingMidiNote:num.integerValue];
}
}
#pragma mark -
#pragma mark Audio session delegate methods
// Respond to an audio interruption, such as a phone call or a Clock alarm.
- (void) beginInterruption {
// Stop any notes that are currently playing.
// [self stopPlayLowNote: self];
// [self stopPlayMidNote: self];
// [self stopPlayHighNote: self];
// Interruptions do not put an AUGraph object into a "stopped" state, so
// do that here.
[self stopAudioProcessingGraph];
}
// Respond to the ending of an audio interruption.
- (void) endInterruptionWithFlags: (NSUInteger) flags {
NSError *endInterruptionError = nil;
[[AVAudioSession sharedInstance] setActive: YES
error: &endInterruptionError];
if (endInterruptionError != nil) {
NSLog (@"Unable to reactivate the audio session.");
return;
}
if (flags & AVAudioSessionInterruptionFlags_ShouldResume) {
/*
In a shipping application, check here to see if the hardware sample rate changed from
its previous value by comparing it to graphSampleRate. If it did change, reconfigure
the ioInputStreamFormat struct to use the new sample rate, and set the new stream
format on the two audio units. (On the mixer, you just need to change the sample rate).
Then call AUGraphUpdate on the graph before starting it.
*/
[self restartAudioProcessingGraph];
}
}
// Stop the audio processing graph
- (void) stopAudioProcessingGraph {
OSStatus result = noErr;
if (self.processingGraph) result = AUGraphStop(self.processingGraph);
NSAssert (result == noErr, @"Unable to stop the audio processing graph. Error code: %d '%.4s'", (int) result, (const char *)&result);
}
// Restart the audio processing graph
- (void) restartAudioProcessingGraph {
OSStatus result = noErr;
if (self.processingGraph) result = AUGraphStart (self.processingGraph);
NSAssert (result == noErr, @"Unable to restart the audio processing graph. Error code: %d '%.4s'", (int) result, (const char *)&result);
}
@end