413 lines
9.2 KiB
Lua
413 lines
9.2 KiB
Lua
VERSION = '2019-10-14'
|
|
print("DRUMSHEET " .. VERSION)
|
|
|
|
require "iuplua"
|
|
require "iupluacontrols"
|
|
require "midiwire"
|
|
|
|
-- now, this can actually play Blue monday!
|
|
-- see https://www.youtube.com/watch?v=Y_hk5P1X7sc&t=164s
|
|
|
|
LEN = 33
|
|
step = 1
|
|
mout = midiwire.open_out(0)
|
|
active_pat = 1
|
|
chan = 10
|
|
|
|
instruments = {
|
|
[1] = {"clap", 39},
|
|
[2] = {"open hat", 46},
|
|
[3] = {"snare", 40},
|
|
[4] = {"bass", 36},
|
|
}
|
|
|
|
patterns = {
|
|
{},
|
|
{},
|
|
{},
|
|
{}
|
|
}
|
|
|
|
-- GUI definition
|
|
|
|
pat_a = iup.button{title="PAT A", fontstyle="Bold"}
|
|
pat_b = iup.button{title="PAT B"}
|
|
pat_c = iup.button{title="PAT C"}
|
|
pat_d = iup.button{title="PAT D"}
|
|
pat_rec = iup.button{title="REC"}
|
|
pat_clean = iup.button{title="ZAP"}
|
|
pat_load = iup.button{title="load"}
|
|
pat_save = iup.button{title="save"}
|
|
|
|
matrix = iup.matrix{numcol=32, numlin=9, widthdef=10}
|
|
matrix.resizematrix = "YES"
|
|
|
|
function print_instruments()
|
|
for i=1,32 do
|
|
matrix:setcell(0,i,i)
|
|
end
|
|
for i=1,8 do
|
|
matrix:setcell(i,0,"")
|
|
end
|
|
for i=1,#instruments do
|
|
matrix:setcell(i,0,instruments[i][1])
|
|
end
|
|
end
|
|
|
|
matrix:setcell(0,0,"DRUMsheet")
|
|
print_instruments()
|
|
|
|
playbtn = iup.button{title="play"}
|
|
stopbtn = iup.button{title="stop"}
|
|
|
|
midi_setup = iup.button{title="MIDI output"}
|
|
|
|
-- TODO - maskovat tyhle věci a validovat ať to necrashuje
|
|
|
|
bpm = iup.text{spin="YES", value="120", spinmin=30, spinmax=240} -- todo: min max sensible
|
|
factor = iup.text{spin="YES", value="4", spinmin=1, spinmax=8}
|
|
len = iup.text{spin="YES", value="32", spinmin=8, spinmax=32}
|
|
|
|
dlg = iup.dialog{
|
|
iup.vbox{
|
|
iup.hbox{pat_a, pat_b, pat_c, pat_d, pat_rec, pat_clean, pat_load, pat_save; gap=5},
|
|
iup.hbox{matrix},
|
|
iup.hbox{playbtn, stopbtn, iup.label{title="tempo: "} , bpm, iup.label{title="/"}, factor, iup.label{title=" || len: "}, len, midi_setup; alignment="ACENTER", gap="5"}
|
|
}
|
|
; title = "DRUMsheet " .. VERSION, size = "560x170"
|
|
}
|
|
|
|
-- GUI actions
|
|
|
|
timer = iup.timer{time=500}
|
|
timer.run="NO"
|
|
|
|
function timer:action_cb()
|
|
-- ONE STEP of sequencer
|
|
if step==1 then
|
|
matrix:setcell(9, LEN-1, "")
|
|
else
|
|
matrix:setcell(9, step-1, "") -- clean from previous step
|
|
end
|
|
matrix:setcell(9, step, "^")
|
|
|
|
for i=1,#instruments do
|
|
if matrix:getcell(i, step)=="x" then
|
|
midiwire.note_on(mout, instruments[i][2], 100, chan)
|
|
end
|
|
if matrix:getcell(i, step)=="*" then
|
|
midiwire.note_on(mout, instruments[i][2], 127, chan)
|
|
end
|
|
end
|
|
|
|
step = ((step + 1) % LEN)
|
|
if step==0 then
|
|
step=1
|
|
end
|
|
matrix.redraw="YES"
|
|
end
|
|
|
|
function silence()
|
|
for i=1,#instruments do
|
|
midiwire.note_off(mout, instruments[i][2], chan)
|
|
end
|
|
end
|
|
|
|
function playbtn:action()
|
|
bpm.active="NO"
|
|
factor.active="NO"
|
|
len.active="NO"
|
|
timer.run="YES"
|
|
end
|
|
|
|
function stopbtn:action()
|
|
bpm.active="YES"
|
|
factor.active="YES"
|
|
len.active="YES"
|
|
timer.run="NO"
|
|
-- todo - tohle lépe
|
|
silence()
|
|
if step==1 then
|
|
matrix:setcell(9, LEN-1, "")
|
|
else
|
|
matrix:setcell(9, step-1, "") -- clean from previous step
|
|
end
|
|
matrix.redraw="YES"
|
|
step = 1
|
|
end
|
|
|
|
function adjust_time(bpm, factor)
|
|
timer.time = math.floor((1000*60) / (bpm*factor) )
|
|
end
|
|
|
|
function bpm:valuechanged_cb()
|
|
if tonumber(bpm.value) and tonumber(factor.value) then
|
|
adjust_time(tonumber(bpm.value), tonumber(factor.value))
|
|
end
|
|
end
|
|
|
|
function factor:valuechanged_cb()
|
|
if tonumber(bpm.value) and tonumber(factor.value) then
|
|
adjust_time(tonumber(bpm.value), tonumber(factor.value))
|
|
end
|
|
end
|
|
|
|
function adjust_len(len)
|
|
LEN = len + 1
|
|
for i=1,LEN do
|
|
matrix:setcell(0, i, i)
|
|
end
|
|
if LEN<33 then
|
|
for i=LEN,32 do
|
|
matrix:setcell(0, i, "")
|
|
end
|
|
end
|
|
end
|
|
|
|
function len:valuechanged_cb()
|
|
if tonumber(len.value) then
|
|
adjust_len(tonumber(len.value))
|
|
matrix.redraw="YES"
|
|
end
|
|
end
|
|
|
|
-- TODO - tohle přepsat na jedno okno
|
|
function midi_setup:action()
|
|
local devices , sel2id = {}, {}
|
|
for i=0,midiwire.device_count()-1 do
|
|
if midiwire.name(i) then
|
|
devices[#devices+1] = midiwire.name(i)
|
|
sel2id[#devices] = i
|
|
end
|
|
end
|
|
|
|
local selected = iup.ListDialog(1, "select MIDI output", #devices, devices, 1, 1, #devices)
|
|
if selected>-1 and sel2id[selected+1] then
|
|
midiwire.close_out(mout)
|
|
if sel2id[selected+1]==0 then
|
|
-- Microsoft GS Wavetable Synth
|
|
mout = midiwire.open_out(sel2id[selected+1])
|
|
chan = 10
|
|
else
|
|
mout = midiwire.open_out(sel2id[selected+1])
|
|
chan = iup.Scanf("Select port\nchannel number%2.2%d\n", 1)
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
|
|
-- easier editing of patterns
|
|
|
|
function matrix:action_cb(key, x, y, edition, value)
|
|
if key==iup.K_SP then
|
|
matrix:setcell(x, y, "")
|
|
matrix.redraw="YES"
|
|
return iup.IGNORE
|
|
elseif key==iup.K_X or key==iup.K_x then
|
|
matrix:setcell(x, y, "x")
|
|
matrix.redraw="YES"
|
|
return iup.IGNORE
|
|
end
|
|
end
|
|
|
|
|
|
-- patterns changing, saving and loading
|
|
|
|
function pat_rec:action()
|
|
patterns[active_pat] = {}
|
|
for x=1,8 do
|
|
patterns[active_pat][x] = {}
|
|
for y=1,32 do
|
|
patterns[active_pat][x][y] = matrix:getcell(x,y) or ""
|
|
end
|
|
-- print(table.concat(patterns[active_pat][x], ","))
|
|
end
|
|
end
|
|
|
|
function pat_clean:action()
|
|
for x=1,8 do
|
|
for y=1,32 do
|
|
matrix:setcell(x,y, "")
|
|
end
|
|
end
|
|
matrix.redraw = "YES"
|
|
end
|
|
|
|
function pat_recall(patno)
|
|
if not patterns[patno] then return end
|
|
local was_running = timer.run == "YES"
|
|
timer.run="NO" -- pause while recalling to not glitch
|
|
silence()
|
|
for x=1,8 do
|
|
patterns[patno][x] = patterns[patno][x] or {}
|
|
for y=1,32 do
|
|
matrix:setcell(x, y, patterns[patno][x][y] or "")
|
|
end
|
|
-- print(table.concat(patterns[active_pat][x], ","))
|
|
end
|
|
active_pat = patno
|
|
if was_running then
|
|
step = 1
|
|
timer.run="YES"
|
|
end
|
|
matrix.redraw="YES"
|
|
end
|
|
|
|
function pat_a:action()
|
|
pat_a.fontstyle="Bold"; pat_b.fontstyle=""; pat_c.fontstyle=""; pat_d.fontstyle=""
|
|
pat_recall(1)
|
|
end
|
|
|
|
function pat_b:action()
|
|
pat_a.fontstyle=""; pat_b.fontstyle="Bold"; pat_c.fontstyle=""; pat_d.fontstyle=""
|
|
pat_recall(2)
|
|
end
|
|
|
|
function pat_c:action()
|
|
pat_a.fontstyle=""; pat_b.fontstyle=""; pat_c.fontstyle="Bold"; pat_d.fontstyle=""
|
|
pat_recall(3)
|
|
end
|
|
|
|
|
|
function pat_d:action()
|
|
pat_a.fontstyle=""; pat_b.fontstyle=""; pat_c.fontstyle=""; pat_d.fontstyle="Bold"
|
|
pat_recall(4)
|
|
end
|
|
|
|
-- saving & loading
|
|
|
|
FILEFORMAT_VERSION = 1
|
|
|
|
function _pat_write(fh, patno)
|
|
fh:write("PAT,"..patno.."\n")
|
|
for i=1,#patterns[patno] do
|
|
fh:write(table.concat(patterns[patno][i],",") .. "\n")
|
|
end
|
|
end
|
|
|
|
function pat_save:action()
|
|
local fname, status = iup.GetFile("./*.csv")
|
|
if status > -1 then
|
|
local fh = io.open(fname, "w+")
|
|
fh:write("DRUMSHEET," .. FILEFORMAT_VERSION .. "," .. VERSION .. "\n")
|
|
fh:write("bpm,"..bpm.value.."\n")
|
|
fh:write("factor,"..factor.value.."\n")
|
|
fh:write("len,"..len.value.."\n")
|
|
fh:write("INSTRUMENTS\n")
|
|
for i=1,#instruments do
|
|
fh:write(instruments[i][1] .. "," .. instruments[i][2] .. "\n")
|
|
end
|
|
-- todo pat_write nebo loop
|
|
if #patterns[1]>1 then _pat_write(fh, 1) end
|
|
if #patterns[2]>1 then _pat_write(fh, 2) end
|
|
if #patterns[3]>1 then _pat_write(fh, 3) end
|
|
if #patterns[4]>1 then _pat_write(fh, 4) end
|
|
fh:close()
|
|
end
|
|
end
|
|
|
|
function explode(div,str) -- credit: http://richard.warburton.it
|
|
if (div=='') then return false end
|
|
local pos,arr = 0,{}
|
|
-- for each divider found
|
|
for st,sp in function() return string.find(str,div,pos,true) end do
|
|
table.insert(arr,string.sub(str,pos,st-1)) -- Attach chars left of current divider
|
|
pos = sp + 1 -- Jump past current divider
|
|
end
|
|
table.insert(arr,string.sub(str,pos)) -- Attach chars right of last divider
|
|
return arr
|
|
end
|
|
|
|
function sheet_read(fname)
|
|
local data = { instruments={}, patterns = {} }
|
|
local mode = ""
|
|
local is_header = {bpm=true, factor=true, len=true}
|
|
local i = 0
|
|
local patno = 0
|
|
for line in io.lines(fname) do
|
|
local fields = explode(',', line)
|
|
-- reads header:
|
|
if mode=="DRUMSHEET" and is_header[fields[1]] then
|
|
data[fields[1]] = tonumber(fields[2])
|
|
end
|
|
|
|
-- switch to PAT mode in INSTRUMENTS
|
|
if mode=="INSTRUMENTS" and fields[1]=="PAT" then
|
|
mode = "PAT"
|
|
end
|
|
|
|
-- reads instruments:
|
|
if mode=="INSTRUMENTS" and fields[1]~="PAT" then
|
|
i = i + 1
|
|
data.instruments[i] = {fields[1], tonumber(fields[2])}
|
|
end
|
|
|
|
-- read patterns:
|
|
if mode=="PAT" and fields[1]=="PAT" then
|
|
patno = tonumber(fields[2])
|
|
data.patterns[patno] = {}
|
|
i = 0
|
|
elseif mode=="PAT" then
|
|
i = i + 1
|
|
data.patterns[patno][i] = fields
|
|
end
|
|
|
|
if mode=="" and fields[1]=="DRUMSHEET" then
|
|
-- todo - kontrolovat verzi drumsheetu
|
|
if not fields[2] or not tonumber(fields[2]) then
|
|
return nil, "File version not found!"
|
|
end
|
|
if tonumber(fields[2])>FILEFORMAT_VERSION then
|
|
return nil, "File too new!"
|
|
end
|
|
mode = "DRUMSHEET"
|
|
|
|
end
|
|
if mode=="DRUMSHEET" and fields[1]=="INSTRUMENTS" then
|
|
mode = "INSTRUMENTS"
|
|
i = 0
|
|
end
|
|
end
|
|
return data
|
|
end
|
|
|
|
function pat_load:action()
|
|
local fname, status = iup.GetFile("./*.csv")
|
|
if status > -1 then
|
|
local data, error = sheet_read(fname)
|
|
if error then
|
|
iup.Message("File not loaded", error)
|
|
else
|
|
bpm.value = data.bpm
|
|
factor.value = data.factor
|
|
len.value = data.len
|
|
adjust_time(data.bpm, data.factor)
|
|
adjust_len(data.len)
|
|
instruments = data.instruments or {}
|
|
print_instruments()
|
|
patterns = data.patterns or { {}, {}, {}, {} }
|
|
if patterns[1] then
|
|
pat_a.fontstyle="Bold"; pat_b.fontstyle=""; pat_c.fontstyle=""; pat_d.fontstyle=""
|
|
pat_recall(1)
|
|
end
|
|
|
|
matrix.redraw = "YES"
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
-- main program
|
|
|
|
adjust_time(120, 4)
|
|
dlg:showxy( iup.CENTER, iup.CENTER )
|
|
|
|
if midiwire.device_count()<2 then
|
|
midi_setup.active = "NO"
|
|
end
|
|
|
|
if (iup.MainLoopLevel()==0) then
|
|
iup.MainLoop()
|
|
end
|