Softcut - time streeeeetch?

So,

  • Pitch is tied to playback rate in softcut. I’m into it
  • wanted to ask what the code might look like to create a pitch corrected rate adjustment, presumably using a granular scrubbing technique. Haven’t quite wrapped my head around metro functionality yet but assume this would be a place to start, although interested to see different takes if there are some. Time stretch has all sorts of flavors.
  • any performance issues to look out for?

Thanks—hope this can be educational!

2 Likes

with one voice, it’s not gonna be granular exactly, there’s no overlap (ed. or rather, there’s at most “grains” ever overlapping,) but you can make a scrubby guy, which moves along at an arbitrary speed uncorrelated with the read/write speed

something like

--- load buffer etc..
sc = softcut -- type less
voice = 1
pos = 1 -- start position
r = 1/4 -- scrub rate
dt = 1/10 -- grain interval
inc = r * dt
grain_func = function()
   sc.position(voice, pos)
   pos = pos + inc
end

m = metro.init(grain_func)
m.time = dt
m:start()   

more things:

  • randomize grain rate / pitch / pos increment
  • use more voices with round-robin assignment

maybe be useful to know that you can set loop to 0 for a voice, and move position, loop_start, loop_end together to make a moving one-shot playback interval

12 Likes

found this thread searching for a way to “scrub” through a section of a softcut buffer.

Ideally I’d like to assign an encoder to scrub through the buffer - at whatever rate the encoder is turned, only when the encoder is turning. How would I stop the playhead in this case? (you can’t detect if enc() is zero I think)

Seems like this would not be a metro/clock solution tho (unless there’s a metro/clock running that turns off the playhead every x ticks?)

Suggestions?

2 Likes
pos = 0
size = 0.01

function enc(n,d) 
    if n=1 then 
       pos = pos + n 
       softcut.loop_start(1, pos)
       softcut.loop_end(1, pos + size)
    end 
end

?

wasn’t sure if you meant granular style or handcrank cassette tape style

1 Like

Thx!

I think more this.

ah - a more interesting challenge. maybe the solution would be more like:

  • function enc(n, d) x = x + d end
  • calculate the rate of change v for x over time interval t
  • set softcut rate to v
  • there will be a lag == t ?

I don’t think that would be too much code but my brain is literally too sleepy to think of it right now

(EDITS)

2 Likes

You tried Reels? It does exactly this.

Not sure it does - I want to manually advance the playhead

(but gonna go dig in the reels code now…)

4 Likes

Are you looking for constant pitch or proportional to rotation?

proportional to rotation, but that part seems easier to manipulate with rate(voice,rate)

2 Likes

Ah, yes, you’re right, you actually control the speed, not the position, which makes it sound similar, but in practice, is not. Sorry for the red herring.

1 Like

I do believe Manglr does this, though. I’m using it with my arc now, it even has a feature called Scrub, where you simply slide across the sample back and forth with the first arc wheel. Not sure that experience translates without an arc, tho.

1 Like

seems like different things:

@circuitghost , angl / mangl are using supercollider GrainBuf under the hood in a straightforward way.

upthread, i pointed out that softcut isn’t designed for “granular” processing in the sense of large polyphony, but for 2-voice polyphony it is simple: use a short loop_start / loop_end, set loop flag to zero for one-shot mode, send pos(voice, x) repeatedly, advancing x. you could get more polyphony by using multiple voices in round robin. but the API surely doesn’t make this very convenient.

@okyeron i think you mean an something like the scrubbing playhead in Reaper or other DAWs, which is like a jog wheel for a virtual audio tape. there exists the concept of an “edit cursor.”

i would just use the encoder to select the rate, with rate smoothing. (probably using a JI table.)

and if you wish, use the phase poll with a small quantum, to update the “edit cursor” position from the actual softcut voice phase.

(alt mode: set the cursor position independently, so by default it restarts from same pos each time; this is also a common DAW option.)

when rate specified by the rate encoder goes from zero to nonzero[^], send pos(voice, cursor).

(alt mode: require explicit keypress or something to restart.)

another thing that the API doesn’t really provide is the ability to just say, “stop playback now (after fadeout.)” which could be added.


[^] here is where you might want some timing logic; e.g. only restart if timeout has elapsed between encoder movements. metro seems fine for this…

3 Likes

Aha! I didn’t think of using rate = 0 to stop the playback. Excellent. Thx!

Yes. This is what I was thinking of.

Sounds handy. Would that be different than just setting play to zero?

Would an optional parameter to play for a specific amount of time be useful? Or perhaps this could be accomplished with loop points if you could specify the number of loops (like just one)? oops… re-read the above and loop(voice, 0) does the one-shot mode I was thinking of. doh!

(still trying to wrap my head around all the options here - deep stuff!)

apologies for bumping a very old thread - wondering if anyone has implemented a softcut scrubbing scheme as sketched out above that i could crib? currently trying to get a set up where i could variably speed up a sample from its original rate up to 4x it while maintaining its starting pitch, not sure if my basic math is bad but i can’t seem to get it working how i’d like.

@mei can you post your code? It may help with feedback. I experimented with a very clunky version of this last night that I can share, but I don’t want to poison the well right off the bat; your approach might hold better promise.

1 Like

ah, actually revisited it tonight with fresh eyes and realized i was just making a silly mistake (and maybe misunderstanding softcut) - what i wanted was to increment the position by a set amount regardless of the rate of the buffer, and have that rate control the pitch without affecting the speed of that increment. i think my problem was misunderstanding softcut’s .position - i had assumed the argument was in seconds, and correlated to the portion being ‘played’, so i was trying to adjust the speed of the increment through a buffer’s position based on the buffer rate, which seems wrong and gave me wonky results now that i’m looking at it. but for the sake of clarity, am i right in assuming that position is the same across buffers, despite their different rates?

1 Like

the position command initiates an immediate crossfade to a new position, which is also clamped to loop_end and loop_start. (really should have named this something like region instead of loop maybe.) so one possible approach is to just repeatedly send position with a changing value. i think that’s what i demo’d / suggested.

but the other way, what you probably want, is just to modulate loop_start and loop_end together with the loop flag engaged.

i’ll try and put together a more explicit demo for this kind of “phonogenic” lo-fi time stretch.

3 Likes