Pregunta

Either there's a serious bug in Apple's MIDI synthesis code, or I'm doing something wrong. Here's my understanding of it. When you send a pitch bend MIDI command, the range of the bend is -8192 to 8191, transposed up to 0. (So the actual range is 0 to 16383.) This number is split up into two 7-bit fields, so really what this means is that you have 128 values of coarse control and 128 values of fine control.

Here's a pitch bend sample that I wrote, similar to the commands in Apple's LoadPresetDemo.

// 'ratio' is the % amount to bend in current pitch range, from -1.0 to 1.0
// 'note' is the MIDI note to bend

NSUInteger bendValue = 8191 + 1 + (8191 * ratio);
NSUInteger bendMSB = (bendValue >> 7) & 0x7F;
NSUInteger bendLSB = bendValue & 0x7F;

UInt32 noteNum = note;
UInt32 noteCommand = kMIDIMessage_PitchBend << 4 | 0;

OSStatus result = MusicDeviceMIDIEvent(self.samplerUnit, noteCommand, noteNum, bendMSB, bendLSB);

When the bendMSB (coarse control) changes, the pitch bends just fine. But when the bendLSB (fine control) changes, nothing happens. In other words, it seems that Apple's MIDI synth is ignoring the LSB, meaning that the note bends only in ugly-sounding discrete chunks.

Here's another way of doing the same thing:

// 'ratio' is the % amount to bend in current pitch range, from -1.0 to 1.0

AudioUnitParameterValue bendValue = 63 + 1 + (63 * ratio); // this is a CGFloat under the hood

AudioUnitSetParameter(self.samplerUnit,
                      kAUGroupParameterID_PitchBend,
                      kAudioUnitScope_Group,
                      0,
                      bendValue,
                      0);

This exhibits identical behavior to the previous example. What's extra-funny about this way of doing things is that the documentation for kAUGroupParameterID_PitchBend specifies that the value range should be -8192 to 8191, which totally doesn't work. The actual range appears to be 0 to 127, and the floating point (fine control) gets ignored.

Finally, if you make the following call to adjust the pitch bend range:

// 'semitones' is the number of semitones (100 cents) to set the pitch bend range to
// 'cents' is the additional number of cents to set the pitch bend range to

UInt32 status = 0xB0 | 0;

MusicDeviceMIDIEvent(self.samplerUnit, status, 0x64, 0x00, 0);        // RPN pitch bend range.
MusicDeviceMIDIEvent(self.samplerUnit, status, 0x65, 0x00, 0);
MusicDeviceMIDIEvent(self.samplerUnit, status, 0x06, semitones, 0);   // Data entry MSB
MusicDeviceMIDIEvent(self.samplerUnit, status, 0x26, cents, 0);       // Data entry LSB (optional)
MusicDeviceMIDIEvent(self.samplerUnit, status, 0x64, 0x7F, 0);        // RPN reset
MusicDeviceMIDIEvent(self.samplerUnit, status, 0x65, 0x7F, 0);

Can you guess what happens? That's right, the LSB message gets ignored and the pitch wheel range only changes by the provided number of semitones.

What's going on here? Is this an Apple bug or am I missing something? (A setup parameter, perhaps?) Or maybe it's not a bug at all? Maybe Apple's synth just doesn't have that level of detail by design? Is that sort of thing legal by the MIDI standard?! Help!


EDIT:

When the pitch bend range is set to 40 semitones, each coarse change makes an audible difference. When the pitch bend range is set to 10 semitones, only every second coarse change makes a difference. At 2 semitones (the default), it takes 4 or more coarse changes to make a difference.

In other words, not only is the LSB apparently ignored, but there also seems to be a minimum # of cents for the pitch to change. Can either of these limitations be fixed? And if not, are there any software synth frameworks for iOS with higher bend resolution?

Hmm... maybe applying kAudioUnitSubType_Varispeed or kAudioUnitSubType_NewTimePitch will yield better results...

¿Fue útil?

Solución

Your pitch bend message is incorrect. Instead of this:

UInt32 noteCommand = kMIDIMessage_PitchBend << 4 | 0;

OSStatus result = MusicDeviceMIDIEvent(self.samplerUnit, noteCommand, noteNum, bendMSB, bendLSB);

do this:

UInt32 bendCommand = kMIDIMessage_PitchBend << 4 | 0;

OSStatus result = MusicDeviceMIDIEvent(self.samplerUnit, bendCommand, bendLSB, bendMSB, 0);

The note value isn't part of the pitch bend command. (Also, I changed the name of the variable noteCommand to bendCommand to more accurately reflect its purpose.)

In LoadPresetDemo I added a property to MainViewController.m:

@property (readwrite) NSInteger bendValue;

and this code:

- (void)sendBendValue:(NSInteger)bendValue {
    //bendValue in the range [-8192, 8191]
    const UInt32 bendCommand = kMIDIMessage_PitchBend << 4 | 0;
    bendValue += 8192;
    UInt32 bendMSB = (bendValue >> 7) & 0x7F;
    UInt32 bendLSB = bendValue & 0x7F;
    NSLog(@"MSB=%d, LSB=%d", (unsigned int)bendMSB, (unsigned int)bendLSB);
    OSStatus result = MusicDeviceMIDIEvent(self.samplerUnit, bendCommand, bendLSB, bendMSB, 0);
    NSAssert (result == noErr, @"Unable to send pitch bend message. Error code: %d '%.4s'", (int) result, (const char *)&result);
}

- (IBAction)bendDown:(id)sender {
    self.bendValue = MAX(-8192, self.bendValue - 0x20);
    [self sendBendValue:self.bendValue];
}

- (IBAction)bendCenter:(id)sender {
    self.bendValue = 0;
    [self setBendRange:50 cents:0];
    [self sendBendValue:self.bendValue];
}

- (IBAction)bendUp:(id)sender {
    self.bendValue = MIN(8191, self.bendValue + 0x20);
    [self sendBendValue:self.bendValue];
}

-(void)setBendRange:(UInt32)semitones cents:(UInt32)cents {
    MusicDeviceMIDIEvent(self.samplerUnit, 0xB0, 0x64, 0, 0);
    MusicDeviceMIDIEvent(self.samplerUnit, 0xB0, 0x65, 0, 0);
    MusicDeviceMIDIEvent(self.samplerUnit, 0xB0, 0x06, semitones, 0);
    MusicDeviceMIDIEvent(self.samplerUnit, 0xB0, 0x26, cents, 0);
    //The following two lines are not really necessary. They only matter if additional controller 0x06 or 0x26 messages are sent
    //MusicDeviceMIDIEvent(self.samplerUnit, 0xB0, 0x64, 0x7F, 0);
    //MusicDeviceMIDIEvent(self.samplerUnit, 0xB0, 0x65, 0x7F, 0);  
}

I created three buttons and assigned them to bendDown:, bendCenter:, and bendUp:.

Run the program and press the bendCenter button. Then, with the trombone sound selected, press and hold the "Mid Note" button. While holding that down, press the bendUp or bendDown buttons. I can hear changes in pitch when the LSB changes and the MSB stays the same.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top