Some useful math functions for working with soundfonts

SoundFont uses some interesting units in its spec - I’ve done some math and written a couple of functions to deal with translating between these and Renoise.

local log = math.log
local exp = math.exp
local min = math.min
local max = math.max
local E = exp(1)

local LOG_TIMECENT = log(2) / 1200
function timecents_to_seconds(tc)
  return exp(LOG_TIMECENT * tc)
end

-- 10th of a decibel, can convert to decibels by dividing by 10
local LOG_CENTIBEL = log(10) / 200
function centibels_to_amp(cb)
  return exp(LOG_CENTIBEL * cb)
end

-- useful for exponential dropoffs - Renoise AHDSR etc operate in
-- amplitude-linear scale, but many tools need dB-linear fades.
local function exerp(a, b, q)
  return exp((1-q)*log(a) + q*log(b))
end

local function revlerp(a, b, x)
  if x == a then return 0 end
  if x == b then return 1 end
  if a == b then error("impossible revlerp") end
  return (x - a) / (b - a)
end

local LOG2 = log(2)
local function log2(x)
  return log(x)/LOG2
end

local function clip(lo, hi, x)
  return min(max(x, lo), hi)
end

-- here's the coup de grace - returns an "optimal" renoise smoothing
-- parameter that mimics an exponential dropoff or increase between
-- values y1 and y2. it does this by ensuring that the midpoint of
-- Renoise's curve equals the midpoint of what the exponential curve
-- would be. There is almost certainly a more optimal number, but the
-- math to get that number is very complicated and I couldn't find a
-- performant way to do it.
--
-- it is based on taktik's example code here:
-- https://forum.renoise.com/t/request-automation-lines-curves-equations/65599/2
-- by setting this evaluated at 0.5 to exerp at 0.5 and solving for the
-- smoothing parameter.
local function optimal_smoothing_between(y1, y2)
  if y1 == y2 then return 0 end
  if y1 < y2 then return -optimal_smoothing_between(y2, y1) end

  local out = exerp(y1, y2, 0.5)
  out = revlerp(y1, y2, out)
  out = log2(1 - out) + 1
  out = - out / 16
  out = out ^ (2/E)
  return -clip(-1, 1, out)
end

Also, according to the FluidSynth implementation, the attack phase of a SoundFont AHDSR is amplitude-linear, but the decay and release phases are dB-linear and must be smoothed.

6 Likes