ooh that is cool! i might suggest the lazy way to do this. you could have both channels do the polling always, from the init. then, only use the specified when when primed (i.e. there should be a check during the callback to see if its activated).

1 Like

Hmm so after playing around with this a bit I’m not sure I understand your suggestion. It looks to me like the input monitoring happens at the beginning of the init() function and triggers the tape_rec function when input is detected. If I poll both channels in the init script then won’t either input trigger recording when primed (even if audio is routed to the correct channel). I may be thinking of this wrong though!

EDIT: left my branch at “works as long as you use the left input” until I can figure this out. I’ll get there, just need a fresh look at it this weekend.

1 Like

could you link me your branch? i can help there directly. my solution probably wasn’t clear (and not entirely sure it will work) but my initial idea is to have two listeners, but each is only activated according to L/R/stereo:

  -- listen to audio
  -- and initiate recording on incoming audio
  p_amp_in=poll.set("amp_in_l")
  -- set period low when primed, default 1 second
  p_amp_in.time=1
  p_amp_in.callback=function(val)
    for i=1,6 do
      if uS.recording[i]==1 and not params:get("input type")==RIGHTONLY then
        -- print("incoming signal = "..val)
        if val>params:get("rec thresh")/1000 then
          tape_rec(i)
        end
      end
    end
  end
  p_amp_in:start()

  p_amp_in2=poll.set("amp_in_r")
  -- set period low when primed, default 1 second
  p_amp_in2.time=1
  p_amp_in2.callback=function(val)
    for i=1,6 do
      if uS.recording[i]==1 and (params:get("input type")==RIGHTONLY or params:get("input type")==STEREO) then
        -- print("incoming signal = "..val)
        if val>params:get("rec thresh")/1000 then
          tape_rec(i)
        end
      end
    end
  end
  p_amp_in2:start()

then, basically every p_amp_in reference in the code (one in tape_stop_rec and one in tape_arm_rec) also need to be modified to check the logic of which p_amp_in should be used based on the input type.

Ahh, that was the route I was heading down last night almost exactly, and I figured out that I needed to add logic to tape_rec_arm but hadn’t gotten to tape_stop_rec. I was trying to put the decision logic that checked params up in the init script and now that I think about it that’s not needed as long as the logic exists downstream.

Probably not gonna have time to take another swing at it until this evening or tomorrow but I think it will work.

For the checking logic in tape_stop_rec and tape_rec_arm I can’t think of a more clever way than two if statements, can you? “If parameter is 1 or 4 then left is valid, if parameter is 2 or 4 then right is valid” else nothing is valid (to account for tape only).

Edit realized there are more conditions and my if logic is weird. That’s what I get for trying to think before I made coffee / type complex things into my phone.

1 Like

that sounds fine to me!

take your time! thanks so much for your contribution :slight_smile: i think its an excellent addition

I really appreciate your patience and guidance!

1 Like

Got it working, your idea was the right way to go. I found one other instance of the callback to the polling method in tape_rec.

Draft PR is here:

Some questions:

  1. Should I add a input (1+2) option? I didn’t because as much as I love options, 4 felt like about at the point where I’d suggest we break out tape and inputs into separate parameters but that’s just me
  2. I’m not super crazy about the labels I chose for the parameters I added, do you have any suggestions? I’m not sure why I dislike them so much, lol.

Also happy to make whatever other changes you’d like to see.

I could see this feature evolving to allow you to set the inputs per-loop, which would be kind of wild. Seems like it wouldn’t be hard to implement, but I can’t wrap my head around how to expose the settings in a way that aren’t super annoying. One thing at a time I suppose!

1 Like

Since the update, I am not able to load my old save.
I don’t record my old preset before upgrade Please, is there a way for loading It ?
Thanks

1 Like

yeah unfortunately i didn’t write a way to convert the old saves into the new saves. the new saves don’t dump the whole softcut buffer so they will take up 10x less space usually, allowing for way more saves (no limit now). to use the old saves you can just clone the previous released version of oooooo.

cd ~/dust/code/
rm -rf oooooo
git clone https://github.com/schollz/oooooo
cd oooooo
git checkout v1.2.0

probably should’ve tagged this new version as v2 since its not compatible with old saves, sorry about that.

1 Like

Thanks for the code review, I think I fixed up my PR so that it’s at least working properly.

In my testing I found something: I find that even when the script switches into “fast” polling that I still miss the first transient if it’s fast enough (a drum hit, etc). Wondering where the delay is since the polling rate in rec arm should be preeeettty fast (assuming polling time is in seconds). Issue doesn’t seem to be the volume pinch, since I’ve reduced it to 0.

1 Like

@infinitedigits ok perfect :ok_hand: . Thank you for your help.
I will make it :blush:
Cheers

edit: Aaah All is here :slight_smile:

1 Like

@kingmetal thanks for the pr! i’ll make release soon with it :slight_smile: such a great addition.

can you try seeing what happens if you make it even faster? like 0.01?

I did and it didn’t seem to help. I also set the softcut.phase_quant to 0.01 and didn’t notice any difference, but I’m not entirely sure what that parameter does to be honest.

Even tried 0.001 across all three parameters and didn’t notice a difference. I do see increased CPU usage between 0.025 and 0.01, which I imagine is expected, but it’s still not catching the beginning of a snare or kick impulse from a drum machine.

Setting the three values to 0 was interesting and caused a HUGE CPU spike (up to 50%) and some crashing, lol.

I’ll have to dig around a bit more. I almost wonder if the issue isn’t polling rate but is the volume pinch staying engaged? I need to play with it more, but considering that the sample always sounds smooth and pop free I wonder if setting the volume pinch to 0 isn’t working. Just a guess.

This is a really cool way to learn more about the Norns, thanks for providing the vehicle for me to finally write some Lua!

2 Likes

the softcut phase poll reports the real current “logical phase” of a softcut voice, reflecting realtime rate changes and so on.

the phase update quantum is a quantization applied to the phase before updating on changes. its unit is seconds. (reliably firing changes when the phasor actually crosses phase quantum boundaries is only possible if the poll time is driven from the DSP like this.)

so for each audio block let’s call its index i, current phase is p_{i} and your phase quantum is q, lets call the quantized phase for that block pq_{i} = \lfloor p_{i} / q \rfloor * q. an update is fired whenever pq_{i} \neq pq_{i-1}.

in practical terms, if you set q=0.01s you will get 100 phase updates per second when rate=1, twice that when rate=2 and so on.

setting the quantum too small will indeed cause a flood of traffic, and quant=0 will attempt to update on every audio block.

sorry, i can’t speak to how phase update is used in oooooo, i’d guess just for drawing. i’d expect the amp polls are what would inform auto-record behavior. those are polled in a more straightforward manner from supercollider control busses, and the only relevant parameter is the poll period.

i believe the control busses are only updated at the top of each audio block so there will be some jitter. if you really want to make a nice autostart it could be a custom engine firing on Onsets.kr.

1 Like

Thank you for that explainer @zebra. I understood at least some of what you just said, lol.

Sounds like maybe phase_quant is being set in oooooo but isn’t necessarily being used (since there’s no waveform visualization that I am aware of). Perhaps setting this to a slow value or removing it altogether would save some CPU time.

Do the supercollider control busses have lower limits to their polling rate? Or do they work like phase_quant and 0 is realtime and any value greater than 0 is accepted?

Well, the volume pinch theory isn’t panning out, I commented out the logic in the tape_rec function (and the tape_stop_rec function just for good measure, although I suspect it was necessary) and I’m still missing the transient.

I’ll keep digging. Makes me wonder what the minimum input latency to the record buffers is. I may have to play with other recorders like Reels to see if I’m getting similar issues.

EDIT: Similar behavior in Reels (although I am catching slightly more of the transient). Seems like maybe this might be an overall system latency thing!

1 Like

i know there is a lot written above, but i’m afraid having a hard time parsing it.

if you don’t mind stating, in one place and as clearly as possible:

  • what you are trying to do
  • what you have tried already

(and this thread may not be the best place for such a tangent, really)

then i can probably help. specifically i can speak to audio latency and control jitter and suggest some methods.

but here’s my attempt at a general purpose answer:

  • for transient-triggered capture to really work, you want to introduce extra latency in the audio path where the buffer is actually written, while minimizing it in the onset detection->control path

  • since (as you suppose) there is inherent delay and jitter, as well as fixed latency, in the chain [analysis DSP -> script -> softcut actions], the extra capture latency you add needs to err on the longer side. (a few 10s of milliseconds should do it though - at a guess.)

  • likewise, it’s important to use the right tool for the job in onset detection, which is probably Onsets.kr, but there is generally a latency/accuracy tradeoff here as well.

  • for reference, i believe the audio buffer latency is 128 samples for capture and 256 for round-trip the way norns JACK is set up. there is also some ADC capture latency but that is very small.


