Is sample rate slideable?

Is the sample commend’s rate param slideable?

I’d like to code up a tape stop/scratch type manipulation of a sample. Is that possible in sonic pi?

Hi Harry,

sorry to sound like the grammar police - by sample rate do you mean the speed of sample playback, or the actual samplerate (as in the bitcrusher FX opt)? Because sample_rate and sample_rate_slide are 2 options with that effect. But updating sample_rate while a sample is still playing would be a challenge :frowning:

PD-Pi

I mean the rate: param to the sample command.

Sorry Harry, I had a go, and am unable to get that opt to slide - I can get it to change though, obv.

PD-Pi

Well, that’s a bummer.

@samaaron any chance of making this option slideable?

In the meanwhile, I think I can fudge up a granular-type pseudo-scratch. I’ll try my hand at it and see how it goes.

Hi, I remember way back when I was designing the sample synths that I couldn’t make it slidable whilst having all of the other functionality. It was a necessary compromise.

At some point I’ll have another look at this - it would certainly be possible to make another sample synth that did support sliding but didn’t support other opts.

If I recall correctly back then I was also limited to making all synth plays have a known duration trigger time. Being able to change the rate of a sample’s playback would completely contravene that.

So, whilst it’s a simple request the solution is unfortunately not at all simple given constraints of the existing design.

It’s definitely something I want to tackle head on in Tau5 though.

I kinda figured.

Let me tackle the granular approach, and see how it sounds. The math should be pretty straightforward.

Well, I got it working! It scratches just as I hoped it would.

I’m working on a massive update for YummyFillings.rb, and it will be incorporated into that.

It’s got some dependencies on other libraries within YummyFillings.rb, but you can grab the current working version from github, at:

..and here’s the code I came up with. (Obviously, remap the path to yummyfillings.rb to where you have it on your system):


eval_file "e:/yummy/yummyfillings.rb"

bar = 16.0
whole = 4.0
half =2.0
quarter =1.0
eighth =0.5
sixteenth =0.25
dotted =1.5
triplet =2.0 / 3
halftone = 1
second = 2
wholetone = 2
min3 = 3
maj3 = 4
fourth = 5
tritone = 6
fifth = 7
min6 = 8
maj6 = 9
min7 = 10
maj7 = 11
octave = 12


define  :slidesamplespeed do |thissample, startingoffset=0, samplespeeds=[1, 0], duration=quarter, grains=16, **kwargs|
  eval overridekwargs(kwargs, method(__method__).parameters)
  cleanargs = stripparams kwargs, method(__method__).parameters
  debugprint "top of slidesamplespeed"
  debugprint "thissample: ", thissample
  debugprint "startingoffset: ", startingoffset
  samplespeeds = [samplespeeds] if !ringorlist samplespeeds
  debugprint "samplespeeds: ", samplespeeds
  debugprint "duration: ", duration
  debugprint "grains: ", grains
  debugprint "kwargs: ", kwargs
  debugprint "cleanargs: ", cleanargs
  
  beatsinsample = cleanargs[:beat_stretch] || cleanargs[:pitch_stretch] || (sample_duration thissample)
  debugprint "beatsinsample: ", beatsinsample
  sign = 1
  sniddler = 0
  
  nearzero = 0.00000001
  legs = samplespeeds.length - 1
  debugprint "legs: ", legs
  debugprint "too few speeds, ending" if legs == 0
  return false if legs == 0
  
  chunksize = duration / (legs * beatsinsample * grains)
  debugprint "chunksize: ", chunksize
  
  
  currentoffset = startingoffset
  debugprint "#currentoffset: ", currentoffset
  samplespeeds.each_cons(2) do |startspeed, stopspeed|
    debugprint "#startspeed: ", startspeed
    debugprint "#stopspeed: ",  stopspeed
    speedline = (line startspeed, stopspeed, steps: grains).to_a
    debugprint "#speedline: ", speedline
    speedline.each do |thisspeed|
      debugprint "#thisspeed: ", thisspeed
      debugprint "#currentoffset: ", currentoffset
      nextoffset = currentoffset + (chunksize * thisspeed * sign)
      debugprint "#nextoffset: ", nextoffset
      if nextoffset > 1
        debugprint "exceeded 1, about to wrap"
        sniddler = nextoffset - 1
        nextoffset = 1
      elsif nextoffset < 0
        debugprint "exceeded 0, about to wrap"
        sniddler = 0 - nextoffset
        nextoffset = 0
      end #if exceeded bounds of 1 or 0
      
      thisspeed = nearzero * sign if thisspeed == 0
      thiscmd =  "sample thissample, start: " + currentoffset.to_s + ", finish: " + nextoffset.to_s + ", rate: " + (thisspeed * sign).to_s + ", **cleanargs"
      debugprint thiscmd
      eval thiscmd
      
      if sniddler != 0
        debugprint "got a sniddler, flipping signs"
        sign *= -1
        debugprint "sign: ", sign
        debugprint "playing the sniddler"
        thiscmd =  "sample thissample, start: " + nextoffset.to_s + ", finish: " + (nextoffset + (sniddler * sign)).to_s + ", rate: " + (thisspeed * sign).to_s + ", **cleanargs"
        debugprint thiscmd
        eval thiscmd
        debugprint "adjusting nextoffset"
        nextoffset += (sniddler * sign)
        debugprint "nextoffset: ", nextoffset
      else
        debugprint "no sniddler, normal processing"
      end
      sample thissample, start: currentoffset, finish: nextoffset, rate: thisspeed, **cleanargs
      thisspeed = 0 if thisspeed.abs == nearzero
      thiscmd = "sleep " + chunksize.to_s
      debugprint thiscmd
      eval thiscmd
      currentoffset = nextoffset
    end #each thisspeed
  end #each_cons startspeed, stopspeed
  
  true #return value
end #define slidesamplespeed




slidesamplespeed :loop_drone_g_97, samplespeeds: [1, 0, 2, 0, 3, 0, 4, 0, 1], duration: whole, beat_stretch: 4




Kind of gnarly… I had to make it flip and play backwards when it overran 1 or 0, so it’ll scratch back and forth for a long phrase against a short sample.

Love to hear feedback. I haven’t done much testing, but I thought I’d share, just as a proof of concept.

Okay, after more testing & debugging I got it working well.

eval_file "e:/yummy/yummyfillings.rb"

bar = 16.0
whole = 4.0
half =2.0
quarter =1.0
eighth =0.5
sixteenth =0.25
dotted =1.5
triplet =2.0 / 3
halftone = 1
second = 2
wholetone = 2
min3 = 3
maj3 = 4
fourth = 5
tritone = 6
fifth = 7
min6 = 8
maj6 = 9
min7 = 10
maj7 = 11
octave = 12


define  :slidesamplespeed do |thissample, startingoffset=0, samplespeeds=[1, 0], duration=quarter, grains=16, **kwargs|
  eval overridekwargs(kwargs, method(__method__).parameters)
  cleanargs = stripparams kwargs, method(__method__).parameters
  debugprint "top of slidesamplespeed"
  debugprint "thissample: ", thissample
  debugprint "startingoffset: ", startingoffset
  samplespeeds = [samplespeeds] if !ringorlist samplespeeds
  debugprint "samplespeeds: ", samplespeeds
  debugprint "duration: ", duration
  debugprint "grains: ", grains
  debugprint "kwargs: ", kwargs
  debugprint "cleanargs: ", cleanargs

  beatsinsample = cleanargs[:beat_stretch] || cleanargs[:pitch_stretch] || (sample_duration thissample)
  debugprint "beatsinsample: ", beatsinsample
  sign = 1
  sniddler = 0

  nearzero = 0.00000001
  legs = samplespeeds.length - 1 
  debugprint "legs: ", legs
  debugprint "too few speeds, ending" if legs == 0
  return false if legs == 0 

  sleepchunk = duration / (legs * grains)
  chunksize = sleepchunk / beatsinsample
  debugprint "chunksize: ", chunksize
  debugprint "sleepchunk: ", sleepchunk
  
  
  currentoffset = startingoffset
  debugprint "#currentoffset: ", currentoffset
  samplespeeds.each_cons(2) do |startspeed, stopspeed|
    debugprint "#startspeed: ", startspeed
    debugprint "#stopspeed: ",  stopspeed
    speedline = (line startspeed, stopspeed, steps: grains).to_a 
    debugprint "#speedline: ", speedline
    speedline.each do |thisspeed|
      debugprint "#thisspeed: ", thisspeed
      nextoffset = currentoffset + (chunksize * sign * thisspeed)
      debugprint "#nextoffset: ", nextoffset
      if nextoffset > 1  
        debugprint "exceeded 1, about to wrap"
        sniddler = nextoffset - 1
        nextoffset = 1 
      elsif nextoffset < 0 
        debugprint "exceeded 0, about to wrap"
        sniddler = 0 - nextoffset  
        nextoffset = 0
      end #if exceeded bounds of 1 or 0

      thisspeed = nearzero * sign if thisspeed == 0  
      thiscmd =  "sample thissample, start: " + currentoffset.to_s + ", finish: " + nextoffset.to_s + ", rate: " + (thisspeed * sign).to_s + ", **cleanargs"
      debugprint thiscmd
      eval thiscmd
      sleepytime = sleepchunk

      if sniddler != 0
        debugprint "got a sniddler, flipping signs"
        sign *= -1  
        debugprint "sign: ", sign
        debugprint "playing the sniddler"
        thiscmd =  "sample thissample, start: " + nextoffset.to_s + ", finish: " + (nextoffset + (sniddler * sign)).to_s + ", rate: " + (thisspeed * sign).to_s + ", **cleanargs"
        debugprint thiscmd
        eval thiscmd
        debugprint "short sleep"
        thiscmd = "sleep " + (sleepchunk - (sniddler * beatsinsample)).to_s   
        debugprint thiscmd
        eval thiscmd
        debugprint "#setting sleepytime to sniddler"
        sleepytime = sniddler * beatsinsample
        debugprint "#adjusting nextoffset"
        nextoffset += (sniddler * sign)
        debugprint "#nextoffset: ", nextoffset
      else
        debugprint "#no sniddler, normal processing"
      end
      sample thissample, start: currentoffset, finish: nextoffset, rate: thisspeed, **cleanargs
      thisspeed = 0 if thisspeed.abs == nearzero
      thiscmd = "sleep " + sleepytime.to_s
      debugprint thiscmd
      eval thiscmd
      debugprint "#resetting sleepytime"
      sleepytime = sleepchunk  
      currentoffset = nextoffset 
    end #each thisspeed
  end #each_cons startspeed, stopspeed  

  true #return value
end #define slidesamplespeed




slidesamplespeed :loop_amen_full, samplespeeds: [1, 0, 2, 0, 3, 0, 4, 0, 1], duration: bar, beat_stretch: 16


1 Like

That really cool, I like it!