Last active
June 16, 2018 18:41
-
-
Save okyeron/44f1c75fbd0aaab186784b5c82099177 to your computer and use it in GitHub Desktop.
earthsea midi -- okyeron version for Livid Block
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
-- 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