Softcut - time streeeeetch?


  • 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!


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

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

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


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?)


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)


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

1 Like


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



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…)


Are you looking for constant pitch or proportional to rotation?

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


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…


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.