Skip to content

Instantly share code, notes, and snippets.

@cfdrake
Created January 28, 2021 16:49
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cfdrake/acac1c1c5e67110dfe18b091466f4390 to your computer and use it in GitHub Desktop.
Save cfdrake/acac1c1c5e67110dfe18b091466f4390 to your computer and use it in GitHub Desktop.
glut-64
-- glut64
--
-- granular sampler in progress
-- (currently requires a grid)
--
-- trigger voices
-- using grid rows 2-8
--
-- mute voices and record
-- patterns using grid row 1
--
-- 64 edition by @cfd90
engine.name = 'Glut'
local g = grid.connect()
local VOICES = 6
local positions = {}
local gates = {}
local voice_levels = {}
for i=1, VOICES do
positions[i] = -1
gates[i] = 0
voice_levels[i] = 0
end
local gridbuf = require 'lib/gridbuf'
local grid_ctl = gridbuf.new(8, 8)
local grid_voc = gridbuf.new(8, 8)
local metro_grid_refresh
local metro_blink
--[[
recorder
]]
local pattern_banks = {}
local pattern_timers = {}
local pattern_leds = {} -- for displaying button presses
local pattern_positions = {} -- playback positions
local record_bank = -1
local record_prevtime = -1
local record_length = -1
local alt = false
local blink = 0
local metro_blink
local function record_event(x, y, z)
if record_bank > 0 then
-- record first event tick
local current_time = util.time()
if record_prevtime < 0 then
record_prevtime = current_time
end
local time_delta = current_time - record_prevtime
table.insert(pattern_banks[record_bank], {time_delta, x, y, z})
record_prevtime = current_time
end
end
local function start_playback(n)
pattern_timers[n]:start(0.001, 1) -- TODO: timer doesn't start immediately with zero
end
local function stop_playback(n)
pattern_timers[n]:stop()
pattern_positions[n] = 1
end
local function arm_recording(n)
record_bank = n
end
local function stop_recording()
local recorded_events = #pattern_banks[record_bank]
if recorded_events > 0 then
-- save last delta to first event
local current_time = util.time()
local final_delta = current_time - record_prevtime
pattern_banks[record_bank][1][1] = final_delta
start_playback(record_bank)
end
record_bank = -1
record_prevtime = -1
end
local function pattern_next(n)
local bank = pattern_banks[n]
local pos = pattern_positions[n]
local event = bank[pos]
local delta, x, y, z = table.unpack(event)
pattern_leds[n] = z
grid_key(x, y, z, true)
local next_pos = pos + 1
if next_pos > #bank then
next_pos = 1
end
local next_event = bank[next_pos]
local next_delta = next_event[1]
pattern_positions[n] = next_pos
-- schedule next event
pattern_timers[n]:start(next_delta, 1)
end
local function record_handler(n)
if alt then
-- clear pattern
if n == record_bank then stop_recording() end
if pattern_timers[n].is_running then stop_playback(n) end
pattern_banks[n] = {}
do return end
end
if n == record_bank then
-- stop if pressed current recording
stop_recording()
else
local pattern = pattern_banks[n]
if #pattern > 0 then
-- toggle playback if there's data
if pattern_timers[n].is_running then stop_playback(n) else start_playback(n) end
else
-- stop recording if it's happening
if record_bank > 0 then
stop_recording()
end
-- arm new pattern for recording
arm_recording(n)
end
end
end
--[[
internals
]]
local function display_voice(phase, width)
local pos = phase * width
local levels = {}
for i = 1, width do levels[i] = 0 end
local left = math.floor(pos)
local index_left = left + 1
local dist_left = math.abs(pos - left)
local right = math.floor(pos + 1)
local index_right = right + 1
local dist_right = math.abs(pos - right)
if index_left < 1 then index_left = width end
if index_left > width then index_left = 1 end
if index_right < 1 then index_right = width end
if index_right > width then index_right = 1 end
levels[index_left] = math.floor(math.abs(1 - dist_left) * 15)
levels[index_right] = math.floor(math.abs(1 - dist_right) * 15)
return levels
end
local function start_voice(voice, pos)
engine.seek(voice, pos)
engine.gate(voice, 1)
gates[voice] = 1
end
local function stop_voice(voice)
gates[voice] = 0
engine.gate(voice, 0)
end
local function grid_refresh()
if g == nil then
return
end
grid_ctl:led_level_all(0)
grid_voc:led_level_all(0)
-- alt
grid_ctl:led_level_set(8, 1, alt and 15 or 1)
-- pattern banks
for i=1, VOICES do
local level = 2
if #pattern_banks[i] > 0 then level = 5 end
if pattern_timers[i].is_running then
level = 15
if pattern_leds[i] > 0 then
level = 1
end
end
grid_ctl:led_level_set(i, 2, level)
end
-- blink armed pattern
if record_bank > 0 then
grid_ctl:led_level_set(7, 1, 15 * blink)
end
-- voices
for i=1, VOICES do
if voice_levels[i] > 0 then
grid_ctl:led_level_set(i, 1, math.min(math.ceil(voice_levels[i] * 15), 15))
grid_voc:led_level_row(1, i + 2, display_voice(positions[i], 8))
end
end
local buf = grid_ctl | grid_voc
buf:render(g)
g:refresh()
end
function grid_key(x, y, z, skip_record)
if y > 2 or (y == 1 and x < 9) then
if not skip_record then
record_event(x, y, z)
end
end
if z > 0 then
-- set voice pos
if y > 2 then
local voice = y - 2
start_voice(voice, (x - 1) / 8)
else
if y == 1 then
if x == 8 then
-- alt
alt = true
elseif x <= 6 then
-- stop
local voice = x
stop_voice(voice)
end
else
if x <= 6 then
-- pattern recorder
record_handler(x)
end
end
end
else
-- alt
if x == 8 and y == 1 then alt = false end
end
end
function init()
g.key = function(x, y, z)
grid_key(x, y, z)
end
-- polls
for v = 1, VOICES do
local phase_poll = poll.set('phase_' .. v, function(pos) positions[v] = pos end)
phase_poll.time = 0.05
phase_poll:start()
local level_poll = poll.set('level_' .. v, function(lvl) voice_levels[v] = lvl end)
level_poll.time = 0.05
level_poll:start()
end
-- recorders
for v = 1, VOICES do
table.insert(pattern_timers, metro.init(function(tick) pattern_next(v) end))
table.insert(pattern_banks, {})
table.insert(pattern_leds, 0)
table.insert(pattern_positions, 1)
end
-- grid refresh timer, 40 fps
metro_grid_refresh = metro.init(function(stage) grid_refresh() end, 1 / 40)
metro_grid_refresh:start()
metro_blink = metro.init(function(stage) blink = blink ~ 1 end, 1 / 4)
metro_blink:start()
local sep = ": "
params:add_taper("reverb_mix", "*"..sep.."mix", 0, 100, 50, 0, "%")
params:set_action("reverb_mix", function(value) engine.reverb_mix(value / 100) end)
params:add_taper("reverb_room", "*"..sep.."room", 0, 100, 50, 0, "%")
params:set_action("reverb_room", function(value) engine.reverb_room(value / 100) end)
params:add_taper("reverb_damp", "*"..sep.."damp", 0, 100, 50, 0, "%")
params:set_action("reverb_damp", function(value) engine.reverb_damp(value / 100) end)
for v = 1, VOICES do
params:add_separator()
params:add_file(v.."sample", v..sep.."sample")
params:set_action(v.."sample", function(file) engine.read(v, file) end)
params:add_taper(v.."volume", v..sep.."volume", -60, 20, 0, 0, "dB")
params:set_action(v.."volume", function(value) engine.volume(v, math.pow(10, value / 20)) end)
params:add_taper(v.."speed", v..sep.."speed", -200, 200, 100, 0, "%")
params:set_action(v.."speed", function(value) engine.speed(v, value / 100) end)
params:add_taper(v.."jitter", v..sep.."jitter", 0, 500, 0, 5, "ms")
params:set_action(v.."jitter", function(value) engine.jitter(v, value / 1000) end)
params:add_taper(v.."size", v..sep.."size", 1, 500, 100, 5, "ms")
params:set_action(v.."size", function(value) engine.size(v, value / 1000) end)
params:add_taper(v.."density", v..sep.."density", 0, 512, 20, 6, "hz")
params:set_action(v.."density", function(value) engine.density(v, value) end)
params:add_taper(v.."pitch", v..sep.."pitch", -24, 24, 0, 0, "st")
params:set_action(v.."pitch", function(value) engine.pitch(v, math.pow(0.5, -value / 12)) end)
params:add_taper(v.."spread", v..sep.."spread", 0, 100, 0, 0, "%")
params:set_action(v.."spread", function(value) engine.spread(v, value / 100) end)
params:add_taper(v.."fade", v..sep.."att / dec", 1, 9000, 1000, 3, "ms")
params:set_action(v.."fade", function(value) engine.envscale(v, value / 1000) end)
end
params:bang()
end
--[[
exports
]]
function enc(n, d)
end
function key(n, z)
end
function redraw()
-- do return end
screen.clear()
screen.level(15)
screen.move(0, 10)
screen.text("^ load samples")
screen.move(0, 20)
screen.text(" via menu > parameters")
screen.update()
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment