Skip to content

Instantly share code, notes, and snippets.

@justmat
Last active November 7, 2019 12:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save justmat/9cc67e69d44a96c20609839b995cd481 to your computer and use it in GitHub Desktop.
Save justmat/9cc67e69d44a96c20609839b995cd481 to your computer and use it in GitHub Desktop.
--
-- oats
--
-- otis and boingg
-- together
--
-- requires otis
--
engine.name = "Decimator"
local sc = include("otis/lib/tlps")
sc.file_path = "/home/we/dust/audio/tape/otis."
local lfo = include("otis/lib/hnds")
local alt = 0
local page = 2
local page_time = 1
local skip_time_L = 1
local skip_time_R = 1
local muted_L = false
local pre_mute_vol_L = 0
local muted_R = false
local pre_mute_vol_R = 0
local rec1 = true
local rec2 = true
local flipped_L = false
local flipped_R = false
local skipped_L = false
local skipped_R = false
local pages = {"mix", "play", "edit", "boingg"}
local skip_options = {"start", "???"}
local speed_options = {"free", "octaves"}
-- for lib/hnds
local lfo_targets = {
"none",
"1pan",
"2pan",
"1vol",
"2vol",
"1feedback",
"2feedback",
"1speed",
"2speed",
"rec L",
"rec R",
"flip L",
"flip R",
"skip L",
"skip R"
}
local g = grid.connect(1)
function g.event(x,y,z) gridkey(x,y,z) end
local GRID_HEIGHT = 8
local DURATION_1 = 1 / 20
local GRID_FRAMERATE = 1 / 60
local SCREEN_FRAMERATE = 1 / 30
local output_options = {"crow cv", "ii jf"}
local notes = { 2, 4, 5, 7, 9, 11, 12, 14, 16, 17, 19, 21, 23, 24, 26, 28 }
local cycles = {}
local cycle_metros = {}
local current_cycle = 1
local transpose = 48
local grid_refresh_metro
local screen_refresh_metro
local function set_output()
if params:get("output") == 2 then
crow.ii.pullup(true)
crow.ii.jf.mode(1)
else
crow.ii.pullup(false)
crow.ii.jf.mode(0)
end
end
local function midicps(m)
return (440 / 32) * math.pow(2, (m - 9) / 12)
end
local function draw_cycle(x, stage)
if g then
for y = 1 , GRID_HEIGHT do
g:led(x, y, (y == cycles[x].led_pos) and 15 or 0)
end
end
end
local function update_cycle(x, stage)
---set led.po
local h = cycles[x].height
local a = (stage-1) % (16-2*h) + 1
cycles[x].led_pos = (a <= (9-h) and 1 or 0) * (a + h - 1) + (a > (9-h) and 1 or 0) * (17 - a - h)
local on = cycles[x].led_pos == 8
if on and math.random(100) <= params:get("probability") then
if params:get("output") == 1 then
crow.output[1].volts = (notes[x] + params:get("transpose")-60)/12
crow.output[2].execute()
elseif params:get("output") == 2 then
crow.ii.jf.play_note(((notes[x] + params:get("transpose")) - 60) / 12, 5)
end
else
end
end
local function start_cycle(x, speed)
local timer = cycle_metros[x]
timer.time = 8 / params:get("tempo") * cycles[x].speed
timer.stage = cycles[x].height
timer:start()
timer.event = function(stage)
update_cycle(x, stage)
draw_cycle(x, stage)
end
cycles[x].running = true
end
local function stop_cycle(x)
cycle_metros[x]:stop()
cycle_metros[x].callback = nil
cycles[x].running = false
if g then
for y = 1 , GRID_HEIGHT do
g:led(x, y, 0)
end
end
end
local function skip(n)
-- reset loop to start, or jump to a random position
if params:get("skip_controls") == 1 then
softcut.position(n, params:get(n .. "loop_start"))
else
local length = params:get(n .. "tape_len")
softcut.position(n, lfo.scale(math.random(), params:get(n .. "loop_start"), 1.0, 0.25, length))
end
end
local function flip(n)
-- flip tape direction
local spd = params:get(n .. "speed")
spd = -spd
params:set(n .. "speed", spd)
end
local function speed_control(n, d)
-- free controls
if params:get("speed_controls") == 1 then
params:delta(n - 1 .. "speed", d / 7.5)
else
-- quantized to octaves
if params:get(n - 1 .. "speed") == 0 then
params:set(n - 1 .. "speed", d < 0 and -0.01 or 0.01)
else
if d < 0 then
params:set(n - 1 .. "speed", params:get(n - 1 .. "speed") / 2)
else
params:set(n - 1 .. "speed", params:get(n - 1 .."speed") * 2)
end
end
end
end
-- for lib/hnds
function lfo.process()
for i = 1, 4 do
local offset = params:get(i .. "offset")
local target = params:get(i .. "lfo_target")
if params:get(i .. "lfo") == 2 then
-- left/right panning
if target > 1 and target <= 3 then
params:set(lfo_targets[target], lfo.scale(lfo[i].slope, -1.0, 1.0, params:get(i .. "lfo_min") + offset, params:get(i .. "lfo_max") + offset) * 0.01)
-- volume, and feedback
elseif target > 3 and target <= 7 then
params:set(lfo_targets[target], lfo.scale(lfo[i].slope, -1.0, 1.0, params:get(i .. "lfo_min"), params:get(i .. "lfo_max")) * 0.01)
-- speed mod
elseif target == 8 or target == 9 then
params:set(lfo_targets[target], lfo.scale(lfo[i].slope, -1.0, 1.0, params:get(i .. "lfo_min") + offset, params:get(i .. "lfo_max") + offset) * 0.01)
-- record L on/off
elseif target == 10 then
if lfo[i].slope > 0 then
if not rec1 then
rec1 = true
softcut.rec(1, 1)
end
else
rec1 = false
softcut.rec(1, 0)
end
-- record R on/off
elseif target == 11 then
if lfo[i].slope > 0 then
if not rec2 then
rec2 = true
softcut.rec(2, 1)
end
else
rec2 = false
softcut.rec(2, 0)
end
-- flip L
elseif target == 12 then
if lfo[i].slope > 0 then
if not flipped_L then
flip(1)
flipped_L = true
end
else flipped_L = false end
-- flip R
elseif target == 13 then
if lfo[i].slope > 0 then
if not flipped_R then
flip(2)
flipped_R = true
end
else flipped_R = false end
-- skip L
elseif target == 14 then
if lfo[i].slope > 0 then
if not skipped_L then
skip(1)
skipped_L = true
end
else skipped_L = false end
-- skip R
elseif target == 15 then
if lfo[i].slope > 0 then
if not skipped_R then
skip(2)
skipped_R = true
end
else skipped_R = false end
end
end
end
end
function init()
-- sample rate
params:add_control("sample_rate", "sample rate", controlspec.new(0, 48000, "lin", 10, 48000, ''))
params:set_action("sample_rate", function(x) engine.srate(x) end)
-- bit depth
params:add_control("bit_depth", "bit depth", controlspec.new(4, 31, "lin", 0, 31, ''))
params:set_action("bit_depth", function(x) engine.sdepth(x) end)
params:add_separator()
for i=1, 16 do
cycle_metros[i] = metro.init()
cycles[i] = { running = false, keys_pressed = 0, speed = 1, led_pos = 0, height = 0}
end
params:add_option("output", "output", output_options, 1)
params:set_action("output", function() set_output() end)
params:add_number("tempo", "tempo", 10, 240, 40)
params:set_action("tempo", function(t)
for i=1,16 do
cycle_metros[i].time = 8 / t * cycles[i].speed
end
end)
params:add_number("probability", "probability", 0, 100, 100)
params:add_number("transpose", "transpose", 0, 127, 48)
-- for crow
crow.output[2].action = "{to(5,0),to(0,0.25)}"
sc.init()
params:add_option("skip_controls", "skip controls", skip_options, 1)
params:add_option("speed_controls", "speed controls", speed_options, 1)
-- for lib/hnds
for i = 1, 4 do
lfo[i].lfo_targets = lfo_targets
end
lfo.init()
params:bang()
softcut.buffer_clear()
local screen_metro = metro.init()
screen_metro.time = 1/30
screen_metro.event = function() redraw() end
screen_metro:start()
if g then g:all(0) end
grid_refresh_metro = metro.init()
grid_refresh_metro.time = GRID_FRAMERATE
grid_refresh_metro.event = function(stage)
if g then g:refresh() end
end
grid_refresh_metro:start()
end
-- norns controls --
local function mix_enc(n, d)
if alt == 1 then
if n == 2 then
params:delta("1pan", d)
elseif n == 3 then
params:delta("2pan", d)
end
else
if n == 2 then
if not muted_L then
params:delta("1vol", d)
end
elseif n == 3 then
if not muted_R then
params:delta("2vol", d)
end
end
end
end
local function play_enc(n, d)
if alt == 1 then
if n == 2 then
params:delta("1feedback", d)
elseif n == 3 then
params:delta("2feedback", d)
end
else
if n == 2 or n == 3 then
speed_control(n, d)
end
end
end
local function edit_enc(n, d)
if alt == 1 then
if n == 2 then
params:delta("1loop_end", d)
elseif n == 3 then
params:delta("2loop_end", d)
end
else
if n == 2 then
params:delta("1loop_start", d)
elseif n == 3 then
params:delta("2loop_start", d)
end
end
end
local function boingg_enc(n, d)
if n == 1 then
-- nav
elseif n == 2 then
if alt == 1 then
params:delta("tempo", d)
else
current_cycle = util.clamp(current_cycle + d, 1, 16)
redraw()
end
elseif n == 3 then
notes[current_cycle] = util.clamp(notes[current_cycle] + d, -32, 32)
redraw()
end
end
function enc(n, d)
-- navigation
if n == 1 then
page = util.clamp(page + d, 1, 4)
page_time = util.time()
end
-- interface pages
if page == 1 then
mix_enc(n,d)
elseif page == 2 then
play_enc(n, d)
elseif page == 3 then
edit_enc(n, d)
elseif page == 4 then
boingg_enc(n, d)
end
end
local function mix_key(n, z)
if n == 2 and z == 1 then
if not muted_L then
pre_mute_vol_L = params:get("1vol")
softcut.level(1, 0)
muted_L = true
else
softcut.level(1, pre_mute_vol_L)
muted_L = false
end
elseif n == 3 and z == 1 then
if not muted_R then
pre_mute_vol_R = params:get("2vol")
softcut.level(2, 0)
muted_R = true
else
softcut.level(2, pre_mute_vol_R)
muted_R = false
end
end
end
local function play_key(n, z)
if alt == 1 then
if n == 2 and z == 1 then
skip(1)
skip_time_L = util.time()
elseif n == 3 and z ==1 then
skip(2)
skip_time_R = util.time()
end
else
if n == 2 and z == 1 then
flip(1)
elseif n == 3 and z == 1 then
flip(2)
end
end
end
local function edit_key(n, z)
if alt == 1 then
if n == 2 and z == 1 then
softcut.buffer_clear_channel(1)
elseif n == 3 and z == 1 then
softcut.buffer_clear_channel(2)
end
else
if n == 2 and z == 1 then
softcut.rec(1, rec1 == true and 0 or 1)
rec1 = not rec1
elseif n == 3 and z == 1 then
softcut.rec(2, rec2 == true and 0 or 1)
rec2 = not rec2
end
end
end
local function boingg_key(n, z)
if z == 1 then
if n == 2 then
start_cycle(current_cycle,1)
elseif n == 3 then
stop_cycle(current_cycle)
end
end
end
function key(n, z)
if n == 1 then alt = z end
if page == 1 then
mix_key(n, z)
elseif page == 2 then
play_key(n, z)
elseif page == 3 then
edit_key(n, z)
elseif page == 4 then
boingg_key(n, z)
end
end
-- screen drawing
local function draw_left()
-- tape direction indicator
screen.line_rel(0, -7)
screen.line_rel(-3, 3)
screen.line_rel(3, 3)
screen.fill()
end
local function draw_right()
-- tape direction indicator
screen.line_rel(0, -7)
screen.line_rel(3, 3)
screen.line_rel(-3, 3)
screen.fill()
end
local function draw_skip()
-- skip indicator
screen.line_rel(0, -5)
screen.line_rel(-11, 0)
screen.line_rel(0, 5)
screen.line_rel(5, 0)
screen.line_rel(-2, -2)
screen.line_rel(0, 4)
screen.line_rel(2, -2)
screen.stroke()
end
local function draw_skip_rand()
screen.text("???")
end
local function draw_page_mix()
-- screen drawing for the mix page
screen.level(alt == 1 and 3 or 15)
screen.move(64, 15)
screen.text_center("volume L : " .. string.format("%.2f", params:get("1vol")))
screen.move(64, 23)
screen.text_center("volume R : " .. string.format("%.2f", params:get("2vol")))
screen.level(alt == 1 and 15 or 3)
screen.move(64, 31)
screen.text_center("pan L : " .. string.format("%.2f", params:get("1pan")))
screen.move(64, 39)
screen.text_center("pan R : " .. string.format("%.2f", params:get("2pan")))
screen.level(muted_L == false and 3 or 15)
screen.move(5, 52)
screen.text("mute L")
screen.level(muted_R == false and 3 or 15)
screen.move(122, 52)
screen.text_right("mute R")
end
local function draw_page_play()
-- screen drawing for the play page
screen.level(alt == 1 and 3 or 15)
screen.move(64, 15)
screen.text_center("speed L : " .. string.format("%.2f", math.abs(params:get("1speed"))))
screen.move(64, 23)
screen.text_center("speed R : " .. string.format("%.2f", math.abs(params:get("2speed"))))
screen.level(alt == 1 and 15 or 3)
screen.move(64, 31)
screen.text_center("feedback L : " .. string.format("%.2f", params:get("1feedback")))
screen.move(64, 39)
screen.text_center("feedback R : " .. string.format("%.2f", params:get("2feedback")))
screen.move(34, 16)
screen.level(params:get("1speed") < 0 and 15 or 0)
draw_left()
screen.move(34, 24)
screen.level(params:get("2speed") < 0 and 15 or 0)
draw_left()
screen.move(96, 16)
screen.level(params:get("1speed") > 0 and 15 or 0)
draw_right()
screen.move(96, 24)
screen.level(params:get("2speed") > 0 and 15 or 0)
draw_right()
screen.level(alt == 1 and 3 or 15)
screen.move(5, 52)
screen.text("flip")
screen.move(122, 52)
screen.text_right("flip")
screen.level(alt == 1 and 15 or 3)
screen.move(5, 60)
screen.text("skip")
screen.move(122, 60)
screen.text_right("skip")
if util.time() - skip_time_L < .15 then
if params:get("skip_controls") == 1 then
screen.move(18, 40)
draw_skip()
else
screen.move(7, 40)
draw_skip_rand()
end
end
if util.time() - skip_time_R < .15 then
if params:get("skip_controls") == 1 then
screen.move(120, 40)
draw_skip()
else
screen.move(109, 40)
draw_skip_rand()
end
end
end
local function draw_page_edit()
-- screen drawing for edit page
screen.level(alt == 1 and 3 or 15)
screen.move(64, 15)
screen.text_center("loop start L : " .. string.format("%.2f", params:get("1loop_start")))
screen.move(64, 23)
screen.text_center("loop start R : " .. string.format("%.2f", params:get("2loop_start")))
screen.level(alt == 1 and 15 or 3)
screen.move(64, 31)
screen.text_center("loop end L : " .. string.format("%.2f", params:get("1loop_end")))
screen.move(64, 39)
screen.text_center("loop end R : " .. string.format("%.2f", params:get("2loop_end")))
screen.level(alt == 1 and 3 or 15)
screen.move(5, 52)
screen.text(rec1 == true and "rec : on" or "rec : off")
screen.move(122, 52)
screen.text_right(rec2 == true and "rec : on" or "rec : off")
screen.level(alt == 1 and 15 or 3)
screen.move(5, 60)
screen.text("clear")
screen.move(122, 60)
screen.text_right("clear")
end
local function draw_page_boingg()
screen.line_width(1)
screen.level(1)
screen.move(0, 48)
screen.line(128,48)
screen.stroke()
for i=1,16 do
local x = (i-1) * 8
local y = 48 - notes[i] - 2
if i == current_cycle then
screen.level(1)
screen.move(x+2, 0)
screen.line(x+2, 64)
screen.stroke()
end
if cycles[i].running then
screen.level(15)
else
screen.level(i == current_cycle and 7 or 2)
end
screen.rect (x, y, 5, 4)
screen.fill()
if cycles[i].running then
screen.circle(x+2,y+(cycles[i].led_pos) * 4 - 32,2)
screen.fill()
end
end
screen.update()
end
function redraw()
screen.clear()
screen.aa(0)
screen.font_face(25)
screen.font_size(6)
screen.move(20 * page, 5)
-- current page indication
if util.time() - page_time < .6 then
screen.level(15)
screen.text(pages[page])
else
screen.level(1)
screen.text(pages[page])
end
if page == 1 then
draw_page_mix()
elseif page == 2 then
draw_page_play()
elseif page == 3 then
draw_page_edit()
elseif page == 4 then
draw_page_boingg()
end
screen.update()
end
function g.key(x, y, s)
if y == GRID_HEIGHT then
if s == 1 then stop_cycle(x)
end
return
else
if s == 1 then
cycles[x].height = y
cycles[x].led_pos = 8-y
start_cycle(x,1)
--- print("Starting col " .. x .. "height" .. y)
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment