Last active
November 15, 2021 19:56
-
-
Save trentgill/f5e45a06ee14520b02f6d5e3bba5f178 to your computer and use it in GitHub Desktop.
a lua-quantizer in the .scale style
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function quantize( volt, scale, divs, range ) | |
-- defaults | |
chrom = {0,1,2,3,4,5,6,7,8,9,10,11} | |
if scale == nil then scale = chrom end | |
local notes = #scale | |
if notes == 0 then | |
scale = chrom | |
notes = #scale | |
end | |
divs,range = divs or 12 | |
, range or 1.0 | |
-- separate note & octave | |
local wrap = 0 | |
if type(divs) == 'string' then -- assume just intonation | |
else | |
while volt > range do | |
end | |
end | |
-- calc index into table | |
local ix = 1+math.floor(volt/range * #scale) | |
-- scale to voltage | |
local v = 0 | |
if type(divs) == 'string' then -- assume just intonation | |
v = math.log(scale[ix]) / math.log(2) | |
else | |
v = scale[ix] / divs | |
end | |
return v * range | |
-- reapply octave | |
end | |
---- from input.scale fix | |
-- nb: remove all tracking of previous outputs | |
function find_bounds(s, ix, oct) | |
-- find ideal voltage for this window | |
local sum = (oct*s.scaling) + ix*s.win -- sum in terms of voltage | |
-- calculate bounds | |
sum = sum - s.offset | |
s.lower = sum - s.hyst | |
s.upper = sum + s.win + s.hyst | |
print(s.lower, s.upper) | |
end | |
function make_scale(scale, scaleLen, divs, scaling) | |
local t = { scale = scale | |
, sLen = scaleLen | |
, divs = divs or 12 | |
, scaling = scaling or 1.0 | |
} | |
if t.sLen == 0 then -- auto-chromatic | |
t.sLen = divs | |
for i=1,divs do t.scale[i] = (i-1)/divs end | |
end | |
-- pre-calc for bounds & hysteresis (optimzation) | |
t.offset = 0.5 * t.scaling / t.divs -- half of a div | |
t.win = (1.0 / t.sLen) * t.scaling -- a note window in terms of voltage | |
t.hyst = t.win/20 -- calculate the hysteresis per window (5%) | |
t.hyst = (t.hyst < 0.006) and 0.006 or t.hyst -- clamp to 1 LSB at 12bit | |
find_bounds(t, 0, -10) -- set to invalid note | |
return t | |
end | |
function do_scale(s, v) | |
if v > s.upper | |
or v < s.lower then | |
-- offset input to ensure we capture noisy notes at the divs | |
v = v + s.offset | |
-- calculate index of input | |
local n_level = v / s.scaling | |
local octaves = math.floor(n_level) | |
local phase = n_level - octaves | |
local fix = phase * s.sLen | |
local ix = math.floor(fix) | |
-- perform scale lookup & prepare outs | |
local note = s.scale[ix+1] | |
local noteOct = note + octaves*s.divs | |
local volts = (note/s.divs + octaves) * s.scaling | |
-- TODO call action | |
-- calculate new bounds | |
find_bounds(s, ix, octaves) | |
end | |
end | |
S = make_scale({5,-12,7,11}, 4) | |
for i=1,2,0.01 do | |
do_scale(S, i) | |
do_scale(S, i) | |
end | |
--- another version? | |
--- issues: | |
--- lastVolts is not initialized | |
--- lastIndex is not initialized | |
--- lastOct is not initialized | |
--- hwin clamp is too big. i think it's missing a zero -> 0.00666 | |
-- 0.066 is only 15 divisions per octave | |
-- it should be *half* a division, so 1/24 for chromatic semitones | |
-- the only reason to clamp it is to avoid microdivs, but that's prob unrealistic? | |
function make_scale(scale, scaleLen, divs, scaling) | |
local t = { action = event | |
, scale = scale | |
, sLen = scaleLen | |
, divs = divs | |
, scaling = scaling | |
, offset = 0.5 * scaling / divs | |
} | |
print('offset',t.offset) | |
t.hwin = t.offset * 1.1 | |
print('hwin',t.hwin) | |
t.hwin = (t.hwin < 0.02) and 0.02 or t.hwin | |
print('hwin\'',t.hwin) | |
print() | |
if t.sLen == 0 then -- auto-chromatic | |
t.sLen = 1 | |
t.scale[1] = 0.0 | |
t.scaling = scaling / divs | |
t.divs = 1.0 | |
end | |
t.lastNote = -100.0 | |
t.lastVolts = -100 | |
return t | |
end | |
function do_scale(s, v) | |
print(v, (s.lastVolts - s.hwin), (s.lastVolts + s.hwin)) | |
-- print((s.lastVolts - s.hwin)) | |
---- the issue is that we compare against s.lastVolts | |
-- however, this is not necessarily a linear (or even ordered) parameter | |
-- instead we *must* determine the window of the input on every sample | |
-- or alternatively, store the previous input voltage & whether a change occured. | |
-- then use hwin to see if it's moved sufficiently far from the previous *input* (not output) | |
if (v > (s.lastVolts + s.hwin)) | |
or (v < (s.lastVolts - s.hwin)) then -- in a new division zone, might be a new note | |
-- print' close' | |
v = v + s.offset | |
local n_level = v / s.scaling | |
local octaves = math.floor(n_level) | |
local phase = n_level - octaves | |
local fix = phase * s.sLen | |
local ix = math.floor(fix) | |
if (ix ~= s.lastIndex) | |
or (octaves ~= s.lastOct) then | |
print('',ix,s.lastIndex, ix ~= s.lastIndex) | |
print('',octaves,s.lastOct, octaves ~= s.lastOct) | |
local note = s.scale[ix+1] | |
local noteOct = note + octaves*s.divs | |
local volts = (note/s.divs + octaves) * s.scaling | |
s.lastIndex = ix | |
s.lastOct = octaves | |
s.lastNote = noteOct | |
s.lastVolts = volts | |
print(' new', ix, octaves, noteOct, volts) | |
end | |
else print' _' | |
end | |
end | |
-- function do_scale(s, v) | |
-- print((s.lastVolts + s.hwin)) | |
-- print((s.lastVolts - s.hwin)) | |
-- if (v > (s.lastVolts + s.hwin)) | |
-- or (v < (s.lastVolts - s.hwin)) then | |
-- v = v + s.offset | |
-- local n_level = v / s.scaling | |
-- local octaves = math.floor(n_level) | |
-- local phase = n_level - octaves | |
-- local fix = phase * s.sLen | |
-- local ix = math.floor(fix) | |
-- if (ix ~= s.lastIndex) | |
-- or (octaves ~= lastOct) then | |
-- local note = s.scale[ix+1] | |
-- local noteOct = note + octaves*s.divs | |
-- local volts = (note/s.divs + octaves) * s.scaling | |
-- s.lastIndex = ix | |
-- s.lastOct = octaves | |
-- s.lastNote = noteOct | |
-- s.lastVolts = volts | |
-- print(ix, octaves, noteOct, volts) | |
-- end | |
-- end | |
-- end | |
S = make_scale({0,4,7,11}, 4, 12, 1.0) | |
do_scale(S, 1) | |
do_scale(S, 1) | |
do_scale(S, 1.1) | |
do_scale(S, 1.1) | |
do_scale(S, 1.2) | |
do_scale(S, 1.22) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment