Skip to content
Permalink
main
Go to file
 
 
Cannot retrieve contributors at this time
432 lines (380 sloc) 9.67 KB
-- k1: exit e1: --
--
--
-- e2: bpm e3: level
--
-- k2: play k3: reroll
configuration = {
start_playing_on_boot = true, -- start playing as soon as you launch the script?
default_bpm = 120, -- change with e2 while running
default_level = 100, -- how loud is the whole thing
sample_directory = "/home/we/dust/audio/common/808", -- change to whatever directory you want
track_1_on = true, -- toggle the sample on and off
track_1_level = 100, -- max level of this sample
track_1_density = 50, -- percentage representing how dense the notes are
track_1_period = 1/4, -- periods can be anything (i think)
track_1_length = 16, -- how many periods before looping back to 1?
track_1_sample = "808-BD.wav", -- specifcy a sample name
track_1_pattern = "x---x---x---x---", -- draw a pattern with "x" and "-"
track_2_on = "random", -- ...or spin the wheel with "randoms"
track_2_level = "random",
track_2_density = "random",
track_2_period = "random",
track_2_length = "random",
track_2_sample = "random",
track_2_pattern = "random",
track_3_on = true,
track_3_level = 100,
track_3_density = 50,
track_3_period = 1/4,
track_3_length = 8,
track_3_sample = "random",
track_3_pattern = "random",
track_4_on = true,
track_4_level = 50,
track_4_density = 50,
track_4_period = 1/8,
track_4_length = 16,
track_4_sample = "random",
track_4_pattern = "random",
track_5_on = true,
track_5_level = 50,
track_5_density = 50,
track_5_period = 1,
track_5_length = "random",
track_5_sample = "random",
track_5_pattern = "random",
track_6_on = true,
track_6_level = 25,
track_6_density = 100,
track_6_period = 1/8,
track_6_length = 4,
track_6_sample = "random",
track_6_pattern = "random",
}
function init()
-- draw
screen.aa(0)
is_screen_dirty = true
bakeneko_frame = 4
-- music
is_playing = configuration.start_playing_on_boot
bpm = configuration.default_bpm
level = configuration.default_level
numerator = 0
denominator = 4
transport = 0
tracks = {}
-- time
softclock.init()
softclock_loop_id = clock.run(softclock.super_tick)
draw_loop_id = clock.run(draw_loop)
-- go
reroll()
end
function key(k, z)
if z == 0 then return
elseif k == 2 then toggle()
elseif k == 3 then reroll() end
update_screen()
end
function enc(e, d)
if e == 1 then -- nothing
elseif e == 2 then update_bpm(d)
elseif e == 3 then update_level(d)
end
update_screen()
end
function toggle()
is_playing = not is_playing
end
function update_level(d)
level = util.clamp(level + d, 0, 100)
end
function update_bpm(d)
bpm = util.clamp((bpm + d), 20, 300)
end
function check_bpm()
if bpm ~= params:get("clock_tempo") then
params:set("clock_tempo", bpm)
end
end
function update_screen()
is_screen_dirty = true
end
function cleanup()
clock.cancel(softclock_loop_id)
clock.cancel(draw_loop_id)
end
-- sample stuff
function reroll()
setup_sampler()
for i = 1, 6 do
tracks[i] = make_track(i)
softcut.buffer_clear_region_channel(i, 0, -1)
softcut.buffer_read_mono(tracks[i].sample, 0, 0, -1, 1, i)
softclock:add(i, tracks[i].period, function(phase) event(i, phase) end)
end
end
function event(i, phase)
local track = tracks[i]
if not track.on then return end
track.current_step = wrap(track.current_step + 1, 1, track.length)
if track.pattern[track.current_step] then
play_sample(i)
end
update_screen()
end
function make_track(i)
local this_track = {}
this_track["on"] = get_on(configuration["track_" .. i .. "_on"])
this_track["sample"] = get_track_sample(configuration["track_" .. i .. "_sample"])
this_track["period"] = get_period(configuration["track_" .. i .. "_period"])
this_track["level"] = get_level(configuration["track_" .. i .. "_level"])
this_track["length"] = get_length(configuration["track_" .. i .. "_length"])
this_track["density"] = get_density(configuration["track_" .. i .. "_density"])
this_track["pattern"] = get_pattern(configuration["track_" .. i .. "_pattern"], this_track.density, this_track.length)
this_track["current_step"] = 0
return this_track
end
function get_on(on)
if on == "random" then
return math.random(0, 1) == 1
else
return on
end
end
function get_track_sample(sample)
if sample == "random" then
return get_random_sample()
else
return configuration.sample_directory .. "/" .. sample
end
end
function get_random_sample()
local i, t, popen = 0, {}, io.popen
local pfile = popen('ls -a "' .. configuration.sample_directory .. '"')
for filename in pfile:lines() do
if filename ~= "." and filename ~= ".." then
i = i + 1
t[i] = filename
end
end
pfile:close()
return configuration.sample_directory .. "/" .. t[math.random(1, #t)]
end
function get_period(period)
if period == "random" then
return math.random(1, 16) / 16
else
return period
end
end
function get_level(level)
if level == "random" then
return math.random(0, 100)
else
return level
end
end
function get_length(length)
if length == "random" then
return math.random(1, 16)
else
return length
end
end
function get_density(density)
if density == "random" then
return math.random(0, 100)
else
return density
end
end
function get_pattern(pattern, density, length)
local p = {}
if pattern == "random" then
for i = 1, length do
p[i] = math.random(0, 100) < density
end
else
for i = 1, #pattern do
p[i] = pattern:sub(i, i) ~= '-'
end
end
return p
end
function setup_sampler()
softcut.reset()
softcut.buffer_clear()
audio.level_cut(1)
audio.level_adc_cut(1)
audio.level_eng_cut(1)
for i = 1, 6 do
softcut.level(i, 1)
softcut.level_input_cut(1, i, 1.0)
softcut.level_input_cut(2, i, 1.0)
softcut.pan(i, 0)
softcut.play(i, 0)
softcut.rate(i, 1)
softcut.loop_start(i, 0)
softcut.loop_end(i, 36)
softcut.loop(i, 0)
softcut.rec(i, 0)
softcut.fade_time(i, 0.02)
softcut.level_slew_time(i, 0.01)
softcut.rate_slew_time(i, 0.01)
softcut.rec_level(i, 1)
softcut.pre_level(i, 1)
softcut.position(i, 0)
softcut.buffer(i, 1)
softcut.enable(i, 1)
softcut.filter_dry(i, 1)
softcut.filter_fc(i, 0)
softcut.filter_lp(i, 0)
softcut.filter_bp(i, 0)
softcut.filter_rq(i, 0)
end
end
function play_sample(i)
softcut.buffer(i, 2)
softcut.play(i, 0)
softcut.level(i, (level / 100) * (tracks[i].level / 100))
softcut.position(i, 0)
softcut.loop_start(i, 0)
softcut.loop_end(i, 16)
softcut.loop(i, 0)
softcut.play(i, 1)
end
-- softclock
softclock = {}
function softclock.init()
softclock.super_period = 96
softclock.transport = 0
softclock.song_clocks = {}
softclock.sources = {}
softclock.sources[1] = "internal"
softclock.sources[2] = "midi"
softclock.sources[3] = "link"
softclock.sources[4] = "crow"
end
function softclock.super_tick()
while true do
clock.sync(1 / softclock.super_period)
softclock.transport = softclock.transport + 1
if is_playing then
if softclock.transport % softclock.super_period == 1 then
numerator = wrap(numerator + 1, 1, denominator)
bakeneko_frame = wrap(bakeneko_frame + 1, 1, 4)
update_screen()
end
for id, song_clock in pairs(softclock.song_clocks) do
song_clock.phase_ticks = song_clock.phase_ticks + 1
if song_clock.phase_ticks > song_clock.period_ticks then
song_clock.phase_ticks = song_clock.phase_ticks - song_clock.period_ticks
song_clock.event(song_clock.phase_ticks)
end
end
end
check_bpm()
check_screen()
end
end
function softclock:add(id, period, event)
local c = {}
c.phase_ticks = 0
c.period_ticks = period / (1 / self.super_period) * denominator
c.event = event
self.song_clocks[id] = c
end
-- draw
function redraw()
screen.clear()
draw_tracks()
draw_bpm()
draw_bakeneko()
screen.update()
end
function draw_loop()
while true do
check_screen()
clock.sleep(1 / 30)
end
end
function check_screen()
if is_screen_dirty then
redraw()
is_screen_dirty = false
end
end
function draw_tracks()
for i = 1, 6 do
ii = 0
for k, v in pairs(tracks[i].pattern) do
local level = v and 5 or 1
local current_step = tracks[i].current_step == ii + 1
level = (current_step and level == 5) and 15 or level
level = tracks[i].on and level or 0
screen.level(level)
if current_step then
screen.rect((i * 7) - 7, (ii * 4) + 1, 5, 3)
screen.fill()
else
screen.rect((i * 7) - 5, 2 + (ii * 4), 2, 2)
screen.stroke()
end
ii = ii + 1
end
end
end
function draw_bpm()
local size = 24
local x, y = 48, 25
screen.level(15)
screen.font_size(size)
screen.font_face(3)
screen.move(80, y)
screen.text_right(bpm)
screen.font_size(12)
screen.move(85, y)
screen.text("bpm")
screen.font_face(0)
screen.font_size(8)
screen.move(85, y - 12)
screen.text(string.upper(softclock.sources[params:get("clock_source")]))
end
function draw_bakeneko()
local frames = {}
frames[1] = "<(=^,^=<)"
frames[2] = "<(=^,^=)>"
frames[3] = "(>=^,^=)>"
frames[4] = "(^=^,^=)^"
screen.level(15)
screen.font_size(20)
screen.font_face(3)
screen.move(82, 54)
screen.text_center(frames[bakeneko_frame])
end
-- utility functions
function wrap(value, min, max)
local y = value
local d = max - min + 1
while y > max do
y = y - d
end
while y < min do
y = y + d
end
return y
end
function table_contains(t, element)
for _, value in pairs(t) do
if value == element then
return true
end
end
return false
end
function rerun()
norns.script.load(norns.state.script)
end