I had the same problem adding the script via maiden. Try adding the folder directly via Cyberduck.
ok that worked thanks
Do combo scripts show in Maidenās updater view? How are those suggestions ācuratedā?
I love everything about this stuff I just canāt piece it together for lack of brainzpower My current norns use case is more buddha machine-like perhaps. just pressing buttons for winding down. Iām so happy for it, and also just passively slow-learning stuff by browsing these threads is great.
Glad you got it working! The decimator engine was added to otis in an update, you may need to update your copy of Otis
Fantastic scripts thankyou!
The updater view presents a list of scripts that have been added to a file in the https://github.com/monome/norns-community repo. There are a number of folks from lines who can add to that, including merge pull requests from other contributors. So if someone created a new script, or combo script, they should also create a pull request against the norns-community repo to expose their script in the updater.
Just curious if itās possible to use say a looper script and a more effects based script at the same time?
I think I read that anything can be recorded to tape so I imagine if I canāt use multiple scripts I could just use an efx script on a recording from a looper script.
Just ordered a Norns shield and really excited. Loopers/samplers r a big part of my workflow and I think the Norns is gonna open some doors for me
the fugu/reels link you posted is down, any chance you could link it again? itās not totally clear to me what should replace āā your scriptā here. would be awesome to add reels to animator, fugu, etc
reels
function init()
reels.init()
end
function key(n,z)
if reels.active then
reels:key(n,z)
else
-- your script
end
end
function enc(n,d)
if reels.active then
reels:enc(n,d)
else
-- your script
end
end
function redraw()
reels:redraw()
end
also, thanks @Justmat for oats and earthbound! bummed iām just finding these now, these are amazing
unfortunately i dont have it anymore.
to include reels into other scripts you need to modify script enc() and key() functions with if-else conditions from my example.
--your script
- line where original function of script should be moved.
reels.init() and reels:redraw() should be just appended into corresponding functions
just getting around to this - works great. thank you! have been thinking about picking up a norns shield for this exact purpose. expands on the stock ātapeā functionality nicely. added reels to animator and showers, will get around to more and update here. quick audio snippet running showers, a field recording on reel 1 and me running my hands over a music easel keyboard into a short buffer on reel 2
edit - added a merged fugu, reels script to github
ooo thatās fun. Iāll have to try this out!
A nice mod might be to sync reels with animatorās clock and add a quantized record option. Would be a bit more work though. Could be fun for combining reels with any clock based script, actually
Do you guys think that pedalboard and MLR could be combined? My idea is to color the input signal with the pedalboard script, and then use that signal as the incoming signal to MLR, I think it could be pretty sweet but not sure if itās possible?
Technically, sure. As far as sound processing goes, Pedalboard doesnāt use Softcut at all, and MLR uses softcut exclusively. I think combining, or switching between, the two UIs would be the only real challenge.
Iām having some trouble getting mlr to work with a UI-less sequencer script Iāve thrown together based on the sequins demo script. The idea is to sequence eurorack via crow while also using mlr. The sequins is determined by a series of letters input into the script itself, so thereās no need for a UI at the moment. It works well on its own. But Iām not sure whatās going on with mlr. Does anyone have any ideas? Hereās the (working) crow sequencer script (which Iām calling lied) on its own:
lied script
--- Lied.
local MusicUtil = require "musicutil"
local TabUtil = require "tabutil"
-- TODO operate by reference so the scale can change without recreating the sequin
dim = {3,6,9,15,18,21,-6}
bass = {-6,-12,-15}
lyd = {12,14,16,18,19,9,11,24}
text = "C l o u d"
function init()
anumbs = string.gsub(text, "a", 1)
bnumbs = string.gsub(anumbs, "b", 2)
cnumbs = string.gsub(bnumbs, "c", 3)
dnumbs = string.gsub(cnumbs, "d", 4)
enumbs = string.gsub(dnumbs, "e", 5)
fnumbs = string.gsub(enumbs, "f", 6)
gnumbs = string.gsub(fnumbs, "g", 7)
hnumbs = string.gsub(gnumbs, "h", 8)
inumbs = string.gsub(hnumbs, "i", 9)
jnumbs = string.gsub(inumbs, "j", 10)
knumbs = string.gsub(jnumbs, "k", 11)
lnumbs = string.gsub(knumbs, "l", 12)
mnumbs = string.gsub(lnumbs, "m", 13)
nnumbs = string.gsub(mnumbs, "n", 14)
onumbs = string.gsub(nnumbs, "o", 15)
pnumbs = string.gsub(onumbs, "p", 16)
qnumbs = string.gsub(pnumbs, "q", 17)
rnumbs = string.gsub(qnumbs, "r", 18)
snumbs = string.gsub(rnumbs, "s", 19)
tnumbs = string.gsub(snumbs, "t", 20)
unumbs = string.gsub(tnumbs, "u", 21)
vnumbs = string.gsub(unumbs, "v", 22)
wnumbs = string.gsub(vnumbs, "w", 23)
xnumbs = string.gsub(wnumbs, "x", 24)
ynumbs = string.gsub(xnumbs, "y", 25)
znumbs = string.gsub(ynumbs, "z", 26)
Anumbs = string.gsub(znumbs, "A", 27)
Bnumbs = string.gsub(Anumbs, "B", 28)
Cnumbs = string.gsub(Bnumbs, "C", 29)
Dnumbs = string.gsub(Cnumbs, "D", 30)
Enumbs = string.gsub(Dnumbs, "E", 31)
Fnumbs = string.gsub(Enumbs, "F", 32)
Gnumbs = string.gsub(Fnumbs, "G", 33)
Hnumbs = string.gsub(Gnumbs, "H", 34)
Inumbs = string.gsub(Hnumbs, "I", 35)
Jnumbs = string.gsub(Inumbs, "J", 36)
Knumbs = string.gsub(Jnumbs, "K", 37)
Lnumbs = string.gsub(Knumbs, "L", 38)
Mnumbs = string.gsub(Lnumbs, "M", 39)
Nnumbs = string.gsub(Mnumbs, "N", 40)
Onumbs = string.gsub(Nnumbs, "O", 41)
Pnumbs = string.gsub(Onumbs, "P", 42)
Qnumbs = string.gsub(Pnumbs, "Q", 43)
Rnumbs = string.gsub(Qnumbs, "R", 44)
Snumbs = string.gsub(Rnumbs, "S", 45)
Tnumbs = string.gsub(Snumbs, "T", 46)
Unumbs = string.gsub(Tnumbs, "U", 47)
Vnumbs = string.gsub(Unumbs, "V", 48)
Wnumbs = string.gsub(Vnumbs, "W", 49)
Xnumbs = string.gsub(Wnumbs, "X", 50)
Ynumbs = string.gsub(Xnumbs, "Y", 51)
Znumbs = string.gsub(Ynumbs, "Z", 52)
print(Znumbs)
crow.input[1].mode('clock')
crow.output[2].action = "{to(5,0),to(0,0.025)}"
crow.output[4].action = "{to(5,0),to(0,0.025)}"
function Split(s, delimiter)
result = {};
for match in (s..delimiter):gmatch("(.-)"..delimiter) do
table.insert(result, match);
end
return result;
end
lied = Split(Znumbs, "%s")
print(lied)
-- create our sequin which interleaves the playback of two tables of notes (from above)
local mysequin =
sequins.new( { sequins.new( lied, 'prev' )
, sequins.count( 5, sequins.new( lied, 'next' ) )
}
, 'next' )
-- iniates a timebase which will pull a note from the sequin on each step
myarp = run_arp( play_note
, mysequin
, 11/5
)
myotherarp = run_arp( play_other_note
, mysequin
, 5/1)
mythirdarp = run_arp( play_with
, mysequin
, 4/1)
end
function play_note(v)
--v = v + 48 -- shift up to good range
local freq = v/12
crow.output[1].volts = freq
crow.output[1].scale( {1,3,5,6,8,10,11,13}, 12, 1.0 )
crow.output[2].execute()
end
function play_other_note(v)
--v = v + 48 -- shift up to good range
local freq = v/12
crow.output[3].volts = freq
crow.output[3].scale( {1,3,5,6,8,10,11,13}, 12, 1.0 )
crow.output[4].execute()
end
function play_with(v)
local freq = v/12
crow.ii.wdel.clock_ratio( 1, v )
end
function run_arp(fn,seq,sync)
return clock.run(
function()
while true do
clock.sync(sync)
fn( seq() )
end
end)
end
--- TODO this is a new file called sequins.lua
sequins = {}
-- arg1: value table eg{0,2,4,7,9}
-- arg2: behaviour enum:{'next','prev',rand','drunk'} -- n as next#
-- retv: function that generates the next note
function sequins.new( vals, behaviour )
local ix = 1
local function generate()
local newix = 0
if behaviour == 'next' then
newix = ix + 1
elseif behaviour == 'prev' then
newix = ix - 1
elseif behaviour == 'rand' then
newix = math.random(#vals)
elseif behaviour == 'drunk' then
newix = ix + math.random(-1,1)
end
-- clamp to vals table length
while newix < 1 do newix = newix + #vals end
while newix > #vals do newix = newix - #vals end
local val = vals[newix]
-- currently we explictly step the index forward in some cases but not others
-- ideally this would be cleaned up & have a single return point
if type(val) == 'function' then
local v, action = val() -- allows nested tables
if action == 'skip' then
ix = newix -- nb: doesn't matter if it's before/after recursion
return generate( vals, behaviour )
elseif action == 'again' then
-- ix stays the same!
else -- was a nested table, but just returned a value (no action)
ix = newix
end
return v
else -- assume a number
ix = newix
return val
end
end
return generate -- named function so we can use recursion inside
end
-- returns a value on everyth call. otherwise instructs the caller to skip it.
function sequins.every( everyth, sequin )
local ev = everyth
return
function()
ev = e%everyth +1
if ev == 1 then
return sequin()
else
return 0, 'skip'
end
end
end
-- returns count values in a row, stopping the calling sequin from moving forward until count is exhausted
function sequins.count( count, sequin )
local cou = count
return
function()
cou = cou%count +1
if cou == 1 then
print'match'
return sequin()
else
return sequin(), 'again'
end
end
end
And hereās the (broken) combo script with mlr:
mlr lied combo script
-- mlr
-- v2.2.4 @tehn
-- llllllll.co/t/21145
--
-- /////////
-- ////
-- ////////////
-- //////////
-- ///////
-- /
-- ////
-- //
-- /////////
-- ///
-- /
--
-- ////
-- /
--
-- /
--
--
local MusicUtil = require "musicutil"
local TabUtil = require "tabutil"
text = "C l o u d"
function init()
anumbs = string.gsub(text, "a", 1)
bnumbs = string.gsub(anumbs, "b", 2)
cnumbs = string.gsub(bnumbs, "c", 3)
dnumbs = string.gsub(cnumbs, "d", 4)
enumbs = string.gsub(dnumbs, "e", 5)
fnumbs = string.gsub(enumbs, "f", 6)
gnumbs = string.gsub(fnumbs, "g", 7)
hnumbs = string.gsub(gnumbs, "h", 8)
inumbs = string.gsub(hnumbs, "i", 9)
jnumbs = string.gsub(inumbs, "j", 10)
knumbs = string.gsub(jnumbs, "k", 11)
lnumbs = string.gsub(knumbs, "l", 12)
mnumbs = string.gsub(lnumbs, "m", 13)
nnumbs = string.gsub(mnumbs, "n", 14)
onumbs = string.gsub(nnumbs, "o", 15)
pnumbs = string.gsub(onumbs, "p", 16)
qnumbs = string.gsub(pnumbs, "q", 17)
rnumbs = string.gsub(qnumbs, "r", 18)
snumbs = string.gsub(rnumbs, "s", 19)
tnumbs = string.gsub(snumbs, "t", 20)
unumbs = string.gsub(tnumbs, "u", 21)
vnumbs = string.gsub(unumbs, "v", 22)
wnumbs = string.gsub(vnumbs, "w", 23)
xnumbs = string.gsub(wnumbs, "x", 24)
ynumbs = string.gsub(xnumbs, "y", 25)
znumbs = string.gsub(ynumbs, "z", 26)
Anumbs = string.gsub(znumbs, "A", 27)
Bnumbs = string.gsub(Anumbs, "B", 28)
Cnumbs = string.gsub(Bnumbs, "C", 29)
Dnumbs = string.gsub(Cnumbs, "D", 30)
Enumbs = string.gsub(Dnumbs, "E", 31)
Fnumbs = string.gsub(Enumbs, "F", 32)
Gnumbs = string.gsub(Fnumbs, "G", 33)
Hnumbs = string.gsub(Gnumbs, "H", 34)
Inumbs = string.gsub(Hnumbs, "I", 35)
Jnumbs = string.gsub(Inumbs, "J", 36)
Knumbs = string.gsub(Jnumbs, "K", 37)
Lnumbs = string.gsub(Knumbs, "L", 38)
Mnumbs = string.gsub(Lnumbs, "M", 39)
Nnumbs = string.gsub(Mnumbs, "N", 40)
Onumbs = string.gsub(Nnumbs, "O", 41)
Pnumbs = string.gsub(Onumbs, "P", 42)
Qnumbs = string.gsub(Pnumbs, "Q", 43)
Rnumbs = string.gsub(Qnumbs, "R", 44)
Snumbs = string.gsub(Rnumbs, "S", 45)
Tnumbs = string.gsub(Snumbs, "T", 46)
Unumbs = string.gsub(Tnumbs, "U", 47)
Vnumbs = string.gsub(Unumbs, "V", 48)
Wnumbs = string.gsub(Vnumbs, "W", 49)
Xnumbs = string.gsub(Wnumbs, "X", 50)
Ynumbs = string.gsub(Xnumbs, "Y", 51)
Znumbs = string.gsub(Ynumbs, "Z", 52)
print(Znumbs)
crow.input[1].mode('clock')
crow.output[2].action = "{to(5,0),to(0,0.025)}"
crow.output[4].action = "{to(5,0),to(0,0.025)}"
function Split(s, delimiter)
result = {};
for match in (s..delimiter):gmatch("(.-)"..delimiter) do
table.insert(result, match);
end
return result;
end
lied = Split(Znumbs, "%s")
print(lied)
-- create our sequin which interleaves the playback of two tables of notes (from above)
local mysequin =
sequins.new( { sequins.new( lied, 'prev' )
, sequins.count( 5, sequins.new( lied, 'next' ) )
}
, 'next' )
-- iniates a timebase which will pull a note from the sequin on each step
myarp = run_arp( play_note
, mysequin
, 11/5
)
myotherarp = run_arp( play_other_note
, mysequin
, 5/1)
mythirdarp = run_arp( play_with
, mysequin
, 4/1)
end
function play_note(v)
--v = v + 48 -- shift up to good range
local freq = v/12
crow.output[1].volts = freq
crow.output[1].scale( {1,3,5,6,8,10,11,13}, 12, 1.0 )
crow.output[2].execute()
end
function play_other_note(v)
--v = v + 48 -- shift up to good range
local freq = v/12
crow.output[3].volts = freq
crow.output[3].scale( {1,3,5,6,8,10,11,13}, 12, 1.0 )
crow.output[4].execute()
end
function play_with(v)
local freq = v/12
crow.ii.wdel.clock_ratio( 1, v )
end
function run_arp(fn,seq,sync)
return clock.run(
function()
while true do
clock.sync(sync)
fn( seq() )
end
end)
end
--- TODO this is a new file called sequins.lua
sequins = {}
-- arg1: value table eg{0,2,4,7,9}
-- arg2: behaviour enum:{'next','prev',rand','drunk'} -- n as next#
-- retv: function that generates the next note
function sequins.new( vals, behaviour )
local ix = 1
local function generate()
local newix = 0
if behaviour == 'next' then
newix = ix + 1
elseif behaviour == 'prev' then
newix = ix - 1
elseif behaviour == 'rand' then
newix = math.random(#vals)
elseif behaviour == 'drunk' then
newix = ix + math.random(-1,1)
end
-- clamp to vals table length
while newix < 1 do newix = newix + #vals end
while newix > #vals do newix = newix - #vals end
local val = vals[newix]
-- currently we explictly step the index forward in some cases but not others
-- ideally this would be cleaned up & have a single return point
if type(val) == 'function' then
local v, action = val() -- allows nested tables
if action == 'skip' then
ix = newix -- nb: doesn't matter if it's before/after recursion
return generate( vals, behaviour )
elseif action == 'again' then
-- ix stays the same!
else -- was a nested table, but just returned a value (no action)
ix = newix
end
return v
else -- assume a number
ix = newix
return val
end
end
return generate -- named function so we can use recursion inside
end
-- returns a value on everyth call. otherwise instructs the caller to skip it.
function sequins.every( everyth, sequin )
local ev = everyth
return
function()
ev = e%everyth +1
if ev == 1 then
return sequin()
else
return 0, 'skip'
end
end
end
-- returns count values in a row, stopping the calling sequin from moving forward until count is exhausted
function sequins.count( count, sequin )
local cou = count
return
function()
cou = cou%count +1
if cou == 1 then
print'match'
return sequin()
else
return sequin(), 'again'
end
end
end
end
local g = grid.connect()
local fileselect = require 'fileselect'
local textentry = require 'textentry'
local pattern_time = require 'pattern_time'
local TRACKS = 6
local FADE = 0.01
-- softcut has ~350s per buffer
local CLIP_LEN_SEC = 45
local MAX_CLIPS = 7
local vREC = 1
local vCUT = 2
local vCLIP = 3
local vTIME = 15
-- events
local eCUT = 1
local eSTOP = 2
local eSTART = 3
local eLOOP = 4
local eSPEED = 5
local eREV = 6
local ePATTERN = 7
local quantize = 0
local quantizer
local function update_tempo()
local t = params:get("clock_tempo")
local d = params:get("quant_div")
local interval = (60/t) / d
print("q > "..interval)
quantizer.time = interval
for i=1,TRACKS do
if track[i].tempo_map == 1 then
update_rate(i)
end
end
end
local prev_tempo = params:get("clock_tempo")
function clock_update_tempo ()
while true do
clock.sync(1/24)
local curr_tempo = params:get("clock_tempo")
if prev_tempo ~= curr_tempo then
prev_tempo = curr_tempo
update_tempo()
end
end
end
function event_record(e)
for i=1,4 do
pattern[i]:watch(e)
end
recall_watch(e)
end
function event(e)
if quantize == 1 then
event_q(e)
else
if e.t ~= ePATTERN then event_record(e) end
event_exec(e)
end
end
local quantize_events = {}
function event_q(e)
table.insert(quantize_events,e)
end
function event_q_clock()
if #quantize_events > 0 then
for k,e in pairs(quantize_events) do
if e.t ~= ePATTERN then event_record(e) end
event_exec(e)
end
quantize_events = {}
end
end
function event_exec(e)
if e.t==eCUT then
if track[e.i].loop == 1 then
track[e.i].loop = 0
softcut.loop_start(e.i,clip[track[e.i].clip].s)
softcut.loop_end(e.i,clip[track[e.i].clip].e)
end
local cut = (e.pos/16)*clip[track[e.i].clip].l + clip[track[e.i].clip].s
softcut.position(e.i,cut)
--softcut.reset(e.i)
if track[e.i].play == 0 then
track[e.i].play = 1
ch_toggle(e.i,1)
end
elseif e.t==eSTOP then
track[e.i].play = 0
track[e.i].pos_grid = -1
ch_toggle(e.i,0)
dirtygrid=true
elseif e.t==eSTART then
track[e.i].play = 1
ch_toggle(e.i,1)
dirtygrid=true
elseif e.t==eLOOP then
track[e.i].loop = 1
track[e.i].loop_start = e.loop_start
track[e.i].loop_end = e.loop_end
--print("LOOP "..track[e.i].loop_start.." "..track[e.i].loop_end)
local lstart = clip[track[e.i].clip].s + (track[e.i].loop_start-1)/16*clip[track[e.i].clip].l
local lend = clip[track[e.i].clip].s + (track[e.i].loop_end)/16*clip[track[e.i].clip].l
--print(">>>> "..lstart.." "..lend)
softcut.loop_start(e.i,lstart)
softcut.loop_end(e.i,lend)
if view == vCUT then dirtygrid=true end
elseif e.t==eSPEED then
track[e.i].speed = e.speed
update_rate(e.i)
--n = math.pow(2,track[e.i].speed + params:get("speed_mod"..e.i))
--if track[e.i].rev == 1 then n = -n end
--engine.rate(e.i,n)
if view == vREC then dirtygrid=true end
elseif e.t==eREV then
track[e.i].rev = e.rev
update_rate(e.i)
--n = math.pow(2,track[e.i].speed + params:get("speed_mod"..e.i))
--if track[e.i].rev == 1 then n = -n end
--engine.rate(e.i,n)
if view == vREC then dirtygrid=true end
elseif e.t==ePATTERN then
if e.action=="stop" then pattern[e.i]:stop()
elseif e.action=="start" then pattern[e.i]:start()
elseif e.action=="rec_stop" then pattern[e.i]:rec_stop()
elseif e.action=="rec_start" then pattern[e.i]:rec_start()
elseif e.action=="clear" then pattern[e.i]:clear()
end
end
end
------ patterns
pattern = {}
for i=1,4 do
pattern[i] = pattern_time.new()
pattern[i].process = event_exec
end
------ recalls
recall = {}
for i=1,4 do
recall[i] = {}
recall[i].recording = false
recall[i].has_data = false
recall[i].active = false
recall[i].event = {}
end
function recall_watch(e)
for i=1,4 do
if recall[i].recording == true then
--print("recall: event rec")
table.insert(recall[i].event, e)
recall[i].has_data = true
end
end
end
function recall_exec(i)
for _,e in pairs(recall[i].event) do
event_exec(e)
end
end
view = vREC
view_prev = view
v = {}
v.key = {}
v.enc = {}
v.redraw = {}
v.gridkey = {}
v.gridredraw = {}
viewinfo = {}
viewinfo[vREC] = 1
viewinfo[vCUT] = 0
viewinfo[vTIME] = 0
focus = 1
alt = 0
track = {}
for i=1,TRACKS do
track[i] = {}
track[i].head = (i-1)%4+1
track[i].play = 0
track[i].rec = 0
track[i].rec_level = 1
track[i].pre_level = 0
track[i].loop = 0
track[i].loop_start = 0
track[i].loop_end = 16
track[i].clip = i
track[i].pos = 0
track[i].pos_grid = -1
track[i].speed = 0
track[i].rev = 0
track[i].tempo_map = 0
end
set_clip_length = function(i, len)
clip[i].l = len
clip[i].e = clip[i].s + len
local bpm = 60 / len
while bpm < 60 do
bpm = bpm * 2
print("bpm > "..bpm)
end
clip[i].bpm = bpm
end
clip_reset = function(i, length)
set_clip_length(i, length)
clip[i].name = "-"
end
clip = {}
for i=1,16 do
clip[i] = {}
clip[i].s = 2 + (i-1)*CLIP_LEN_SEC
clip[i].name = "-"
set_clip_length(i,4)
end
calc_quant = function(i)
local q = (clip[track[i].clip].l/16)
print("q > "..q)
return q
end
calc_quant_off = function(i, q)
local off = q
while off < clip[track[i].clip].s do
off = off + q
end
off = off - clip[track[i].clip].s
print("off > "..off)
return off
end
set_clip = function(i, x)
--track[i].play = 0
--ch_toggle(i,0)
track[i].clip = x
softcut.loop_start(i,clip[track[i].clip].s)
softcut.loop_end(i,clip[track[i].clip].e)
local q = calc_quant(i)
local off = calc_quant_off(i, q)
softcut.phase_quant(i,q)
softcut.phase_offset(i,off)
--track[i].loop = 0
end
set_rec = function(n)
if track[n].rec == 1 then
softcut.pre_level(n,track[n].pre_level)
softcut.rec_level(n,track[n].rec_level)
else
softcut.pre_level(n,1)
softcut.rec_level(n,0)
end
end
held = {}
heldmax = {}
done = {}
first = {}
second = {}
for i = 1,8 do
held[i] = 0
heldmax[i] = 0
done[i] = 0
first[i] = 0
second[i] = 0
end
key = function(n,z) _key(n,z) end
enc = function(n,d)
if n==1 then params:delta("output_level",d)
else _enc(n,d) end
end
redraw = function() _redraw() end
g.key = function(x,y,z) _gridkey(x,y,z) end
set_view = function(x)
--print("set view: "..x)
if x == -1 then x = view_prev end
view_prev = view
view = x
_key = v.key[x]
_enc = v.enc[x]
_redraw = v.redraw[x]
_gridkey = v.gridkey[x]
_gridredraw = v.gridredraw[x]
redraw()
dirtygrid=true
end
gridredraw = function()
if not g then return end
if dirtygrid == true then
_gridredraw()
dirtygrid = false
end
end
function ch_toggle(i,x)
softcut.play(i,x)
softcut.rec(i,x)
end
UP1 = controlspec.new(0, 1, 'lin', 0, 1, "")
UP0 = controlspec.new(0, 1, 'lin', 0, 0, "")
cs_PAN = controlspec.new(-1, 1, 'lin', 0, 0, "")
BI1 = controlspec.new(-1, 1, 'lin', 0, 0, "")
-------------------- init
init = function()
params:set_action("clock_tempo", function() update_tempo() end)
params:add_number("quant_div", "quant div", 1, 32, 4)
params:set_action("quant_div",function() update_tempo() end)
p = {}
audio.level_cut(1)
audio.level_adc_cut(1)
for i=1,TRACKS do
params:add_separator()
softcut.enable(i,1)
softcut.level_input_cut(1, i, 1.0)
softcut.level_input_cut(2, i, 1.0)
softcut.play(i,0)
softcut.rec(i,0)
softcut.level(i,1)
softcut.pan(i,0)
softcut.buffer(i,1)
softcut.pre_level(i,1)
softcut.rec_level(i,0)
softcut.fade_time(i,FADE)
softcut.level_slew_time(i,0.1)
softcut.rate_slew_time(i,0)
softcut.loop_start(i,clip[track[i].clip].s)
softcut.loop_end(i,clip[track[i].clip].e)
softcut.loop(i,1)
softcut.position(i, clip[track[i].clip].s)
params:add_control(i.."vol", i.."vol", UP1)
params:set_action(i.."vol", function(x) softcut.level(i,x) end)
params:add_control(i.."pan", i.."pan", cs_PAN)
params:set_action(i.."pan", function(x) softcut.pan(i,x) end)
params:add_control(i.."rec", i.."rec", UP1)
params:set_action(i.."rec",
function(x)
track[i].rec_level = x
set_rec(i)
end)
params:add_control(i.."pre", i.."pre", controlspec.UNIPOLAR)
params:set_action(i.."pre",
function(x)
track[i].pre_level = x
set_rec(i)
end)
params:add_control(i.."speed_mod", i.."speed_mod", controlspec.BIPOLAR)
params:set_action(i.."speed_mod", function() update_rate(i) end)
params:add_control(i.."rate_slew", i.."rate_slew", UP0)
params:set_action(i.."rate_slew", function(x) softcut.rate_slew_time(i,x) end)
params:add_control(i.."level_slew", i.."level_slew", controlspec.new(0.0,10.0,"lin",0.1,0.1,""))
params:set_action(i.."level_slew", function(x) softcut.level_slew_time(i,x) end)
params:add_file(i.."file", i.."file", "")
params:set_action(i.."file",
--function(n) print("FILESELECT > "..i.." "..n) end)
function(n) fileselect_callback(n,i) end)
update_rate(i)
set_clip(i,i)
--softcut.phase_quant(i,calc_quant(i))
end
quantizer = metro.init()
quantizer.time = 0.125
quantizer.count = -1
quantizer.event = event_q_clock
quantizer:start()
--pattern_init()
set_view(vREC)
update_tempo()
gridredrawtimer = metro.init(function() gridredraw() end, 0.02, -1)
gridredrawtimer:start()
dirtygrid = true
grid.add = draw_grid_connected
screenredrawtimer = metro.init(function() redraw() end, 0.1, -1)
screenredrawtimer:start()
params:bang()
softcut.event_phase(phase)
softcut.poll_start_phase()
clock.run(clock_update_tempo)
end
-- poll callback
phase = function(n, x)
--if n == 1 then print(x) end
local pp = ((x - clip[track[n].clip].s) / clip[track[n].clip].l)-- * 16 --TODO 16=div
--x = math.floor(track[n].pos*16)
--if n==1 then print("> "..x.." "..pp) end
x = math.floor(pp * 16)
if x ~= track[n].pos_grid then
track[n].pos_grid = x
if view == vCUT then dirtygrid=true end
end
end
update_rate = function(i)
local n = math.pow(2,track[i].speed + params:get(i.."speed_mod"))
if track[i].rev == 1 then n = -n end
if track[i].tempo_map == 1 then
local bpmmod = params:get("clock_tempo") / clip[track[i].clip].bpm
--print("bpmmod: "..bpmmod)
n = n * bpmmod
end
softcut.rate(i,n)
end
gridkey_nav = function(x,z)
if z==1 then
if x==1 then
if alt == 1 then softcut.buffer_clear() end
set_view(vREC)
elseif x==2 then set_view(vCUT)
elseif x==3 then set_view(vCLIP)
elseif x>4 and x<9 then
local i = x - 4
if alt == 1 then
local e={t=ePATTERN,i=i,action="rec_stop"} event(e)
local e={t=ePATTERN,i=i,action="stop"} event(e)
local e={t=ePATTERN,i=i,action="clear"} event(e)
elseif pattern[i].rec == 1 then
local e={t=ePATTERN,i=i,action="rec_stop"} event(e)
local e={t=ePATTERN,i=i,action="start"} event(e)
elseif pattern[i].count == 0 then
local e={t=ePATTERN,i=i,action="rec_start"} event(e)
elseif pattern[i].play == 1 then
local e={t=ePATTERN,i=i,action="stop"} event(e)
else
local e={t=ePATTERN,i=i,action="start"} event(e)
end
elseif x>8 and x<13 then
local i = x-8
if alt == 1 then
--print("recall: clear "..i)
recall[i].event = {}
recall[i].recording = false
recall[i].has_data = false
recall[i].active = false
elseif recall[i].recording == true then
--print("recall: stop")
recall[i].recording = false
elseif recall[i].has_data == false then
--print("recall: rec")
recall[i].recording = true
elseif recall[i].has_data == true then
--print("recall: exec")
recall_exec(i)
recall[i].active = true
end
elseif x==15 and alt == 0 then
quantize = 1 - quantize
if quantize == 0 then quantizer:stop()
else quantizer:start()
end
elseif x==15 and alt == 1 then
set_view(vTIME)
elseif x==16 then alt = 1
end
elseif z==0 then
if x==16 then alt = 0
elseif x==15 and view == vTIME then set_view(-1)
elseif x>8 and x<13 then recall[x-8].active = false end
end
dirtygrid=true
end
gridredraw_nav = function()
-- indicate view
g:led(view,1,15)
if alt==1 then g:led(16,1,9) end
if quantize==1 then g:led(15,1,9) end
for i=1,4 do
-- patterns
if pattern[i].rec == 1 then g:led(i+4,1,15)
elseif pattern[i].play == 1 then g:led(i+4,1,9)
elseif pattern[i].count > 0 then g:led(i+4,1,5)
else g:led(i+4,1,3) end
-- recalls
local b = 2
if recall[i].recording == true then b=15
elseif recall[i].active == true then b=11
elseif recall[i].has_data == true then b=5 end
g:led(i+8,1,b)
end
end
-------------------- REC
v.key[vREC] = function(n,z)
if n==2 and z==1 then
viewinfo[vREC] = 1 - viewinfo[vREC]
redraw()
end
end
v.enc[vREC] = function(n,d)
if viewinfo[vREC] == 0 then
if n==2 then
params:delta(focus.."vol",d)
elseif n==3 then
params:delta(focus.."speed_mod",d)
end
else
if n==2 then
params:delta(focus.."rec",d)
elseif n==3 then
params:delta(focus.."pre",d)
end
end
redraw()
end
v.redraw[vREC] = function()
screen.clear()
screen.level(15)
screen.move(10,16)
screen.text("REC > "..focus)
local sel = viewinfo[vREC] == 0
screen.level(sel and 15 or 4)
screen.move(10,32)
screen.text(params:string(focus.."vol"))
screen.move(70,32)
screen.text(params:string(focus.."speed_mod"))
screen.level(3)
screen.move(10,40)
screen.text("volume")
screen.move(70,40)
screen.text("speed mod")
screen.level(not sel and 15 or 4)
screen.move(10,52)
screen.text(params:string(focus.."rec"))
screen.move(70,52)
screen.text(params:string(focus.."pre"))
screen.level(3)
screen.move(10,60)
screen.text("rec level")
screen.move(70,60)
screen.text("overdub")
screen.update()
end
v.gridkey[vREC] = function(x, y, z)
if y == 1 then gridkey_nav(x,z)
else
if z == 1 then
i = y-1
if x>2 and x<8 then
if alt == 1 then
track[i].tempo_map = 1 - track[i].tempo_map
update_rate(i)
elseif focus ~= i then
focus = i
redraw()
end
elseif x==1 and y<TRACKS+2 then
track[i].rec = 1 - track[i].rec
print("REC "..track[i].rec)
set_rec(i)
elseif x==16 and y<TRACKS+2 then
if track[i].play == 1 then
e = {}
e.t = eSTOP
e.i = i
event(e)
else
e = {}
e.t = eSTART
e.i = i
event(e)
end
elseif x>8 and x<16 and y<TRACKS+2 then
local n = x-12
e = {} e.t = eSPEED e.i = i e.speed = n
event(e)
elseif x==8 and y<TRACKS+2 then
local n = 1 - track[i].rev
e = {} e.t = eREV e.i = i e.rev = n
event(e)
end
dirtygrid=true
end
end
end
v.gridredraw[vREC] = function()
g:all(0)
g:led(3,focus+1,7)
g:led(4,focus+1,7)
for i=1,TRACKS do
local y = i+1
g:led(1,y,3)--rec
if track[i].rec == 1 then g:led(1,y,9) end
if track[i].tempo_map == 1 then g:led(5,y,7) end -- tempo.map
g:led(8,y,3)--rev
g:led(16,y,3)--stop
g:led(12,y,3)--speed=1
g:led(12+track[i].speed,y,9)
if track[i].rev == 1 then g:led(8,y,7) end
if track[i].play == 1 then g:led(16,y,15) end
end
gridredraw_nav()
g:refresh();
end
--------------------CUT
v.key[vCUT] = function(n,z)
print("CUT key")
end
v.enc[vCUT] = function(n,d)
if n==2 then
params:delta(focus.."vol",d)
end
redraw()
end
v.redraw[vCUT] = function()
screen.clear()
screen.level(15)
screen.move(10,16)
screen.text("CUT > "..focus)
if viewinfo[vCUT] == 0 then
screen.move(10,32)
screen.text(params:string(focus.."vol"))
--screen.move(70,50)
--screen.text(params:get("loop_mod"..focus))
screen.level(3)
screen.move(10,40)
screen.text("volume")
--screen.move(70,60)
--screen.text("speed mod")
else
screen.move(10,50)
screen.text(params:get(focus.."rec"))
screen.move(70,50)
screen.text(params:get(focus.."pre"))
screen.level(3)
screen.move(10,60)
screen.text("rec level")
screen.move(70,60)
screen.text("overdub")
end
screen.update()
end
v.gridkey[vCUT] = function(x, y, z)
if z==1 and held[y] then heldmax[y] = 0 end
held[y] = held[y] + (z*2-1)
if held[y] > heldmax[y] then heldmax[y] = held[y] end
--print(held[y])
if y == 1 then gridkey_nav(x,z)
elseif y == 8 then return
else
i = y-1
if z == 1 then
if focus ~= i then
focus = i
redraw()
end
if alt == 1 and y<TRACKS+2 then
if track[i].play == 1 then
e = {} e.t = eSTOP e.i = i
else
e = {} e.t = eSTART e.i = i
end
event(e)
elseif y<TRACKS+2 and held[y]==1 then
first[y] = x
local cut = x-1
--print("pos > "..cut)
e = {} e.t = eCUT e.i = i e.pos = cut
event(e)
elseif y<TRACKS+2 and held[y]==2 then
second[y] = x
end
elseif z==0 then
if y<TRACKS+2 and held[y] == 1 and heldmax[y]==2 then
e = {}
e.t = eLOOP
e.i = i
e.loop = 1
e.loop_start = math.min(first[y],second[y])
e.loop_end = math.max(first[y],second[y])
event(e)
end
end
end
end
v.gridredraw[vCUT] = function()
g:all(0)
gridredraw_nav()
for i=1,TRACKS do
if track[i].loop == 1 then
for x=track[i].loop_start,track[i].loop_end do
g:led(x,i+1,4)
end
end
if track[i].play == 1 then
g:led((track[i].pos_grid+1)%16, i+1, 15)
end
end
g:refresh();
end
--------------------CLIP
clip_actions = {"load","clear","save"}
clip_action = 1
clip_sel = 1
clip_clear_mult = 3
function fileselect_callback(path, c)
print("FILESELECT "..c)
if path ~= "cancel" and path ~= "" then
local ch, len = audio.file_info(path)
if ch > 0 and len > 0 then
print("file > "..path.." "..clip[track[c].clip].s)
print("file length > "..len/48000)
--softcut.buffer_read_mono(path, 0, clip[track[clip_sel].clip].s, len/48000, 1, 1)
softcut.buffer_read_mono(path, 0, clip[track[c].clip].s, CLIP_LEN_SEC, 1, 1)
local l = math.min(len/48000, CLIP_LEN_SEC)
set_clip_length(track[c].clip, l)
clip[track[c].clip].name = path:match("[^/]*$")
-- TODO: STRIP extension
set_clip(c,track[c].clip)
update_rate(c)
params:set(c.."file",path)
else
print("not a sound file")
end
-- TODO re-set_clip any tracks with this clip loaded
screenredrawtimer:start()
redraw()
end
end
function textentry_callback(txt)
if txt then
local c_start = clip[track[clip_sel].clip].s
local c_len = clip[track[clip_sel].clip].l
print("SAVE " .. _path.audio .. "mlr/" .. txt .. ".wav", c_start, c_len)
util.make_dir(_path.audio .. "mlr")
softcut.buffer_write_mono(_path.audio.."mlr/"..txt..".wav",c_start,c_len,1)
clip[track[clip_sel].clip].name = txt
else
print("save cancel")
end
screenredrawtimer:start()
redraw()
end
v.key[vCLIP] = function(n,z)
if n==2 and z==0 then
if clip_actions[clip_action] == "load" then
screenredrawtimer:stop()
fileselect.enter(os.getenv("HOME").."/dust/audio",
function(n) fileselect_callback(n,clip_sel) end)
elseif clip_actions[clip_action] == "clear" then
local c_start = clip[track[clip_sel].clip].s * 48000
print("clear_start: " .. c_start)
--softcut.clear_range(c_start, CLIP_LEN_SEC * 48000) -- two minutes
clip[track[clip_sel].clip].name = '-'
redraw()
elseif clip_actions[clip_action] == "save" then
screenredrawtimer:stop()
textentry.enter(textentry_callback, "mlr-" .. (math.random(9000)+1000))
end
elseif n==3 and z==1 then
clip_reset(clip_sel,60/params:get("clock_tempo")*(2^(clip_clear_mult-2)))
set_clip(clip_sel,track[clip_sel].clip)
update_rate(clip_sel)
end
end
v.enc[vCLIP] = function(n,d)
if n==2 then
clip_action = util.clamp(clip_action + d, 1, 3)
elseif n==3 then
clip_clear_mult = util.clamp(clip_clear_mult+d,1,6)
end
redraw()
dirtygrid=true
end
local function truncateMiddle (str, maxLength, separator)
maxLength = maxLength or 30
separator = separator or "..."
if (maxLength < 1) then return str end
if (string.len(str) <= maxLength) then return str end
if (maxLength == 1) then return string.sub(str, 1, 1) .. separator end
midpoint = math.ceil(string.len(str) / 2)
toremove = string.len(str) - maxLength
lstrip = math.ceil(toremove / 2)
rstrip = toremove - lstrip
return string.sub(str, 1, midpoint - lstrip) .. separator .. string.sub(str, 1 + midpoint + rstrip)
end
v.redraw[vCLIP] = function()
screen.clear()
screen.level(15)
screen.move(10,16)
screen.text("CLIP > TRACK "..clip_sel)
screen.move(10,52)
screen.text(truncateMiddle(clip[track[clip_sel].clip].name, 18))
screen.level(3)
screen.move(10,60)
screen.text("clip "..track[clip_sel].clip .. " " .. clip_actions[clip_action])
screen.move(100,52)
screen.text(2^(clip_clear_mult-2))
screen.level(3)
screen.move(100,60)
screen.text("resize")
screen.update()
end
v.gridkey[vCLIP] = function(x, y, z)
if y == 1 then gridkey_nav(x,z)
elseif z == 1 and y < TRACKS+2 and x < MAX_CLIPS+1 then
clip_sel = y-1
if x ~= track[clip_sel].clip then
set_clip(clip_sel,x)
end
redraw()
dirtygrid=true
end
end
v.gridredraw[vCLIP] = function()
g:all(0)
gridredraw_nav()
for i=1,16 do g:led(i,clip_sel+1,4) end
for i=1,TRACKS do g:led(track[i].clip,i+1,10) end
g:refresh();
end
--------------------TIME
v.key[vTIME] = function(n,z)
print("TIME key")
end
v.enc[vTIME] = function(n,d)
if n==2 then
params:delta("clock_tempo",d)
elseif n==3 then
params:delta("quant_div",d)
end
redraw()
end
v.redraw[vTIME] = function()
screen.clear()
screen.level(15)
screen.move(10,30)
screen.text("TIME")
if viewinfo[vTIME] == 0 then
screen.move(10,50)
screen.text(params:get("clock_tempo"))
screen.move(70,50)
screen.text(params:get("quant_div"))
screen.level(3)
screen.move(10,60)
screen.text("tempo")
screen.move(70,60)
screen.text("quant div")
end
screen.update()
end
v.gridkey[vTIME] = function(x, y, z)
if y == 1 then gridkey_nav(x,z) end
end
v.gridredraw[vTIME] = function()
g:all(0)
gridredraw_nav()
g:refresh();
end
function draw_grid_connected()
dirtygrid=true
gridredraw()
end
function cleanup()
for i=1,4 do
pattern[i]:stop()
pattern[i] = nil
end
grid.add = function() end
end
I thought this would be super easy since thereās no overlap in functionality, and one of the scripts doesnāt have a UI. But it seems like Iām missing something, and I canāt figure out what it is. Iām assuming the error has something to do with clock because the error message Iām getting right now is
lua:
/home/we/norns/lua/core/clock.lua:59: bad argument #1 to āresumeā (thread expected)
stack traceback:
[C]: in function ācoroutine.resumeā
/home/we/norns/lua/core/clock.lua:59: in function ācore/clock.resumeā
Just a wild shot in the dark; is there an extra āendā here?
end
end
local g = grid.connect()
local fileselect
I think there might have been! That got the lua error to go away. Now MLR is running normally, but the other script isnāt doing anything, as far as I can tell. Nothingās coming out of any of the crow outputs.
looks like youāre also defining two init scripts. if you combine them into one it might get you close(r)
or you could rename the first one seq_init
and call it at the end of the other/mlr init function.
untested with crow (itās printing match
to the REPL tho!), but i feel like itād be best to create a standalone lied
script file which could be included as a library within another script.
hereās a version of lied
which can be placed in its own code/lied
folder and can be called in another script with Lied = include 'lied/lied'
, then initialized with Lied.init()
: lied.lua Ā· GitHub
to give it a go:
- making a
code/lied
folder- dropping that
lied.lua
file into it
- dropping that
- at the top of the mlr script (line 24?) paste
Lied = include 'lied/lied'
- paste in
Lied.init()
at the end of mlrās init
if this works, iām happy to outline the key differences ā if it doesnāt, lmk!
edit: i put in some additional debugging prints and it seems like it should work
it definitely seems like it should work! I checked out your changes to the lied code, and it doesnāt seem to run on its own, but Iām not entirely sure whether itās supposed to? Maiden isnāt throwing an error per se, but I do get this message when I run the script, and nothing comes out of crowās outputs while the script is running
from maiden
reading PMAP /home/we/dust/data/lied/untitled/untitled.pmap
m.read: /home/we/dust/data/lied/untitled/untitled.pmap not read.
When I try to run the combo with mlr by calling lied from within mlr as youāve described, I get this error from Maiden:
from maiden
lua:
/home/we/norns/lua/core/clock.lua:59: bad argument #1 to āresumeā (thread expected)
stack traceback:
[C]: in function ācoroutine.resumeā
/home/we/norns/lua/core/clock.lua:59: in function ācore/clock.resumeā
Not sure why this is. Something to do with the clock, it seems, but Iām not sure what. Any ideas?
Thank you so much for taking the time to get back to me on this!
happy to help!
nah, this is in a library-friendly form, sticking with the idea of combining it into other scripts
once you confirm itās all workinā, we can get it running on its own.
hmm, not seeing that on my end at all ā just to confirm, hereās my version of mlr. if you copy/paste that over your current mlr.lua
file, you should see things initialize correctly by simply running mlr. lmk where it goes weird!
if it helps, hereās how things look on my end (i added some additional playing note
debugging prints to the gist to confirm stuff should be sending to crow):