Thank you for the input @zebra. After thinking about this a bit more and discussing with @dani_derks (thanks dan!), I have come up with an alternative approach, adding a trigger in the EDIT menu so users can themselves reset parameter values related to the number of PSETs they have added or deleted. It isn’t quite as elegant as what I had in mind but doesn’t require any changes to core norns code.
Thanks for the info @zebra. I might try that. Do you know of any example scripts that use this method that I could pick apart?
1 Like
zebra
966
dunno if any scripts are specifcally using the softcut buffer just as a renderer. (it is a bit weird.)
here are scripts from the community repo that define a softcut render callback:
emb@inkpad:~$ cd norns-all-scripts/
emb@inkpad:~/norns-all-scripts$ ack event_render
beets/beets.lua
34:softcut.event_render(function(ch, start, i, s)
thirtythree/lib/renderer.lua
26: softcut.event_render(function(ch,start,i,s)
cryptkeeper/cryptkeeper.lua
229: softcut.event_render(on_render)
larc/larc.lua
245: softcut.event_render(on_render)
amen/amen.lua
126: softcut.event_render(function(ch,start,i,s)
abacus/abacus.lua
364: softcut.event_render(update_render)
cheat_codes_2/lib/start_up.lua
39: softcut.event_render(on_render)
downtown/downtown.lua
488: softcut.event_render(on_render)
3 Likes
lol it is weird…but I am using the softcut buffer only as a renderer in thirtythree and amen (if in playback mode)…
you might checkout amen as an example of this. amen plays a sound in the engine and the engine sends back position info via osc which is displayed on the waveform. unfortunately my code go a little convoluted on the way to my goal but I’m happy to explain pieces. feel free to reuse anything you see.
1 Like
Cool, thanks very much, @zebra and @infinitedigits! Good to know it’s possible.
hey, what’s the proper way to define a param’s behavior? rather than a min and a max on a number, i’d like to set a range and have each option be * or / 2. (so, 0.25, .5, 1, 2, 4…). or even better, represent as a fraction (1/4, 1/2, 1, 2…) i’m sure it’s easy, just couldn’t puzzle it out from the docs.
also—how might one map a set of values and option labels separately? as in, the value in code (let’s say a number) does not match the value the user sees, which could be a string label.
cheers!
1 Like
you could use an option type param.
local my_options = {"1/4", "1/2", "1", "2"}
local function option_handler(v)
if v == 1 then
-- do 1/4 stuff here
elseif v == 2 then
-- do 1/2 stuff here
end
end
function init()
param:add_option("this_option", "this option", my_options, 1)
param:set_action("this_option", function(v) option_handler(v) end)
end
this is also a way to handle your second question. the user will see what’s in the options table, you will get the index of the user selection and then act accordingly in your param action/handler function.
3 Likes
thank you! managed to puzzle it out somewhat last night but very nice to see it written out properly 
one more: running these lines…
screen.circle(64, 12, 10)
screen.stroke()
…within my redraw() function produces no errors and draws a circle (with an undesired line) but causes the screen to become unresponsive until i restart norns. any idea why that might be? drawing text works fine. in fact, drawing any type of shape (even copying the rect function directly from docs) causes the rest of my redraw function not to run and locks up the screen…
zebra
972
maybe share the entire script?
Hello, my Norns just got delivered a few weeks ago and I am loving it! I’ve been digging deep into the documentation and trying to write my first lua script but running into a small issue that may be more of a math problem rather than coding.
I’m trying to write a function that will draw/graph a smooth line with an array of points. See item 1 in the following image:
Using the screen.line() function I am able to send my function an array of y coordinate points but this doesn’t create a smooth line, but one with sharp points (as expected). See item 2 in image above.
function init()
pointsArray={}
for i=1,32 do
pointsArray[i] = math.random(1, 64)
end
end
function redraw()
screen.clear()
drawGraph(pointsArray)
screen.update()
end
function drawGraph(pointsAry)
screen.move(0,32)
for i=1,#pointsAry do
screen.line(i*4,pointsAry[i])
end
screen.stroke()
end
I was thinking I could use screen.pixel() instead and write a function that draws individual pixels along the curve, but not sure how to calculate the points in between with a curve added to them.
I saw there’s a lib.Graph library in the Norns api. I’ve been trying to get it to work but can’t seem to get it to show anything on the screen. I see it has options like add_point() and remove_point() and an option to make the line warp “exp” rather than linear which I would imagine makes it rounder. Would this be a better option? If so, are there any simple code examples of it being used?
2 Likes
hi hi! hope all’s well + welcome! 
I think the Graph lib is going to be more what you’re after, though you’re right that there aren’t many documented uses – it’s mostly used in Passersby and Timber (both by @markeats , who wrote the Graph library) to draw nicely curved lines for waves, though!
in case it helps you start poking, I pulled out some of the stuff from Passersby and approximated that first shape:

this stuff is always super over my head, so thankful for any additional insights from folks who are minded this way!
4 Likes
Oh wow, yeah that’s super helpful! Just got it running on my Norns.
This is definitely a good jumping off point. Now that I have something at least showing on the screen, I can explore the functions in the api. I’ll check out Passerby and Timber as well.
Thanks again! 
1 Like
is there a way to determine when a softcut voice has reached the end of a loop?
2 Likes
zebra
977
I’m afraid not at the moment. On the feature list.
You can slap something together from the phase polls. I think usual suspects @dani_derks and @infinitedigits have explored this in their scripts and may have more specific observations
2 Likes
ok, thank you. i’ll experiment.
1 Like
I haven’t done this exactly, but I would probably go about it like this now:
softcut.event_phase(function update_positions(i,x)
-- keep track of voice (i) current position (x) and last position
-- if not reversing and current position < last position, then its looped
-- if reversing and current position > last position, then its looped
-- set last position to current position
end)
softcut.poll_start_phase()
in oooooo I actually just keep track of time (duration playing loop / loop duration) which works well for recording but wouldn’t use generally for playback (it is a missing feature actually in oooooo, which I might add soon so loop resets can trigger other loops without having to quantize to the clock).
@zebra - I’m interested in implementing something like this for softcut! like via a callback that gets fired when the loop turns over. just finished the Stroustrup book and took a look at softcut to get a feel for how to do it. if its within scope I’m happy to give it a try. however, I go back and forth thinking that maybe the phase poll (like above) is just well suited to do it too.
2 Likes
thanks @infinitedigits! i’ll give this a try.
EDIT: after much head scratching I got this working for loops going forwards and backwards. thanks again for the help!
EDIT 2: at the moment, the only time the approach isn’t working is when I have voices going in opposite directions but I imagine with a little thought/assistance or a lot of random code generation I can get this to work.
1 Like
zebra
981
I also go back and forth. Implementing it on crone side is basically simple. But there are edge cases and limitations that are not so great:
-
updates happen at most once per audio block. So no gain in timing resolution over phase poll…
-
on the other hand, loops in softcut can be arbitrarily small (1 sample…) Of course we can’t fire OSC packets and callbacks that fast, and even once per block would likely overwhelm the network stack and lead to dropped commands, borked timing, and possible soft locks of norns ui
I tend to think in practice it will work just as well to take a phase poll and look for sign changes in delta(phase)… And at the same time script author can choose appropriate quantum for the application… But i have not actually tried building on this assumptions, maybe it would be onerous. And certainly can imagine times when you want to act on loop points, but an appropriately small phase poll quantum would be exorbitant. (this last possibility would be the main motivation to adding a dedicated OSC trigger from the audio process.)
2 Likes
zebra
982
the warp option is for changing the scaling of one or both axes, which is probably not what you want. what you probably do want if you’re using Graph.new is to set style to spline.
under the hood this uses screen.curve to connect points by bezier splines. screen.arc may also be of interest to you.
and finally, if you do want to just generate 128 rendered points directly, look into interpolation methods. specifically, interpolation by cubic polynomials
-- interpolate between y1 and y2, x in [0, 1]
function interp_cubic(x, y0, y1, y2, y3)
-- 4-point, 3rd-order hermite interpolant:
-- [[
-- all steps:
local c0 = y1
local c1 = 0.5 * (y2 - y0)
local c2 = y0 - 2.5 * y1 + 2. * y2 - 0.5 * y3
local c3 = 0.5 * (y3 - y0) + 1.5 * (y1 - y2)
return ((c3 * x + c2) * x + c1) * x + c0
--]]
-- inlined:
return (((0.5 * (y3 - y0) + 1.5 * (y1 - y2)) * x + (y0 - 2.5 * y1 + 2. * y2 - 0.5 * y3)) * x + 0.5 * (y2 - y0)) * x + y1
end
or maybe by cosines:
-- interpolate between y0 and y1, x in [0, 1]
function interp_cos(x, y0, y1)
local f = math.cos((x+ 1) * math.pi) * 0.5 + 0.5
return y0 + (y1 - y0) * f
end
3 Likes
– original post –
Flora attempts to extend Graph to create a class called ArbitraryGraph that allows shapes with an arbitrary number of curve segments. Flora uses this to create multisegment envelopes (see Envelope.lua for those details).
I’m not sure if this is useful to you, but just wanted to point out another example that leverages @markeats super awesome code.
– new post on a new topic –
I am writing a new script that includes rerouting the norns’ connection graph and I’d like to share the results of my (somewhat meager) explorations.
Rerouting norns connections has been discussed in a couple of other places:
The secret sauce @dani_derks is referring to is found in @infinitedigits stonesoup script. This script forms much of the foundation for the script I’m writing. Combining @infinitedigits ‘secret sauce’ with the suggestion given by @zebra in the first quote above, I’ve been able to create a parameter that provides three options for how the audio connections to SuperCollider:in are connected:
- audio in + softcut output → supercollider
- audio in → supercollider
- softcut output → supercollider
Here's the code:
rerouting_audio = false
function route_audio()
clock.sleep(0.5)
local selected_route = params:get("audio_routing")
if rerouting_audio == true then
rerouting_audio = false
if selected_route == 1 then -- audio in + softcut output -> supercollider
--print(1)
os.execute("jack_connect crone:output_5 SuperCollider:in_1;")
os.execute("jack_connect crone:output_6 SuperCollider:in_2;")
os.execute("jack_connect softcut:output_1 SuperCollider:in_1;")
os.execute("jack_connect softcut:output_2 SuperCollider:in_2;")
elseif selected_route == 2 then --just audio in -> supercollider
--print(2)
os.execute("jack_connect crone:output_5 SuperCollider:in_1;")
os.execute("jack_connect crone:output_6 SuperCollider:in_2;")
os.execute("jack_disconnect softcut:output_1 SuperCollider:in_1;")
os.execute("jack_disconnect softcut:output_2 SuperCollider:in_2;")
elseif selected_route == 3 then -- just softcut output -> supercollider
--print(3)
os.execute("jack_disconnect crone:output_5 SuperCollider:in_1;")
os.execute("jack_disconnect crone:output_6 SuperCollider:in_2;")
os.execute("jack_connect softcut:output_1 SuperCollider:in_1;")
os.execute("jack_connect softcut:output_2 SuperCollider:in_2;")
end
end
end
params:add{
type = "option", id = "audio_routing", name = "audio routing",
options = {"in+cut->eng","in->eng","cut->eng"},
min = 1, max = 3, default = 1,
action = function(value)
rerouting_audio = true
clock.run(route_audio)
end
}
Also, anyone doing similar rewiring in a script should reset the connections to their defaults in the script’s cleanup function, like:
Cleanup
function cleanup ()
os.execute("jack_disconnect softcut:output_1 SuperCollider:in_1;")
os.execute("jack_disconnect softcut:output_2 SuperCollider:in_2;")
os.execute("jack_connect crone:output_5 SuperCollider:in_1;")
os.execute("jack_connect crone:output_6 SuperCollider:in_2;")
end
This code was just written in the past couple of hours and hasn’t yet been tested super extensively but it appears to do what I was intending.
10 Likes