Skip to content
Permalink
master
Go to file
 
 
Cannot retrieve contributors at this time
257 lines (222 sloc) 4.85 KB
-- caliper
--
-- tuner with reference tone,
-- frequency counter,
-- v/oct calibration helper
--
-- e1 - set reference freq
-- e2 - set reference tone amp
-- e3 - set voltage offset
-- k1 - hold for fine control
-- k2/k3 - lower/raise voltage
-- offset in octaves
engine.name = 'Caliper'
musicutil = require 'musicutil'
filters = require 'filters'
log2 = math.log(2)
c = musicutil.note_num_to_freq(60)
volts_per_octave = 1.2
in_freq_poll = nil
in_freq_detected = false
in_freq = 0
in_filter = nil
dummy_filter = {
next = function(_, v) return v end
}
reference_freq = c
out_offset = 0
out_freq = c
out_volts = 0
diff = 0
held_keys = {
fine = false,
down = false,
up = false
}
dirty = false
redraw_metro = metro.init{
time = 1 / 15,
event = function()
if dirty then
redraw()
dirty = false
end
end
}
function update_in_freq(new_freq)
local was_detected = in_freq_detected
in_freq_detected = new_freq > 0
if in_freq_detected then
in_freq = in_filter:next(new_freq)
compare_freqs()
elseif was_detected then
dirty = true
end
end
function update_output()
local ratio = math.pow(2, out_offset)
out_freq = reference_freq * ratio
engine.sine_freq(out_freq)
out_volts = out_offset * volts_per_octave
crow.output[1].volts = out_volts
compare_freqs()
end
function compare_freqs()
local ratio = in_freq / out_freq
diff = math.log(ratio) / log2
dirty = true
end
function crow_setup()
crow.clear()
crow.output[1].slew = 0.02 -- match ReferenceTuner's sine lag
crow.output[1].shape = 'linear'
end
function init()
params:set('monitor_level', 0) -- full
params:set('monitor_mode', 2) -- mono
params:set('reverb', 1) -- off
params:add_separator()
params:add{
type = 'control',
id = 'reference_tone_amp',
name = 'ref. tone amp',
controlspec = controlspec.DB,
action = function(value)
engine.sine_amp(util.dbamp(value))
end
}
params:add{
type = 'control',
id = 'reference_freq',
name = 'ref. frequency',
controlspec = controlspec.new(27.5, 3520, 'exp', 0, c, 'Hz'),
action = function(value)
reference_freq = value
update_output()
end
}
params:add{
type = 'control',
id = 'offset',
name = 'offset',
controlspec = controlspec.new(-5, 5, 'lin', 0, 0, 'oct'),
action = function(value)
out_offset = value
update_output()
end
}
params:add{
type = 'option',
id = 'volts_per_octave',
name = 'volts/octave',
options = { '1', '1.2' },
default = 1,
action = function(value)
volts_per_octave = value == 1 and 1 or 1.2
update_output()
end
}
params:add{
type = 'number',
id = 'in freq smoothing',
name = 'in freq smoothing',
min = 0,
max = 6,
default = 2,
action = function(value)
if value == 0 then
in_filter = dummy_filter
else
in_filter = filters.mean.new(value)
end
end
}
params:bang()
crow.add = crow_setup
crow_setup()
in_freq_poll = poll.set('input_freq', update_in_freq)
in_freq_poll.time = 1 / 15
in_freq_poll:start()
redraw_metro:start()
end
function draw_line(n, label, value, show_sign)
local y = n * 9 + 7
screen.move(14, y)
screen.text(label)
screen.move(114, y)
screen.text_right(string.format(show_sign and '%+.2f' or '%.2f', value))
end
function draw_tuner()
local y = 56.5
local abscents = math.floor(math.abs(diff * 1200) + 0.5)
local base_level = 2
local notch_level = 4
local notch_height = 2
if in_freq_detected then
base_level = math.max(2, 4 - abscents)
notch_level = math.max(7, 15 - abscents)
notch_height = math.max(1, 4 - abscents)
end
screen.move(63.5, y - 2.5)
screen.line(63.5, y + 2.5)
screen.level(1)
screen.stroke()
screen.move(0, y)
screen.line(128, y)
screen.level(base_level)
screen.stroke()
screen.move(63.5 + math.max(-63, math.min(63, math.atan(diff * 3) * 44)), y - notch_height)
screen.line_rel(0, notch_height * 2 + 1)
screen.level(notch_level)
screen.stroke()
end
function redraw()
screen.clear()
screen.level(in_freq_detected and 10 or 2)
draw_line(1, 'in freq:', in_freq)
screen.level(10)
draw_line(2, 'reference:', out_freq)
draw_line(3, 'volts:', out_volts, true)
draw_line(4, 'diff (cents):', diff * 1200, true)
draw_tuner()
screen.update()
end
function key(n, z)
if n == 1 then
held_keys.fine = z == 1
elseif n == 2 then
held_keys.down = z == 1
if z == 1 then
if held_keys.up then
params:set('offset', 0)
else
params:delta('offset', -10)
end
end
elseif n == 3 then
held_keys.up = z == 1
if z == 1 then
if held_keys.down then
params:set('offset', 0)
else
params:delta('offset', 10)
end
end
end
end
function enc(n, d)
local fine = held_keys.fine and 0.05 or 1
if n == 1 then
params:delta('reference_freq', d * fine / 4)
elseif n == 2 then
params:delta('reference_tone_amp', d)
elseif n == 3 then
params:delta('offset', d * fine / 2)
end
end
function cleanup()
if in_freq_poll ~= nil then
in_freq_poll:stop()
end
redraw_metro:stop()
end