Skip to content
Permalink
main
Go to file
 
 
Cannot retrieve contributors at this time
689 lines (602 sloc) 17.5 KB
--
-- one record head
-- three play heads
-- arc control
--
-- v1.01 @justmat
local a = arc.connect(1)
local lfo = include("lib/larc_hnds")
local tau = math.pi * 2
local alt = false
local recording = true
local settings_mode = false
local togd = false
local pre_speed = 1
local last_arc = -1
local last_arc_time = -1
local pop_up_timeout = 1
local start_time = util.time()
local filter_options = {
"none",
"lowpass",
"highpass",
"bandpass",
"band reject"
}
local arc_choices = {
"amplitude",
"speed",
"pan",
"cutoff"
}
-- for hnds
local lfo_targets = {
"none",
"1amp",
"2amp",
"3amp",
"1pan",
"2pan",
"3pan",
"1filter_cutoff",
"2filter_cutoff",
"3filter_cutoff",
"rec toggle"
}
-- WAVEFORM DRAWING
local interval = 0
local waveform_samples = {}
local scale = 25
local level = .75
function on_render(ch, start, i, s)
waveform_samples = s
interval = i
end
function update_content()
softcut.render_buffer(1, params:get("loop_in"), params:get("loop_out") - params:get("loop_in"), 128)
end
-- softcut polls
local positions = { -1, -1, -1, -1}
local function update_positions(i, pos)
positions[i] = pos
end
-- keep everything inside the loop points
local function set_loop_in(v)
if params:get("loop_out") == 0.1 then
v = 0
else
v = util.clamp(v, 0, params:get("loop_out") - 0.1)
end
for i = 1, 4 do
softcut.loop_start(i, v)
end
params:set("loop_in", v)
end
local function set_loop_out(v)
if v == params:get("loop_in") then
v = v + 0.1
else
v = util.clamp(v, params:get("loop_in") + 0.1, 16)
end
for i = 1, 4 do
softcut.loop_end(i, v)
end
params:set("loop_out", v)
if positions[4] >= v then
softcut.position(4, params:get("loop_in"))
positions[4] = params:get("loop_in")
end
end
-- toggle recording
local function toggle_record()
local fdbk = params:get("feedback")
if recording then
softcut.rec_level(4, 0)
softcut.pre_level(4, 1)
else
softcut.rec_level(4, 1)
softcut.pre_level(4, fdbk)
end
recording = not recording
end
-- softcut filter mode selection
local function set_filter_mode(voice, mode)
if mode == 1 then
-- none
softcut.post_filter_dry(voice, 1)
softcut.post_filter_lp(voice, 0)
softcut.post_filter_hp(voice, 0)
softcut.post_filter_bp(voice, 0)
softcut.post_filter_br(voice, 0)
elseif mode == 2 then
-- lowpass
softcut.post_filter_dry(voice, 0)
softcut.post_filter_lp(voice, 1)
softcut.post_filter_hp(voice, 0)
softcut.post_filter_bp(voice, 0)
softcut.post_filter_br(voice, 0)
elseif mode == 3 then
-- highpass
softcut.post_filter_dry(voice, 0)
softcut.post_filter_lp(voice, 0)
softcut.post_filter_hp(voice, 1)
softcut.post_filter_bp(voice, 0)
softcut.post_filter_br(voice, 0)
elseif mode == 4 then
-- bandpass
softcut.post_filter_dry(voice, 0)
softcut.post_filter_lp(voice, 0)
softcut.post_filter_hp(voice, 0)
softcut.post_filter_bp(voice, 1)
softcut.post_filter_br(voice, 0)
elseif mode == 5 then
-- band reject
softcut.post_filter_dry(voice, 0)
softcut.post_filter_lp(voice, 0)
softcut.post_filter_hp(voice, 0)
softcut.post_filter_bp(voice, 0)
softcut.post_filter_br(voice, 1)
end
end
-- remaining softcut set up
local function sc_init()
audio.level_cut(1.0)
audio.level_adc_cut(1)
audio.level_eng_cut(1)
softcut.level_slew_time(1,0.25)
softcut.level_input_cut(1, 4, 1.0)
softcut.level_input_cut(2, 4, 1.0)
for i = 1, 4 do
softcut.buffer(i, 1)
softcut.pan(i, 0.0)
softcut.enable(i, 1)
softcut.level(i, i == 4 and 0 or 1.0)
softcut.play(i, 1)
softcut.rate(i, i == 4 and 1 or 1 / i)
softcut.rate_slew_time(i, 0.25)
softcut.loop_start(i, 0)
softcut.loop_end(i, 8)
softcut.loop(i, 1)
softcut.fade_time(i, 0.2)
softcut.position(i, 0)
end
-- use voice 4 for recording. records at speed == 1, no playback?
softcut.rec(4, 1)
softcut.rec_level(4, 0)
softcut.pre_level(4, 0.15)
softcut.level(4, 0)
end
function v_scale(old_value, old_min, old_max, new_min, new_max)
-- scale ranges
-- only used in redraw
-- should probably use util.linlin but i couldn't get it to work
local old_range = old_max - old_min
if old_range == 0 then
old_range = new_min
end
local new_range = new_max - new_min
local new_value = (((old_value - old_min) * new_range) / old_range) + new_min
return new_value
end
function lfo.process()
for i = 1, 4 do
local target = params:get(i .. "lfo_target")
if params:get(i .. "lfo") == 2 then
if target >= 2 and target <= 4 then
params:set(lfo_targets[target], lfo.scale(lfo[i].slope, -1, 1, 0.0, 1 ))
elseif target >= 5 and target <= 7 then
params:set(lfo_targets[target], lfo[i].slope)
elseif target >= 8 and target <= 10 then
params:set(lfo_targets[target], lfo.scale(lfo[i].slope, -1, 1, 10, 12000 ))
elseif target == 11 then
-- for rec toggle
if lfo[i].slope > 0 then
if not togd then
toggle_record()
togd = true
end
else togd = false end
end
end
end
end
function init()
-- init softcut
sc_init()
-- set up softcut position polls
for i = 1, 4 do
softcut.phase_quant(i, .001)
softcut.event_phase(update_positions)
softcut.poll_start_phase()
end
softcut.event_render(on_render)
params:add_separator()
params:add_control('loop_in', 'loop in', controlspec.new(0, 15.5, "lin", 0.01, 0))
params:set_action('loop_in', function(v) set_loop_in(v) end)
params:add_control('loop_out', 'loop out', controlspec.new(0.1, 16, "lin", 0.01, 8))
params:set_action('loop_out', function(v) set_loop_out(v) end)
params:add_control('rec_speed', 'rec speed', controlspec.new(-2, 2, "lin", 0.01, 1))
params:set_action('rec_speed', function(v) softcut.rate(4, v) end)
params:add_control('feedback', 'feedback', controlspec.new(0, 1, "lin", 0.01, .30))
params:set_action('feedback', function(v) softcut.pre_level(4, v) end)
params:add_option("arc_focus", "arc focus", arc_choices)
params:hide("arc_focus")
params:add_binary("rec_toggle", "rec toggle")
params:set_action("rec_toggle", function(v) toggle_record() end)
params:add_separator()
for i = 1, 3 do
params:add_group("playhead " .. i, 8)
params:add_control(i .. "amp", i .. " amp", controlspec.new(0, 1, "lin", 0, i == 1 and .5 or 0))
params:set_action(i .. "amp", function(v) softcut.level(i, v) end)
params:add_control(i .. "speed", i .. " speed", controlspec.new(-2, 2, "lin", 0, 1/i))
params:set_action(i .. "speed", function(v) softcut.rate(i, v) end)
-- tape speed slew controls
params:add_control(i .. "speed_slew", i .. " speed slew", controlspec.new(0, 1, "lin", 0, 0.1, ""))
params:set_action(i .. "speed_slew", function(x) softcut.rate_slew_time(i, x) end)
params:add_control(i .. "pan", i .. " pan", controlspec.new(-1, 1, "lin", 0.01, 0))
params:set_action(i .. "pan", function(v) softcut.pan(i, v) end)
params:add_control(i .. "pan_slew", i.. " pan slew", controlspec.new(0, 1, "lin", 0.01, 0.05, ""))
params:set_action(i .. "pan_slew", function(x) softcut.pan_slew_time(i, x) end)
-- filter mode
params:add_option(i .. "filter_mode", i .. " filter mode", filter_options, 2)
params:set_action(i .. "filter_mode", function(v) set_filter_mode(i, v) end)
-- filter cut off
params:add_control(i .. "filter_cutoff", i .. " filter cutoff", controlspec.new(10, 12000, 'exp', 1, 12000, "Hz"))
params:set_action(i .. "filter_cutoff", function(x) softcut.post_filter_fc(i, x) softcut.pre_filter_fc(i, x) end)
-- filter q
params:add_control(i .. "filter_q", i .. " filter q", controlspec.new(0.0005, 8.0, 'exp', 0, 8.0, ""))
params:set_action(i .. "filter_q", function(x) softcut.post_filter_rq(i, x) softcut.pre_filter_rq(i, x) end)
end
for i = 1, 4 do
lfo[i].lfo_targets = lfo_targets
end
lfo.init()
params:bang()
local arc_redraw_timer = metro.init()
arc_redraw_timer.time = 1/30
arc_redraw_timer.event = function() arc_redraw() end
arc_redraw_timer:start()
local redraw_timer = metro.init()
redraw_timer.time = 1/30
redraw_timer.event = function() redraw() end
redraw_timer:start()
end
-- norns hardware
function key(n, z)
if n == 1 then alt = z == 1 and true or false end
if n == 2 and z == 1 then
if alt then
softcut.buffer_clear()
else
settings_mode = not settings_mode
end
end
if n == 3 and z == 1 then toggle_record() end
end
function enc(n, d)
last_arc_time = pop_up_timeout + 1
if settings_mode then
-- feedback, loop start, loop end
if n == 1 then
if alt then
params:delta("rec_speed", d)
else
params:delta("feedback", d)
end
elseif n == 2 then
params:delta("loop_in", d)
elseif n == 3 then
params:delta("loop_out", d)
end
else
-- follow arc focus
end
last_enc_ = n
last_enc_time = util.time()
end
-- arc
function a.delta(n, d)
last_enc_time = pop_up_timeout + 1
local focus = params:get("arc_focus")
if settings_mode then
if n == 1 then
if alt then
params:delta("rec_speed", d / 100)
else
params:delta("feedback", d / 10)
end
elseif n == 2 then
params:delta("loop_in", d / 100)
elseif n == 3 then
params:delta("loop_out", d / 100)
elseif n == 4 then
params:delta("loop_in", d / 100)
params:delta("loop_out", d / 100)
end
else
if alt then
params:set("arc_focus", n)
else
if focus == 1 and n < 4 then
-- amplitude
params:delta(n .. "amp", d/10)
elseif focus == 2 then
-- speed
if n == 4 then
if alt then d = d/10000 else d = d/500 end
-- select a pre_speed value
pre_speed = util.clamp(pre_speed + d, 0.0000, 2.0000)
else
-- set voice speed to pre_speed on touched ring/voice
if d > 0 then
params:set(n .. 'speed', pre_speed)
else
params:set(n .. 'speed', -pre_speed)
end
end
-- panning
elseif focus == 3 then
d = d/10
if n <= 3 then
params:delta(n .. "pan", d)
elseif n == 4 then
end
elseif focus == 4 then
d = d/20
-- cutoff
if n <= 3 then
params:delta(n .. "filter_cutoff", d)
else
-- ring 4
end
end
end
end
last_arc = n
last_arc_time = util.time()
end
local function arc_draw_amp(bool)
if bool then
for i = 1, 3 do
a:segment(i, util.degs_to_rads(180), util.degs_to_rads(util.linlin(0, 1, 0, 359, params:get(i .. "amp")) - 180), 15)
end
else
a:segment(1, util.degs_to_rads(180), util.degs_to_rads(360), 15)
end
end
local function arc_draw_speed(bool)
-- if true, draw speed as the arc focus
-- if false, draw one speed ring to be the focus selector
if bool then
for i = 1, 3 do
a:segment(i, positions[i] * tau/params:get("loop_out"), tau * positions[i]/params:get("loop_out") + 0.2, 15)
end
local r = util.linlin(0, 2, 0, tau - 0.4, pre_speed)
a:segment(4, math.pi + 0.2, (math.pi + 0.2) + r, 5)
a:led(4, 0, 15)
a:led(4, 30, 15)
a:led(4, 42, 15)
a:led(4, 49, 15)
else
-- arc focus selection thing
a:segment(2, positions[1] * tau/params:get("loop_out"), tau * positions[1]/params:get("loop_out") + 0.2, 15)
end
end
local function arc_draw_pan(bool)
-- if true, draw panning as the arc focus
if bool then
for i = 1, 3 do
local r = (util.linlin( -1, 1, -2, 2, params:get(i .. "pan")))
a:segment(i, r - .3, r + 0.4 , 8)
a:led(i, 41, 15)
a:led(i, 1, 15)
a:led(i, 25, 15)
end
else
local r = util.linlin(-1, 1, -2, 2, 0)
a:segment(3, r - .3, r + 0.4 , 8)
a:led(3, 41, 15)
a:led(3, 1, 15)
a:led(3, 25, 15)
end
end
local function arc_draw_cutoff(bool)
if bool then
for i = 1, 3 do
a:segment(i, util.degs_to_rads(270), util.degs_to_rads(util.linlin(10, 12000, 0, 180, params:get(i .. "filter_cutoff")) - 90), 15)
end
else
a:segment(4, util.degs_to_rads(270), util.degs_to_rads(33), 15)
end
end
function arc_redraw()
a:all(0)
local focus = params:get("arc_focus")
-- rings 1-3:
--for i = 1, 3 do
if alt and not settings_mode then
arc_draw_amp(false)
arc_draw_speed(false)
arc_draw_pan(false)
arc_draw_cutoff(false)
elseif not settings_mode then
if focus == 1 then
arc_draw_amp(true)
elseif focus == 2 then
arc_draw_speed(true)
elseif focus == 3 then
arc_draw_pan(true)
elseif focus == 4 then
arc_draw_cutoff(true)
end
else
-- settings mode
end
a:refresh()
end
-- for making window things
local function window(x, y, w, h, header)
if not header then header = " --- " end
-- draw a popup window
screen.blend_mode(0)
screen.level(0)
screen.rect(x , y - 1, w , h - 1 ) -- border
screen.stroke()
screen.blend_mode(6)
screen.rect(x + 1, y, w + 1, h + 1) -- make it float
screen.fill()
screen.stroke()
screen.blend_mode(0)
screen.level(10)
screen.rect(x + 1, y, w - 1, h - 3) -- window
screen.stroke()
screen.level(10)
screen.rect(x, y , w - 1 , 8) -- title bar
screen.fill()
screen.stroke()
screen.level(0)
screen.move(x + (w / 2), y + 6)
screen.text_center(header) -- title
end
local function draw_amp()
window(8, 15, 40, 25, "amp 1")
window(40, 34, 40, 25, "amp 2")
window(84, 30, 40, 25, " amp 3")
screen.level(8)
screen.move(28, 32)
screen.text_center(string.format("%.2f", params:get("1amp")))
screen.move(60, 51)
screen.text_center(string.format("%.2f", params:get("2amp")))
screen.move(105, 47)
screen.text_center(string.format("%.2f", params:get("3amp")))
end
local function draw_speed()
window(17, 14, 55, 45, "cur-speed")
window(68, 37, 50, 25, "pre-speed")
screen.level(8)
screen.move(29, 32)
screen.text("1: " .. string.format("%.3f", params:get("1speed")))
screen.move(29, 42)
screen.text("2: " .. string.format("%.3f", params:get("2speed")))
screen.move(29, 52)
screen.text("3: " .. string.format("%.3f", params:get("3speed")))
screen.move(92, 55)
screen.text_center(string.format("%.3f", pre_speed))
end
local function draw_pan()
window(10, 14, 90, 32, "panning")
screen.level(8)
screen.move(25, 34)
screen.text_center(string.format("%.2f", params:get("1pan")))
screen.move(55, 34)
screen.text_center(string.format("%.2f",params:get("2pan")))
screen.move(85, 34)
screen.text_center(string.format("%.2f",params:get("3pan")))
end
local function draw_cutoff()
window(8, 12, 42, 30, "cutoff 1")
window(45, 24, 42, 30, "cutoff 2")
window(82, 36, 42, 30, "cutoff 3")
screen.level(8)
screen.move(30, 32)
screen.text_center(params:get("1filter_cutoff"))
screen.move(65, 44)
screen.text_center(params:get("2filter_cutoff"))
screen.move(103, 55)
screen.text_center(params:get("3filter_cutoff"))
end
function redraw()
-- update waveform content
update_content()
local loop_in = params:get("loop_in")
local loop_out = params:get("loop_out")
-- start drawing stuff
screen.clear()
screen.level(6)
screen.font_size(8)
screen.font_face(0)
-- top of screen. recording/looping indicator and record head position
screen.move (2, 10) -- recording or looping
screen.text(recording and "R" or "L")
screen.move(126, 8) -- record head position
screen.text_right(string.format("%.2f", positions[4]))
-- wave drawing
screen.level(8)
local x_pos = 0
for i,s in ipairs(waveform_samples) do
local height = util.round(math.abs(s) * (scale*level))
screen.move(v_scale(0, loop_out - loop_in, 0, 126, x_pos), 35 - height)
screen.line_rel(0, 2 * height)
screen.stroke()
x_pos = x_pos + 1
end
-- play head positions
for i = 1, 3 do
if params:get(i .. "amp") > 0 then
screen.level(params:get(i .. "amp") == 0 and 1 or 5)
screen.move(util.linlin(loop_in, loop_out, 1, 126, positions[i]), 16)
screen.line_rel(0, 40)
screen.move_rel(-2, 8)
screen.text(i)
screen.stroke()
end
end
-- settings mode
-- feedback, loop start/end, panning, and record head speed
if settings_mode then
window(15, 12, 35, 20, "fdbk")
window(55, 13, 60, 30, "loop")
screen.level(8)
screen.move(31, 27)
screen.text_center(params:get("feedback"))
screen.move(85, 29)
screen.text_center("in: " .. loop_in)
screen.move(85, 37)
screen.text_center("out: " .. loop_out)
screen.move(68, 56)
if alt then
window(6, 24, 50, 30, "rec speed")
screen.move(30, 45)
screen.level(8)
screen.font_size(16)
screen.text_center(params:get("rec_speed"))
end
else
-- "regular" mode
-- draw amp popups when encoders are touched
if alt then
window(30, 30, 75, 30, "arc focus")
screen.move(66, 49)
screen.level(8)
screen.text_center(arc_choices[params:get("arc_focus")])
last_arc_time = pop_up_timeout
end
-- popups
if util.time() - last_arc_time < pop_up_timeout then
local focus = params:get("arc_focus")
if focus == 1 then
-- amp
draw_amp()
elseif focus == 2 then
-- speed
draw_speed()
elseif focus == 3 then
-- pan
draw_pan()
elseif focus == 4 then
-- cutoff
draw_cutoff()
end
end
end
-- splash screen type logo thing
if util.time() - start_time < 1.5 then
screen.clear()
screen.font_size(32)
screen.level(4)
screen.move(64, 42)
screen.font_face(50)
screen.text_center("larc")
end
screen.update()
end