Norns 2.0: softcut

Just wondering… I don’t suppose there’s also a “filter cutoff slew” control that would be possible to expose to lua?

no, there isn’t. filter coefficient is relatively expensive to recalculate and so it is not slewed. considering an approximation that would make per-sample modulation more reasonable. downsides are reduced frequency range and accuracy.

2 Likes

Hello All,

I have an observation I could use help sorting. I have a simple looper script that:

  1. E3 to start recording, E3 to stop recording and set the end of loop.
  2. E3 to start/stop overdub by simply toggling soft cut.rec.

If while overdubbing the running loop crosses back over the punch in time (i.e. at least one full loop while overdubbing), the new sounds are recorded in time with the loop as I created them. However, if I punch in and out shorter than one full loop, the new material is placed in the loop at t = start_of_loop + (new_sounds_time - punch_in_time).

I could constrain punch out to > one loop length, but wondering if folks have ideas for keeping time for quick punch ins? Could I be missing a soft cut setting somewhere?

Thanks!

Edit: here’s the script.

Summary
-- Simple Looper
--
local amirecording = 0
local base_loop = 0
local end_of_loop = 61
local time_ref = 0

function init()
  softcut.buffer_clear()
  audio.level_adc_cut(0.75)
  for i = 1,2 do
    if (i % 2) == 0 then
      softcut.level_input_cut(2,i,0.75)
      softcut.buffer(i,2)
      softcut.pan(i,0.9)
    else
      softcut.level_input_cut(1,i,0.75)
      softcut.buffer(i,1)
      softcut.pan(i,-0.9)
    end
    softcut.play(i,1)
    softcut.enable(i,1)
    softcut.rec_offset(i,-0.06)
    softcut.level(i,0.75)
    softcut.loop(i,1)
    softcut.loop_start(i,1)
    softcut.loop_end(i,end_of_loop + 1)
    softcut.position(i,1)
    softcut.rate(i,1.0)
    softcut.fade_time(i,0)
    softcut.rec(i,amirecording)
    softcut.rec_level(i,0.7)
    softcut.pre_level(i,0.9)
    softcut.level_slew_time(i,0.5)
    softcut.rate_slew_time(i,0.05)
  end
  redraw()
end

function key(n,z)
    if n==3 and z==1 then
      if amirecording == 1 then
        softcut.rec(1,0)
        softcut.rec(2,0)
        amirecording = 0
        if base_loop == 0 then
          end_of_loop = util.time() - time_ref + 1
          trimmed_loop_end = end_of_loop
          softcut.loop_end(1,end_of_loop)
          softcut.loop_end(2,end_of_loop)
          base_loop = 1
        end
      elseif amirecording == 0 then
        if base_loop == 0 then
          time_ref = util.time()
          softcut.position(1,1)
          softcut.position(2,1)
        end
        softcut.rec(1,1)
        softcut.rec(2,1)
        amirecording = 1
      end
    end
  redraw()
end

function redraw()
  screen.clear()
  screen.aa(0)
  screen.level(10)
  screen.font_size(8)
  screen.move(64,25)
  if amirecording == 0 and base_loop == 0 then
    screen.text_center("K3 to Record")
  elseif amirecording == 1 and base_loop == 0 then
    screen.text_center("Recording ... K3 to Stop")
  elseif amirecording == 0 and base_loop == 1 then
    screen.text_center("Got It ... K3 to Overdub")
  elseif amirecording == 1 and base_loop == 1 then
    screen.text_center("Overdubbing ... K3 to Stop")
  end
  screen.update()
end

Share script please?

Doh! post updated with the script. Thanks!

1 Like

cool, thanks.

well i have good news and bad news.

bad: that does indeed seem like a regression, which i can’t quite explain yet. thanks for spotting it and i’ll try and fix it quickly.

under the hood, there are two R/W head pairs in each voice, for crossfading, and it sounds like the wrong one is getting activated in this condition: enabling record while already playing, and loop/crossfade hasn’t occurred since the last explicit position change / voice-state change / something.

this isn’t too surprising since there are actually different sample callback functions depending on which state the voice is in, this was done relatively recently in a refactor, so i will double check on the logic around that.

good: there is a better way to implement this, which is to use rec_level instead of the rec flag. the former is intended for realtime modulation. the latter should be thought of as a configuration / optimization setting. so here’s a version that seems to work as expected and doesn’t have clicks on punch-in/out:

-- Simple Looper (updated)
--
amirecording = 0
base_loop = 0
end_of_loop = 61
time_ref = 0

pan = {-0.75, 0.75}

function init()
  print("simple hello")
  softcut.buffer_clear()
  audio.level_adc_cut(0.75)
  
  for i = 1,2 do
    softcut.level_input_cut(i,i,1)
    softcut.buffer(i,i)
    softcut.pan(i, pan[i])
    
    softcut.play(i,1)
    softcut.rec(i,1)
    softcut.enable(i,1)
    softcut.rec_offset(i,-0.06)
    
    softcut.pre_level(i,1)
    softcut.rec_level(i, 1)
    
    softcut.level(i,0.75)
    softcut.loop(i,1)
    softcut.loop_start(i,1)
    softcut.loop_end(i,end_of_loop + 1)
    
    softcut.position(i,1)
    
    softcut.rate(i,1.0)
    softcut.fade_time(i,0)
    
    softcut.level_slew_time(i,0.5)
    softcut.rate_slew_time(i,0.05)
  end
  redraw()
end

function key(n,z)
    
    if n==3 and z==1 then
      if amirecording == 1 then
        softcut.rec_level(1,0)
        softcut.rec_level(2,0)
        amirecording = 0
        if base_loop == 0 then
          end_of_loop = util.time() - time_ref + 1
          print("end_of_loop: "..end_of_loop)
          softcut.loop_end(1,end_of_loop)
          softcut.loop_end(2,end_of_loop)
          base_loop = 1
        end
      elseif amirecording == 0 then
        if base_loop == 0 then
          time_ref = util.time()
          softcut.position(1,1)
          softcut.position(2,1)
        end
        softcut.rec_level(1,1)
        softcut.rec_level(2,1)
        amirecording = 1
      end
    end
  redraw()
end

function redraw()
  screen.clear()
  screen.aa(0)
  screen.level(10)
  screen.font_size(8)
  screen.move(64,25)
  if amirecording == 0 and base_loop == 0 then
    screen.text_center("K3 to Record")
  elseif amirecording == 1 and base_loop == 0 then
    screen.text_center("Recording ... K3 to Stop")
  elseif amirecording == 0 and base_loop == 1 then
    screen.text_center("Got It ... K3 to Overdub")
  elseif amirecording == 1 and base_loop == 1 then
    screen.text_center("Overdubbing ... K3 to Stop")
  end
  screen.update()
end

setting pre_level=1 and rec_level=0 makes a lossless loop.

8 Likes

Excellent! Super helpful. Thanks for this @zebra.

Hi guys! I think I have a problem with Softcut. After the last update, apps that use it like MLR, Cranes, Cheat Codes … sometimes stop recording, the app works but doesn’t record. To solve I have to restart Norns. How could I solve it?
Thank’s :slight_smile:

I haven’t experienced this.

Which update exactly?
Maybe re-run the update?

Any other notes to reproduce? (After long uptime? After some interaction, immediately after loading a script, or “out of the blue”?)

If possible, shell into norns when issue occurs and do
sudo journalctl -u norns-crone
and same with norns-jack, norns-matron

Thanks for your interest. The update is 191201. How can i re-run the update? if I do it directly from norns it tells me “up to date”.
I have not yet been able to understand if there is something that creates the problem. When it happens I try to follow your advice!

when i do norns-jack it tell me that

can you run the 191230 update and continue testing from there?

1 Like

I just did the update, for the moment everything seems to be working.
Thanks Tehn! :slight_smile:

are all of the softcut params (controllable via lua) listed in the docs somewhere? I see some/most here and maybe a few more in the studies (note: softcut API docs link at bottom is dead) but I don’t think that’s all of them. diving in again and I think filters have changed over the last year or so?

you’re right - that page is a little sparse and a little stale. honestly not sure we need a “new 2.0 syntax” page at this point.

i also noticed some weirdness with the norns API pages yesterday, but today they appear to be back online here (https://monome.org/norns/) - which is… definitely wrong so maybe @tehn and @dan_derks would like to be aware

here is the generated softcut api docs
https://monome.org/norns/modules/softcut.html

they are also in github

and are just generated from the lua source comments

1 Like

Yeah I ran into this yesterday as well. Glad it wasn’t just me.

mistaken file deletion— i’ve just restored it. hopefully the URLs will return to normal momentarily

2 Likes

cool, that’s what I was looking for @zebra. @tehn I think the link just needs to be updated at the bottom of the studies (to remove /docs from path)

1 Like

done and…deployed!

1 Like

@zebra are there any known bugs around play/record subheads going out of sync ?

running into some bugs using softcut as a loop pedal, overdubs sound like they’re popping up at the wrong points in the buffer and it’s hard to imagine a bug in the script (wrms) causing this

can elaborate / make a gh issue just wanted to check if it’s on the radar already