Frequency to Note calculation in Lua?

Can anyone suggest a way how-to calculate from pitch to note in Lua?

I am working on a Ensoniq Mirage template.
The template displays sample time, frequency, lowest frequency and note.
The Note comes from a table, written by hand into a list.

https://www.phys.unsw.edu.au/music/note/

Here is written howto calculate from frequency to note

h = 12 log2(P / C0).

Now I found Lua has only has natural or base10 log. no log base 2

Here is a possible way todo it with 2 logs.

logb(a) = logc(a) / logc(b)

Here is a python example

from math import log2, pow

A4 = 440
C0 = A4*pow(2, -4.75)
name = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]
    
def pitch(freq):
    h = round(12*log2(freq/C0))
    octave = h // 12
    n = h % 12
    return name[n] + str(octave)

Anyhow thanks for any hints on this

not around any computer where I could test, but I think lua math.log has base as second optional argument. try print log(8, 2)

2 Likes

Great thanks, that does work.
Now I can try to make it work.

Thanks

I’d ask ChatGPT that, seriously, I’ve been working with python code all day and the amount of fixes it delivered is mind blowing. Sometimes you have to correct it, but it is very helpful and impressive

1 Like

Hmmm chatGPT was not giving anything working untill I input the working python code and gave the formula.
Once at least the note came out I asked to include offset in cents.
Haven’t tested it properly but this is what I got and it seem to work. !!!

Its surprising how confident chatGPT give the wrong answers :slight_smile:
But still in the end i got what i wanted.
Pretty cool!

local A4 = 440
local C0 = A4 * 2^(-4.75)
local name = {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"}

function pitch(freq)

    if freq < 27.5 or freq > 4186.01 then
        error("Frequency out of range: " .. freq)
    end

    local h = math.floor(12 * math.log(freq / C0, 2) + 0.5)
    local octave = math.floor(h / 12)
    local n = h % 12 + 1
    local cents_offset = math.floor(1200 * math.log(freq / (C0 * 2^(h/12)), 2) + 0.5)
    return name[n] .. octave, cents_offset
end

--usage
local freq = 261.63 
local note, offset = pitch(freq)
print(note, offset)

freq = 440
note, offset = pitch(freq)
print(note, offset)

freq = 444
note, offset = pitch(freq)
print(note, offset)

For the Mirage to get sample frequency into the note range we do

--Ensoniq Mirage Sample frequency range
if freq < 5000 or freq > 50000 then
        error("Input frequency out of range: " .. freq)
    end

    -- Divide the input frequency by 256 to get it into the note range
    freq = freq / 256

Pretty cool I got all the tools to generate a note with offset so the Mirage can sample ‘perfect’ loops.

This is not tested yet.

function get_pitch_bend_value(cents_offset, bend_range)
    -- Calculate the pitch bend units per cent
    local pitch_bend_units_per_cent = 8192 / bend_range / 100

    -- Calculate the pitch bend value based on the cents offset
    local pitch_bend_value = math.floor(cents_offset * pitch_bend_units_per_cent)

    -- Convert the pitch bend value to a signed 14-bit integer
    pitch_bend_value = pitch_bend_value + 8192

    -- Clamp the pitch bend value to the range of 0 to 16383
    pitch_bend_value = math.min(16383, math.max(0, pitch_bend_value))

    return pitch_bend_value
end

-- Generate pitch bend value for a 100 cent up bend with a pitch bend range of 2 semitones
local pitch_bend_value = get_pitch_bend_value(-100, 200)
print("The pitchbend value " .. pitch_bend_value)

pitchbendUnits = pitch_bend_value - 8192

print(pitchbendAmound)

But all the data can be then used to send a midi note and a bend message to trigger the exact frequency that fits into the Mirage.

3 Likes