Last active
December 12, 2022 21:16
-
-
Save millxing/8630412f07a8ac162a9b08908af7c536 to your computer and use it in GitHub Desktop.
ISOKB is a 2-channel MIDI keyboard for Norns and Grid. It uses an isomorphic "in key" note layout similar to Ableton Push.
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
-- ISOKB | |
-- | |
-- 2-channel MIDI keyboard for Norns and Grid | |
-- Isomorphic "in key" note layout (similar to Ableton Push) | |
-- Select a tonic note and one of nine 7-note scales (default C Major) | |
-- Two independent keyboards, each sends MIDI to a different MIDI channel | |
-- Left hand notes go to MIDI 1 | |
-- Right hand notes go to MIDI 2 | |
-- There will be multiple buttons for each note and all buttons corresponding to a pressed note will light up | |
-- | |
-- Rob Schoen (email: millxing at gmail dot com) | |
music = require 'mark_eats/musicutil' -- Thanks Mark! | |
function init() | |
-- change these paramters to send the MIDI to channels other than 1 & 2 | |
midi_channel_left = 1 | |
midi_channel_right = 2 | |
math.randomseed(os.time()) | |
opening_animation() | |
mode = 1 | |
toniclist = music.NOTE_NAMES | |
tonicnum = 1 | |
new_scale() | |
redraw() | |
redraw_grid() | |
end | |
function new_scale() | |
-- fill scal with notes from selected scale | |
scale = music.generate_scale_of_length(36+(tonicnum-1),music.SCALES[mode].name,80) | |
base = scale[1] % 12 | |
-- initialize keyboard | |
pressed = {} | |
notes = {} | |
root = {} | |
local c | |
local r | |
for c = 1,16 do | |
pressed[c] = {} | |
notes[c] = {} | |
root[c] = {} | |
for r = 8,1,-1 do | |
pressed[c][r] = 0 | |
notes[c][r] = 0 | |
root[c][r] = 0 | |
end | |
end | |
-- fill notes and root table | |
local temp | |
local i | |
for r = 8,1,-1 do | |
for c = 1,7 do | |
if r==8 then | |
notes[c][8] = scale[c] | |
notes[c+9][8] = scale[c] | |
if notes[c][8] % 12 == base then root[c][8] = 1; end | |
if notes[c+9][8] % 12 == base then root[c+9][8] = 1; end | |
else | |
for i = 1,#scale do | |
if notes[c][r+1] == scale[i] then temp = i; end | |
end | |
notes[c][r] = scale[temp+3] | |
notes[c+9][r] = scale[temp+3] | |
if notes[c][r] % 12 == base then | |
root[c][r] = 1; | |
root[c+9][r] = 1; | |
end | |
end | |
end | |
end | |
clearAllNotes() | |
end | |
g = grid.connect() | |
m = midi.connect() | |
g.event = function(x,y,z) | |
-- events for button press | |
if z==1 then | |
pressed[x][y] = 1 | |
if x > 8 then cplus = 9 else cplus = 0 end | |
for c = 1,7 do | |
for r = 1,8 do | |
if notes[c+cplus][r] == notes[x][y] then | |
pressed[c+cplus][r] = 1 | |
end | |
end | |
end | |
print(x,y,notes[x][y], music.note_num_to_name(notes[x][y])) | |
if x<9 then | |
m.note_on(notes[x][y],90,1) | |
else | |
m.note_on(notes[x][y],90,2) | |
end | |
end | |
--events for button release | |
if z==0 then | |
pressed[x][y] = 0 | |
if x > 8 then cplus = 9 else cplus = 0 end | |
for c = 1,7 do | |
for r = 1,8 do | |
if notes[c+cplus][r] == notes[x][y] then | |
pressed[c+cplus][r] = 0 | |
end | |
end | |
end | |
if x<9 then | |
m.note_off(notes[x][y],0,1) | |
else | |
m.note_off(notes[x][y],0,2) | |
end | |
end | |
redraw_grid() | |
end | |
function redraw_grid() | |
g.all(2) | |
local r | |
local c | |
for r = 8,1,-1 do | |
for c = 1,7 do | |
g.led(c,r,pressed[c][r]==1 and 15 or (root[c][r]==1 and 10 or 5)) | |
g.led(c+9,r, pressed[c+9][r]==1 and 15 or (root[c+9][r]==1 and 10 or 5)) | |
end | |
end | |
g.refresh() | |
end | |
function redraw() | |
screen.clear() | |
screen.font_size(8) | |
screen.level(15) | |
screen.move(0,10) | |
screen.text("left side is MIDI ch 1") | |
screen.move(0,20) | |
screen.text("right side is MIDI ch 2") | |
screen.move(0,30) | |
screen.text("encoder 2 changes root note") | |
screen.move(0,40) | |
screen.text("encoder 3 changes scale") | |
screen.move(0,60) | |
screen.text("scale: " .. toniclist[tonicnum] .. " " .. music.SCALES[mode].name) | |
screen.update() | |
end | |
function enc(n,d) | |
-- select tonic note | |
if n==2 then | |
tonicnum = tonicnum + d | |
if tonicnum>12 then tonicnum = 1; end | |
if tonicnum<1 then tonicnum = 12; end | |
new_scale() | |
end | |
-- select scale | |
if n==3 then | |
mode = mode + d | |
if mode>9 then mode = 1; end | |
if mode<1 then mode = 9; end | |
new_scale() | |
end | |
redraw() | |
end | |
-- gratuitous opening animation | |
function opening_animation() | |
local a; local i; local j | |
local speed = .05 | |
for a = 8,1,-1 do | |
g.all(0) | |
for i = 1,8 do | |
for j = 1+(a-1),16-(a-1) do | |
g.led(j,i,math.random(0,15)) | |
end | |
end | |
g.refresh() | |
sleep(speed) | |
end | |
for a = 1,8 do | |
g.all(0) | |
for i = 1,8 do | |
for j = 1+(a-1),16-(a-1) do | |
g.led(j,i,math.random(0,15)) | |
end | |
end | |
g.refresh() | |
sleep(speed) | |
end | |
g.all(0) | |
g.refresh() | |
end | |
-- pause or delay lua code | |
function sleep(s) | |
local ntime = os.clock() + s | |
repeat until os.clock() > ntime | |
end | |
-- clear midi buffer in case of stuck notes | |
function clearAllNotes() | |
local i; local j | |
for i = 1,2 do | |
for j = 1,127 do | |
m.note_off(j,0,i) | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment