Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I increase MIDI synth volume/gain without distortion on iOS?

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:

  • modify all MIDI Note On event's velocity property to the maximum of 127. This slightly but insufficiently raises the volume.
  • Add a mixer node to the AUGraph as described here. This sufficiently raises the volume, but instantly distorts the signal so badly that the quality is way too poor.
  • Raise the volume of the soundfont's samples using a soundfont editor like Polyphone. This has no perceivable effect.

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?


Code Snippets

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];
}
like image 960
Double M Avatar asked Feb 07 '23 16:02

Double M


2 Answers

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.

like image 103
hotpaw2 Avatar answered Feb 13 '23 07:02

hotpaw2


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.

like image 45
Double M Avatar answered Feb 13 '23 05:02

Double M