Last active
October 13, 2018 07:20
-
-
Save j-flee/658f1900444179a47c3f8ef17a931915 to your computer and use it in GitHub Desktop.
earthsea grid and pattern recording + passersby engine demo
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
-- Earth + Passersby | |
-- | |
-- MIDI controlled West Coast | |
-- style mono synth. | |
-- | |
-- ENC1/KEY2 : Change page | |
-- KEY3 : Change tab | |
-- ENC2/3 : Adjust parameters | |
-- | |
-- grid pattern player: | |
-- 1 1 record toggle | |
-- 1 2 play toggle | |
-- 1 4-7 octave shift (-1 to +2) | |
-- 1 8 transpose mode | |
-- | |
-- Responds to MIDI and grid | |
-- | |
-- v1.0.1 Mark Eats | |
-- Earthsea grid pattern added: | |
-- Johnathan F. Lee | |
-- | |
local MusicUtil = require "mark_eats/musicutil" | |
local Graph = require "mark_eats/graph" | |
local EnvGraph = require "mark_eats/envgraph" | |
local Passersby = require "mark_eats/passersby" | |
-- from earthsea | |
local tab = require 'tabutil' | |
local pattern_time = require 'pattern_time' | |
local g = grid.connect() | |
local mode_transpose = 0 | |
local root = { x=5, y=5 } | |
local trans = { x=5, y=5 } | |
local lit = {} | |
local range = 0 | |
local MAX_NUM_VOICES = 4 | |
-- current count of active voices | |
local nvoices = 0 | |
-- from earthsea end | |
local SCREEN_FRAMERATE = 15 | |
local screen_refresh_metro | |
local screen_dirty = true | |
local PAGES = 4 | |
local page_id = 1 | |
local tab_id = 1 | |
local input_indicator_active = false | |
local input_indicator_metro | |
local wave_table = {} | |
local wave = {} | |
local wave_graph | |
local SUB_SAMPLING = 4 | |
local fm_graph | |
local lpg_graph | |
local lpg_status = {} | |
local lpg_status_metro | |
local spring_path = {} | |
local lfo_graph | |
local dice_throw_vel = 0 | |
local dice_throw_progress = 0 | |
local dice_thrown = false | |
local dice_need_update = false | |
local dice = {} | |
local mod_wheel = 0 | |
local wave_shape = {actual = 0, modu = 0, dirty = true} | |
local wave_folds = {actual = 0, modu = 0, dirty = true} | |
local fm1_amount = {actual = 0, modu = 0, dirty = true} | |
local fm2_amount = {actual = 0, modu = 0, dirty = true} | |
local lpg_peak = {actual = 0, modu = 1, dirty = true} | |
local lpg_decay = {actual = 0, modu = 0, dirty = true} | |
local reverb_mix = {actual = 0, modu = 0, dirty = true} | |
local lfo_freq = {dirty = true} | |
local lfo_amount = {dirty = true} | |
local lfo_destinations = {dirty = true} | |
local drift = {actual = 0, dirty = true} | |
engine.name = "Passersby" | |
-- earthsea grid | |
function g.event(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>3 and y<8 then | |
range = 6-y | |
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 = (24+(12*range)) + ((7-e.y)*5) + e.x | |
if e.state > 0 then | |
if nvoices < MAX_NUM_VOICES then | |
--engine.start(id, getHz(x, y-1)) | |
--print("grid > "..id.." "..note) | |
--engine.start(e.id, getHzET(note)) | |
engine.noteOn(e.id, MusicUtil.note_num_to_freq(note), 100) | |
-- start_screen_note(note) | |
lit[e.id] = {} | |
lit[e.id].x = e.x | |
lit[e.id].y = e.y | |
nvoices = nvoices + 1 | |
end | |
else | |
if lit[e.id] ~= nil then | |
-- engine.stop(e.id) | |
-- stop_screen_note(note) | |
lit[e.id] = nil | |
nvoices = nvoices - 1 | |
end | |
end | |
gridredraw() | |
end | |
function grid_note_trans(e) | |
local note = (24+(12*range)) + ((7-e.y+(root.y-trans.y))*5) + e.x + (trans.x-root.x) | |
if e.state > 0 then | |
if nvoices < MAX_NUM_VOICES then | |
--engine.start(id, getHz(x, y-1)) | |
--print("grid > "..id.." "..note) | |
--engine.start(e.id, getHzET(note)) | |
engine.noteOn(e.id, MusicUtil.note_num_to_freq(note), 127) | |
-- 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,3 + pat.rec * 10) | |
g.led(1,2,3 + pat.play * 10) | |
g.led(1,8,3 + mode_transpose * 10) | |
for i=4,7 do | |
g.led(1,i,2) | |
end | |
g.led(1,6,3) | |
g.led(1,6-range,7) | |
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 | |
-- earthsea grid end | |
-- Utilities | |
local function generate_wave_table(cycles, length) | |
local wave_table = {{},{},{},{}} | |
for sx = 1, length do | |
local x = util.linlin(1, length, 0, cycles, sx) | |
local square = math.abs(x * 2 % 2 - 1) - 0.5 | |
square = square > 0 and 0.5 or math.floor(square) * 0.5 | |
table.insert(wave_table[1], math.sin(x * 2 * math.pi)) -- Sine | |
table.insert(wave_table[2], math.abs((x * 2 - 0.5) % 2 - 1) * 2 - 1) -- Tri | |
table.insert(wave_table[3], square) -- Square | |
table.insert(wave_table[4], (1 - (x + 0.25) % 1) * 2 - 1) -- Saw | |
end | |
return wave_table | |
end | |
local function generate_wave(x) | |
x = util.round(x) | |
local index_f = wave_shape.actual * (#wave_table - 1) + 1 | |
local index = util.round(index_f) | |
local delta = index_f - index | |
local index_offset = delta < 0 and -1 or 1 | |
local y | |
-- Wave table lookup | |
if delta == 0 then | |
y = wave_table[index][x] | |
else | |
y = wave_table[index + index_offset][x] * math.abs(delta) + wave_table[index][x] * (1 - math.abs(delta)) | |
end | |
-- Wave folding | |
y = y * (1 + wave_folds.actual) | |
local abs_y = math.abs(y) | |
if abs_y > 1 then | |
local folded = abs_y % 1 | |
if math.floor(abs_y - 1) % 2 == 0 then | |
folded = 1 - folded | |
else | |
folded = folded | |
end | |
folded = folded * 2 - 1 | |
if y < 0 then folded = folded * -1 end | |
y = folded | |
end | |
return y | |
end | |
local function generate_spring_path(width, height, turns) | |
local spring_path = {} | |
for y = 0, height - 1 do | |
local progress = util.linlin(0, height - 1, 0, math.pi * turns * 2, y) | |
table.insert(spring_path, {x = width * 0.5 + math.sin(progress) * width * 0.5, y = y}) | |
end | |
return spring_path | |
end | |
local function generate_lfo_wave(x) | |
x = x * util.linlin(Passersby.specs.LFO_FREQ.minval, Passersby.specs.LFO_FREQ.maxval, 0.5, 10, params:get("lfo_frequency")) | |
return (math.abs((x * 2 - 0.5) % 2 - 1) * 2 - 1) * params:get("lfo_amount") | |
end | |
local function randomize_dice() | |
for i = 1, 2 do | |
local direction = 0 | |
if dice[i] then | |
direction = (dice[i].top_angle - dice[i].table_angle > 0) and 1 or -1 | |
end | |
dice[i] = {} | |
dice[i].face = math.random(6) | |
dice[i].top_angle = 1 + math.random() * 2 | |
if math.random() > 0.5 then dice[i].top_angle = dice[i].top_angle * -1 end | |
dice[i].table_angle = math.random() * 0.5 - 0.25 + (2 * math.pi * direction) | |
end | |
end | |
local function set_dice_throw_vel(vel_delta) | |
dice_throw_vel = util.clamp(dice_throw_vel + vel_delta, -0.35, 0.3) | |
dice_need_update = true | |
end | |
local function start_input_indicator_timeout() | |
input_indicator_active = true | |
screen_dirty = true | |
if input_indicator_metro.is_running then | |
input_indicator_metro:stop() | |
end | |
input_indicator_metro:start(0.25, 1) | |
end | |
local function start_lpg_status_timeout(status_text, x, y) | |
lpg_status.text = status_text | |
lpg_status.x, lpg_status.y = x, y | |
if lpg_status_metro.is_running then | |
lpg_status_metro:stop() | |
end | |
lpg_status_metro:start(1, 1) | |
end | |
local function update_tabs() | |
if tab_id == 1 then | |
wave_graph:set_active(true) | |
lpg_graph:set_active(true) | |
lfo_graph:set_active(true) | |
else | |
wave_graph:set_active(false) | |
lpg_graph:set_active(false) | |
lpg_status.text = "" | |
lfo_graph:set_active(false) | |
end | |
end | |
local function set_page(id) | |
page_id = util.clamp(id, 1, PAGES) | |
tab_id = 1 | |
lpg_status.text = "" | |
update_tabs() | |
screen_dirty = true | |
end | |
local function set_page_delta(delta, loop) | |
local id | |
if loop then | |
id = page_id % PAGES + delta | |
else | |
id = page_id + delta | |
end | |
set_page(id) | |
end | |
local function set_tab(id) | |
tab_id = util.clamp(id, 1, 2) | |
update_tabs() | |
screen_dirty = true | |
end | |
local function set_tab_delta(delta, loop) | |
local id | |
if loop then | |
id = tab_id % 2 + delta | |
else | |
id = tab_id + delta | |
end | |
set_tab(id) | |
end | |
-- Engine functions | |
local function note_on(note_num, vel) | |
engine.noteOn(note_num, MusicUtil.note_num_to_freq(note_num), util.linlin(0, 127, 0, 1, vel)) | |
end | |
local function note_off(note, vel) | |
engine.stop(note) | |
end | |
local function set_pitch_bend(bend_st) | |
engine.pitchBend(MusicUtil.interval_to_ratio(bend_st)) | |
end | |
local function set_mod_wheel(value) | |
mod_wheel = value * 0.5 | |
engine.waveFolds(params:get("wave_folds") + mod_wheel) | |
wave_folds.dirty = true | |
end | |
-- Updaters | |
local function update_wave_shape() | |
wave_shape.actual = util.clamp(params:get("wave_shape") + wave_shape.modu, Passersby.specs.WAVE_SHAPE.minval, Passersby.specs.WAVE_SHAPE.maxval) | |
wave_graph:update_functions() | |
wave_shape.dirty = false | |
screen_dirty = true | |
end | |
local function update_wave_folds() | |
wave_folds.actual = util.clamp(params:get("wave_folds") + mod_wheel + wave_folds.modu, Passersby.specs.WAVE_FOLDS.minval, Passersby.specs.WAVE_FOLDS.maxval) | |
wave_graph:update_functions() | |
wave_folds.dirty = false | |
screen_dirty = true | |
end | |
local function update_fm1_amount() | |
fm1_amount.actual = util.clamp(params:get("fm_low_amount") + fm1_amount.modu, Passersby.specs.FM_LOW_AMOUNT.minval, Passersby.specs.FM_LOW_AMOUNT.maxval) | |
fm1_amount.dirty = false | |
screen_dirty = true | |
end | |
local function update_fm2_amount() | |
fm2_amount.actual = util.clamp(params:get("fm_high_amount") + fm2_amount.modu, Passersby.specs.FM_HIGH_AMOUNT.minval, Passersby.specs.FM_HIGH_AMOUNT.maxval) | |
fm2_amount.dirty = false | |
screen_dirty = true | |
end | |
local function update_lpg_peak() | |
lpg_peak.actual = util.clamp(params:get("lpg_peak") * lpg_peak.modu, Passersby.specs.LPG_PEAK.minval, Passersby.specs.LPG_PEAK.maxval) | |
local norm_peak = util.explin(Passersby.specs.LPG_PEAK.minval * 0.5, Passersby.specs.LPG_PEAK.maxval, 0, 1, lpg_peak.actual) | |
lpg_graph:edit_ar(nil, nil, norm_peak) | |
lpg_peak.dirty = false | |
screen_dirty = true | |
end | |
local function show_lpg_peak_status() | |
local norm_peak = util.explin(Passersby.specs.LPG_PEAK.minval * 0.5, Passersby.specs.LPG_PEAK.maxval, 0, 1, params:get("lpg_peak") * lpg_peak.modu) | |
start_lpg_status_timeout(params:string("lpg_peak"), 57, util.linlin(0, 1, 56, 26, norm_peak)) | |
screen_dirty = true | |
end | |
local function update_lpg_decay() | |
lpg_decay.actual = util.clamp(params:get("lpg_decay") + lpg_decay.modu, Passersby.specs.LPG_DECAY.minval, Passersby.specs.LPG_DECAY.maxval) | |
local norm_decay = util.explin(Passersby.specs.LPG_DECAY.minval * 0.5, Passersby.specs.LPG_DECAY.maxval, 0, 1, lpg_decay.actual) | |
lpg_graph:edit_ar(nil, norm_decay) | |
lpg_decay.dirty = false | |
screen_dirty = true | |
end | |
local function show_lpg_decay_status() | |
local norm_decay = util.explin(Passersby.specs.LPG_DECAY.minval * 0.5, Passersby.specs.LPG_DECAY.maxval, 0, 1, params:get("lpg_decay") + lpg_decay.modu) | |
start_lpg_status_timeout(params:string("lpg_decay"), util.linlin(0, 1, 36, 58, norm_decay), 48) | |
screen_dirty = true | |
end | |
local function update_reverb_mix() | |
reverb_mix.actual = util.clamp(params:get("reverb_mix") + reverb_mix.modu, Passersby.specs.REVERB_MIX.minval, Passersby.specs.REVERB_MIX.maxval) | |
reverb_mix.dirty = false | |
screen_dirty = true | |
end | |
local function update_lfo_freq() | |
lfo_graph:update_functions() | |
lfo_freq.dirty = false | |
screen_dirty = true | |
end | |
local function update_lfo_amount() | |
lfo_graph:update_functions() | |
lfo_amount.dirty = false | |
screen_dirty = true | |
end | |
local function update_lfo_destinations() | |
lfo_destinations.dirty = false | |
screen_dirty = true | |
end | |
local function update_drift() | |
drift.dirty = false | |
screen_dirty = true | |
end | |
-- Init | |
function init() | |
-- from earthsea | |
pat = pattern_time.new() | |
pat.process = grid_note_trans | |
if g then gridredraw() end | |
-- Add params | |
local channels = {"All"} | |
for i = 1, 16 do table.insert(channels, i) end | |
params:add_option("midi_channel", "MIDI Channel", channels) | |
params:add_separator() | |
Passersby.add_params() | |
-- Override param actions | |
params:set_action("wave_shape", function(value) | |
engine.waveShape(value) | |
wave_shape.dirty = true | |
end) | |
params:set_action("wave_folds", function(value) | |
engine.waveFolds(value + mod_wheel) | |
wave_folds.dirty = true | |
end) | |
params:set_action("fm_low_amount", function(value) | |
engine.fm1Amount(value) | |
fm1_amount.dirty = true | |
end) | |
params:set_action("fm_high_amount", function(value) | |
engine.fm2Amount(value) | |
fm2_amount.dirty = true | |
end) | |
params:set_action("lpg_peak", function(value) | |
engine.lpgPeak(value) | |
if page_id == 2 then show_lpg_peak_status() end | |
lpg_peak.dirty = true | |
end) | |
params:set_action("lpg_decay", function(value) | |
engine.lpgDecay(value) | |
if page_id == 2 then show_lpg_decay_status() end | |
lpg_decay.dirty = true | |
end) | |
params:set_action("reverb_mix", function(value) | |
engine.reverbMix(value) | |
reverb_mix.dirty = true | |
end) | |
params:set_action("lfo_frequency", function(value) | |
engine.lfoFreq(value) | |
lfo_freq.dirty = true | |
end) | |
params:set_action("lfo_amount", function(value) | |
engine.lfoAmount(value) | |
lfo_amount.dirty = true | |
end) | |
for i = 1, 2 do | |
params:set_action("lfo_destination_" .. i, function(value) | |
engine.lfoDest(i - 1, value - 1) | |
lfo_destinations.dirty = true | |
end) | |
end | |
params:set_action("drift", function(value) | |
engine.drift(value) | |
drift.dirty = true | |
end) | |
wave_shape.actual = params:get("wave_shape") | |
wave_folds.actual = params:get("wave_folds") | |
fm1_amount.actual = params:get("fm_low_amount") | |
fm2_amount.actual = params:get("fm_high_amount") | |
lpg_peak.actual = params:get("lpg_peak") | |
lpg_decay.actual = params:get("lpg_decay") | |
reverb_mix.actual = params:get("reverb_mix") | |
drift.actual = params:get("drift") | |
input_indicator_metro = metro.alloc() | |
input_indicator_metro.callback = function(stage) | |
input_indicator_active = false | |
screen_dirty = true | |
end | |
-- Init graphs | |
wave_graph = Graph.new(0, 2, "lin", -1, 1, "lin", nil, true, false) | |
wave_graph:set_position_and_size(8, 22, 49, 36) | |
wave_table = generate_wave_table(2, wave_graph:get_width() * SUB_SAMPLING) | |
local wave_func = function(x) | |
return generate_wave(util.linlin(0, 2, 1, wave_graph:get_width() * SUB_SAMPLING, x)) | |
end | |
wave_graph:add_function(wave_func, SUB_SAMPLING) | |
lpg_graph = EnvGraph.new_ar(0, 1, 0, 1, 0.003, util.explin(Passersby.specs.LPG_DECAY.minval * 0.5, Passersby.specs.LPG_DECAY.maxval, 0, 1, lpg_decay.actual), util.explin(Passersby.specs.LPG_PEAK.minval * 0.5, Passersby.specs.LPG_PEAK.maxval, 0, 1, lpg_peak.actual), -4) | |
lpg_graph:set_position_and_size(8, 22, 49, 36) | |
lpg_graph:set_show_x_axis(true) | |
lfo_graph = Graph.new(0, 1, "lin", -1, 1, "lin", nil, true, false) | |
lfo_graph:set_position_and_size(8, 18, 49, 36) | |
lfo_graph:add_function(generate_lfo_wave, SUB_SAMPLING) | |
lpg_status.text = "" | |
lpg_status.x, lpg_status.y = 0, 0 | |
lpg_status_metro = metro.alloc() | |
lpg_status_metro.callback = function(stage) | |
lpg_status.text = "" | |
screen_dirty = true | |
end | |
spring_path = generate_spring_path(10, 36, 5) | |
randomize_dice() | |
-- Init polls | |
local wave_shape_poll = poll.set("waveShapeMod", function(value) | |
if wave_shape.modu ~= value then | |
wave_shape.modu = value | |
wave_shape.dirty = true | |
end | |
end) | |
wave_shape_poll:start() | |
local wave_folds_poll = poll.set("waveFoldsMod", function(value) | |
if wave_folds.modu ~= value then | |
wave_folds.modu = value | |
wave_folds.dirty = true | |
end | |
end) | |
wave_folds_poll:start() | |
local fm1_amount_poll = poll.set("fm1AmountMod", function(value) | |
if fm1_amount.modu ~= value then | |
fm1_amount.modu = value | |
fm1_amount.dirty = true | |
end | |
end) | |
fm1_amount_poll:start() | |
local fm2_amount_poll = poll.set("fm2AmountMod", function(value) | |
if fm2_amount.modu ~= value then | |
fm2_amount.modu = value | |
fm2_amount.dirty = true | |
end | |
end) | |
fm2_amount_poll:start() | |
local lpg_peak_poll = poll.set("lpgPeakMulMod", function(value) | |
if lpg_peak.modu ~= value then | |
lpg_peak.modu = value | |
lpg_peak.dirty = true | |
end | |
end) | |
lpg_peak_poll:start() | |
local lpg_decay_poll = poll.set("lpgDecayMod", function(value) | |
if lpg_decay.modu ~= value then | |
lpg_decay.modu = value | |
lpg_decay.dirty = true | |
end | |
end) | |
lpg_decay_poll:start() | |
local reverb_mix_poll = poll.set("reverbMixMod", function(value) | |
if reverb_mix.modu ~= value then | |
reverb_mix.modu = value | |
reverb_mix.dirty = true | |
end | |
end) | |
reverb_mix_poll:start() | |
-- Start drawing to screen | |
screen_refresh_metro = metro.alloc() | |
screen_refresh_metro.callback = function(stage) | |
update() | |
if screen_dirty then | |
screen_dirty = false | |
redraw() | |
end | |
end | |
screen_refresh_metro:start(1 / SCREEN_FRAMERATE) | |
end | |
-- Input functions | |
-- Encoder input | |
function enc(n, delta) | |
if n == 1 then | |
-- Page scroll | |
set_page_delta(util.clamp(delta, -1, 1), false) | |
end | |
if page_id == 1 then | |
if tab_id == 1 then | |
-- Wave | |
if n == 2 then | |
params:delta("wave_shape", delta) | |
elseif n == 3 then | |
params:delta("wave_folds", delta) | |
end | |
else | |
-- FM | |
if n == 2 then | |
params:delta("fm_low_amount", delta) | |
elseif n == 3 then | |
params:delta("fm_high_amount", delta) | |
end | |
end | |
elseif page_id == 2 then | |
if tab_id == 1 then | |
-- LPG | |
if n == 2 then | |
params:delta("lpg_peak", delta) | |
elseif n == 3 then | |
params:delta("lpg_decay", delta) | |
end | |
else | |
-- Reverb | |
if n == 2 then | |
params:delta("reverb_mix", delta) | |
end | |
end | |
elseif page_id == 3 then | |
if tab_id == 1 then | |
-- LFO | |
if n == 2 then | |
params:delta("lfo_frequency", delta) | |
elseif n == 3 then | |
params:delta("lfo_amount", delta) | |
end | |
else | |
-- Destinations | |
if n == 2 then | |
params:delta("lfo_destination_" .. 1, util.clamp(delta, -1, 1)) | |
elseif n == 3 then | |
params:delta("lfo_destination_" .. 2, util.clamp(delta, -1, 1)) | |
end | |
end | |
elseif page_id == 4 then | |
-- Randomize | |
if n == 2 then | |
if not dice_thrown then set_dice_throw_vel(delta * 0.05) end | |
-- Drift | |
elseif n == 3 then | |
params:delta("drift", delta) | |
end | |
end | |
end | |
-- Key input | |
function key(n, z) | |
if z == 1 then | |
if n == 2 then | |
set_page_delta(1, true) | |
elseif n == 3 then | |
set_tab_delta(1, true) | |
end | |
end | |
end | |
-- MIDI input | |
local function midi_event(data) | |
if #data == 0 then return end | |
local midi_status = data[1] | |
local data1 = data[2] | |
local data2 = data[3] | |
-- Note on | |
if (params:get("midi_channel") == 1 and midi_status >= 144 and midi_status <= 159 ) or (params:get("midi_channel") > 1 and midi_status == 144 + params:get("midi_channel") - 2) then | |
note_on(data1, data2) | |
start_input_indicator_timeout() | |
-- Note off | |
-- elseif (params:get("MIDI Channel") == 1 and midi_status >= 128 and midi_status <= 143 ) or (params:get("MIDI Channel") > 1 and midi_status == 128 + params:get("MIDI Channel") - 2) then | |
-- CC | |
elseif (params:get("midi_channel") == 1 and midi_status >= 176 and midi_status <= 191 ) or (params:get("midi_channel") > 1 and midi_status == 176 + params:get("midi_channel") - 2) then | |
-- Mod wheel | |
if data1 == 1 then | |
set_mod_wheel(util.linlin(0, 127, 0, 1, data2)) | |
end | |
-- Pitch bend | |
elseif (params:get("midi_channel") == 1 and midi_status >= 224 and midi_status <= 239 ) or (params:get("midi_channel") > 1 and midi_status == 224 + params:get("midi_channel") - 2) then | |
local bend_14bit = bit32.bor(bit32.lshift(data2, 7), data1) | |
local bend_st = (util.round(bend_14bit / 2)) / 8192 * 2 -1 -- Convert to -1 to 1 | |
set_pitch_bend(bend_st * 2) -- 2 Semitones of bend | |
end | |
end | |
midi.add = function(dev) | |
dev.event = midi_event | |
end | |
function cleanup() | |
-- from earthsea | |
pat:stop() | |
pat = nil | |
-- from earthsea end | |
for id, dev in pairs(midi.devices) do | |
dev.event = nil | |
end | |
end | |
-- Draw functions | |
local function rotate(x, y, center_x, center_y, angle_rads) | |
local sin_a = math.sin(angle_rads) | |
local cos_a = math.cos(angle_rads) | |
x = x - center_x | |
y = y - center_y | |
return (x * cos_a - y * sin_a) + center_x, (x * sin_a + y * cos_a) + center_y | |
end | |
local function draw_page_dots(index, total_pages) | |
local dots_y = util.round((64 - total_pages * 4 - (total_pages - 1) * 2) * 0.5) | |
for i = 1, total_pages do | |
if i == index then screen.level(5) | |
else screen.level(1) end | |
screen.rect(127, dots_y, 1, 4) | |
screen.fill() | |
dots_y = dots_y + 6 | |
end | |
end | |
local function draw_input_indicator() | |
screen.level(4) | |
screen.move(0, 1) | |
screen.line(5, 1) | |
screen.line(2.5, 4) | |
screen.close() | |
screen.fill() | |
end | |
local function draw_background_rects() | |
screen.level(1) | |
screen.rect(8, 18, 49, 44) | |
screen.fill() | |
screen.rect(71, 18, 49, 44) | |
screen.fill() | |
end | |
local function draw_tabs(titles_array, active) | |
local margin = 8 | |
local gutter = 14 | |
local col_width = (128 - (margin * 2) - gutter * (#titles_array - 1)) / #titles_array | |
for i = 1, #titles_array do | |
if i == active then screen.level(15) | |
else screen.level(3) end | |
screen.move(margin + col_width * 0.5 + ((col_width + gutter) * (i - 1)), 6) | |
screen.text_center(titles_array[i]) | |
end | |
end | |
local function draw_dial(value, x, y, size, active) | |
local radius = size * 0.5 | |
local start_angle = math.pi * 0.7 | |
local end_angle = math.pi * 2.3 | |
screen.level(5) | |
screen.arc(x + radius, y + radius, radius - 0.5, util.linlin(0, 1, start_angle, end_angle, value), end_angle) | |
screen.stroke() | |
screen.level(15) | |
screen.line_width(2.5) | |
screen.arc(x + radius, y + radius, radius - 0.5, start_angle, util.linlin(0, 1, start_angle, end_angle, value)) | |
screen.stroke() | |
screen.line_width(1) | |
if active then screen.level(15) else screen.level(3) end | |
screen.move(x + radius, y + size + 6) | |
screen.text_center(util.round(value * 100, 1)) | |
screen.fill() | |
end | |
local function draw_slider(value, x, y, width, height, active) | |
screen.level(3) | |
screen.rect(x + 0.5, y + 0.5, width - 1, height - 1) | |
screen.stroke() | |
local filled_height = util.round(util.linlin(0, 1, 0, height, value)) | |
screen.rect(x, y + height - filled_height, width, filled_height) | |
if active then screen.level(15) else screen.level(5) end | |
screen.fill() | |
end | |
local function draw_spring(x, y, active) | |
screen.move(x + spring_path[1].x + 0.5, y + spring_path[1].y + 0.5) | |
for i = 2, #spring_path do | |
screen.line(x + spring_path[i].x + 0.5, y + spring_path[i].y + 0.5) | |
end | |
if active then | |
screen.level(15) | |
screen.line_width(0.7) | |
else | |
screen.level(5) | |
end | |
screen.stroke() | |
screen.line_width(1) | |
end | |
local function draw_die(x, y, rotation_rads, face) | |
screen.level(15) | |
local size = 9 | |
screen.move(rotate(x - size + 0.5, y - size + 0.5, x, y, rotation_rads)) | |
screen.line(rotate(x + size - 0.5, y - size + 0.5, x, y, rotation_rads)) | |
screen.line(rotate(x + size - 0.5, y + size - 0.5, x, y, rotation_rads)) | |
screen.line(rotate(x - size + 0.5, y + size - 0.5, x, y, rotation_rads)) | |
screen.close() | |
screen.stroke() | |
local dot_size = 1 | |
local dx, dy | |
if face == 1 then | |
dot_size = 1.5 | |
dx, dy = rotate(x, y, x, y, rotation_rads) | |
screen.circle(dx, dy, dot_size) | |
screen.fill() | |
elseif face == 2 then | |
dx, dy = rotate(x + 3, y - 3, x, y, rotation_rads) | |
screen.circle(dx, dy, dot_size) | |
screen.fill() | |
dx, dy = rotate(x - 3, y + 3, x, y, rotation_rads) | |
screen.circle(dx, dy, dot_size) | |
screen.fill() | |
elseif face == 3 then | |
dx, dy = rotate(x + 4, y - 4, x, y, rotation_rads) | |
screen.circle(dx, dy, dot_size) | |
screen.fill() | |
dx, dy = rotate(x, y, x, y, rotation_rads) | |
screen.circle(dx, dy, dot_size) | |
screen.fill() | |
dx, dy = rotate(x - 4, y + 4, x, y, rotation_rads) | |
screen.circle(dx, dy, dot_size) | |
screen.fill() | |
elseif face == 4 then | |
dx, dy = rotate(x - 3, y - 3, x, y, rotation_rads) | |
screen.circle(dx, dy, dot_size) | |
screen.fill() | |
dx, dy = rotate(x + 3, y - 3, x, y, rotation_rads) | |
screen.circle(dx, dy, dot_size) | |
screen.fill() | |
dx, dy = rotate(x + 3, y + 3, x, y, rotation_rads) | |
screen.circle(dx, dy, dot_size) | |
screen.fill() | |
dx, dy = rotate(x - 3, y + 3, x, y, rotation_rads) | |
screen.circle(dx, dy, dot_size) | |
screen.fill() | |
elseif face == 5 then | |
dx, dy = rotate(x, y, x, y, rotation_rads) | |
screen.circle(dx, dy, dot_size) | |
screen.fill() | |
dx, dy = rotate(x - 4, y - 4, x, y, rotation_rads) | |
screen.circle(dx, dy, dot_size) | |
screen.fill() | |
dx, dy = rotate(x + 4, y - 4, x, y, rotation_rads) | |
screen.circle(dx, dy, dot_size) | |
screen.fill() | |
dx, dy = rotate(x + 4, y + 4, x, y, rotation_rads) | |
screen.circle(dx, dy, dot_size) | |
screen.fill() | |
dx, dy = rotate(x - 4, y + 4, x, y, rotation_rads) | |
screen.circle(dx, dy, dot_size) | |
screen.fill() | |
elseif face == 6 then | |
dx, dy = rotate(x - 3, y - 4, x, y, rotation_rads) | |
screen.circle(dx, dy, dot_size) | |
screen.fill() | |
dx, dy = rotate(x + 3, y - 4, x, y, rotation_rads) | |
screen.circle(dx, dy, dot_size) | |
screen.fill() | |
dx, dy = rotate(x - 3, y, x, y, rotation_rads) | |
screen.circle(dx, dy, dot_size) | |
screen.fill() | |
dx, dy = rotate(x + 3, y, x, y, rotation_rads) | |
screen.circle(dx, dy, dot_size) | |
screen.fill() | |
dx, dy = rotate(x - 3, y + 4, x, y, rotation_rads) | |
screen.circle(dx, dy, dot_size) | |
screen.fill() | |
dx, dy = rotate(x + 3, y + 4, x, y, rotation_rads) | |
screen.circle(dx, dy, dot_size) | |
screen.fill() | |
end | |
end | |
local function draw_dice() | |
draw_die(20, util.linlin(0, 1, 32, 17, dice_throw_progress), util.linlin(0, 1, dice[1].table_angle, dice[1].top_angle, dice_throw_progress), dice[1].face) | |
draw_die(45, util.linlin(0, 1, 46, 61, dice_throw_progress), util.linlin(0, 1, dice[2].table_angle, dice[2].top_angle, dice_throw_progress), dice[2].face) | |
end | |
local function update_dice() | |
if not dice_need_update then return end | |
if dice_thrown then | |
if dice_throw_progress < 0.05 then | |
for i = 1, 2 do | |
local direction = (dice[i].table_angle > 0) and 1 or -1 | |
dice[i].table_angle = dice[i].table_angle - (2 * math.pi * direction) | |
end | |
dice_thrown = false | |
end | |
set_dice_throw_vel(-0.08) | |
else | |
if dice_throw_progress > 0.9 then | |
dice_thrown = true | |
randomize_dice() | |
Passersby.randomize_params() | |
set_dice_throw_vel(-0.3) | |
else | |
set_dice_throw_vel(util.linlin(0, 1, -0.02, -0.03, dice_throw_progress)) | |
end | |
end | |
if dice_throw_progress <= 0 then | |
dice_throw_vel = math.max(dice_throw_vel, 0) | |
end | |
dice_throw_progress = util.clamp(dice_throw_progress + dice_throw_vel, 0, 1) | |
if page_id == 4 then screen_dirty = true end | |
if dice_throw_progress == 0 and not dice_thrown then dice_need_update = false end | |
end | |
function update() | |
if page_id == 1 then | |
if wave_shape.dirty then update_wave_shape() end | |
if wave_folds.dirty then update_wave_folds() end | |
if fm1_amount.dirty then update_fm1_amount() end | |
if fm2_amount.dirty then update_fm2_amount() end | |
elseif page_id == 2 then | |
if lpg_peak.dirty then update_lpg_peak() end | |
if lpg_decay.dirty then update_lpg_decay() end | |
if reverb_mix.dirty then update_reverb_mix() end | |
elseif page_id == 3 then | |
if lfo_freq.dirty then update_lfo_freq() end | |
if lfo_amount.dirty then update_lfo_amount() end | |
if lfo_destinations.dirty then update_lfo_destinations() end | |
elseif page_id == 4 then | |
if drift.dirty then update_drift() end | |
end | |
update_dice() | |
end | |
function redraw() | |
screen.clear() | |
screen.aa(1) | |
draw_page_dots(page_id, PAGES) | |
if input_indicator_active then draw_input_indicator() end | |
-- draw_background_rects() | |
if page_id == 1 then | |
draw_tabs({"Wave", "FM"}, tab_id) | |
-- Wave | |
wave_graph:redraw() | |
-- FM | |
screen.level(3) | |
screen.move(83, 33) | |
screen.text_center("L") | |
screen.move(108, 48) | |
screen.text_center("H") | |
screen.fill() -- Prevents extra line | |
draw_dial(fm1_amount.actual, 72, 19, 22, tab_id == 2) | |
draw_dial(fm2_amount.actual, 97, 34, 22, tab_id == 2) | |
elseif page_id == 2 then | |
draw_tabs({"LPG", "Reverb"}, tab_id) | |
-- LPG | |
lpg_graph:redraw() | |
screen.level(3) | |
screen.move(lpg_status.x, lpg_status.y) | |
screen.text_right(lpg_status.text) | |
-- Reverb | |
screen.fill() | |
draw_spring(82, 22, tab_id == 2) | |
screen.rect(100, 40, 7, 1) | |
screen.level(3) | |
screen.fill() | |
draw_slider(reverb_mix.actual, 102, 22, 3, 36, true) | |
elseif page_id == 3 then | |
draw_tabs({"LFO", "Targets"}, tab_id) | |
-- LFO | |
lfo_graph:redraw() | |
screen.level(3) | |
screen.move(8, 62) | |
screen.text(params:string("lfo_frequency")) | |
-- LFO Targets | |
if tab_id == 2 then screen.level(15) else screen.level(3) end | |
screen.move(71, 33) | |
screen.text(Passersby.LFO_DESTINATIONS[params:get("lfo_destination_1")]) | |
screen.move(71, 49) | |
screen.text(Passersby.LFO_DESTINATIONS[params:get("lfo_destination_2")]) | |
elseif page_id == 4 then | |
draw_tabs({"Fate"}, 1) | |
-- Dice | |
draw_dice() | |
-- Drift | |
screen.move(96, 23) | |
screen.text_center("Drift") | |
screen.fill() | |
draw_dial(params:get("drift"), 85, 28, 22, true) | |
end | |
screen.update() | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment