I'm playing back MIDI files on iPhone using a MIKMIDISynthesizer
from this library. Unfortunately, the volume is very low, even when turning iPhone's system volume all the way up. To increase it further, I tried these things:
AUGraph
as described here. This sufficiently raises the volume, but instantly distorts the signal so badly that the quality is way too poor.Right now I'm running out of options. Is there any additional parameter or level (like AVAudioSession
or CoreMIDI
, maybe) that I've missed and provides the possibility to adjust volume/gain? Or MIDI commands that do the same?
1. Using a mixer node
This is the portion of MIKMIDISynthesizer
that I modified in order to control the volume with a mixer node.
- (BOOL)setupAUGraph
{
AUGraph graph;
OSStatus err = 0;
// 1. Create new AUGraph
if ((err = NewAUGraph(&graph))) {
NSLog(@"Unable to create AU graph: %@", @(err));
return NO;
}
// 2. Create output node
AudioComponentDescription outputcd = {0};
outputcd.componentType = kAudioUnitType_Output;
outputcd.componentSubType = kAudioUnitSubType_RemoteIO;
outputcd.componentManufacturer = kAudioUnitManufacturer_Apple;
AUNode outputNode;
if ((err = AUGraphAddNode(graph, &outputcd, &outputNode))) {
NSLog(@"Unable to add ouptput node to graph: %@", @(err));
return NO;
}
// 3. Create the instrument node
AudioComponentDescription instrumentcd = self.componentDescription;
AUNode instrumentNode;
if ((err = AUGraphAddNode(graph, &instrumentcd, &instrumentNode))) {
NSLog(@"Unable to add instrument node to AU graph: %@", @(err));
return NO;
}
if ((err = AUGraphOpen(graph))) {
NSLog(@"Unable to open AU graph: %@", @(err));
return NO;
}
AudioUnit instrumentUnit;
if ((err = AUGraphNodeInfo(graph, instrumentNode, NULL, &instrumentUnit))) {
NSLog(@"Unable to get instrument AU unit: %@", @(err));
return NO;
}
// 4. Create the mixer node
AUNode mixerNode;
AudioComponentDescription cd = {};
cd.componentManufacturer = kAudioUnitManufacturer_Apple;
cd.componentType = kAudioUnitType_Mixer;
cd.componentSubType = kAudioUnitSubType_MultiChannelMixer;
if ((err = AUGraphAddNode(graph, &cd, &mixerNode))) {
NSLog(@"Unable to add mixer node to AU graph: %@", @(err));
return NO;
}
if ((err = AUGraphNodeInfo(graph, mixerNode, 0, &_mixerUnit))) {
NSLog(@"Unable to fetch mixer node reference from AU graph: %@", @(err));
return NO;
}
UInt32 busCount = 2;
err = AudioUnitSetProperty(_mixerUnit,
kAudioUnitProperty_ElementCount,
kAudioUnitScope_Input,
0,
&busCount,
sizeof (busCount)
);
if (err) {
NSLog(@"Unable to set mixer unit properties");
return NO;
}
// 5.1 Connect the instrument node as the mixer node's input
if ((err = AUGraphConnectNodeInput(graph, instrumentNode, 0, mixerNode, 0))) {
NSLog(@"Unable to connect input node and mixer node: %@", @(err));
return NO;
}
// 5.2 Connect the mixer node as the output node's input
if ((err = AUGraphConnectNodeInput(graph, mixerNode, 0, outputNode, 0))) {
NSLog(@"Unable to connect input node and mixer node: %@", @(err));
return NO;
}
if ((err = AUGraphInitialize(graph))) {
NSLog(@"Unable to initialize AU graph: %@", @(err));
return NO;
}
// 6. Finally, start the graph
if ((err = AUGraphStart(graph))) {
NSLog(@"Unable to start AU graph: %@", @(err));
return NO;
}
self.graph = graph;
self.instrumentUnit = instrumentUnit;
return YES;
}
After setting up the mixer node, I can change the volume like so (introduces clipping!):
-(void) setVolume: (float) volume {
AudioUnitSetParameter(_mixerUnit, kMultiChannelMixerParam_Volume, kAudioUnitScope_Output, 0, volume, 0);
}
2. Changing MIDI Events
The second approach sets the velocity
property of every Node On event to the maximum of 127. It increases the volume without any noise, but only to a certain level. Code resides within MIKMIDINoteEvent
:
+ (MIKMIDINoteOnCommand *)noteOnCommandFromNoteEvent:(MIKMIDINoteEvent *)noteEvent clock:(MIKMIDIClock *)clock
{
MIKMutableMIDINoteOnCommand *noteOn = [MIKMutableMIDINoteOnCommand commandForCommandType:MIKMIDICommandTypeNoteOn];
noteOn.midiTimestamp = [clock midiTimeStampForMusicTimeStamp:noteEvent.timeStamp];
noteOn.channel = noteEvent.channel;
noteOn.note = noteEvent.note;
noteOn.velocity = 127; // <------- Here's the magic
return [noteOn copy];
}
This can happen for certain types of sampled waveform used by MIDI synths. The peak volume of a sampled waveform might already contain samples near or at the max possible for the format (e.g. +-32767 for 16-bit samples, or +-1.0 for float samples), even though the average volume (energy sum) is too low. Any simple linear increase in volume will thus create clipping noise which can sound quite bad.
Various compressor techniques or filters might be able to increase the average volume without clipping, but that can add a quality reducing (but less annoying than clipping) distortion of its own.
Added: Using another midi sampler, sound font, or voice that has a near constant volume (lower PAPR, peak to average power ratio) is another possibility.
I finally figured it out. My AVSession
's category was set to AVAudioSessionCategoryPlayAndRecord
because I was also utilizing the microphone, which automatically outputs all audio over iPhone's small ear speaker at the top. This is why it appeared so quiet. In order to route the output to the main speakers, you have to call
[session overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker error:&err];
Nonetheless thank you all for the troubleshooting, I appreciate it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With