Skip to content
Permalink
feature-midi-in
Go to file
 
 
Cannot retrieve contributors at this time
778 lines (680 sloc) 17.8 KB
-- Awake __
-- -Midi __ __
-- __
-- __
-- __
-- _______________
-- __
--
--
-- 2.2.1 @frederickk
-- 2.2.0 @tehn
-- llllllll.co/t/21022
--
-- top loop plays notes
-- transposed by bottom loop
--
-- (grid optional)
--
-- E1 changes modes:
-- STEP/LOOP/SOUND/OPTION
--
-- K1 held is alt *
--
-- STEP
-- E2/E3 move/change
-- K2 toggle *clear
-- K3 morph *rand
--
-- LOOP
-- E2/E3 loop length
-- K2 reset position
-- K3 jump position
--
-- SOUND
-- K2/K3 selects
-- E2/E3 changes
--
-- OPTION
-- *toggle
-- E2/E3 changes
engine.name = "PolyPerc"
-- Properties
local hs = include("lib/halfsecond")
local MusicUtil = require "musicutil"
local BeatClock = require "beatclock"
local tu = require "tabutil"
local options = {}
options.OUTPUT = {"audio", "midi", "audio + midi", "crow out 1+2", "crow ii JF"}
options.STEP_LENGTH_NAMES = {"1 bar", "1/2", "1/3", "1/4", "1/6", "1/8", "1/12", "1/16", "1/24", "1/32", "1/48", "1/64"}
options.STEP_LENGTH_DIVIDERS = {1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 48, 64}
local g = grid.connect()
local alt = false
local mode = 1
local mode_names = {"STEP", "LOOP", "SOUND", "OPTION"}
-- defaults are now saved in data/awake/awake.pset
local one = {
pos = 0,
length = 8,
start = 1,
data = {1,0,3,5,6,7,8,7,0,0,0,0,0,0,0,0}
}
local two = {
pos = 0,
length = 7,
start = 1,
data = {5,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
}
local midi_out_device
local mini_in_device
local midi_out_channel
local midi_root_note_change = false
local scale_names = {}
local notes = {}
local active_notes = {}
local edit_ch = 1
local edit_pos = 1
snd_sel = 1
local snd_names = {"cut", "gain", "pw", "rel", "fb", "rate", "pan", "delay_pan"}
local snd_params = {"cutoff", "gain", "pw", "release", "delay_feedback", "delay_rate", "pan", "delay_pan"}
local NUM_SND_PARAMS = #snd_params
local clk = BeatClock.new()
-- local clk_midi = midi.connect()
-- clk_midi.event = function(data)
-- print("clk")
-- clk:process_midi(data)
-- end
local notes_off_metro = metro.init()
local set_loop_data = function(which, step, val)
params:set(which.."_data_"..step, val)
end
-- Local methods
local function all_notes_off()
if (params:get("output") == 2 or params:get("output") == 3) then
for _, a in pairs(active_notes) do
midi_out_device:note_off(a, nil, midi_out_channel)
end
end
active_notes = {}
end
local function random()
for i = 1,one.length do
set_loop_data("one", i, math.floor(math.random() * 9))
end
for i = 1,two.length do
set_loop_data("two", i, math.floor(math.random() * 9))
end
end
local function morph(loop, which)
for i = 1,loop.length do
if loop.data[i] > 0 then
set_loop_data(which, i, util.clamp(loop.data[i] + math.floor(math.random() * 3) - 1, 1, 8))
end
end
end
local function step()
all_notes_off()
one.pos = one.pos + 1
if one.pos > one.length then one.pos = 1 end
two.pos = two.pos + 1
if two.pos > two.length then two.pos = 1 end
if one.data[one.pos] > 0 then
local note_num = notes[one.data[one.pos] + two.data[two.pos]]
local freq = MusicUtil.note_num_to_freq(note_num)
-- Trig Probablility
if math.random(100) <= params:get("probability") then
-- Audio engine out
if params:get("output") == 1 or params:get("output") == 3 then
engine.hz(freq)
elseif params:get("output") == 4 then
crow.output[1].volts = (note_num - 60) / 12
crow.output[2].execute()
elseif params:get("output") == 5 then
crow.ii.jf.play_note((note_num - 60) / 12, 5)
end
-- MIDI out
if (params:get("output") == 2 or params:get("output") == 3) then
midi_out_device:note_on(note_num, 96, midi_out_channel)
table.insert(active_notes, note_num)
-- Note off timeout
if params:get("note_length") < 4 then
notes_off_metro:start((60 / clk.bpm / clk.steps_per_beat / 4) * params:get("note_length"), 1)
end
end
end
end
if params:get("crow_clock") == 2 then
crow.output[1]:execute()
end
if g then
gridredraw()
end
redraw()
end
local function stop()
all_notes_off()
end
-- Init
function init()
for i = 1, #MusicUtil.SCALES do
table.insert(scale_names, string.lower(MusicUtil.SCALES[i].name))
end
midi_in_device = midi.connect(1)
midi_in_device.event = on_midi_event
midi_out_device = midi.connect(2)
midi_out_device.event = function() end
clk.on_step = step
clk.on_stop = stop
clk.on_select_internal = function()
clk:start()
end
clk.on_select_external = reset_pattern
clk:add_clock_params()
params:set("bpm", 90)
params:add{
type = "option",
id = "crow_clock",
name = "crow clock out",
options = {"off", "on"},
action = function(value)
if value == 2 then
crow.output[1].action = "{to(5,0),to(5,0.05),to(0,0)}"
end
end}
notes_off_metro.event = all_notes_off
params:add{
type = "option",
id = "output",
name = "output",
options = options.OUTPUT,
action = function(value)
all_notes_off()
if value == 4 then
crow.output[2].action = "{to(5,0),to(0,0.25)}"
elseif value == 5 then
crow.ii.pullup(true)
crow.ii.jf.mode(1)
end
end}
params:add{
type = "number",
id = "midi_out_device",
name = "midi out device",
min = 2,
max = 4,
default = 1,
action = function(value)
midi_out_device = midi.connect(value)
end}
params:add{
type = "number",
id = "midi_out_channel",
name = "midi out channel",
min = 1,
max = 16,
default = 1,
action = function(value)
all_notes_off()
midi_out_channel = value
end}
params:add_separator()
params:add{
type = "option",
id = "step_length",
name = "step length",
options = options.STEP_LENGTH_NAMES,
default = 8,
action = function(value)
clk.ticks_per_step = 96 / options.STEP_LENGTH_DIVIDERS[value]
clk.steps_per_beat = options.STEP_LENGTH_DIVIDERS[value] / 4
clk:bpm_change(clk.bpm)
end}
params:add{
type = "option",
id = "note_length",
name = "note length",
options = {"25%", "50%", "75%", "100%"},
default = 4}
params:add{
type = "option",
id = "scale_mode",
name = "scale mode",
options = scale_names,
default = 5,
action = function() build_scale() end}
params:add{
type = "number",
id = "root_note",
name = "root note",
min = 0,
max = 127,
default = 60,
formatter = function(param)
return MusicUtil.note_num_to_name(param:get(), true)
end,
action = function() build_scale() end}
params:add{
type = "number",
id = "probability",
name = "probability",
min = 0,
max = 100,
default = 100,}
params:add_separator()
cs_AMP = controlspec.new(0, 1, "lin", 0, 0.5, "")
params:add{
type = "control",
id = "amp",
controlspec = cs_AMP,
action = function(x) engine.amp(x) end}
cs_PW = controlspec.new(0, 100, "lin", 0, 50, "%")
params:add{
type="control",
id="pw",
controlspec = cs_PW,
action = function(x) engine.pw(x / 100) end}
cs_REL = controlspec.new(0.1, 3.2, "lin", 0, 1.2, "s")
params:add{
type="control",
id="release",
controlspec = cs_REL,
action = function(x) engine.release(x) end}
cs_CUT = controlspec.new(50, 5000, "exp", 0, 800, "hz")
params:add{
type="control",
id="cutoff",
controlspec = cs_CUT,
action = function(x) engine.cutoff(x) end}
cs_GAIN = controlspec.new(0, 4, "lin", 0, 1, "")
params:add{
type="control",
id="gain",
controlspec = cs_GAIN,
action = function(x) engine.gain(x) end}
cs_PAN = controlspec.new(-1, 1, "lin", 0, 0, "")
params:add{
type="control",
id="pan",
controlspec = cs_PAN,
action = function(x) engine.pan(x) end}
crow.input[1].mode("change", 1, 0.05, "rising")
crow.input[1].change = function(s)
morph(one, "one")
morph(two, "two")
end
crow.input[2].mode("change", 1, 0.05, "rising")
crow.input[2].change = random
-- clk:start()
hs.init()
add_pattern_params()
-- params:default()
params:read()
params:bang()
end
function add_pattern_params()
params:add_separator()
params:add{
type = "number",
id = "one_length",
name = "<one> length]",
min = 1,
max = 16,
default = one.length,
action = function(x) one.length = x end}
params:add{
type = "number",
id = "one_start",
name = "<one> start]",
min = 1,
max = 16,
default = one.start,
action = function(x) one.start = x end }
for i = 1,16 do
params:add{
type = "number",
id= ("one_data_"..i),
name = ("<one> data "..i),
min = 0,
max = 8,
default = one.data[i],
action = function(x)one.data[i] = x end }
end
params:add_separator()
params:add{
type = "number",
id = "two_length",
name = "<two> length]",
min = 1,
max = 16,
default = two.length,
action = function(x)two.length = x end}
params:add{
type = "number",
id = "two_start",
name = "<two> start]",
min = 1,
max = 16,
default = two.start,
action = function(x)two.start = x end }
for i = 1,16 do
params:add{
type = "number",
id= "two_data_"..i,
name = "<two> data "..i,
min = 0,
max = 8,
default = two.data[i],
action = function(x) two.data[i] = x end }
end
params:add_separator()
end
-- Utils
function build_scale()
notes = MusicUtil.generate_scale_of_length(params:get("root_note"), params:get("scale_mode"), 16)
local num_to_add = 16 - #notes
for i = 1, num_to_add do
table.insert(notes, notes[16 - num_to_add])
end
end
-- UI
function redraw()
screen.clear()
screen.line_width(1)
screen.aa(0)
-- edit point
if mode == 1 then
screen.move(26 + edit_pos * 6, edit_ch == 1 and 33 or 63)
screen.line_rel(4, 0)
screen.level(15)
if alt then
screen.move(0, 30)
screen.level(1)
screen.text("prob")
screen.move(0, 45)
screen.level(15)
screen.text(params:get("probability"))
end
screen.stroke()
end
-- loop lengths
screen.move(32, 30)
screen.line_rel(one.length * 6 - 2, 0)
screen.move(32, 60)
screen.line_rel(two.length * 6 - 2, 0)
screen.level(mode == 2 and 6 or 1)
screen.stroke()
-- steps
for i = 1,one.length do
screen.move(26 + i * 6, 30 - one.data[i] * 3)
screen.line_rel(4, 0)
screen.level(i == one.pos and 15 or ((edit_ch == 1 and one.data[i] > 0) and 4 or (mode == 2 and 6 or 1)))
screen.stroke()
end
for i = 1,two.length do
screen.move(26 + i * 6, 60 - two.data[i] * 3)
screen.line_rel(4, 0)
screen.level(i == two.pos and 15 or ((edit_ch == 2 and two.data[i] > 0) and 4 or (mode == 2 and 6 or 1)))
screen.stroke()
end
-- txt
--[[ screen.level((not alt and not KEY3) and 15 or 4)
screen.move(0, 10)
screen.text("bpm:"..params:get("bpm"))
screen.level(alt and 15 or 4)
screen.move(0, 20)
screen.text("sc:"..params:get("scale_mode"))
screen.level(KEY3 and 15 or 4)
screen.move(0, 30)
screen.text("rt:"..MusicUtil.note_num_to_name(params:get("root_note"), true))
screen.level(4)
screen.move(0, 60)
if alt then screen.text("cut/rel")
elseif KEY3 then screen.text("loop") end
--]]
screen.level(4)
screen.move(0, 10)
screen.text(mode_names[mode])
if mode == 3 then
screen.level(1)
screen.move(0, 30)
screen.text(snd_names[snd_sel])
screen.level(15)
screen.move(0, 40)
screen.text(params:string(snd_params[snd_sel]))
screen.level(1)
screen.move(0, 50)
screen.text(snd_names[snd_sel + 1])
screen.level(15)
screen.move(0, 60)
screen.text(params:string(snd_params[snd_sel + 1]))
elseif mode == 4 then
screen.level(1)
screen.move(0, 30)
screen.text(alt == false and "bpm" or "div")
screen.level(15)
screen.move(0, 40)
screen.text(alt == false and params:get("bpm") or params:string("step_length"))
screen.level(1)
screen.move(0, 50)
screen.text(alt == false and "root" or "scale")
screen.level(15)
screen.move(0, 60)
screen.text(alt == false and params:string("root_note") or params:string("scale_mode"))
end
screen.update()
end
function gridredraw()
local grid_h = g.rows
g:all(0)
if edit_ch == 1 or grid_h == 16 then
for x = 1,16 do
if one.data[x] > 0 then g:led(x, 9 - one.data[x], 5) end
end
if one.pos > 0 and one.data[one.pos] > 0 then
g:led(one.pos, 9 - one.data[one.pos], 15)
else
g:led(one.pos, 1, 3)
end
end
if edit_ch == 2 or grid_h == 16 then
local y_offset = 0
if grid_h == 16 then y_offset = 8 end
for x = 1,16 do
if two.data[x] > 0 then g:led(x, 9 - two.data[x] + y_offset, 5) end
end
if two.pos > 0 and two.data[two.pos] > 0 then
g:led(two.pos, 9 - two.data[two.pos] + y_offset, 15)
else
g:led(two.pos, 1 + y_offset, 3)
end
end
g:refresh()
end
-- Events
function on_midi_event(data)
clk:process_midi(data)
-- print(clk.bpm)
event = midi.to_msg(data)
-- print("/*------------------*/")
-- tu.print(event)
if event.cc then
-- 0x0x messages for loop value
if event.cc == 1 then
midi_loop_data = (event.val / 8) + 1
if midi_loop_data <= 8 then
set_loop_data("one", one.pos, midi_loop_data)
else
set_loop_data("two", two.pos, midi_loop_data)
end
end
-- 0x1x messages for loop one length
if event.cc == 2 then
params:set("one_length", (event.val / 8) + 1)
end
-- 0x2x messages for loop two length
if event.cc == 3 then
params:set("two_length", (event.val / 8) + 1)
end
-- 0x3x messages for step length
if event.cc == 7 then
params:set("step_length", (event.val / 8) + 1)
end
-- 0x4x messages for root note, followed by noteon event to determine note
if event.cc == 10 then
midi_root_note_change = true
end
-- 0x5x messages for scale mode
if event.cc == 11 then
midi_scale_mode = (event.val / 8) + 1
if midi_scale_mode <= 13 then
params:set("scale_mode", midi_scale_mode)
elseif midi_scale_mode == 14 then
params:set("scale_mode", 18)
elseif midi_scale_mode == 15 then
params:set("scale_mode", 30)
elseif midi_scale_mode == 16 then
params:set("scale_mode", 47)
end
end
end
if event.note then
if midi_root_note_change then
params:set("root_note", event.note + 12)
midi_root_note_change = false
end
end
end
function enc(n, delta)
if n == 1 then
mode = util.clamp(mode + delta, 1, 4)
elseif mode == 1 then --step
if n == 2 then
if alt then
params:delta("probability", delta)
else
local p = (edit_ch == 1) and one.length or two.length
edit_pos = util.clamp(edit_pos + delta, 1, p)
end
elseif n == 3 then
if edit_ch == 1 then
params:delta("one_data_"..edit_pos, delta)
else
params:delta("two_data_"..edit_pos, delta)
end
end
elseif mode == 2 then --loop
if n == 2 then
params:delta("one_length", delta)
elseif n == 3 then
params:delta("two_length", delta)
end
elseif mode == 3 then --sound
if n == 2 then
params:delta(snd_params[snd_sel], delta)
elseif n == 3 then
params:delta(snd_params[snd_sel + 1], delta)
end
elseif mode == 4 then --option
if n == 2 then
if alt == false then
params:delta("bpm", delta)
else
params:delta("step_length", delta)
end
elseif n == 3 then
if alt == false then
params:delta("root_note", delta)
else
params:delta("scale_mode", delta)
end
end
end
redraw()
end
function key(n, z)
if n == 1 then
alt = z == 1
elseif mode == 1 then --step
if n == 2 and z == 1 then
if not alt == true then
-- toggle edit
if edit_ch == 1 then
edit_ch = 2
if edit_pos > two.length then edit_pos = two.length end
else
edit_ch = 1
if edit_pos > one.length then edit_pos = one.length end
end
else
-- clear
for i = 1,one.length do params:set("one_data_"..i, 0) end
for i = 1,two.length do params:set("two_data_"..i, 0) end
end
elseif n == 3 and z == 1 then
if not alt == true then
-- morph
if edit_ch == 1 then morph(one, "one") else morph(two, "two") end
else
-- random
random()
gridredraw()
end
end
elseif mode == 2 then --loop
if n == 2 and z == 1 then
one.pos = 0
two.pos = 0
if alt == true then clk:reset() end
elseif n == 3 and z == 1 then
one.pos = math.floor(math.random() * one.length)
two.pos = math.floor(math.random() * two.length)
end
elseif mode == 3 then --sound
if n == 2 and z == 1 then
snd_sel = util.clamp(snd_sel - 2, 1, NUM_SND_PARAMS - 1)
elseif n == 3 and z == 1 then
snd_sel = util.clamp(snd_sel + 2, 1, NUM_SND_PARAMS - 1)
end
elseif mode == 4 then --option
if n == 2 then
elseif n == 3 then
end
end
--[[
gridredraw()
if not clk.external then
if clk.playing then
clk:stop()
else
clk:start()
end
end
]]--
redraw()
end
function g.key(x, y, z)
local grid_h = g.rows
if z > 0 then
if (grid_h == 8 and edit_ch == 1) or (grid_h == 16 and y <= 8) then
if one.data[x] == 9 - y then
set_loop_data("one", x, 0)
else
set_loop_data("one", x, 9 - y)
end
end
if (grid_h == 8 and edit_ch == 2) or (grid_h == 16 and y > 8) then
if grid_h == 16 then y = y - 8 end
if two.data[x] == 9 - y then
set_loop_data("two", x, 0)
else
set_loop_data("two", x, 9 - y)
end
end
gridredraw()
redraw()
end
end
function cleanup()
params:write()
clk:stop()
end