MIDI synthesis in iOS misbehaving WRT pitch bending: LSB ignored - ios

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...

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.

Related

How to get more precise output out of an FFT?

I am trying to make a colored waveform using the output of the following code. But when I run it, I only get certain numbers (see the freq variable, it uses the bin size, frame rate and index to make these frequencies) as output frequencies. I'm no math expert, even though I cobbled this together from existing code and answers.
//
// colored_waveform.c
// MixDJ
//
// Created by Jonathan Silverman on 3/14/19.
// Copyright © 2019 Jonathan Silverman. All rights reserved.
//
#include "colored_waveform.h"
#include "fftw3.h"
#include <math.h>
#include "sndfile.h"
//int N = 1024;
// helper function to apply a windowing function to a frame of samples
void calcWindow(double* in, double* out, int size) {
for (int i = 0; i < size; i++) {
double multiplier = 0.5 * (1 - cos(2*M_PI*i/(size - 1)));
out[i] = multiplier * in[i];
}
}
// helper function to compute FFT
void fft(double* samples, fftw_complex* out, int size) {
fftw_plan p;
p = fftw_plan_dft_r2c_1d(size, samples, out, FFTW_ESTIMATE);
fftw_execute(p);
fftw_destroy_plan(p);
}
// find the index of array element with the highest absolute value
// probably want to take some kind of moving average of buf[i]^2
// and return the maximum found
double maxFreqIndex(fftw_complex* buf, int size, float fS) {
double max_freq = 0;
double last_magnitude = 0;
for(int i = 0; i < (size / 2) - 1; i++) {
double freq = i * fS / size;
// printf("freq: %f\n", freq);
double magnitude = sqrt(buf[i][0]*buf[i][0] + buf[i][1]*buf[i][1]);
if(magnitude > last_magnitude)
max_freq = freq;
last_magnitude = magnitude;
}
return max_freq;
}
//
//// map a frequency to a color, red = lower freq -> violet = high freq
//int freqToColor(int i) {
//
//}
void generateWaveformColors(const char path[]) {
printf("Generating waveform colors\n");
SNDFILE *infile = NULL;
SF_INFO sfinfo;
infile = sf_open(path, SFM_READ, &sfinfo);
sf_count_t numSamples = sfinfo.frames;
// sample rate
float fS = 44100;
// float songLengLengthSeconds = numSamples / fS;
// printf("seconds: %f", songLengLengthSeconds);
// size of frame for analysis, you may want to play with this
float frameMsec = 5;
// samples in a frame
int frameSamples = (int)(fS / (frameMsec * 1000));
// how much overlap each frame, you may want to play with this one too
int frameOverlap = (frameSamples / 2);
// color to use for each frame
// int outColors[(numSamples / frameOverlap) + 1];
// scratch buffers
double* tmpWindow;
fftw_complex* tmpFFT;
tmpWindow = (double*) fftw_malloc(sizeof(double) * frameSamples);
tmpFFT = (fftw_complex*) fftw_malloc(sizeof(fftw_complex) * frameSamples);
printf("Processing waveform for colors\n");
for (int i = 0, outptr = 0; i < numSamples; i += frameOverlap, outptr++)
{
double inSamples[frameSamples];
sf_read_double(infile, inSamples, frameSamples);
// window another frame for FFT
calcWindow(inSamples, tmpWindow, frameSamples);
// compute the FFT on the next frame
fft(tmpWindow, tmpFFT, frameSamples);
// which frequency is the highest?
double freqIndex = maxFreqIndex(tmpFFT, frameSamples, fS);
printf("%i: ", i);
printf("Max freq: %f\n", freqIndex);
// map to color
// outColors[outptr] = freqToColor(freqIndex);
}
printf("Done.");
sf_close (infile);
}
Here is some of the output:
2094216: Max freq: 5512.500000
2094220: Max freq: 0.000000
2094224: Max freq: 0.000000
2094228: Max freq: 0.000000
2094232: Max freq: 5512.500000
2094236: Max freq: 5512.500000
It only shows certain numbers, not a wide variety of frequencies like it maybe should. Or am I wrong? Is there anything wrong with my code you guys can see? The color stuff is commented out because I haven't done it yet.
The frequency resolution of an FFT is limited by the length of the data sample you have. The more samples you have, the higher the frequency resolution.
In your specific case you chose frames of 5 milliseconds, which is then transformed to a number of samples on the following line:
// samples in a frame
int frameSamples = (int)(fS / (frameMsec * 1000));
This corresponds to only 8 samples at the specified 44100Hz sampling rate. The frequency resolution with such a small frame size can be computed to be
44100 / 8
or 5512.5Hz, a rather poor resolution. Correspondingly, the observed frequencies will always be one of 0, 5512.5, 11025, 16537.5 or 22050Hz.
To get a higher resolution you should increase the number of samples used for analysis by increasing frameMsec (as suggested by the comment "size of frame for analysis, you may want to play with this").

Efficiently generate a Sine wave in IOS

What is the most efficient way of generating a sine wave for a device running IOS. For the purposes of the exercise assume a frequency of 440Hz and a sampling rate of 44100Hz and 1024 samples.
A vanilla C implementation looks something like.
#define SAMPLES 1024
#define TWO_PI (3.14159 * 2)
#define FREQUENCY 440
#define SAMPLING_RATE 44100
int main(int argc, const char * argv[]) {
float samples[SAMPLES];
float phaseIncrement = TWO_PI * FREQUENCY / SAMPLING_RATE;
float currentPhase = 0.0;
for (int i = 0; i < SAMPLES; i ++){
samples[i] = sin(currentPhase);
currentPhase += phaseIncrement;
}
return 0;
}
To take advantage of the Accelerate Framework and the vecLib vvsinf function the loop can be changed to only do the addition.
#define SAMPLES 1024
#define TWO_PI (3.14159 * 2)
#define FREQUENCY 440
#define SAMPLING_RATE 44100
int main(int argc, const char * argv[]) {
float samples[SAMPLES] __attribute__ ((aligned));
float results[SAMPLES] __attribute__ ((aligned));
float phaseIncrement = TWO_PI * FREQUENCY / SAMPLING_RATE;
float currentPhase = 0.0;
for (int i = 0; i < SAMPLES; i ++){
samples[i] = currentPhase;
currentPhase += phaseIncrement;
}
vvsinf(results, samples, SAMPLES);
return 0;
}
But is just applying the vvsinf function as far as I should go in terms of efficiency?
I don't really understand the Accelerate framework well enough to know if I can also replace the loop. Is there a vecLib or vDSP function I can use?
For that matter is it possible to use an entirely different alogrithm to fill a buffer with a sine wave?
Given that you are computing the sine of a phase argument which increases in fixed increments, it is generally much faster to implement the signal generation with a recurrence equation as described in this "How to Create Oscillators in Software" post and some more in this "DSP Trick: Sinusoidal Tone Generator" post, both on dspguru:
y[n] = 2*cos(w)*y[n-1] - y[n-2]
Note that this recurrence equation can be subject to numerical roundoff error accumulation, you should avoid computing too many samples at a time (your selection of SAMPLES == 1024 should be fine). This recurrence equation can be used after you have obtained the first two values y[0] and y[1] (the initial conditions). Since you are generating with an initial phase of 0, those are simply:
samples[0] = 0;
samples[1] = sin(phaseIncrement);
or more generally with an arbitrary initial phase (particularly useful to reinitialize the recurrence equation every so often to avoid the numerical roundoff error accumulation I mentioned earlier):
samples[0] = sin(initialPhase);
samples[1] = sin(initialPhase+phaseIncrement);
The recurrence equation can then be implemented directly with:
float scale = 2*cos(phaseIncrement);
// initialize first 2 samples for the 0 initial phase case
samples[0] = 0;
samples[1] = sin(phaseIncrement);
for (int i = 2; i < SAMPLES; i ++){
samples[i] = scale * samples[i-1] - samples[i-2];
}
Note that this implementation could be vectorized by computing multiple tones (each with the same frequency, but with larger phase increments between samples) with appropriate relative phase shifts, then interleaving the results to obtain the original tone (e.g. computing sin(4*w*n), sin(4*w*n+w), sin(4*w*n+2*w) and sin(4*w*n+3*w)). This would however make the implementation a lot more obscure, for a relatively small gain.
Alternatively the equation can be implemented by making use of vDsp_deq22:
// setup dummy array which will hold zeros as input
float nullInput[SAMPLES];
memset(nullInput, 0, SAMPLES * sizeof(float));
// setup filter coefficients
float coefficients[5];
coefficients[0] = 0;
coefficients[1] = 0;
coefficients[2] = 0;
coefficients[3] = -2*cos(phaseIncrement);
coefficients[4] = 1.0;
// initialize first 2 samples for the 0 initial phase case
samples[0] = 0;
samples[1] = sin(phaseIncrement);
vDsp_deq22(nullInput, 1, coefficients, samples, 1, SAMPLES-2);
If efficiency is required, you could pre-load a 440hz (44100 / 440) sine waveform look-up table and loop around it without further mapping or pre-load a 1hz (44100 / 44100) sine waveform look-up table and loop around by skipping samples to reach 440hz just as you did by incrementing a phase counter. Using look-up tables should be faster than computing sin().
Method A (using 440hz sine waveform):
#define SAMPLES 1024
#define FREQUENCY 440
#define SAMPLING_RATE 44100
#define WAVEFORM_LENGTH (SAMPLING / FREQUENCY)
int main(int argc, const char * argv[]) {
float waveform[WAVEFORM_LENGTH];
LoadSinWaveForm(waveform);
float samples[SAMPLES] __attribute__ ((aligned));
float results[SAMPLES] __attribute__ ((aligned));
for (int i = 0; i < SAMPLES; i ++){
samples[i] = waveform[i % WAVEFORM_LENGTH];
}
vvsinf(results, samples, SAMPLES);
return 0;
}
Method B (using 1hz sine waveform):
#define SAMPLES 1024
#define FREQUENCY 440
#define TWO_PI (3.14159 * 2)
#define SAMPLING_RATE 44100
#define WAVEFORM_LENGTH SAMPLING_RATE // since it's 1hz
int main(int argc, const char * argv[]) {
float waveform[WAVEFORM_LENGTH];
LoadSinWaveForm(waveform);
float samples[SAMPLES] __attribute__ ((aligned));
float results[SAMPLES] __attribute__ ((aligned));
float phaseIncrement = TWO_PI * FREQUENCY / SAMPLING_RATE;
float currentPhase = 0.0;
for (int i = 0; i < SAMPLES; i ++){
samples[i] = waveform[floor(currentPhase) % WAVEFORM_LENGTH];
currentPhase += phaseIncrement;
}
vvsinf(results, samples, SAMPLES);
return 0;
}
Please note that:
Method A is susceptible to frequency inaccuracy due to assuming that your frequency always divides correctly the sampling rate, which is not true. That means you may get 441hz or 440hz with a glitch.
Method B is susceptible to aliasing as the frequency goes up an gets closer to the Nyquist frequency, but it's a good trade-off between performance, quality and memory consumption if synthesizing reasonable low frequencies such as the one in your example.

Goertzel algorithm giving infinite result

I have a sinewave at 20hz - 1 amplitude that I have created using Audacity software. It is also only 500ms.
I am using following algorithm to detect the frequency.
All I want to detect if tone amplitude passes a threshold and gives me positive result at 20 hz frequency cycles.
static float goertzel_mag(int numSamples,int TARGET_FREQUENCY,int SAMPLING_RATE, float* data)
{
int k,i;
float floatnumSamples;
float omega,sine,cosine,coeff,q0,q1,q2,magnitude,real,imag;
float scalingFactor = numSamples / 2.0;
floatnumSamples = (float) numSamples;
k = (int) (0.5 + ((floatnumSamples * TARGET_FREQUENCY) / SAMPLING_RATE));
omega = (2.0 * M_PI * k) / floatnumSamples;
sine = sin(omega);
cosine = cos(omega);
coeff = 2.0 * cosine;
q0=0;
q1=0;
q2=0;
for(i=0; i<numSamples; i++)
{
q0 = coeff * q1 - q2 + data[i];
q2 = q1;
q1 = q0;
}
// calculate the real and imaginary results
// scaling appropriately
real = (q1 - q2 * cosine) / scalingFactor;
imag = (q2 * sine) / scalingFactor;
magnitude = sqrtf(real*real + imag*imag);
return magnitude;
}
call the function
// If there's more packets, read them
inCompleteAQBuffer->mAudioDataByteSize = numBytes;
CheckError(AudioQueueEnqueueBuffer(inAQ,
inCompleteAQBuffer,
(sound->packetDescs?nPackets:0),
sound->packetDescs),
"couldn't enqueue buffer");
sound->packetPosition += nPackets;
NSLog(#"number of packets %i",nPackets);
float *data=(float*)inCompleteAQBuffer->mAudioData;
int nn = sizeof(data)/sizeof(float);
float gort = goertzel_mag(nn, 20, 44100, data);
NSLog(#"gort:%f", gort);
if (gort == INFINITY)
NSLog(#"positive infinity");
break point inside of the function
output
number of packets 8192
gort:36029896530591744.000000
number of packets 8192
gort:inf
positive infinity
number of packets 5666
gort:inf
positive infinity
Why I am getting inf result? I don't know how to read the return value, I understand magnitude always has to be positive value but I create the file with 1 amplitude, shouldn't I be getting 0 to 1 results?
EDIT;
Aduio info
afinfo 500ms.aiff
File: 500ms.aiff
File type ID: AIFF
Num Tracks: 1
----
Data format: 1 ch, 44100 Hz, 'lpcm' (0x0000000E) 16-bit big-endian signed integer
no channel layout.
estimated duration: 0.500000 sec
audio bytes: 44100
audio packets: 22050
bit rate: 705600 bits per second
packet size upper bound: 2
maximum packet size: 2
audio data file offset: 54
optimized
source bit depth: I16
I think one problem is with these lines:
float *data=(float*)inCompleteAQBuffer->mAudioData;
int nn = sizeof(data)/sizeof(float);
which I believe is intended to tell you the number of samples. I don't have the information or resources to reproduce your code, but can reproduce the bug with this:
#include <stdio.h>
#include <stdlib.h>
int main(void) {
float *data=malloc(sizeof(float) * 10);
printf ("Sizeof 'data' = %d\n", sizeof(data));
return 0;
}
Program output
Sizeof 'data' = 4
which on my 32-bit compilation is the size of the array pointer, not the array. And using sizeof(*data) won't get you anywhere since that just tells you the size of the data type float, not the array.
There is no way you can ascertain the size of the array, or number of elements, from its pointer, so my answer is, sadly, you need more information, perhaps numBytes? Or numBytes/sizeof(float)?

iOS FFT Draw spectrum

I've read these question:
Using the Apple FFT and Accelerate Framework
How do I set up a buffer when doing an FFT using the Accelerate framework?
iOS FFT Accerelate.framework draw spectrum during playback
They all describe how to setup fft with the accelerate framework. With their help I was able to setup fft and get a basic spectrum analyzer. Right now, I'm displaying all values I got from the fft. However, I only want to show 10-15, or a variable number, of bars respreseting certain frequencies. Just like the iTunes or WinAmp Level Meter.
1. Do I need to average magnitude values from a range of frequencies? Or do they just show you a magnitude for the specific frequency bar?
2. Also, do I need to convert my magnitude values to db?
3. How do I map my data to a certain range. Do I map against the max db range for my sounds bitdepth? Getting the max Value for a bin will lead to jumping max mapping values.
My RenderCallback:
static OSStatus PlaybackCallback(void *inRefCon,
AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberFrames,
AudioBufferList *ioData)
{
UInt32 maxSamples = kAudioBufferNumFrames;
UInt32 log2n = log2f(maxSamples); //bins
UInt32 n = 1 << log2n;
UInt32 stride = 1;
UInt32 nOver2 = n/2;
COMPLEX_SPLIT A;
float *originalReal, *obtainedReal, *frequencyArray, *window, *in_real;
in_real = (float *) malloc(maxSamples * sizeof(float));
A.realp = (float *) malloc(nOver2 * sizeof(float));
A.imagp = (float *) malloc(nOver2 * sizeof(float));
memset(A.imagp, 0, nOver2 * sizeof(float));
obtainedReal = (float *) malloc(n * sizeof(float));
originalReal = (float *) malloc(n * sizeof(float));
frequencyArray = (float *) malloc(n * sizeof(float));
//-- window
UInt32 windowSize = maxSamples;
window = (float *) malloc(windowSize * sizeof(float));
memset(window, 0, windowSize * sizeof(float));
// vDSP_hann_window(window, windowSize, vDSP_HANN_DENORM);
vDSP_blkman_window(window, windowSize, 0);
vDSP_vmul(ioBuffer, 1, window, 1, in_real, 1, maxSamples);
//-- window
vDSP_ctoz((COMPLEX*)in_real, 2, &A, 1, maxSamples/2);
vDSP_fft_zrip(fftSetup, &A, stride, log2n, FFT_FORWARD);
vDSP_fft_zrip(fftSetup, &A, stride, log2n, FFT_INVERSE);
float scale = (float) 1.0 / (2 * n);
vDSP_vsmul(A.realp, 1, &scale, A.realp, 1, nOver2);
vDSP_vsmul(A.imagp, 1, &scale, A.imagp, 1, nOver2);
vDSP_ztoc(&A, 1, (COMPLEX *) obtainedReal, 2, nOver2);
vDSP_zvmags(&A, 1, obtainedReal, 1, nOver2);
Float32 one = 1;
vDSP_vdbcon(obtainedReal, 1, &one, obtainedReal, 1, nOver2, 0);
for (int i = 0; i < nOver2; i++) {
frequencyArray[i] = obtainedReal[i];
}
// Extract the maximum value
double fftMax = 0.0;
vDSP_maxmgvD((double *)obtainedReal, 1, &fftMax, nOver2);
float max = sqrt(fftMax);
}
Playing some music, I get values from -96db to 0db.
Plotting a point at:
CGPointMake(i, kMaxSpectrumHeight * (1 - frequencyArray[i]/-96.));
is giving my a rather rounded curve:
plot1
If I don't convert to db I can plot by multiplying my array value by 10000 and get nice peaks.
plot2
Am I doing something totally wrong? And how do I get to showing a variable number of bars?
Do I need to average magnitude values from a range of frequencies? Or do they just show you a magnitude for the specific frequency bar?
Yes, you definitely need to average values across the bands you've defined. Showing just one FFT bin is madness.
Also, do I need to convert my magnitude values to db?
Yes: dB is a log scale. Not coincidentally, human hearing also works (roughly) on a log scale. The values will therefore look more natural to humans if you take log2() of the values before plotting them.
How do I map my data to a certain range. Do I map against the max db range for my sounds bitdepth? Getting the max Value for a bin will
lead to jumping max mapping values.
I find the easiest thing to do (conceptually at least) is to convert your values from whatever format into a 0..1, i.e. 'normalised and scaled' float value. Then from there you can convert if necessary to something you need to plot. For example
SInt16 rawValue = fft[0]; // let's say this comes back as 12990
float scaledValue = rawValue/32767.; // This is MAX_INT for 16-bit;
// dividing we get .396435438 which is much easier for most people
// to see conceptually as 39% of our max possible value
float displayValue = log2(scaledValue);
my_fft[0] = displayValue;

Calculating CoreMIDI Pitch Bend Values For iOS?

I need to hand assemble 14bit MIDI Pitch Bend values from raw UInt16 values in iOS. I'm wondering if anybody out there has had a chance to come up with an elegant solution? Here's where I'm at - I'll get a chance to test this probably later today, but if I hear back before then, great:
First, some MIDI preliminaries for anybody curious.
MIDI Pitch Bend is broken up into one Status Byte followed by two Data Bytes (it's a 14bit controller), these two Data Bytes are associated with their Status Byte by both leading with a Zero status bit, MIDI Spec has them appearing in the order of MSB -> LSB
(Edit: Update, it's actually Status -> LSB -> MSB )
( ie 1110 0000, 0111 1111, 0111 1111 )
The challenge is how to break up an ARM/Intel 16bit UInt16 into two 7 bit segments on iOS, and have it make sense for MIDI?
Please keep in mind that, because we're dealing with an unsigned integer, a 0 value is NOT neutral pitch bend, but rather full pitch down - where as neutral pitch bend is defined as 8192 - and 16,383 is full pitch up.
So here's my best guess as to how to do this:
UInt16 msbAnd = base10ValueUInt16 & 16256; //clearing out LSB
UInt16 msbAndShift = msbAnd << 1; //shift into leading Byte, with 0 status bit
UInt16 lsbAnd = base10ValueUInt16 & 127; //isolating LSB
UInt16 finalTwoBytePitchWord = msbFinalAndShift | lsbAnd; //make UInt16 word
UInt16 finalTwoBytePitchWordFlipped = CFSwapInt16HostToBig(finalTwoBytePitchWord); //Endian tweak
This code runs fine and seems to create the two Data Bytes with the required zero status bits and flips them around from little endian Intel/ARM which seems to be necessary for MIDI (MIDI is STATUS -> MSB -> LSB ): I can slap on the leading Status Byte with the appropriate MIDI channel later.
So, does this make sense? Has anybody come up with a more elegant solution? ( is there a Library I'm overlooking? ) ... I'll check back in later and also let folks know if this actually worked on the sampler I have to target it at.
Thanks
I think your code is close to right, but it's overly complicated. This question has nothing to do with iOS or endianness or ARM or Intel; it's just plain old C bit-twiddling. If you write the code correctly, it will work on any reasonable platform without modification. You don't need a library; it's only a couple lines of code.
It's best to work with MIDI on a byte-by-byte basis. You want a function that takes a 16-bit unsigned integer (which we'll trust has at most 14 bits worth of value) and returns two single-byte values, one with the most significant bits, one with the least significant bits.
Later on, when you send the message, you assemble the bytes in the appropriate order. According to the specification, pitch wheel messages are three bytes: STATUS, then LSB, then MSB. You have them backwards in your question!
The least-significant 7 bits are easy: just mask off those bits from the original value. The most-significant 7 bits are similar: mask off the next higher 7 bits from the original value, then shift them down.
It doesn't matter whether the 16-bit integers are little-endian or big-endian in memory on your machine; the compiler takes care of that.
Here's a function and a test tool.
#include <stdio.h>
#include <stdint.h> // for C standard uint8_t and uint16_t
// or, if you prefer, use unsigned char and unsigned short, or Byte and UInt16;
// they'll all work, although some are more portable than others
void encode14BitValue(uint16_t value, uint8_t *out_msb, uint8_t *out_lsb)
{
uint16_t mask = 0x007F; // low 7 bits on
// "(1 << 7) - 1" is arguably clearer
*out_lsb = value & mask;
*out_msb = (value & (mask << 7)) >> 7;
}
int main(int argc, const char * argv[])
{
typedef struct {
uint16_t in;
uint8_t expected_msb;
uint8_t expected_lsb;
} test_case;
test_case cases[] = {
{ 0x0000, 0x00, 0x00 },
{ 0x0001, 0x00, 0x01 },
{ 0x0002, 0x00, 0x02 },
{ 0x0004, 0x00, 0x04 },
{ 0x0008, 0x00, 0x08 },
{ 0x0009, 0x00, 0x09 },
{ 0x000F, 0x00, 0x0F },
{ 0x0010, 0x00, 0x10 },
{ 0x0011, 0x00, 0x11 },
{ 0x001F, 0x00, 0x1F },
{ 0x0020, 0x00, 0x20 },
{ 0x0040, 0x00, 0x40 },
{ 0x0070, 0x00, 0x70 },
{ 0x007F, 0x00, 0x7F },
{ 0x0080, 0x01, 0x00 },
{ 0x0081, 0x01, 0x01 },
{ 0x008F, 0x01, 0x0F },
{ 0x0090, 0x01, 0x10 },
{ 0x00FF, 0x01, 0x7F },
{ 0x0100, 0x02, 0x00 },
{ 0x0200, 0x04, 0x00 },
{ 0x0400, 0x08, 0x00 },
{ 0x0800, 0x10, 0x00 },
{ 0x1000, 0x20, 0x00 },
{ 0x1FFF, 0x3F, 0x7F },
{ 0x2000, 0x40, 0x00 },
{ 0x2001, 0x40, 0x01 },
{ 0x3FFF, 0x7F, 0x7F },
};
int passed = 1;
for (int i = 0, c = sizeof(cases) / sizeof(cases[0]); i < c; i++) {
uint8_t msb, lsb;
encode14BitValue(cases[i].in, &msb, &lsb);
if (cases[i].expected_msb != msb || cases[i].expected_lsb != lsb) {
printf("failed: 0x%04hX expected 0x%02hhX 0x%02hhX got 0x%02hhX 0x%02hhX\n", cases[i].in, cases[i].expected_msb, cases[i].expected_lsb, msb, lsb);
passed = 0;
}
}
return passed ? 0 : 1;
}
In your code, trying to pack the two bytes of result into one 16-bit integer just adds confusion. I don't know why you're doing that, since you're going to have to extract individual bytes again, whenever you send the MIDI anywhere else. That's where any worries about endianness come up, since your packing and unpacking code have to agree. You might as well not bother. I bet your code was incorrect, but your error in swapping MSB and LSB compensated for it.

Resources