Skip to content

Instantly share code, notes, and snippets.

@okyeron
Last active June 16, 2018 18:41
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 okyeron/44f1c75fbd0aaab186784b5c82099177 to your computer and use it in GitHub Desktop.
Save okyeron/44f1c75fbd0aaab186784b5c82099177 to your computer and use it in GitHub Desktop.
earthsea midi -- okyeron version for Livid Block
-- earthsea midi
-- okyeron version for Livid Block - WORK IN PROGRESS
--
-- subtractive polysynth
-- controlled by midi or grid
--
-- grid pattern player:
-- 1 1 record toggle
-- 1 2 play toggle
-- 1 8 transpose mode
local tab = require 'tabutil'
local pattern_time = require 'pattern_time'
local mode_transpose = 0
local root = { x=5, y=5 }
local trans = { x=5, y=5 }
local lit = {}
local screen_framerate = 15
local screen_refresh_metro
local ripple_repeat_rate = 1 / 0.3 / screen_framerate
local ripple_decay_rate = 1 / 0.5 / screen_framerate
local ripple_growth_rate = 1 / 0.02 / screen_framerate
local screen_notes = {}
engine.name = 'PolySub'
-- midigridxy may need to be different for other grid controllers
-- update this with coordinates for your midi controller
-- code assumes 0-64 note numbers from grid buttons
local midigridxy = {{1,1},{1,2},{1,3},{1,4},{1,5},{1,6},{1,7},{1,8},{2,1},{2,2},{2,3},{2,4},{2,5},{2,6},{2,7},{2,8},{3,1},{3,2},{3,3},{3,4},{3,5},{3,6},{3,7},{3,8},{4,1},{4,2},{4,3},{4,4},{4,5},{4,6},{4,7},{4,8},{5,1},{5,2},{5,3},{5,4},{5,5},{5,6},{5,7},{5,8},{6,1},{6,2},{6,3},{6,4},{6,5},{6,6},{6,7},{6,8},{7,1},{7,2},{7,3},{7,4},{7,5},{7,6},{7,7},{7,8},{8,1},{8,2},{8,3},{8,4},{8,5},{8,6},{8,7},{8,8}}
-- pythagorean minor/major, kinda
local ratios = { 1, 9/8, 6/5, 5/4, 4/3, 3/2, 27/16, 16/9 }
local base = 27.5 -- low A
local function getHz(deg,oct)
return base * ratios[deg] * (2^oct)
end
local function getHzET(note)
return 55*2^(note/12)
end
-- current count of active voices
local nvoices = 0
function init()
pat = pattern_time.new()
--pat.process = grid_note_trans
pat.process = midigrid_note_trans
params:add_control("shape", controlspec.new(0,1,"lin",0,0,""))
params:set_action("shape", function(x) engine.shape(x) end)
params:add_control("timbre", controlspec.new(0,1,"lin",0,0.5,""))
params:set_action("timbre", function(x) engine.timbre(x) end)
params:add_control("noise", controlspec.new(0,1,"lin",0,0,""))
params:set_action("noise", function(x) engine.noise(x) end)
params:add_control("cut", controlspec.new(0,32,"lin",0,8,""))
params:set_action("cut", function(x) engine.cut(x) end)
params:add_control("fgain", controlspec.new(0,6,"lin",0,0,""))
params:set_action("fgain", function(x) engine.fgain(x) end)
params:add_control("cutEnvAmt", controlspec.new(0,1,"lin",0,0,""))
params:set_action("cutEnvAmt", function(x) engine.cutEnvAmt(x) end)
params:add_control("detune", controlspec.new(0,1,"lin",0,0,""))
params:set_action("detune", function(x) engine.detune(x) end)
params:add_control("ampAtk", controlspec.new(0.01,10,"lin",0,0.05,""))
params:set_action("ampAtk", function(x) engine.ampAtk(x) end)
params:add_control("ampDec", controlspec.new(0,2,"lin",0,0.1,""))
params:set_action("ampDec", function(x) engine.ampDec(x) end)
params:add_control("ampSus", controlspec.new(0,1,"lin",0,1,""))
params:set_action("ampSus", function(x) engine.ampSus(x) end)
params:add_control("ampRel", controlspec.new(0.01,10,"lin",0,1,""))
params:set_action("ampRel", function(x) engine.ampRel(x) end)
params:add_control("cutAtk", controlspec.new(0.01,10,"lin",0,0.05,""))
params:set_action("cutAtk", function(x) engine.cutAtk(x) end)
params:add_control("cutDec", controlspec.new(0,2,"lin",0,0.1,""))
params:set_action("cutDec", function(x) engine.cutDec(x) end)
params:add_control("cutSus", controlspec.new(0,1,"lin",0,1,""))
params:set_action("cutSus", function(x) engine.cutSus(x) end)
params:add_control("cutRel", controlspec.new(0.01,10,"lin",0,1,""))
params:set_action("cutRel", function(x) engine.cutRel(x) end)
engine.level(0.05)
engine.stopAll()
stop_all_screen_notes()
params:read("tehn/earthsea.pset")
params:bang()
if g then gridredraw() end
screen_refresh_metro = metro.alloc()
screen_refresh_metro.callback = function(stage)
update()
redraw()
end
screen_refresh_metro:start(1 / screen_framerate)
local startup_ani_count = 1
local startup_ani_metro = metro.alloc()
startup_ani_metro.callback = function(stage)
start_screen_note(-startup_ani_count)
stop_screen_note(-startup_ani_count)
startup_ani_count = startup_ani_count + 1
end
startup_ani_metro:start( 0.1, 3 )
end
-- original grid functions
function gridkey(x, y, z)
if x == 1 then
if z == 1 then
if y == 1 and pat.rec == 0 then
mode_transpose = 0
trans.x = 5
trans.y = 5
pat:stop()
engine.stopAll()
stop_all_screen_notes()
pat:clear()
pat:rec_start()
elseif y == 1 and pat.rec == 1 then
pat:rec_stop()
if pat.count > 0 then
root.x = pat.event[1].x
root.y = pat.event[1].y
trans.x = root.x
trans.y = root.y
pat:start()
end
elseif y == 2 and pat.play == 0 and pat.count > 0 then
if pat.rec == 1 then
pat:rec_stop()
end
pat:start()
elseif y == 2 and pat.play == 1 then
pat:stop()
engine.stopAll()
stop_all_screen_notes()
nvoices = 0
lit = {}
elseif y == 8 then
mode_transpose = 1 - mode_transpose
end
end
else
if mode_transpose == 0 then
local e = {}
e.id = x*8 + y
e.x = x
e.y = y
e.state = z
pat:watch(e)
grid_note(e)
else
trans.x = x
trans.y = y
end
end
gridredraw()
end
function grid_note(e)
local note = ((7-e.y)*5) + e.x
if e.state > 0 then
if nvoices < 6 then
--engine.start(id, getHz(x, y-1))
--print("grid > "..id.." "..note)
engine.start(e.id, getHzET(note))
start_screen_note(note)
lit[e.id] = {}
lit[e.id].x = e.x
lit[e.id].y = e.y
nvoices = nvoices + 1
end
else
engine.stop(e.id)
stop_screen_note(note)
lit[e.id] = nil
nvoices = nvoices - 1
end
gridredraw()
end
function grid_note_trans(e)
local note = ((7-e.y+(root.y-trans.y))*5) + e.x + (trans.x-root.x)
if e.state > 0 then
if nvoices < 6 then
--engine.start(id, getHz(x, y-1))
--print("grid > "..id.." "..note)
engine.start(e.id, getHzET(note))
start_screen_note(note)
lit[e.id] = {}
lit[e.id].x = e.x + trans.x - root.x
lit[e.id].y = e.y + trans.y - root.y
nvoices = nvoices + 1
end
else
engine.stop(e.id)
stop_screen_note(note)
lit[e.id] = nil
nvoices = nvoices - 1
end
gridredraw()
end
function gridredraw()
g:all(0)
g:led(1,1,2 + pat.rec * 10)
g:led(1,2,2 + pat.play * 10)
g:led(1,8,2 + mode_transpose * 10)
if mode_transpose == 1 then g:led(trans.x, trans.y, 4) end
for i,e in pairs(lit) do
g:led(e.x, e.y,15)
end
g:refresh()
end
-- midi grid functions
function midigrid_note_trans(e)
local note = (e.x-1 + (trans.x-root.x)) * 8 + e.y+(root.y-trans.y)-1 --((7-e.y+(root.y-trans.y))*5) + e.x + (trans.x-root.x)
if e.state > 0 then
if nvoices < 6 then
--engine.start(id, getHz(x, y-1))
--print("grid > "..id.." "..note)
engine.start(e.id, getHzET(note))
start_screen_note(note)
midi.send(midi_device, {144, note, 64})
lit[e.id] = {}
lit[e.id].x = e.x + trans.x - root.x
lit[e.id].y = e.y + trans.y - root.y
nvoices = nvoices + 1
end
else
engine.stop(e.id)
stop_screen_note(note)
midi.send(midi_device, {144, note, 0})
lit[e.id] = nil
nvoices = nvoices - 1
end
midigridredraw()
end
function midigridredraw()
--g:all(0)
--g:led(1,1,2 + pat.rec * 10)
--g:led(1,2,2 + pat.play * 10)
--g:led(1,8,2 + mode_transpose * 10)
--midi.send(midi_device, {144, 0, 64})
midi.send(midi_device, {144, 1, 64})
--print(pat.rec)
if pat.rec == 1 then
midi.send(midi_device, {144, 0, 64})
else
midi.send(midi_device, {144, 0, 0})
end
--if mode_transpose == 1 then g:led(trans.x, trans.y, 4) end
if mode_transpose == 1 then midi.send(midi_device, {144, 7, 64}) end
if mode_transpose == 0 then midi.send(midi_device, {144, 7, 0}) end
for i,e in pairs(lit) do
--g:led(e.x, e.y,15)
local notenum = (e.x-1) * 8 + e.y-1
--midi.send(midi_device, {144, notenum, 64})
end
--g:refresh()
--midi.send(midi_device, {144, 0, 0})
--midi.send(midi_device, {144, 1, 0})
--midi.send(midi_device, {144, 7, 0})
end
function enc(n,delta)
if n == 1 then
mix:delta("output", delta)
end
end
function key(n,z)
end
function start_screen_note(note)
local screen_note = nil
-- Get an existing screen_note if it exists
local count = 0
for key, val in pairs(screen_notes) do
if val.note == note then
screen_note = val
break
end
count = count + 1
if count > 8 then return end
end
if screen_note then
screen_note.active = true
else
screen_note = {note = note, active = true, repeat_timer = 0, x = math.random(128), y = math.random(64), init_radius = math.random(6,18), ripples = {} }
table.insert(screen_notes, screen_note)
end
add_ripple(screen_note)
end
function stop_screen_note(note)
for key, val in pairs(screen_notes) do
if val.note == note then
val.active = false
break
end
end
end
function stop_all_screen_notes()
for key, val in pairs(screen_notes) do
val.active = false
end
end
function add_ripple(screen_note)
if tab.count(screen_note.ripples) < 6 then
local ripple = {radius = screen_note.init_radius, life = 1}
table.insert(screen_note.ripples, ripple)
end
end
function update()
for n_key, n_val in pairs(screen_notes) do
if n_val.active then
n_val.repeat_timer = n_val.repeat_timer + ripple_repeat_rate
if n_val.repeat_timer >= 1 then
add_ripple(n_val)
n_val.repeat_timer = 0
end
end
local r_count = 0
for r_key, r_val in pairs(n_val.ripples) do
r_val.radius = r_val.radius + ripple_growth_rate
r_val.life = r_val.life - ripple_decay_rate
if r_val.life <= 0 then
n_val.ripples[r_key] = nil
else
r_count = r_count + 1
end
end
if r_count == 0 and not n_val.active then
screen_notes[n_key] = nil
end
end
end
function redraw()
screen.clear()
screen.aa(0)
screen.line_width(1)
local first_ripple = true
for n_key, n_val in pairs(screen_notes) do
for r_key, r_val in pairs(n_val.ripples) do
if first_ripple then -- Avoid extra line when returning from menu
screen.move(n_val.x + r_val.radius, n_val.y)
first_ripple = false
end
screen.level(math.max(1,math.floor(r_val.life * 15 + 0.5)))
screen.circle(n_val.x, n_val.y, r_val.radius)
screen.stroke()
end
end
screen.update()
end
function midigrid_note(e)
local note = (e.x-1) * 8 + e.y-1 --((7-e.y)*5) + e.x
if e.state > 0 then
if nvoices < 6 then
--engine.start(id, getHz(x, y-1))
--print("grid > "..id.." "..note)
engine.start(e.id, getHzET(note))
start_screen_note(note)
midi.send(midi_device, {144, note, 64})
--print(note)
lit[e.id] = {}
lit[e.id].x = e.x
lit[e.id].y = e.y
nvoices = nvoices + 1
end
else
engine.stop(e.id)
stop_screen_note(note)
midi.send(midi_device, {144, note, 0})
lit[e.id] = nil
nvoices = nvoices - 1
--print(note)
end
midigridredraw()
end
function midigridkey(x,y,z)
if x == 1 then
if z == 1 then
if y == 1 and pat.rec == 0 then
mode_transpose = 0
trans.x = 5
trans.y = 5
pat:stop()
engine.stopAll()
stop_all_screen_notes()
pat:clear()
pat:rec_start()
elseif y == 1 and pat.rec == 1 then
pat:rec_stop()
if pat.count > 0 then
root.x = pat.event[1].x
root.y = pat.event[1].y
trans.x = root.x
trans.y = root.y
pat:start()
end
elseif y == 2 and pat.play == 0 and pat.count > 0 then
if pat.rec == 1 then
pat:rec_stop()
end
pat:start()
elseif y == 2 and pat.play == 1 then
pat:stop()
engine.stopAll()
stop_all_screen_notes()
nvoices = 0
lit = {}
elseif y == 8 then
mode_transpose = 1 - mode_transpose
end
end
else
if mode_transpose == 0 then
local e = {}
e.id = (x-1) * 8 + y-1 -- x*8 + y
e.x = x
e.y = y
e.state = z
pat:watch(e)
midigrid_note(e)
else
trans.x = x
trans.y = y
end
end
midigridredraw()
end
local function note_on(note, vel)
x = midigridxy[note+1][1]
y = midigridxy[note+1][2]
z = 1
midigridkey(x,y,z)
if nvoices < 6 then
-- send midi note back to device to light up LED
midi.send(midi_device, {144, note, vel})
--engine.start(id, getHz(x, y-1))
engine.start(note, getHzET(note))
start_screen_note(note)
nvoices = nvoices + 1
end
midigridredraw()
end
local function note_off(note, vel)
x = midigridxy[note+1][1]
y = midigridxy[note+1][2]
z = 0
midigridkey(x,y,z)
-- send midi note back to device to turn off LED
midi.send(midi_device, {144, note, 0})
engine.stop(note)
stop_screen_note(note)
nvoices = nvoices - 1
end
local function midi_event(data)
-- tab.print(data)
if data[1] == 144 and data[3] > 0 then
note_on(data[2], data[3])
elseif data[1] == 144 and data[3] == 0 then
note_off(data[2])
elseif data[1] == 176 then
--cc(data1, data2)
elseif data[1] == 224 then
--bend(data1, data2)
end
end
midi.add = function(dev)
print('earthsea: midi device added', dev.id, dev.name)
dev.event = midi_event
midi_device = dev
end
function cleanup()
engine.stopAll()
stop_all_screen_notes()
pat:stop()
pat = nil
for id,dev in pairs(midi.devices) do
dev.event = nil
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment