[sdiy] MIDI to freq calc (was Wavetable Design Update)
Richie Burnett
rburnett at richieburnett.co.uk
Thu Feb 3 18:13:19 CET 2011
The method I use for MIDI note number to frequency (DDS phase increment) value
is as follows...
I have one lookup table that contains one complete octave worth of frequency
values going from a 'C' to the next 'C' an octave above in semitone steps. This
table contains thirteen 16-bit unsigned values. You generate the table by
calculating the DDS phase increments at your chosen sample rate for one complete
octave of notes, (13 values) then store them as left-aligned 16-bit numbers.
(i.e. At the time of creating the table you binary shift all of the frequency
values as far as you can to the left so they use as many bits as possible.
Block floating point if you like. This maximises numeric precision of the
frequencies stored in the LUT.)
Then when you want to play a note, you just subtract 12 from the MIDI note
number until it is in the range 0-11. The resulting value 0-11 tells you which
letter note you are playing and which frequency entry to pull from the LUT. The
number of 12's you subtracted previously tells you which octave you're in, and
therefore how many right shifts to apply to the value you just pulled from the
LUT to scale it for the right octave.
Even though your phase accumulator is probably 24 or 32-bits to get sufficient
frequency resolution in the oscillator, the lookup table only needs to store
16-bit values as long as they are maximised to make use of as many bits as
possible. 16-bits effectively gives you a maximum error of one part in 32768
which is about a 20th of a cent! The 16-bit values pulled from the LUT are then
shifted to the appropriate place within the 32-bit phase increment register
depending on which octave you're in. As someone already pointed out, unless you
shift the 16-bit value outside of the 32-bit phase accumulator no bits are lost
so pitch accuracy is maintained. Even if a few bits fall off the LSB end of the
accumulator during the shift you're still talking milli-hertz errors in
frequency.
This is all fine for notes that fall on exact semitones. For notes that are
pitch-bent between whole semitones the nearest two values are pulled from the
LUT and linear interpolation of the resulting frequency is used. Although the
musical pitch vs frequency curve is exponential over it's full range, a straight
line is a very close approximation to the exponential over a small interval like
one semitone. Maximum error with linear interpolation is when the pitch is bent
almost exactly half way between two semitones, and is about +0.72 cents.
Something that is not likely to be a problem as the error is very small and
notes do not usually linger half-way between semitones unless to sound
deliberately out of tune anyway.
The neat thing about this method is that it requires nothing more than two LUT
operations, linear interpolation and a barrel shift. You only need a small
lookup table of 13x16bit entries (26 bytes) to cover all of the MIDI notes. You
can also support alternative tuning methods (just intonation, equal temp. etc.)
by switching between alternative 13x16bit LUTs.
-Richie Burnett,
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
> * How to convert from my note numbers (these will be all the MIDI-defined
notes plus intermediate detune values) to the prescaler/counter values to plug
into AVR's onboard counter. I would appear to have two options: a lookup
table (would not fit in ATTINY2313 FLASH) or to calculate these at note-on.
I'm favouring the second approach for flexibility (would even allow an actual
frequency to be specified rather than a note number) but haven't the foggiest
how to work out the algorithm required.
I've battled with this one, and tried two approaches.
Attempt One:
As Dave Manley described. There's a exponential 'Pitch' number based on MIDI
notes. I used a 16-bit variable where the top 7 bits were the MIDI note and the
bottom 9 bits gave the between-notes position -eg 512 steps per semitone.
This then has to be converted to a linear frequency increment, in my case 32-bit.
I found two different ways to do this conversion. One was to use a large lookup
table. I had 512x32-bit entries, or an entry every 1/4 of a semitone, and then
do linear interp between those points. The second way is to work out the note
and octave first, which involves dividing the note number by 12 to get the
octave and using the modulus as the note. Now, I hate division since it's about
the most expensive operation there is. But once you've got the two parts, you
can look up the note in a table that only covers one octave, and then use shifts
to move it for other octaves. If you store the increments for the top octave
(the biggest increments) then you can use downshifts and not lose any accuracy.
If you store the bottom octave and upshift instead, you start shifting zeros
into the low bits when you get up to the top octaves and lose accuracy.
Attempt two:
I gave up on the MIDI note numbers. Instead I had a 16-bit Pitch variable based
on octaves. It covered an 8 octave range (although 16 would be easy to) with the
top 3 bits indicating octave and the bottom 13bits indicating position within
the octave. This gives 682 and 2/3rds values per semitone, slightly better than
method one. This is because the range has been reduced from MIDI's 10 octaves to
8. I used another simple lookup table to convert between incoming MIDI note
numbers and the Pitch variable. Modulations are then applied to the pitch
variable, which is then converted to a frequency increment using a one octave
table and shifted as described above.
Obviously now no division has to be done now, since we have the octave and
sub-octave position by simply masking the required bits. The only downside of
this method is that MIDI notes do not fall *exactly* on the available pitch
values. However, since the steps are so small (1/682th of a semitone) this is so
far beyond being perceptible that you'll never know.
So, there you go. My history with Note Numbers and Frequency Increments. Ack!
Tom
More information about the Synth-diy
mailing list