Last active
November 7, 2019 12:08
-
-
Save justmat/9cc67e69d44a96c20609839b995cd481 to your computer and use it in GitHub Desktop.
This file contains 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
-- | |
-- 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