overall, if transient-triggered capture is to be a core feature of oooooo, i’d suggest that it use a supercollider engine to generate onset triggers and to apply an extra delay stage before feeding the softcut buffers. (alternatively, i’d accept a PR to crone that added a simple delay line before the softcut input sinks, and/or a PR to norns/sc/CroneAudioContext that added an opt-in, system-level onsets poll to complement the pitch poll.)

1 Like

You’re absolutely right, I think that I’ve wandered a bit away from a coherent problem statement. I think your general purpose answer covers my question very well and pointed me in the right direction. Just for clarity though, I’ll rephrase my problem:

Problem
I find that oooooo “misses” recording of fast initial transients. The testing I’ve been doing has been with a Volca Sample playing short kick and snare samples into oooooo. Input detection works as I would expect but playback tends to miss most of the first transient.

Troubleshooting Steps
I’ve tried:

  • Reducing the polling time below 0.025 to 0.01, 0.001 and even 0 - relevant original code
  • Bypassed the volume pinching feature in the tape_rec function to ensure that it wasn’t interfering with the recording - relevant original code
  • Tested an older version of oooooo to make sure that my changes to the input handling didn’t muck things up :slight_smile:

What you say about input buffering makes perfect sense and I suspect that’s where my issue is. I also suspect that I am the only person who probably cares about this feature. The only reason I even noticed is that I had a Volca Sample near me when I needed to test the new input selection logic I’d written and so I happen to notice the problem right away.

I’m certainly in over my head at this point, but this is an interesting research topic for me. I’m not sure that going from “writing simple input logic” to “using a different Super-collider Class to do faster input detection and write a delay into the softcut input sinks” is necessarily a good thing for me to tackle as my second piece of Lua / first ever supercollider project, but I’m certainly going to do some research and hopefully ask some halfway decent questions in the future.

And agreed this might have strayed too far outside of scope of oooooo, since what I am experiencing does not seem to be oooooo specific.

Thank you for your detailed and thoughtful responses, this is a fantastic way for me to learn!

1 Like

I also suspect that I am the only person who probably cares about this feature

i also care about this feature :). i really really appreciate the exploration you commenced @kingmetal and your strategies for investigation. its impressive that this is your second piece of lua! i’d love to help too to make this better.

it would be interesting to try Onsets.kr though unless its latency overall is ~ms between detection and recording it might stlil be problematic.

i think an optimal solution might might entail the following (well this is how i interpreted what @zebra said about adding extra latency): say you “arm” a loop that is 4 seconds long. “arming” the loop turns on the onset detection to start recording when onset is detected. however, to keep transients, “arming” should also immediately start recording in a tiny loop at the front of the full loop (maybe 100 ms loop?). then, when onset is detected, the tiny loop is expanded to the full 4 second loop with a start position changed to when the onset was detected, minus a few tens of milliseconds to compensate for latency. (though not sure how to be quantitative about latency compensation).

3 Likes

you could certainly try and hack something like that where you effectively using a softcut voice as an input delay. it will be a little clunky, using phase polls, maybe glitching on loop points, etc.

the lowest-hanging fruit would i think be an engine that just adds some delay to the ADC, and continue using the amplitude poll:

Engine_SimpleDelay : CroneEngine {
  var synth;

  *new { arg context, doneCallback;
    ^super.new(context, doneCallback);
  }

  alloc { 
    synth = { arg delay=0.01;
      var input = SoundIn.ar([0, 1]);
      DelayC.ar(input, maxDelayTime:0.1, delayTime:delay)
    }.play(context.server);

    this.addCommand("delay", "f", { arg msg; synth.set(\delay, msg[1]); });
  }
}

and make sure engine->softcut level is up and adc->softcut is down.

this would just be a way to quickly test if that approach is enough to get the job done.

[ed] oh lastly, (this for @infinitedigits), i meant to say that i have looked at the volume pinch code, and this is a good opportunity to say:

usually its better to leave rec (the recording flag) enabled all the time for voices that will be using it, and just set rec_level and pre_level. these are affected by recpre_slew_time, which has the same intention as your volume-pinch parameter (smooths out discontinuities when toggling record), but can do the job more reliably and efficiently on the DSP side. rec=0 and pre=1 has exactly the same effect as setting the flag to zero.

(more control over the slew shape is on the softcut-major-update milestone.)

2 Likes