Good morning, collective wisdom!
I have written an iOS 16-channel General MIDI tone generator using an AUGraph, and it works great with some DLSs and sound fonts, and sounds terrible with others. With some DLSs and sound fonts, I can play a sequence and get essentially the same sound as I hear from other iOS apps (e.g. bs-16i) playing the same sequence and using the same DLS or sound font; but with DLSs or sound fonts, I get a much worse sound quality than from the other app, notes that don't sound, etc.--not even close to acceptable. It's particularly surprising since the difference in quality exists only for some DLSs and sound fonts--so what I'm doing works for some, but is not sufficient for others. Below is the function newAUGraph(), which creates an AUGraph consisting of 16 AUSamplers (one for each channel), each with an output to a mixer, which combines the 16 channels and routes them to a reverb unit, which then sends the sounds to the output unit; the AUGraph and the sixteen AUSamplers are stored in the external (global) variables auGraph and auSampler[16]. Presets are subsequently loaded into each channel's AUSampler, and notes and other MIDI messages are sent via MusicDeviceMIDIEvent(). If anybody has time to slog through this and give me a hint as to what I'm missing, I'll be forever grateful. In any case, thanks for your attention! All best, Frank
****************
AUGraph auGraph; AudioUnit auSampler[16];
#define mustHaveNoError(functionCall) functionCall; if (result != noErr) {NSLog (@"error %d", (int)result); return result;}
static OSStatus newAUGraph (void) { AudioUnit mixerAudioUnit, reverbAudioUnit, outputAudioUnit; AUNode samplerNode[16], mixerNode, reverbNode, outputNode;
AVAudioSession* avAudioSession = [AVAudioSession sharedInstance]; [avAudioSession setCategory: AVAudioSessionCategoryPlayback error: nil]; [avAudioSession setPreferredHardwareSampleRate:44100.0 error:nil]; [avAudioSession setActive:YES error:nil]; Float64 sampleRate = avAudioSession.currentHardwareSampleRate;
AudioComponentDescription description; description.componentManufacturer = kAudioUnitManufacturer_Apple; description.componentFlags = description.componentFlagsMask = 0;
OSStatus result = mustHaveNoError(NewAUGraph(&auGraph));
description.componentType = kAudioUnitType_Output; description.componentSubType = kAudioUnitSubType_RemoteIO; result = mustHaveNoError(AUGraphAddNode (auGraph, &description, &outputNode));
description.componentType = kAudioUnitType_Effect; description.componentSubType = kAudioUnitSubType_Reverb2; result = mustHaveNoError(AUGraphAddNode (auGraph, &description, &reverbNode));
description.componentType = kAudioUnitType_Mixer; description.componentSubType = kAudioUnitSubType_MultiChannelMixer; result = mustHaveNoError(AUGraphAddNode (auGraph, &description, &mixerNode));
result = mustHaveNoError(AUGraphConnectNodeInput(auGraph, reverbNode, 0, outputNode, 0)); result = mustHaveNoError(AUGraphConnectNodeInput(auGraph, mixerNode, 0, reverbNode, 0));
const UInt32 numberOfChannels = 16; for (unsigned int i = 0; i < numberOfChannels; i += 1) { description.componentType = kAudioUnitType_MusicDevice; description.componentSubType = kAudioUnitSubType_Sampler; result = mustHaveNoError(AUGraphAddNode (auGraph, &description, &samplerNode[i])); result = mustHaveNoError(AUGraphConnectNodeInput(auGraph, samplerNode[i], 0, mixerNode, i)); }
result = mustHaveNoError(AUGraphOpen (auGraph));
result = mustHaveNoError(AUGraphNodeInfo(auGraph, mixerNode, NULL, &mixerAudioUnit)); result = mustHaveNoError(AUGraphNodeInfo(auGraph, reverbNode, NULL, &reverbAudioUnit)); result = mustHaveNoError(AUGraphNodeInfo(auGraph, outputNode, NULL, &outputAudioUnit));
UInt32 framesPerSlice; UInt32 framesPerSlicePropertySize = sizeof(framesPerSlice); result = mustHaveNoError(AudioUnitGetProperty(outputAudioUnit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, 0, &framesPerSlice, &framesPerSlicePropertySize)); assert(framesPerSlicePropertySize == sizeof(framesPerSlice));
result = mustHaveNoError(AudioUnitInitialize(outputAudioUnit)); result = mustHaveNoError(AudioUnitSetProperty(outputAudioUnit, kAudioUnitProperty_SampleRate, kAudioUnitScope_Output, 0, &sampleRate, sizeof(sampleRate))); result = mustHaveNoError(AudioUnitSetProperty(outputAudioUnit, kAudioUnitProperty_SampleRate, kAudioUnitScope_Input, 0, &sampleRate, sizeof(sampleRate))); result = mustHaveNoError(AudioUnitSetProperty(reverbAudioUnit, kAudioUnitProperty_SampleRate, kAudioUnitScope_Output, 0, &sampleRate, sizeof(sampleRate))); result = mustHaveNoError(AudioUnitSetProperty(reverbAudioUnit, kAudioUnitProperty_SampleRate, kAudioUnitScope_Input, 0, &sampleRate, sizeof(sampleRate))); result = mustHaveNoError(AudioUnitSetProperty(reverbAudioUnit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, 0, &framesPerSlice, sizeof(framesPerSlice))); result = mustHaveNoError(AudioUnitSetProperty(mixerAudioUnit, kAudioUnitProperty_SampleRate, kAudioUnitScope_Output, 0, &sampleRate, sizeof(sampleRate))); result = mustHaveNoError(AudioUnitSetProperty(mixerAudioUnit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, 0, &framesPerSlice, sizeof(framesPerSlice)));
result = mustHaveNoError(AudioUnitSetProperty(mixerAudioUnit, kAudioUnitProperty_ElementCount, kAudioUnitScope_Input, 0, &numberOfChannels, sizeof(numberOfChannels)));
result = mustHaveNoError(AudioUnitSetParameter(reverbAudioUnit, kReverb2Param_DryWetMix, kAudioUnitScope_Global, 0, 20, 0)); result = mustHaveNoError(AudioUnitSetParameter(reverbAudioUnit, kReverb2Param_DecayTimeAt0Hz, kAudioUnitScope_Global, 0, 3, 0)); result = mustHaveNoError(AudioUnitSetParameter(reverbAudioUnit, kReverb2Param_DecayTimeAtNyquist, kAudioUnitScope_Global, 0, 1, 0));
for (unsigned int i = 0; i < numberOfChannels; i += 1) { result = mustHaveNoError(AUGraphNodeInfo(auGraph, samplerNode[i], NULL, &auSampler[i])); result = mustHaveNoError(AudioUnitSetProperty(auSampler[i], kAudioUnitProperty_SampleRate, kAudioUnitScope_Output, 0, &sampleRate, sizeof(sampleRate))); result = mustHaveNoError(AudioUnitSetProperty(auSampler[i], kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, 0, &framesPerSlice, sizeof(framesPerSlice))); result = mustHaveNoError(AudioUnitSetProperty(mixerAudioUnit, kAudioUnitProperty_SampleRate, kAudioUnitScope_Input, i, &sampleRate, sizeof(sampleRate))); result = mustHaveNoError(AudioUnitSetProperty(mixerAudioUnit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, i, &framesPerSlice, sizeof(framesPerSlice))); result = mustHaveNoError(loadOnBoardSynthPatch(0, i)); result = mustHaveNoError(AudioUnitSetParameter(mixerAudioUnit, kMultiChannelMixerParam_Volume, kAudioUnitScope_Input, i, 1, 0)); }
result = mustHaveNoError(AUGraphInitialize(auGraph)); result = mustHaveNoError(AUGraphStart(auGraph));
CAShow(auGraph); return result; }
****************
Frank Weinstock Professor Emeritus of Piano College-Conservatory of Music
|