-
Notifications
You must be signed in to change notification settings - Fork 0
/
caliper.lua
257 lines (222 loc) · 4.86 KB
/
caliper.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
-- 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()
norns.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