Created
March 13, 2019 05:12
-
-
Save zunaito/7299d4270ea81e8f03fa0c6ab54a1b09 to your computer and use it in GitHub Desktop.
ekombi for norns 2.0 beta
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
-- Ekombi | |
-- | |
-- polyrhythmic sampler | |
-- | |
-- | |
-- 4, two-track channels | |
-- ------------------------------------------ | |
-- trackA: sets the length | |
-- of the tuplet | |
-- | |
-- trackB: sets length of the | |
-- 'measure' in quarter notes | |
-- ------------------------------------------- | |
-- | |
-- grid controls | |
-- --------------------------------- | |
-- hold a key and press another | |
-- key in the same row to set | |
-- the length of the track | |
-- | |
-- tapping gridkeys toggles the | |
-- tuplet subdivisions and | |
-- quarter notes on/off | |
-- ------------------------------------------- | |
-- | |
-- norns controls | |
-- ------------------------------------------- | |
-- enc1: bpm | |
-- enc2: select pattern | |
-- enc3: filter cutoff | |
-- | |
-- key1: save pattern | |
-- key2: load pattern | |
-- key3: stop clock | |
-- --------------------------------------------- | |
engine.name = 'Ack' | |
local ack = require 'ack' | |
local g = grid.connect() | |
--[[whats next?: | |
- enc3 meter | |
- patterns display on screen before loading for a set amount of time, | |
then returns to displaying current grid pattern | |
- continue optimizing | |
- beatclock integration for midi sync | |
]]-- | |
--[[current issues: | |
- are rhythms totally accurate? | |
--]] | |
------------ | |
-- variables | |
------------ | |
-- clocking variables | |
position = 0 | |
q_position = 0 | |
bpm = 60 | |
counter = nil | |
running = false | |
ppq = 480 -- pulses per quarter, lower this if you come across performance issues. | |
-- pattern variables | |
pattern_select = 1 | |
-- display variables | |
pattern_display = "default" | |
meter_display = 50 | |
-- grid variables | |
-- for holding one gridkey and pressing another further right | |
held = {} | |
heldmax = {} | |
done = {} | |
first = {} | |
second = {} | |
for row = 1,8 do | |
held[row] = 0 | |
heldmax[row] = 0 | |
done[row] = 0 | |
first[row] = 0 | |
second[row] = 0 | |
end | |
-- 4, two-track channels (A is even rows, TrackB is odd rows) | |
track = {} | |
for i=1,8 do | |
if i % 2 == 1 then | |
track[i] = {} | |
track[i][1] = {} | |
track[i][1][1] = 0 | |
else | |
track[i] = {} | |
for n=1, 16 do | |
track[i][n] = {} | |
for j=1, 16 do | |
track[i][n][j] = 1 | |
end | |
end | |
end | |
end | |
---------------- | |
-- initilization | |
---------------- | |
function init() | |
-- parameters | |
params:add_number("bpm", 15, 400, 60) | |
ack.add_effects_params() | |
for channel=1,4 do | |
ack.add_channel_params(channel) | |
end | |
params:read("gittifer/ekombi.pset") | |
-- metronome setup | |
counter = metro.init() | |
counter.time = 60 / (params:get("bpm") * ppq) | |
counter.count = -1 | |
counter.event = count | |
-- counter:start() | |
gridredraw() | |
redraw() | |
end | |
------------------------- | |
-- grid control functions | |
------------------------- | |
function g.key(x, y, z) | |
-- sending data to two separate functions | |
gridkeyhold(x,y,z) | |
gridkey(x,y,z) | |
end | |
function gridkey(x,y,z) | |
if z == 1 then | |
cnt = tab.count(track[y]) | |
-- error control | |
if cnt == 0 or cnt == nil then | |
if x > 1 then | |
return | |
elseif x == 1 then | |
track[y] = {} | |
track[y][x] = {} | |
track[y][x][x] = 1 | |
gridredraw() | |
end | |
return | |
else | |
-- track-B un-reset-able | |
if x == 16 and y % 2 == 1 then | |
track[y] = {} | |
track[y][1] = {} | |
track[y][1][1] = 0 | |
return | |
end | |
-- note toggle on/off | |
if x > cnt then | |
return | |
else | |
if track[y][cnt][x] == 1 then | |
track[y][cnt][x] = 0 | |
else | |
track[y][cnt][x] = 1 | |
end | |
end | |
-- automatic clock startup | |
if running == false then | |
counter:start() | |
running = true | |
end | |
end | |
end | |
redraw() | |
gridredraw() | |
end | |
function gridkeyhold(x, y, z) | |
if z == 1 and held[y] then heldmax[y] = 0 end | |
held[y] = held[y] + (z*2 -1) | |
if held[y] > heldmax[y] then heldmax[y] = held[y] end | |
if y > 8 and held[y] == 1 then | |
first[y] = x | |
elseif y <= 8 and held[y] == 2 then | |
second[y] = x | |
elseif z == 0 then | |
if y <= 8 and held[y] == 1 and heldmax[y] == 2 then | |
track[y] = {} | |
for i = 1, second[y] do | |
track[y][i] = {} | |
for n=1, i do | |
track[y][i][n] = 1 | |
end | |
end | |
end | |
end | |
redraw() | |
gridredraw() | |
end | |
--------------------------- | |
-- norns control functions | |
--------------------------- | |
function enc(n,d) | |
if n == 1 then | |
params:delta("bpm",d) | |
end | |
if n == 2 then | |
pattern_select = util.clamp(pattern_select + d, 1, 16) | |
print("pattern:"..pattern_select) | |
end | |
if n == 3 then | |
for i=1, 4 do | |
params:delta(i..": filter cutoff", d) | |
end | |
end | |
redraw() | |
end | |
function key(n,z) | |
if z == 1 then | |
if n == 1 then | |
save_pattern() | |
end | |
if n == 2 then | |
load_pattern() | |
pattern_display = pattern_select | |
end | |
if n == 3 then | |
if running then | |
counter:stop() | |
running = false | |
else | |
position = 0 | |
counter:start() | |
running = true | |
end | |
end | |
end | |
gridredraw() | |
redraw() | |
end | |
------------------ | |
-- active functions | |
------------------- | |
--[[ | |
this is the heart of polyrhythm generating, each track is checked to see which note divisions are on or off, | |
first, the B track is checked (the 'quarter' note, before the tuplet division) then if the note is on, we check | |
each of the subdivisions, and if those turn out to be on, the nth subdivision of the tuple of the track is triggered. | |
The complicated divisons and multiplations of each of the track sets and subsets is to find the exact position value, | |
that when % by that value returns 1, the track triggers. | |
]]-- | |
function count(c) | |
position = (position + 1) % (ppq) | |
counter.time = 60 / (params:get("bpm") * ppq) | |
if position == 0 then | |
q_position = q_position + 1 | |
fast_gridredraw() | |
end | |
for i=2, 8, 2 do | |
cnt = tab.count(track[i]) | |
if cnt == 0 or cnt == nil then | |
return | |
else | |
if track[i][cnt][(q_position%cnt)+1] == 1 then | |
cnt = tab.count(track[i-1]) | |
if cnt == 0 or cnt == nil then | |
return | |
else | |
for n=1, cnt do | |
if position / ( ppq // (tab.count(track[i-1][cnt]))) == n-1 then | |
if track[i-1][cnt][n] == 1 then | |
engine.trig(i//2 -1) -- samples are only 0-3 | |
end | |
end | |
end | |
end | |
else | |
-- pass | |
end | |
end | |
end | |
end | |
--------------------------- | |
-- refresh/redraw functions | |
--------------------------- | |
function redraw() | |
screen.clear() | |
screen.aa(0) | |
screen.level(15) | |
-- grid pattern preset display | |
for i=1, 8 do | |
for n=1, tab.count(track[i]) do | |
if track[i][tab.count(track[i])][n] == 1 then | |
screen.rect((n-1)*7, 1 + i*7, 6, 6) | |
screen.fill() | |
screen.move(tab.count(track[i])*7, i*7 + 7) | |
screen.text(tab.count(track[i])) | |
else | |
screen.rect(1 + (n-1)*7, 2 + i*7, 5, 5) | |
screen.stroke() | |
screen.move(tab.count(track[i])*7, 7 + i*7) | |
screen.text(tab.count(track[i])) | |
end | |
end | |
end | |
-- param display | |
screen.move(0,5) | |
screen.text("bpm:"..params:get("bpm")) | |
screen.move(64,5) | |
screen.level(15) | |
screen.text_center("pattern:"..pattern_select) | |
-- pause icon | |
if not running then | |
screen.rect(123,57,2,6) | |
screen.rect(126,57,2,6) | |
screen.fill() | |
end | |
-- currently selected pattern | |
screen.level(1) | |
screen.move(128,5) | |
screen.text_right(pattern_display) | |
screen.update() | |
end | |
function gridredraw() | |
g.all(0) | |
-- draw channels with sub divisions on/off | |
for i=1, 8 do | |
for n=1, tab.count(track[i]) do | |
ct = tab.count(track[i]) | |
if ct == 0 or nil then return | |
else | |
if i % 2 == 1 then | |
if track[i][ct][n] == 1 then | |
g.led(n, i, 12) | |
else | |
g.led(n, i, 4) | |
end | |
elseif i % 2 == 0 then | |
if track[i][ct][n] == 1 then | |
g.led(n, i, 8) | |
else | |
g.led(n, i, 2) | |
end | |
g.led((q_position % ct) + 1, i, 15) | |
end | |
end | |
end | |
end | |
g.refresh() | |
end | |
function fast_gridredraw() | |
for i=1, 8 do | |
for n=1, tab.count(track[i]) do | |
ct = tab.count(track[i]) | |
if ct == 0 or nil then return | |
else | |
if i % 2 == 0 then | |
if track[i][ct][n] == 1 then | |
g.led(n, i, 8) | |
else | |
g.led(n, i, 2) | |
end | |
g.led((q_position % ct) + 1, i, 15) | |
end | |
end | |
end | |
end | |
g.refresh() | |
end | |
------------------ | |
-- save/load functions | |
---------------------- | |
-- each pattern takes up 136 lines of data | |
-- 1 :first line is the length of the track | |
-- 1 :ON following lines display on/off state | |
-- 0 :OFF | |
-- 00:NIL following 16-n lines are nil values | |
-- | |
function save_pattern() | |
local count = 0 | |
local file = io.open(data_dir .. "gittifer/ekombi.data", "r+") | |
io.output(file) | |
for l=1, ((pattern_select - 1) * 136) do | |
file:read("*line") | |
end | |
for i = 1, 8 do | |
count = tab.count(track[i]) | |
if count == nil then | |
io.close(file) | |
return | |
end | |
io.write(count .. "\n") | |
for n=1, count do | |
io.write(track[i][count][n] .. "\n") | |
end | |
for n=1, 16 - count do | |
io.write("00\n") | |
end | |
end | |
print("SAVE COMPLETE") | |
io.close(file) | |
end | |
function load_pattern() | |
local tracklen = 0 | |
local file = io.open(data_dir .. "gittifer/ekombi.data", "r") | |
if file then | |
print("datafile found") | |
io.input(file) | |
for l=1, ((pattern_select - 1) * 136) do | |
file:read("*line") | |
end | |
for i = 1, 8 do | |
track[i] = {} | |
tracklen = tonumber(io.read("*line")) | |
for n=1, tracklen do | |
track[i][n] = {} | |
end | |
for j=1, tracklen do | |
track[i][tracklen][j] = tonumber(io.read("*line")) | |
end | |
for m = 1, 16 - tab.count(track[i]) do | |
io.read("*line") | |
end | |
end | |
print("LOAD COMPLETE") | |
io.close(file) | |
end | |
-- for i = 1, 8 do reer(i) end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment