Skip to content

Instantly share code, notes, and snippets.

@justmat
Created October 20, 2019 19:52
Show Gist options
  • Save justmat/e047c761a65f39541fa2acc60f5f2ac1 to your computer and use it in GitHub Desktop.
Save justmat/e047c761a65f39541fa2acc60f5f2ac1 to your computer and use it in GitHub Desktop.
-- awake: time changes
-- 2.0.0 @tehn
-- llllllll.co/t/21022
--
-- top loop plays notes
-- transposed by bottom loop
--
-- (grid optional)
--
-- E1 changes modes:
-- STEP/LOOP/SOUND/OPTION
--
-- K1 held is alt *
--
-- STEP
-- E2/E3 move/change
-- K2 toggle *clear
-- K3 morph *rand
--
-- LOOP
-- E2/E3 loop length
-- K2 reset position
-- K3 jump position
--
-- SOUND
-- K2/K3 selects
-- E2/E3 changes
--
-- OPTION
-- *toggle
-- E2/E3 changes
engine.name = 'KarplusRings'
hs = include('lib/halfsecond')
local MusicUtil = require "musicutil"
local options = {}
options.OUTPUT = {"audio", "midi", "audio + midi", "crow out 1+2", "crow ii JF"}
options.STEP_LENGTH_NAMES = {"1 bar", "1/2", "1/3", "1/4", "1/6", "1/8", "1/12", "1/16", "1/24", "1/32", "1/48", "1/64"}
options.STEP_LENGTH_DIVIDERS = {1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 48, 64}
local g = grid.connect()
local alt = false
mode = 1
mode_names = {"STEP","LOOP","SOUND","OPTION"}
one = {
pos = 0,
length = 8,
start = 1,
data = {1,0,3,5,6,7,8,7,0,0,0,0,0,0,0,0}
}
two = {
pos = 0,
length = 7,
start = 1,
data = {5,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
}
local midi_out_device
local midi_out_channel
local scale_names = {}
notes = {}
local active_notes = {}
local edit_ch = 1
local edit_pos = 1
snd_sel = 1
snd_names = {"decay","coef","lpff","lpfg","bpff","bpfr"}
snd_params = {"decay","coef","lpf freq","lpf gain","bpf freq","bpf res"}
local BeatClock = require 'beatclock'
local clk = BeatClock.new()
local clk_midi = midi.connect()
clk_midi.event = function(data)
clk:process_midi(data)
end
local notes_off_metro = metro.init()
function build_scale()
notes = MusicUtil.generate_scale_of_length(params:get("root_note"), params:get("scale_mode"), 16)
local num_to_add = 16 - #notes
for i = 1, num_to_add do
table.insert(notes, notes[16 - num_to_add])
end
end
local function all_notes_off()
if (params:get("output") == 2 or params:get("output") == 3) then
for _, a in pairs(active_notes) do
midi_out_device:note_off(a, nil, midi_out_channel)
end
end
active_notes = {}
end
local function step()
all_notes_off()
one.pos = one.pos + 1
if one.pos > one.length then one.pos = 1 end
two.pos = two.pos + 1
if two.pos > two.length then two.pos = 1 end
if one.data[one.pos] > 0 then
local note_num = notes[one.data[one.pos]+two.data[two.pos]]
local freq = MusicUtil.note_num_to_freq(note_num)
-- Trigger Probability
if math.random(100) <= params:get("probability") then
-- Audio engine out
if params:get("output") == 1 or params:get("output") == 3 then
engine.hz(freq)
elseif params:get("output") == 4 then
crow.output[1].volts = (note_num-60)/12
crow.output[2].execute()
elseif params:get("output") == 5 then
crow.ii.jf.play_note((note_num-60)/12,5)
end
-- MIDI out
if (params:get("output") == 2 or params:get("output") == 3) then
midi_out_device:note_on(note_num, 96, midi_out_channel)
table.insert(active_notes, note_num)
-- Note off timeout
if params:get("note_length") < 4 then
notes_off_metro:start((60 / clk.bpm / clk.steps_per_beat / 4) * params:get("note_length"), 1)
end
end
end
end
if g then
gridredraw()
end
redraw()
end
local function stop()
all_notes_off()
end
function init()
for i = 1, #MusicUtil.SCALES do
table.insert(scale_names, string.lower(MusicUtil.SCALES[i].name))
end
midi_out_device = midi.connect(1)
midi_out_device.event = function() end
clk.on_step = step
clk.on_stop = stop
clk.on_select_internal = function() clk:start() end
clk.on_select_external = reset_pattern
clk:add_clock_params()
params:set("bpm", 91)
notes_off_metro.event = all_notes_off
params:add{type = "option", id = "output", name = "output",
options = options.OUTPUT,
action = function(value)
all_notes_off()
if value == 4 then crow.output[2].action = "{to(5,0),to(0,0.25)}"
elseif value == 5 then
crow.ii.pullup(true)
crow.ii.jf.mode(1)
end
end}
params:add{type = "number", id = "midi_out_device", name = "midi out device",
min = 1, max = 4, default = 1,
action = function(value) midi_out_device = midi.connect(value) end}
params:add{type = "number", id = "midi_out_channel", name = "midi out channel",
min = 1, max = 16, default = 1,
action = function(value)
all_notes_off()
midi_out_channel = value
end}
params:add_separator()
params:add{type = "option", id = "step_length", name = "step length", options = options.STEP_LENGTH_NAMES, default = 8,
action = function(value)
clk.ticks_per_step = 96 / options.STEP_LENGTH_DIVIDERS[value]
clk.steps_per_beat = options.STEP_LENGTH_DIVIDERS[value] / 4
clk:bpm_change(clk.bpm)
end}
params:add{type = "option", id = "note_length", name = "note length",
options = {"25%", "50%", "75%", "100%"},
default = 4}
params:add{type = "option", id = "scale_mode", name = "scale mode",
options = scale_names, default = 5,
action = function() build_scale() end}
params:add{type = "number", id = "root_note", name = "root note",
min = 0, max = 127, default = 60, formatter = function(param) return MusicUtil.note_num_to_name(param:get(), true) end,
action = function() build_scale() end}
params:add{type = "number", id = "probability", name = "probability",
min = 0, max = 100, default = 100,}
params:add_separator()
cs_AMP = controlspec.new(0,1,'lin',0,0.5,'')
params:add{type="control",id="amp",controlspec=cs_AMP,
action=function(x) engine.amp(x) end}
cs_DECAY = controlspec.new(0,10,'lin',0,0.5,'')
params:add{type="control",id="decay",controlspec=cs_DECAY,
action=function(x) engine.decay(x) end}
cs_COEF = controlspec.new(0,1,'lin',0,0.1,'')
params:add{type="control",id="coef",controlspec=cs_COEF,
action=function(x) engine.coef(x) end}
cs_LPF_F = controlspec.new(10,20000,'exp',0,1200,'hz')
params:add{type="control",id="lpf freq",controlspec=cs_LPF_F,
action=function(x) engine.lpf_freq(x) end}
cs_LPF_G = controlspec.new(0,2,'lin',0,0.5,'')
params:add{type="control",id="lpf gain",controlspec=cs_LPF_G,
action=function(x) engine.lpf_gain(x) end}
cs_BPF_F = controlspec.new(10,20000,'exp',0,3000,'hz')
params:add{type="control",id="bpf freq",controlspec=cs_BPF_F,
action=function(x) engine.bpf_freq(x) end}
cs_BPF_R = controlspec.new(0.01,2,'lin',0,0.5,'')
params:add{type="control",id="bpf res",controlspec=cs_BPF_R,
action=function(x) engine.bpf_res(x) end}
params:default()
clk:start()
hs.init()
end
function g.key(x, y, z)
local grid_h = g.rows
if z > 0 then
if (grid_h == 8 and edit_ch == 1) or (grid_h == 16 and y <= 8) then
if one.data[x] == 9-y then
one.data[x] = 0
else
one.data[x] = 9-y
end
end
if (grid_h == 8 and edit_ch == 2) or (grid_h == 16 and y > 8) then
if grid_h == 16 then y = y - 8 end
if two.data[x] == 9-y then
two.data[x] = 0
else
two.data[x] = 9-y
end
end
gridredraw()
redraw()
end
end
function gridredraw()
local grid_h = g.rows
g:all(0)
if edit_ch == 1 or grid_h == 16 then
for x = 1, 16 do
if one.data[x] > 0 then g:led(x, 9-one.data[x], 5) end
end
if one.pos > 0 and one.data[one.pos] > 0 then
g:led(one.pos, 9-one.data[one.pos], 15)
else
g:led(one.pos, 1, 3)
end
end
if edit_ch == 2 or grid_h == 16 then
local y_offset = 0
if grid_h == 16 then y_offset = 8 end
for x = 1, 16 do
if two.data[x] > 0 then g:led(x, 9-two.data[x] + y_offset, 5) end
end
if two.pos > 0 and two.data[two.pos] > 0 then
g:led(two.pos, 9-two.data[two.pos] + y_offset, 15)
else
g:led(two.pos, 1 + y_offset, 3)
end
end
g:refresh()
end
function enc(n, delta)
if n==1 then
mode = util.clamp(mode+delta,1,4)
elseif mode == 1 then --step
if n==2 then
if alt then
params:delta("probability", delta)
else
local p = (edit_ch == 1) and one.length or two.length
edit_pos = util.clamp(edit_pos+delta,1,p)
end
elseif n==3 then
if edit_ch == 1 then
one.data[edit_pos] = util.clamp(one.data[edit_pos]+delta,0,8)
else
two.data[edit_pos] = util.clamp(two.data[edit_pos]+delta,0,8)
end
end
elseif mode == 2 then --loop
if n==2 then
one.length = util.clamp(one.length+delta,1,16)
elseif n==3 then
two.length = util.clamp(two.length+delta,1,16)
end
elseif mode == 3 then --sound
if n==2 then
params:delta(snd_params[snd_sel], delta)
elseif n==3 then
params:delta(snd_params[snd_sel+1], delta)
end
elseif mode == 4 then --option
if n==2 then
if alt==false then
params:delta("bpm", delta)
else
params:delta("step_length",delta)
end
elseif n==3 then
if alt==false then
params:delta("root_note", delta)
else
params:delta("scale_mode", delta)
end
end
end
redraw()
end
function key(n,z)
if n==1 then
alt = z==1
elseif mode == 1 then --step
if n==2 and z==1 then
if not alt==true then
-- toggle edit
if edit_ch == 1 then
edit_ch = 2
if edit_pos > two.length then edit_pos = two.length end
else
edit_ch = 1
if edit_pos > one.length then edit_pos = one.length end
end
else
-- clear
for i=1,one.length do one.data[i] = 0 end
for i=1,two.length do two.data[i] = 0 end
end
elseif n==3 and z==1 then
if not alt==true then
-- morph
if edit_ch == 1 then
for i=1,one.length do
if one.data[i] > 0 then
one.data[i] = util.clamp(one.data[i]+math.floor(math.random()*3)-1,0,8)
end
end
else
for i=1,two.length do
if two.data[i] > 0 then
two.data[i] = util.clamp(two.data[i]+math.floor(math.random()*3)-1,0,8)
end
end
end
else
-- random
for i=1,one.length do one.data[i] = math.floor(math.random()*9) end
for i=1,two.length do two.data[i] = math.floor(math.random()*9) end
gridredraw()
end
end
elseif mode == 2 then --loop
if n==2 and z==1 then
one.pos = 0
two.pos = 0
if alt==true then clk:reset() end
elseif n==3 and z==1 then
one.pos = math.floor(math.random()*one.length)
two.pos = math.floor(math.random()*two.length)
end
elseif mode == 3 then --sound
if n==2 and z==1 then
snd_sel = util.clamp(snd_sel - 2,1,5)
elseif n==3 and z==1 then
snd_sel = util.clamp(snd_sel + 2,1,5)
end
elseif mode == 4 then --option
if n==2 then
elseif n==3 then
end
end
--[[
gridredraw()
if not clk.external then
if clk.playing then
clk:stop()
else
clk:start()
end
end
]]--
redraw()
end
function redraw()
screen.clear()
screen.line_width(1)
screen.aa(0)
-- edit point
if mode==1 then
screen.move(26 + edit_pos*6, edit_ch==1 and 33 or 63)
screen.line_rel(4,0)
screen.level(15)
if alt then
screen.move(0, 30)
screen.level(1)
screen.text("prob")
screen.move(0, 45)
screen.level(15)
screen.text(params:get("probability"))
end
screen.stroke()
end
-- loop lengths
screen.move(32,30)
screen.line_rel(one.length*6-2,0)
screen.move(32,60)
screen.line_rel(two.length*6-2,0)
screen.level(mode==2 and 6 or 1)
screen.stroke()
-- steps
for i=1,one.length do
screen.move(26 + i*6, 30 - one.data[i]*3)
screen.line_rel(4,0)
screen.level(i == one.pos and 15 or ((edit_ch == 1 and one.data[i] > 0) and 4 or (mode==2 and 6 or 1)))
screen.stroke()
end
for i=1,two.length do
screen.move(26 + i*6, 60 - two.data[i]*3)
screen.line_rel(4,0)
screen.level(i == two.pos and 15 or ((edit_ch == 2 and two.data[i] > 0) and 4 or (mode==2 and 6 or 1)))
screen.stroke()
end
-- txt
--[[ screen.level((not alt and not KEY3) and 15 or 4)
screen.move(0,10)
screen.text("bpm:"..params:get("bpm"))
screen.level(alt and 15 or 4)
screen.move(0,20)
screen.text("sc:"..params:get("scale_mode"))
screen.level(KEY3 and 15 or 4)
screen.move(0,30)
screen.text("rt:"..MusicUtil.note_num_to_name(params:get("root_note"), true))
screen.level(4)
screen.move(0,60)
if alt then screen.text("cut/rel")
elseif KEY3 then screen.text("loop") end
--]]
screen.level(4)
screen.move(0,10)
screen.text(mode_names[mode])
if mode==3 then
screen.level(1)
screen.move(0,30)
screen.text(snd_names[snd_sel])
screen.level(15)
screen.move(0,40)
screen.text(params:string(snd_params[snd_sel]))
screen.level(1)
screen.move(0,50)
screen.text(snd_names[snd_sel+1])
screen.level(15)
screen.move(0,60)
screen.text(params:string(snd_params[snd_sel+1]))
elseif mode==4 then
screen.level(1)
screen.move(0,30)
screen.text(alt==false and "bpm" or "div")
screen.level(15)
screen.move(0,40)
screen.text(alt==false and params:get("bpm") or params:string("step_length"))
screen.level(1)
screen.move(0,50)
screen.text(alt==false and "root" or "scale")
screen.level(15)
screen.move(0,60)
screen.text(alt==false and params:string("root_note") or params:string("scale_mode"))
end
screen.update()
end
function cleanup ()
clk:stop()
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment