(norns/circle/01) drone in three worlds

I’ve gotten the first bit of functionality down, minus an issue with levels, which you can reproduce with the code below:

-- K3 invert level

level = 1.0

function key(n,z)
  if z == 1 then
    if n == 3 then
      level = 1.0 - level
      softcut.level(1, level)
    end
  end
end

function init()
  file = _path.code .. "nc01-drone/lib/eb.wav"
  softcut.buffer_read_mono(file, 0, 0, -1, 1, 1)
  softcut.enable(1, 1)
  softcut.buffer(1, 1)
  softcut.loop(1, 1)
  softcut.loop_start(1, 0)
  softcut.loop_end(1, 3)
  softcut.position(1,1)
  softcut.rate(1, 1.0)

  softcut.level_slew_time(1, 10.0)
  softcut.level(1, 0.0)
  softcut.play(1, 1)
  softcut.level(1, level)
end

When the above script starts, it starts playing at full blast - I was expecting it to ramp up.
I then press K3, and get the 10-second slope until sound goes out, as I expected. Pressing K3 again, however, takes it straight back to 1.0, no slewing.

Is this the expected behaviour, or am I overlooking something obvious?

unfortunately we can’t presently guarantee that the slew and level commands are processed in the order they were issued, without a workaround like adding a short delay on the lua side.

here’s an example with short delay

-- K3 invert level

level = 1.0

function key(n,z)
  if z == 1 then
    if n == 3 then
      level = 1.0 - level
      softcut.level(1, level)
    end
  end
end

-- helper
function delay(func, time)
    if time == nil then time = 0.005 end
    local m = metro.init(func)
    m:start(time, 1)
end

function init()
  file = _path.code .. "nc01-drone/lib/eb.wav"
  softcut.buffer_read_mono(file, 0, 0, -1, 1, 1)
  softcut.enable(1, 1)
  softcut.buffer(1, 1)
  softcut.loop(1, 1)
  softcut.loop_start(1, 0)
  softcut.loop_end(1, 3)
  softcut.position(1,1)
  softcut.rate(1, 1.0)
  softcut.level(1, 0.0)
  softcut.play(1, 1)
  softcut.level_slew_time(1, 10.0)
    delay(function() 
    softcut.level(1, level)
  end)
end

i forgot that i promised @dan_derks i’d add a combined command for envelopes to make this use case easier, since he ran into the same thing in cheatcodes.

now that it’s on my mind i’ll look right now. there might be a simpler backend change i could make that would (at least) guarantee slew commands are always executed before level commands if they arrive in the same control period.

(and actually now that i’m debugging at this i’m not exactly sure why the slew isn’t taking effect first, since the commands are arriving in the correct order. so i’ll continue to look at it. would be nice if it’s simply a case of re-ordering some statements in the audio thread.)

long-term, i do have a design that will replace OSC for this transport layer and make everything more efficient and predictable. it’s on the list.

3 Likes

there’s another quirk to note about the level fades: they are simple 1-pole smoothers in the linear amplitude domain. as a consequence, fade-ins sound much, much faster than fade-outs.

actually, looking closely at this it’s worse than i realized.

the up/down ramps in the linear domain look like this:


but the real problem is that in the perceptual loudness domain (exponential), the exponential decay curve becomes linear, but the exponential attack curve is doubly exponential:


opened GH issue.

9 Likes

Wow, a picture is indeed worth a thousand words. Excellent stuff, thank you for taking the time to explain this.

Alright, so I have my first draft finished, and I’m pretty excited. But I have a question:
The way I’m changing filter frequency is pretty brute-force. Is there a way to slew it so the changes in frequency are smoother? maybe controlspec? I know it will do exp freq mapping, which will help a bit, but I am just wondering if there’s an additional way.

function enc(n,d)
    if  n == 2 then
          filter_freq = util.clamp(filter_freq + d*50, 500, 12000)
          softcut.post_filter_fc(i,filter_freq)
    end
end

EDIT: Question 2: I’m attempting to cycle through changing worlds with modulo. But my math is definitely wrong.

function key(n,z)
    if n == 3 then
    -- new world (mode_number from 1..3)
        if z == 1 then
            mode_number = ((mode_number + 1) %3) + 1
            print(mode_number)
        end
    end
end

makes the number decrement by 1 each time. If I do %6 or some number larger than 3, I see that the number is incrementing by 2 each time.
Am I wrong in thinking that the modulo should be applied first, and then scaled up by 1? ex. (x+1)%3 = 0…2 +1 = 1…3

no sorry, no smoothing on filters now, too expensive, may update, search around for details

qeuestion 2:

3 Likes

Thanks for going to the trouble of digging up your replies. I’ll try searching more first before I ask another question! Also, amazing work with softcut. It’s a real joy to work with.

yr most welcome.

also, i think you just want mode_number = (mode_number %3) + 1 - you were incrementing twice.

break down

x        -- in range [1, 3]
+ 1      -- maps to range [2, 4]
- 1      -- you need this to get back in range [1, 3] before taking modulo!
% 3      -- maps [1, 3] -> [0, 2]
+ 1      -- now back to  [1, 3]
2 Likes

just under a month to go!

curious to hear how people are doing— have the softcut studies been clear?

any questions we can answer? anyone need suggestions?

1 Like

I haven’t even started yet :grimacing: but I plan on getting things going tonight!

1 Like

me: if I have 4-5 hours to spend on Norns scripting I’ve got a ton of things to do to Kria and at least two other scripts that need to be finished and published

also me:

(the three worlds sounds a bit similar in that video but they aren’t - and they all bear quite long term listening - I’ve found myself just leaving it on)

I’m way too lazy to have gone through and curated loops etc - all three worlds are aleatoric systems that evolve over time - all seem kind of slow to respond to the controls in that video since they are designed to be slow moving but they have a strong influence over time!

It’s been a fantastic way of learning softcut!

Need to fix a stupid bug in the world switching and make the visualisations now

8 Likes

Was enjoying working on it until i stupidly broke my norns and had to ship it off for repair - i’m an idiot.

I referred a lot to the softcut studies and to scripts like MLR and Cranes/Cheat codes - i’d say these were more useful resources than the API documentation as i work better with seeing real use-cases. Will be super curious to dig into the repo when everyone pushes up their scripts!

1 Like

I think in general the API docs can be kind of mysterious until you’ve seen an example. I still ended up digging around in other peoples source code (but FULL marks to maiden for making that trivial - it’s a lovely environment to mess about in for the Lua side).

Essentially the studies are a good introduction but as soon as you want to go a bit deeper the docs are a little brief. On the other hand it’s so easy to look at source and examples it’s not too bad

(I probably should add that compared to an awful lot of things I’ve worked on over the years it is absolute luxury in terms of documentation. :slight_smile: )

1 Like

Here’s a video I just recorded on my phone. I think my first attempt is done.
A huge thanks to @tehn, @zebra, @neauoire, and anyone else who has contributed to the tutorials.

I figured out some basic approaches/strategies I want to take in the future – for instance, making parameters that are adjusted by encoders and used for graphics scaled from 0. - 1., and then mapping them separately for the sonic and the animated elements.

I am curious – I read somewhere about making all variables and functions local to avoid messing with system vars. I’ve also noticed this in a lot of the scripts that are already out there. Should I just make everything that I declare local? And for instance, is local scope at the top level accessible from functions that are declared/nested below it? Sorry if these terms aren’t making sense; here’s an example:

local billy = 10
local function addBilly(arg)
    local foo = arg + billy
    return foo
end

Is that all copacetic? Can I add billy or do I need to keep that var global? Thank youuuu

Edit: also, do I need to do anything in particular to get my script to go into Tape? I’m not seeing levels when I’m making sound. maybe this is a DIY shield thing, or a config thing?

Edit 2: Also THIS IS SOOOO FUUNNNNN

14 Likes

maybe worth noting that i did invest a little time in making the softcut API doc comments slightly more verbose in latest update / master branch.

but yea it does need a manual. there are a number of little caveats and gotchas.

for example @andrew recently ran into (i think) the hard limit on rate, which is a ratio of +/- 64 (or 6 octaves.) exceeding this will crash the audio process! (woops.) (fix in flight)

4 Likes

so in general it is a good idea to keep scope in mind and make things local when they can be. in particular it’s a really good idea to keep vars in functions local.

but actually @ppqq and @tehn made a really nice change a while back which is to swap out the global environment between scripts. this means you can absolutely declare globals with impunity in scripts, and in fact this is now recommended practice for top-level variables. (it is less verbose and makes it so you can access those vars in the REPL.)

you still do need to watch out for overriding fields in system globals. there’s not really a feasible way for us to prevent you from, say, breaking the menu by saying menu.redraw = nil or something.

i believe that in the present version of lua you can always access locals declared in surrounding scopes. (in older versions there were weird edge cases with nested functions.)

oh but @wheelersounds i guess i should say that in response to that example (and i know it’s not real, so this is just a point for discussion/pedantry), IMHO it’s much better practice to make functions that don’t rely on any implicit knowledge of state outside their scope. so in that case, to just pass billy as a function parameter.

in the extreme case, you can make all your functions immutable (they don’t modify anything and just return whatever mutated state you need), then you’re doing Functional programming. (lua helps you with this because you can’t pass parameters by reference, and functions can return multiple values and/or tables.)

--- @returns new billy
function addBillly(arg, oldBilly) return oldBilly + arg end

or if you find yourself wanting to mutate external state a lot, without copying big tables on return, then you can make functions into “methods” that operate on specific data structures. then you’re doing Object Oriented programming. (lua helps with this by providing the “colon” syntax that hides a “self” argument)

Thing = { billy = 10 } 
--- (... insert boilerplate: Thing factory, &c)
--- mutates the `billy` of a Thing
function Thing:addBilly(arg) self.billy = self.billy + arg end

those programming patterns arose because things become pretty difficult to manage without them.


no, it should just work. but, what levels? TAPE doesn’t have a dedicated level meter since it just takes the main output bus. you mean you don’t see the output levels you’re hearing? and/or, you get silence in the recording even though output is audible?

anyone who hasn’t yet might like to check out the routing diagrams
https://github.com/monome/norns/blob/master/doc/crone/crone-process-routing.pdf

6 Likes

yeah i noticed just after writing the above, super appreciate that - especially knowing now what are 1-indexed etc

Thank you again for being so helpful.

I realized that the TAPE output was all the way down in the mixer. I cranked it and now I can listen to everything!

deadline is march 1 (let’s say end-of-day) which is next sunday!

gatherings

  • saturday feb 22 1-3pm EST
  • saturday feb 29 time TBA

basically i’ll be here working on my script, but fully available to help debug and answer questions in realtime here in the thread. if anyone wants to livestream during this time it could be fun to peek in on progress.

but in the meantime, please post questions and really hoping to see some creations.

and remember that keeping it simple is totally perfect! :star:

8 Likes

tomorrow 1-3pm EST!

13 Likes