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?
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 ![]()
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
That really cool, I like it!