MixerController.mm
/* |
Copyright (C) 2016 Apple Inc. All Rights Reserved. |
See LICENSE.txt for this sample’s licensing information |
Abstract: |
The Main Mixer Controller |
*/ |
#import "MixerController.h" |
#import <AudioUnit/AudioUnitParameters.h> |
#import <AudioUnit/AudioUnitProperties.h> |
#import "MeteringView.h" |
#import "CAStreamBasicDescription.h" |
#import "CAComponentDescription.h" |
#import "MatrixMixerVolumes.h" |
#define RequireNoErr(error) do { if( (error) != noErr ) throw OSStatus(error); } while (false) |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
#pragma mark ____AUComponentDescription |
// a convenience wrapper for ComponentDescription |
//const Float64 kGraphSampleRate = 44100.; |
const Float64 kGraphSampleRate = 48000.; |
OSStatus renderInput(void *inRefCon, |
AudioUnitRenderActionFlags *ioActionFlags, |
const AudioTimeStamp *inTimeStamp, |
UInt32 inBusNumber, |
UInt32 inNumberFrames, |
AudioBufferList *ioData) |
{ |
SynthData& d = *(SynthData*)inRefCon; // get access to Sinewave's data |
UInt32 bufSamples = d.bufs[inBusNumber].numFrames << 1; |
float *in = d.bufs[inBusNumber].data; |
float *outA = (float*)ioData->mBuffers[0].mData; |
float *outB = (float*)ioData->mBuffers[1].mData; |
if (!in) { |
for (UInt32 i=0; i<inNumberFrames; ++i) |
{ |
outA[i] = 0.f; |
outB[i] = 0.f; |
} |
} else { |
UInt32 phase = d.bufs[inBusNumber].phase; |
for (UInt32 i=0; i<inNumberFrames; ++i) |
{ |
outA[i] = in[phase++]; |
outB[i] = in[phase++]; |
if (phase >= bufSamples) phase = 0; |
} |
d.bufs[inBusNumber].phase = phase; |
} |
return noErr; |
} |
@implementation MixerController |
- (void)awakeFromNib |
{ |
isPlaying = false; |
automate = false; |
automatePhase = 0; |
memset(&d, 0, sizeof(d)); |
[self initializeGraph]; |
meterInPreArray = [[NSMutableArray arrayWithCapacity:4] retain]; |
meterInArray = [[NSMutableArray arrayWithCapacity:4] retain]; |
meterOutArray = [[NSMutableArray arrayWithCapacity:5] retain]; |
xpmeterArray = [[NSMutableArray arrayWithCapacity:20] retain]; |
NSView *theContentView = theWindow.contentView; |
for (int i=0, k=8101; i<4; ++i, k+=100) { // matrix meter tags for these controls are 8101 - 8105, 8201 - 8205, 8301 - 8305, 8401 - 8405 |
for (int j=0; j<5; ++j) { |
NSSlider *oldMeter = [theContentView viewWithTag:k+j]; |
NSRect newFrame = [oldMeter frame]; |
newFrame.size.width = 11; |
MeteringView* mv = [[[MeteringView alloc] initWithFrame: newFrame] autorelease]; |
[mv setNumChannels: 1]; |
NSView* parent = [oldMeter superview]; |
[parent replaceSubview: oldMeter with: mv]; |
[xpmeterArray addObject:mv]; |
} |
} |
for (int j=5000; j<5005; ++j) { // output meter tags for these controls are 5000 - 5004 |
NSSlider *oldMeter = [theContentView viewWithTag:j]; |
NSRect newFrame = [oldMeter frame]; |
newFrame.size.width = 11; |
MeteringView* mv = [[[MeteringView alloc] initWithFrame: newFrame] autorelease]; |
[mv setNumChannels: 1]; |
NSView* parent = [oldMeter superview]; |
[parent replaceSubview: oldMeter with: mv]; |
[meterOutArray addObject:mv]; |
} |
for (int j=7101; j<7501; j+=100) { // input meter tags for these controls are 7101 - 7501 |
NSSlider *oldMeter = [theContentView viewWithTag:j]; |
NSRect newFrame = [oldMeter frame]; |
newFrame.size.width = 11; |
MeteringView* mv = [[[MeteringView alloc] initWithFrame: newFrame] autorelease]; |
[mv setNumChannels: 1]; |
NSView* parent = [oldMeter superview]; |
[parent replaceSubview: oldMeter with: mv]; |
[meterInArray addObject:mv]; |
} |
for (int j=6101; j<6501; j+=100) { // pre input meter tags for these controls are 6101 - 6501 |
NSSlider *oldMeter = [theContentView viewWithTag:j]; |
NSRect newFrame = [oldMeter frame]; |
newFrame.size.width = 11; |
MeteringView* mv = [[[MeteringView alloc] initWithFrame: newFrame] autorelease]; |
[mv setNumChannels: 1]; |
NSView* parent = [oldMeter superview]; |
[parent replaceSubview: oldMeter with: mv]; |
[meterInPreArray addObject:mv]; |
} |
mTimer = [NSTimer scheduledTimerWithTimeInterval: 0.02 target: self selector: @selector(doTimer:) userInfo: nil repeats: YES]; |
[[NSRunLoop currentRunLoop] addTimer: mTimer forMode: NSModalPanelRunLoopMode]; |
[[NSRunLoop currentRunLoop] addTimer: mTimer forMode: NSEventTrackingRunLoopMode]; |
} |
- (void)dealloc |
{ |
[mTimer invalidate]; |
[self stop:nil]; |
DisposeAUGraph(mGraph); |
[meterInPreArray release]; |
[meterInArray release ]; |
[meterOutArray release]; |
[xpmeterArray release]; |
[super dealloc]; |
} |
extern double dbamp(double x); |
- (void)doTimer: (NSTimer*) timer |
{ |
// set meters |
Float32 value = 0.0f; |
OSStatus err; |
MeteringView *meter; |
NSSlider **slider; |
if (automate) { |
automatePhase++; |
} |
for (int i=0; i<4; ++i) { |
float amps[2] = {0}; |
err = AudioUnitGetParameter(mixer, kMatrixMixerParam_PostAveragePower, kAudioUnitScope_Input, i, &s[0]); |
if (err) printf("AudioUnitGetParameter %ld", (long)err); |
err = AudioUnitGetParameter(mixer, kMatrixMixerParam_PostPeakHoldLevel, kAudioUnitScope_Input, i, &s[1]); |
if (err) printf("AudioUnitGetParameter %ld", (long)err); |
value = sqrt(dbamp(value)); |
// input meters |
meter = [meterInArray objectAtIndex:i]; |
[meter updateMeters: amps]; |
err = AudioUnitGetParameter(mixer, kMatrixMixerParam_PreAveragePower, kAudioUnitScope_Input, i, &s[0]); |
if (err) printf("AudioUnitGetParameter %ld", (long)err); |
err = AudioUnitGetParameter(mixer, kMatrixMixerParam_PrePeakHoldLevel, kAudioUnitScope_Input, i, &s[1]); |
if (err) printf("AudioUnitGetParameter %ld", (long)err); |
value = sqrt(dbamp(value)); |
// pre meters |
meter = [meterInPreArray objectAtIndex:i]; |
[meter updateMeters: amps]; |
} |
for (int i=0; i<5; ++i) { |
float amps[2] = {0}; |
err = AudioUnitGetParameter(mixer, kMatrixMixerParam_PostAveragePower, kAudioUnitScope_Output, i, &s[0]); |
if (err) printf("AudioUnitGetParameter %ld", (long)err); |
err = AudioUnitGetParameter(mixer, kMatrixMixerParam_PostPeakHoldLevel, kAudioUnitScope_Output, i, &s[1]); |
if (err) printf("AudioUnitGetParameter %ld", (long)err); |
value = sqrt(dbamp(value)); |
// output meters |
meter = [meterOutArray objectAtIndex:i]; |
[meter updateMeters: amps]; |
} |
Float32 phaseinc = ( 2. * 3.14159 ) / 32.0; |
slider = &xpslider11; |
for (int i=0, k=0; i<4; ++i) { |
for (int j=0; j<5; ++j, ++k) { |
float amps[2] = {0}; |
UInt32 element = (i<<16) | j; |
err = AudioUnitGetParameter(mixer, kMatrixMixerParam_PostAveragePower, kAudioUnitScope_Global, element, &s[0]); |
if (err) printf("AudioUnitGetParameter %ld", (long)err); |
err = AudioUnitGetParameter(mixer, kMatrixMixerParam_PostPeakHoldLevel, kAudioUnitScope_Global, element, &s[1]); |
if (err) printf("AudioUnitGetParameter %ld", (long)err); |
meter = [xpmeterArray objectAtIndex:k]; |
[meter updateMeters: amps]; |
if (automate) { |
Float32 phase = phaseinc * automatePhase * (1.0 + 0.1 * k); |
Float32 volume = 0.5 + 0.5 * sin(phase); |
volume = volume > 0.7 ? volume : 0.0; |
// this should be ModelViewController, but for demo purposes, cheat.. |
[slider[k] setFloatValue: volume * 100.]; |
err = AudioUnitSetParameter(mixer, kMatrixMixerParam_Volume, kAudioUnitScope_Global, element, volume, 0); |
if (err) printf("AudioUnitSetParameter %ld", (long)err); |
} |
} |
} |
} |
- (IBAction)play:(id)sender |
{ |
if (isPlaying) return; |
printf("PLAY\n"); |
OSStatus err = AUGraphStart(mGraph); |
isPlaying = true; |
printf("AUGraphStart %08lX\n", (long)err); |
// print some diagnostic info |
const char* identifier = "Input "; |
err = PrintBuses(stdout, identifier, mixer, kAudioUnitScope_Input ); |
if (err) printf("PrintBuses %ld", (long)err); |
identifier = "Output"; |
err = PrintBuses(stdout, identifier, mixer, kAudioUnitScope_Output ); |
if (err) printf("PrintBuses %ld", (long)err); |
// you may use this to dump volumes to the console |
// at opportune times for diagnostic purposes |
// PrintMatrixMixerVolumes(stdout, mixer); |
} |
- (IBAction)setInputVolume:(id)sender |
{ |
UInt32 inputNum = [sender tag] / 100 - 1; |
AudioUnitSetParameter(mixer, kMatrixMixerParam_Volume, kAudioUnitScope_Input, inputNum, [sender doubleValue] * .01, 0); |
} |
- (IBAction)setMasterVolume:(id)sender |
{ |
AudioUnitSetParameter(mixer, kMatrixMixerParam_Volume, kAudioUnitScope_Global, 0xFFFFFFFF, [sender doubleValue] * .01, 0); |
} |
- (IBAction)setMatrixVolume:(id)sender |
{ |
UInt32 inputNum = [sender tag] / 100 - 1; |
UInt32 outputNum = [sender tag] % 100 - 1; |
UInt32 element = (inputNum << 16) | (outputNum & 0x0000FFFF); |
AudioUnitSetParameter(mixer, kMatrixMixerParam_Volume, kAudioUnitScope_Global, element, [sender doubleValue] * .01, 0); |
} |
- (IBAction)setOutputVolume:(id)sender |
{ |
UInt32 outputNum = [sender tag] % 100 - 1; |
AudioUnitSetParameter(mixer, kMatrixMixerParam_Volume, kAudioUnitScope_Output, outputNum, [sender doubleValue] * .01, 0); |
} |
- (IBAction)stop:(id)sender |
{ |
if (!isPlaying) return; |
printf("STOP\n"); |
OSStatus err = AUGraphStop(mGraph); |
printf("AUGraphStop %08lX\n", (long)err); |
isPlaying = false; |
} |
- (IBAction)enableInput:(id)sender |
{ |
UInt32 inputNum = [sender tag] % 1000 - 1; |
AudioUnitSetParameter(mixer, kMatrixMixerParam_Enable, kAudioUnitScope_Input, inputNum, [sender doubleValue], 0); |
} |
- (IBAction)enableOutput:(id)sender |
{ |
UInt32 outputNum = [sender tag] % 1000 - 1; |
AudioUnitSetParameter(mixer, kMatrixMixerParam_Enable, kAudioUnitScope_Output, outputNum, [sender doubleValue], 0); |
} |
- (void)initializeGraph |
{ |
CAStreamBasicDescription desc; |
OSStatus result = noErr; |
result = NewAUGraph(&mGraph); |
AUNode outputNode; |
AUNode mixerNode; |
printf("Creating AUGraph\n"); |
CAComponentDescription output_desc(kAudioUnitType_Output, |
kAudioUnitSubType_DefaultOutput, |
kAudioUnitManufacturer_Apple); |
output_desc.componentFlags = kAudioComponentFlag_SandboxSafe; |
output_desc.Print(); |
CAComponentDescription mixer_desc(kAudioUnitType_Mixer, |
kAudioUnitSubType_MatrixMixer, |
kAudioUnitManufacturer_Apple); |
mixer_desc.componentFlags = kAudioComponentFlag_SandboxSafe; |
mixer_desc.Print(); |
result = AUGraphAddNode(mGraph, &output_desc, &outputNode); |
if (result) { |
printf("AUGraphAddNode 1 result %lu %4.4s\n", (unsigned long)result, (char*)&result); |
return; |
} |
result = AUGraphAddNode(mGraph, &mixer_desc, &mixerNode ); |
if (result) { |
printf("AUGraphAddNode 2 result %lu %4.4s\n", (unsigned long)result, (char*)&result); |
return; |
} |
result = AUGraphConnectNodeInput(mGraph, mixerNode, 0, outputNode, 0 ); |
if (result) { |
printf("AUGraphConnectNodeInput result %lu %4.4s\n", (unsigned long)result, (char*)&result); |
return; |
} |
result = AUGraphOpen(mGraph); |
if (result) { |
printf("AUGraphOpen result %u %4.4s\n", (unsigned int)result, (char*)&result); |
return; |
} |
result = AUGraphNodeInfo(mGraph, mixerNode, NULL, &mixer ); |
if (result) { |
printf("AUGraphNodeInfo result %u %4.4s\n", (unsigned int)result, (char*)&result); |
return; |
} |
UInt32 size; |
UInt32 data = 1; |
// turn metering ON |
result = AudioUnitSetProperty( mixer, |
kAudioUnitProperty_MeteringMode, |
kAudioUnitScope_Global, |
0, |
&data, |
sizeof(data) ); |
UInt32 numbuses; |
size = sizeof(numbuses); |
// set bus counts |
numbuses = 2; |
printf("set input bus count %u\n", (unsigned int)numbuses); |
result = AudioUnitSetProperty( mixer, |
kAudioUnitProperty_ElementCount, |
kAudioUnitScope_Input, |
0, |
&numbuses, |
sizeof(UInt32) ); |
numbuses = 1; |
printf("set output bus count %u\n", (unsigned int)numbuses); |
result = AudioUnitSetProperty( mixer, |
kAudioUnitProperty_ElementCount, |
kAudioUnitScope_Output, |
0, |
&numbuses, |
sizeof(UInt32) ); |
for (int i=0; i<2; ++i) { |
// set render callback |
printf("\nset render callback for bus %d\n", i); |
AURenderCallbackStruct rcbs; |
rcbs.inputProc = &renderInput; |
rcbs.inputProcRefCon = &d; |
result = AudioUnitSetProperty( mixer, |
kAudioUnitProperty_SetRenderCallback, |
kAudioUnitScope_Input, |
i, |
&rcbs, |
sizeof(rcbs) ); |
// set input stream format |
size = sizeof(desc); |
result = AudioUnitGetProperty( mixer, |
kAudioUnitProperty_StreamFormat, |
kAudioUnitScope_Input, |
i, |
&desc, |
&size ); |
printf(">> input format from bus %d\n", i); |
desc.Print(); |
desc.ChangeNumberChannels(2, false); |
desc.mSampleRate = kGraphSampleRate; |
printf(">> set input format for bus %d\n", i); |
desc.Print(); |
result = AudioUnitSetProperty( mixer, |
kAudioUnitProperty_StreamFormat, |
kAudioUnitScope_Input, |
i, |
&desc, |
sizeof(desc) ); |
} |
// set output stream format |
result = AudioUnitGetProperty( mixer, |
kAudioUnitProperty_StreamFormat, |
kAudioUnitScope_Output, |
0, |
&desc, |
&size ); |
desc.ChangeNumberChannels(5, false); |
desc.mSampleRate = kGraphSampleRate; |
printf("\n>> set output format for bus %d\n", 0); |
desc.Print(); |
result = AudioUnitSetProperty( mixer, |
kAudioUnitProperty_StreamFormat, |
kAudioUnitScope_Output, |
0, |
&desc, |
sizeof(desc) ); |
printf("\nAUGraphInitialize\n"); |
// NOW that we've set everything up we can initialize the graph |
// (which will also validate the connections) |
RequireNoErr(AUGraphInitialize(mGraph)); |
CAShow(mGraph); |
} |
- (IBAction)addFile:(id)sender |
{ |
[self stop: nil]; |
NSInteger result; |
NSArray *fileTypes = [NSArray arrayWithObjects: @"AIFF", @"aif", @"aiff", @"aifc", @"wav", @"WAV", nil]; |
NSOpenPanel *oPanel = [NSOpenPanel openPanel]; |
[oPanel setAllowsMultipleSelection:YES]; |
[oPanel setAllowedFileTypes:fileTypes]; |
[oPanel setDirectoryURL:[[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] objectAtIndex:0]]; |
result = [oPanel runModal]; |
if (result == NSOKButton) { |
NSArray *filesToOpen = [oPanel URLs]; |
int i, count = [filesToOpen count]; |
for (i=0; i<MAXBUFS; ++i) |
{ |
if (d.bufs[i].data) |
{ |
free(d.bufs[i].data); |
d.bufs[i].data = 0; |
} |
if (d.bufs[i].name) |
{ |
[d.bufs[i].name release]; |
d.bufs[i].name = 0; |
} |
} |
for (i=0; i<count && i<MAXBUFS; i++) |
{ |
NSURL *aFile = [filesToOpen objectAtIndex: i]; |
NSString *name = [aFile lastPathComponent]; |
NSLog(@"loading file %d, %@\n", i, aFile); |
ExtAudioFileRef xafref; |
OSStatus err = ExtAudioFileOpenURL((CFURLRef)aFile, &xafref); |
if (err || !xafref) { |
printf("couldn't open file\n"); |
continue; |
} |
UInt32 propSize = sizeof(UInt64); |
AudioStreamBasicDescription fileDataFormat; |
propSize = sizeof(fileDataFormat); |
err = ExtAudioFileGetProperty(xafref, kExtAudioFileProperty_FileDataFormat, &propSize, &fileDataFormat); |
if (err) { |
printf("couldn't get file data format\n"); |
continue; |
} |
double rateRatio = kGraphSampleRate / fileDataFormat.mSampleRate; |
// clientFormat.SetAUCanonical(2, true); deprecated |
CAStreamBasicDescription clientFormat = CAStreamBasicDescription(kGraphSampleRate, 2, CAStreamBasicDescription::kPCMFormatFloat32, YES); |
propSize = sizeof(clientFormat); |
err = ExtAudioFileSetProperty(xafref, kExtAudioFileProperty_ClientDataFormat, propSize, &clientFormat); |
if (err) { |
printf("couldn't set file client format\n"); |
continue; |
} |
propSize = sizeof(UInt64); |
UInt64 numFrames = 0; |
err = ExtAudioFileGetProperty(xafref, kExtAudioFileProperty_FileLengthFrames, &propSize, &numFrames); |
if (err) { |
printf("couldn't get file length\n"); |
continue; |
} |
numFrames = (UInt32)(numFrames * rateRatio); // account for sample rate conversion |
d.bufs[i].numFrames = numFrames; |
d.bufs[i].asbd = clientFormat; |
d.bufs[i].name = name; |
[d.bufs[i].name retain]; |
int samples = numFrames * d.bufs[i].asbd.mChannelsPerFrame; |
d.bufs[i].data = (float*)calloc(samples, sizeof(Float32)); |
d.bufs[i].phase = 0; |
AudioBufferList bufList; |
bufList.mNumberBuffers = 1; |
bufList.mBuffers[0].mNumberChannels = 2; |
bufList.mBuffers[0].mData = d.bufs[i].data; |
bufList.mBuffers[0].mDataByteSize = samples * sizeof(Float32); |
UInt32 numPackets = numFrames; |
err = ExtAudioFileRead(xafref, &numPackets, &bufList); |
if (err) { |
printf("couldn't read data\n"); |
free(d.bufs[i].data); |
d.bufs[i].data = 0; |
continue; |
} |
ExtAudioFileDispose(xafref); |
} |
d.numbufs = count; |
} |
} |
- (IBAction)automateOn:(id)sender; |
{ |
automate = true; |
} |
- (IBAction)automateOff:(id)sender; |
{ |
automate = false; |
} |
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication |
{ |
return YES; |
} |
@end |
Copyright © 2016 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2016-02-11