project name: | repl-looper |
project url: | https://github.com/awwaiid/repl-looper |
author: | awwaiid |
description: | mash of REPL and looper; a live performance code sequencer with web UI |
discussion url: | https://llllllll.co/t/51485 |
tags: | sequencer looper grid |
Anagogically integrated UI for Norns / Matron / Grid
Experimental performance and creative tool, mashing together several things that I like. REPL for interactive code creating. Grid to have a tactile UI (maybe we’ll throw in a midi pedal too). Dance between client and host, between recording loops and loops that can modify other loops and themselves.
Check out the dev branch and dev branch development journal for ongoing development work. The main
branch is the current “stable” (haha) version.
all
helperloop:pan(-0.5)
to pan the whole looploop:noLoop()
to stop loopingloop:once()
to play onceloop:align(other_loop)
to slide events into alignmentloop:slice(s1)
now takes sample-variable instead of sample-variable-nameloop:split(other_loop)
to work more reliably and take a base-commandloop:merge(other_loop)
to keep relative timings (using the new loop:align
)ALL
to all
Install directly from maiden or by running this in the maiden/matron console:
;install https://github.com/awwaiid/repl-looper
Then start repl-looper
on the norns. The norns screen will show some status information. Interaction is primarily done through a web-browser and grid. You can access the web interface at:
http://norns.local/api/v1/dust/code/repl-looper/ui/dist/repl-looper.html (or http://bit.ly/norns-repl-looper)
The browser interface is an alternative to maiden’s REPL with a few different features and style. The basic idea is the same – you run Lua commands and see the results. Verify that everything is running with some simple math
2+2
Which should output 4
(give or take). The built-in engine is a mash-up of Timber, Goldeneye, and Molly the Poly. There is a shortcut for a Timber-based piano sample, p
. There is also an instance of Molly pre-defined. Try these:
-- Timber piano
p'c'
p'd'
p(68)
molly:note(60)
molly:randomize_params()
molly:note(62)
molly:stop()
Next thing to play with is loops/sequences. There are 8 pre-defined 16-step loops, one for each row, put into variables a
..h
. Start by recording into loop a
:
a:rec()
-- loop 'a' is now playing and recording! You can see the steps
-- both on the grid
p'c'
-- now wait a bit
p'd'
-- the loop is playing and will wrap back around
a:stop() -- this stops recording but keeps playing
a:stop() -- stop again to also stop playing
-- At this point press some of the grid buttons to trigger all the
-- events recorded at that step immediately (ignoring their sub-step timing)
a:lua() -- show the loop contents
a:play() -- start loop again
a:clear() -- erase loop contents
Now that you have the script running on norns and the local UI up in a browser and everything working together, read through Techniques to get some ideas!
<up>
and <down>
arrows to select commands and output from history<enter>
to execute the most recent item from history<tab>
to see what functions/methods are availablep
instead of p()
s808.<tab>
and have shortcuts like CP
a
..h
– loops, one per grid-row
loops
is a table of all eight, so you can do all(loops):amp(0, 10)
to fade everything at oncep
– a Timber-based piano sample playerpiano
– the underlying piano sample (via Timber)
piano:reverse()
molly
– an instance of Molly, which wraps Molly The Poly
molly2
.. molly8
– more pre-defined Mollysmollies
is a table of all eight, so you can do all(mollies):stop()
h:gen("molly:note(`60+m`)")
will put one molly-note on each step for a playable synth!s808.*
with these shortcuts
BD
, BD
, CH
, CY
, LC
, MC
, RS
, BS
, CL
, HC
, LT
, MT
, SD
, CB
, CP
, HT
, MA
, OH
CP
or CP()
or s808.CP:play()
s808.BS:amp(2)
h:gen(keys(s808))
will put one sample on each step for a playable drum kit!a
as the example)
Loop.new()
– Create a new Loop (I usually use a
..h
pre-defined loops instead)a.current_step
– current step (integer)a:setLength(steps)
– Set the number of steps (quarter-notes)a:lua()
– Convert into a lua-style stringa:setStep(step)
– Set the current stepa:play()
– Start playing and loopinga:once()
– Play once but do not loopa:stop()
– Stop recording or stop playinga:noLoop()
– Stop looping when we get to the enda:nextStep()
– Increment the current step to the next stepa:prevStep()
– Decrement the current step to the previous stepa:rec()
– Start recording. Manually triggered events or running commands in the REPL get recordeda:clear()
– Remove all eventsa:put(step, command)
– Assign a command at a specific step
a:gen(code_string, condition, offset)
– Generate events in a loop programmatically, with macro expansion
a:gen("CH")
– puts the “CH” function on every stepa:gen("CH", 1/2)
– puts the “CH” on every half stepa:gen("CH", 2, 3)
– puts the “CH” on every other step starting with step 3a:gen("CH", { 1, 3, 4.5 })
– puts the “CH” on the given steps (even fractional)a:gen({"BD","SD","CP"})
– puts each one on each stepn
for the one-based step/column and m
for the zero-based step/colum
a:gen("p(`50+m`)")
– Generates p(50)
, p(51)
, p(52)
, etca:gen("piano:filterFreq(`100 * n`)")
– Generates low-pass for 100 hz, 200 hz, 300 hz, etca:gen("molly:note(`60+m`)", "molly:offNote(`60+m`)")
– only for grid triggered events the “off” event will be executed on button releasea:quantize()
– Events are recorded at sub-steps by default; this immediately rounds them to the nearest stepa:clone(other_loop)
– Copy this loop onto another (empty the other out)a:merge(other_loop)
– Merge other_loop into this one. New loop is the longest of the twoa:split(other_loop)
– Split the current loop, putting some events into other_loop
and keeping some
a:split(other_loop, base_cmd)
– You can pass in a base-command and it will try to keep things like that in the current loopa:slice(sample, step_offset, step_count, reverse)
– Take a Goldeneye/Timber sample and slice it
reverse
is true then start from the end of the samplea:align(other_loop)
– Slides events into alignment with another loop; does not modify the loop otherwise
all(loops):align(a)
a:amp(level)
– Set the amp (0..1) for the whole loop; the included engine loops everything through the triggered loop!
a:amp(level, lag_time)
– Fade to the given volume over the given number of secondsa:amp(0, 10)
will fade outa:pan(amount)
– Pan the whole loop (-1 to 1, 0 is center)t1:<tab>
to explore! Generally does what the engine does
t1 = Timber.new("path/to/file.wav")
– Timber wrapper (works with .ogg too!)s1 = Sample.new("path/to/file.wav")
– Goldeneye wrapper (works with .ogg too!)m1 = Molly.new()
– Molly the Poly wrapper (you can have several!)
molly:randomize()
– Randomize the synth params!molly
and molly2..molly8
are pre-created for convenienceLoop:slice(...)
method particularly cares. Otherwise this is arbitrary lua code getting sequencedmolly:<tab>
(press the tab
key) to get a list of all the methodsall
object wrapper. This can be used on a table of objects and then call a given method on each of them. Examples:
all{a,b,c}:play()
– Play all three loops, equivalent of a:play();b:play();c:play()
all{a,b,c}:play(1):amp(1, 10)
– Chained command that both plays and fades-in the loopsall(loops):amp(0, 10)
– All the pre-created loops are stored in loops
, this fades them all out!all(mollies):stop()
– All the pre-created mollies are stored in mollies
, this stops them all!When developing you can run the UI directly on your laptop, but you’ll need to use docker or install dependencies (nodejs/npm). You get live-reload of changes and such. Slightly-evil the dist
dir is then checked in to git for serving from the norns/maiden webserver. For the UI (a VueJS app):
cd ui
# Direct
npm install
npm run dev
# OR Docker, if you like
docker-compose up
On the norns side we need to run the lua server-side. During dev I do it this way:
# Auto-push to norns
./util/watch-sync.sh