Here’s the content for YummyFillings.rb.
##| YummyFillings.rb
##| Useful constants and functions for sonic pi
##| Harry LeBlanc 2024, gnu public license
##| HarryLeBlancLPCC@gmail.com
##| useful constants for specifying time intervals in notes
##| assumes one beat is a quarter note
##| allows combinations like dotted * eighth or quarter * triplet
whole = 4.0
half =2.0
quarter =1.0
eighth =0.5
sixteenth =0.25
dotted =1.5
triplet =2.0 / 3
debugmode = true
#scrub
#small utility function to clean nils with the specified value (defaults to "")
define :scrub do |value, cleanvalue = ""|
(value == nil ? cleanvalue : value)
end
##| debugprint is a utility function to optionally print out debugging messages,
##| controlled by the debugmode variable. If not set, defaults to false and prints nothing.
##| label: a text string to explain what the value means.
##| value: the value being displayed for debugging purposes. If nil, just displays the label.
define :debugprint do |label, value=nil|
if !local_variables.include? "debugmode".to_sym
debugmode = false
end #if debugmode defined
if debugmode
puts scrub(label).to_s + scrub(value).to_s
end #if debug
end #define
##| samplebpm -- utility to return the bpm of any sample loop.
##| thissample: the sample to extract the bpm from.
##| beats: the number of beats used to calculate bpm. Defaults to 4
##| example:
##| puts samplebpm :loop_amen
##| puts samplebpm :loop_amen_full, 16
define :samplebpm do |thissample, beats=4|
with_sample_bpm thissample, num_beats: beats do
current_bpm
end
end
##| transposesample
##| transposes a sample up or down by specified rpitch, while pitch_stretching to keep tempo.
##| You may need to fiddle with time_dis, window_size and pitch_dis to tweak the sound.
##| example:
##| mysample = "D:\\Loops\\Afroplug - Soul and Jazz Guitar Loops\\looperman-l-6258600-0353860-spilled-coffee.wav"
##| [90, 120, 150].each do |thisbpm|
##| [0, -5, 3, 7].each do |thispitch|
##| use_bpm thisbpm
##| transposesample mysample, 16, thispitch
##| sleep 16
##| end
##| sleep 2
##| end
define :transposesample do |thissample, pitch_stretch, rpitch, \
time_dis=0.01, \
window_size=0.1, \
pitch_dis=0.01|
ratio = midi_to_hz(60 + rpitch) / midi_to_hz(60)
puts "ratio: " + ratio. to_s
sample thissample \
, pitch_stretch: pitch_stretch * ratio \
, rpitch: rpitch \
, time_dis: time_dis \
, window_soze: window_size \
, pitch_dis: pitch_dis
end
##| envelope -- applies an adsr envelope to any slideable param on any synth note or sample.
##| best results when you set the sample/note's modulated value to the startlevel when playing the sample/note,
##| otherwise you'll hear an audible glitch at the beginning of the sound.
##| handle -- the node returned by sample/play commands.
##| param -- the parameter being modulated by the envelope.
##| attack -- attack time, in beats.
##| decay -- decay time, in beats.
##| sustain -- sustain time, in beats.
##| relase -- release time, in beats.
##| startlevel -- the level at the bottom of the attack phase. Scaled to what the param expects.
##| peaklevel -- the level reached at the top of the attack phase, before gliding down to the sustain phase.
##| sustainlevel -- the level sustained during the sustain phase
##| Example:
##| use_bpm 60
##| use_synth :bass_highend
##| handle = play 60, sustain: 8, decay: 8,res: 0.7
##| puts "handle: " + handle.to_s
##| env(handle, "drive", 1, 1, 3, 3, 0, 5, 3)
define :env do |handle, param, attack=0.25, decay=0, sustain=1, release=0.25, startlevel=0, peaklevel=1, sustainlevel=0.5|
slideparam = param + "_slide"
in_thread do
#attack phase
puts "attack phase"
cmd = "control handle, " + slideparam + ": " + attack.to_s + ", " + param + ":" + startlevel.to_s
puts cmd
eval cmd
sleep attack
#decay phase
puts "decay phase"
if decay > 0
puts "got decay time"
cmd = "control handle, " + slideparam + ": " + decay.to_s + ", " + param + ":" + peaklevel.to_s
puts cmd
eval cmd
sleep decay
end #if decay > 0
#sustain phase
puts "sustain phase"
cmd = "control handle, " + param + ":" + sustainlevel.to_s
puts cmd
eval cmd
sleep sustain
#decay phase
puts "decay phase"
cmd = "control handle, " + slideparam + ": " + decay.to_s + ", " + param + ": " + sustainlevel.to_s
puts cmd
eval cmd
sleep decay
#post-decay phase
puts "post-decay phase"
cmd = "control handle, " + param + ": " + startlevel.to_s
puts cmd
eval cmd
sleep decay
end #thread
end #define
##| lfo -- provides an all-purpose lfo for any slideable param for any synth note or sample.
##| best results when you set the sample/note's modulated value to the startlevel when playing the sample/note,
##| otherwise you'll hear an audible glitch at the beginning of the sound.
##| handle: the node returned when playing a note or sample.
##| param: the parameter being modulated by the lfo.
##| duration: how long the lfo effect will last.
##| period: the period(s) of the lfo cycle.
##| Can be a single value, a list/ring, or a comma-delimited string with symbolic values.
##| e.g. "w,dq,ht,4s".
##| w for whole.
##| h for half.
##| q for quarter.
##| e for eighth.
##| s for sixteenth.
##| d for dotted (can be stacked).
##| t for triplet.
##| [0-9]* for how many reps (4w is four whole notes).
##| one beat is a quarter note.
##| span: the lower and upper limits of the lfo sweep. Can be a pair of values, or a ring/list, or a comma-delimited list.
##| examples:
##| use_bpm 120
##| use_synth :bass_highend
##| handle = play 60, sustain: 8, decay: 0,res: 0.7, amp: 0
##| puts "handle: " + handle.to_s
##| lfo handle, "amp", 10, "q,q,e,e,e,e", "0,1,0,0.5,0,0.5", "square"
##| handle = sample :ambi_drone, pitch_stretch: 4
##| lfo handle, "amp", 4, "e,e,s,s,s,s", "0,1,0,0.5,0,0.5", "square"
define :lfo do |handle, param, duration, period=[0.5], span=(ring 0, 1), lfotype="triangle", delay=0, rampupperiods=0, rampdowntime=0, lfocurve=0|
#force args to rings
lfotype = [lfotype].flatten.ring
lfocurve = [lfocurve].flatten.ring
puts "span: " + span.to_s
puts "lfotype: " + lfotype.to_s
puts "lfocurve: " + lfocurve.to_s
slideparam = param + "_slide"
shapeparam = slideparam + "_shape"
curveparam = slideparam + "_curve"
loops = 1
downramp = 0.0
rampratio = 1.0
timetorampdown = duration
lastspan = 0.0
if period.is_a? String
debugprint "period is a string"
mylist = []
period.split(",").each do |item|
debugprint "item: ", item
triplet = 1
dots = 1
thisnumber = ""
notetime = 0
restsign = 1
item.chars.each do |letter|
debugprint "letter: ", letter
case letter
when "r"
restsign = -1
when "w"
notetime += 4.0
debugprint "w ", notetime
when "h"
notetime += 2.0
debugprint "h ", notetime
when "q"
notetime += 1.0
debugprint "q ", notetime
when "e"
notetime += 0.5
debugprint "e ", notetime
when "s"
notetime += 0.25
debugprint "s ", notetime
when "d"
dots *= 1.5
debugprint "d ", dots
when "t"
triplet = 2.0 / 3
debugprint "t ", triplet
when /\d/, "."
thisnumber = thisnumber + letter
debugprint "thisnumber: " + thisnumber
else
debugprint "garbage letter, ignoring"
end #case letter
end #each letter
notetime *= restsign
debugprint "notetime: " , notetime
debugprint "thisnumber: " , thisnumber
debugprint "triplet: " ,triplet
debugprint "dots: ", dots
thisnumber = ( thisnumber == "" ? 1 : thisnumber.to_f )
notetime *= dots * triplet * thisnumber
debugprint "final notetime: " , notetime
mylist << notetime
debugprint "mylist: ", mylist
end #each item
period = mylist.ring
debugprint "period: ", period
else
debugprint "period is not a string"
period = [period].flatten.ring
end #if period is a string
debugprint "period: ", period
if span.is_a? String
debugprint "span is a string"
mylist = []
span.split(",").each do |item|
mylist << item.to_f
end #each item
span = mylist.ring
else
debugprint "gutter is not a string"
span = [span].flatten.ring
end #if span a string
debugprint "span ", span
if rampdowntime > 0
puts "calculating ramp ratio"
tempduration = duration
tempperiod = period.to_a.ring #to force new object
while tempduration > 0 do
puts "tempduration: " + tempduration.to_s
if tempduration <= rampdowntime
timetorampdown -= tempperiod.tick
downramp += 1
end #if
end #while
rampdownratio = 1 / rampdownloops
end #if calc rampdowntime
in_thread do
#initialize param at first span
##| puts "initial setting of param"
##| shape = 1 #stub it out, makes no difference
##| cmd = "control handle, " + param + ": " + span.look.to_s + ", " + slideparam.to_s + ": " + period.look.to_s + ", " + shapeparam.to_s + ": " + shape.to_s + ", " + curveparam.to_s + ": " + lfocurve.look.to_s
##| puts cmd
##| eval cmd
sleep delay
duration -= delay
while duration > 0 do
print "loop " + loops.to_s
puts "look: " + look.to_s
case lfotype.look[0..2].downcase
when "tri"
puts "triangle"
shape = 1 #linear
when "saw"
puts "saw"
shape = 1 #linear
when "sin"
puts "sine"
shape = 3 #sine
when "smo"
puts "smooth random"
shape = 3 #sine
when "ran"
puts "random"
shape = 3 #sine
when "ste"
puts "step random"
shape = 0 #step
when "squ"
puts "square"
shape = 0 #step
when "cus"
puts "custom"
shape = 5 #custom
else
puts "garbage, defaulting to triangle"
shape = 1 #for garbage
end
if rampupperiods > 0 and loops <= rampupperiods
rampratio = loops / rampupperiods
rampup -= 1
end #if rampup
if duration <= rampdowntime
puts "time to ramp down"
rampratio = downramp * rampdownratio
downramp -= 1
end #if ramping down
case lfotype.look[0..2].downcase
when "ran", "smo", "ste"
puts "random style lfo"
thisvalue = rrand(lastspan, span.look)
else
puts "toggle style lfo"
thisvalue = span.look
end #case lfo type
puts "thisvalue: " + thisvalue.to_s
puts "rampratio: " + rampratio.to_s
thisvalue *= rampratio
puts "adjusted thisvalue: " + thisvalue.to_s
puts "this period: " + period.look.to_s
cmd = "control handle, " + slideparam + ": " + period.look.to_s + ", " + shapeparam + ": " + shape.to_s + ", " + param + ": " + thisvalue.to_s + ", " + curveparam + ": " + lfocurve.look.to_s
puts cmd
eval cmd
if lfotype.look == "saw"
puts "saw, jumping to next span"
cmd = "control handle, " + slideparam + ": " + period.tick.to_s + ", " + shapeparam + ": 0, " + param + ": " + span.tick.to_s + ", " + curveparam + ": " + lfocurve.look.to_s
puts cmd
eval cmd
else
puts "not saw"
end #if saw
puts "about to sleep " + period.look.to_s
sleep period[loops -3]
lastspan = span.look
duration -= period[loops -3]
loops += 1
tick
puts "bottom of while loop"
end #while
end #thread
end #define
##| spreadtobeats -- a utility function designed to take a spread,
##| and convert it to a string of comma-delimited beat values to feed into arrange.
##| thisspread: the ring of booleans produced by the spread function, mapping the beats.to_s
##| beatvalue: duration of each beat, defaults to sixteenth
##| Example:
##| spreadtobeats spread(3, 8, 2), 0.5
define :spreadtobeats do |thisspread, beatvalue=sixteenth|
puts "thisspread: " + thisspread.to_s
puts "beatvalue: " + beatvalue.to_s
beats = ""
isnote = false
duration = 0
comma=""
whole = 4.0
half =2.0
quarter =1.0
eighth =0.5
sixteenth =0.25
dotted =1.5
triplet =2.0 / 3
firstrest = true
puts "beatvalue: " + beatvalue.to_s
puts "sixteenth: " + sixteenth.to_s
chunk = (beatvalue / sixteenth).to_i
puts "chunk: " + chunk.to_s
thisspread.each do |thisoneisnote|
if thisoneisnote #got a new note, finish old note
puts "got a new note"
duration *= chunk #in case overrode beatvalue
beats += comma
comma = ","
if firstrest
beats += "r"
end
firstrest = false
{whole=>"w", half=>"h", quarter=>"q", eighth=>"e", sixteenth=>"s"}.each do |size,code|
puts "size: "
puts size
puts "code: "
puts code
(duration / size).times do
beats += code
end #looping on size
duration = duration % size
puts "beats: " + beats
end #each beat size
duration = 0
end #if got a new note
duration += 1
end #each note
if duration > 0
beats += comma
if firstrest
beats += "r"
end
duration *= chunk
{whole=>"w", half=>"h", quarter=>"q", eighth=>"e", sixteenth=>"s"}.each do |size,code|
puts "size: "
puts size
puts "code: "
puts code
(duration / size).times do
beats += code
end #looping on size
duration = duration % size
puts "beats: " + beats
end #each beat size
end #if got a last note
beats
end #define
##| eucliciate: a utility function wrapping spreadtobeats, bypasses need to create spread.
##| beats: how many beats to play.
##| duration: how many beats in the whole cycle.
##| rotations: how many offsets for the euclidean rhythm.
##| chunk: how big is each beat; defaults to sixteenth (0.25)
##| Example:
##| euclidiate 3, 8, 2, 0.5
define :euclidiate do |beats,duration,rotations=0,chunk=sixteenth|
spreadtobeats spread(beats,duration).rotate(rotations), chunk
end
##| trancegate -- a trancegate that manipulates the volume up and down. Defaults to square wave, but you can use other lfo shapes.
##| note that the trancegate does not work in the release section, so arrange your sounds accordingly.
##| also, please set your initial amp: setting to match the maxvol param, to avoid glitches.
##| handle: the node returned by sample or play commands.
##| duration: how long the effect lasts. Should line up with sustain of played sound.
##| Please note that the effect does not work on the decay phase.
##| period: how long the gate lasts. Can be a single value, a ring/list, or a comma-delimited list.
##| maxvol: the max amplitude when the gate is open. Defaults to 1.
##| minvol: the min amplitude when the gate is closed. Defaults to 0.
##| gutter: how long the silence lasts between chunks. Can be a single value, list/ring or comma-delimited list.
##| lfotype: defaults to square, but supports all lfotypes.
##| curve: lfo type curve param. Used for custom lfo types
##| Examples:
##| use_bpm 120
##| use_synth :bass_highend
##| handle = play 60, sustain: 16, decay: 1,res: 0.7, amp: 0
##| puts "handle: " + handle.to_s
##| trancegate handle, 16, euclidiate("s", 16, 5)
##| handle = sample :ambi_drone, 16
##| trancegate handle, 16, euclidiate("s", 16, 5)
define :trancegate do |handle, duration, period=[0.5], gutter=[0.1], delay=0, maxvol= [1], minvol=[0], lfotype="square", curve=0|
#cook args to rings
debugprint "handle: ", handle
debugprint " duration: " , duration
debugprint "period: ", period
debugprint "delay: ", delay
debugprint "maxvol: ", maxvol
debugprint "minvol: ", minvol
debugprint "lfotype: " , lfotype
debugprint "curve: " , curve
puts "top of trancegate function, cooking args"
debugmode = true
debugprint "are we printing in debug?"
if period.is_a? String
debugprint "period is a string"
mylist = []
period.split(",").each do |item|
debugprint "item: ", item
triplet = 1
dots = 1
thisnumber = ""
notetime = 0
restsign = 1
item.chars.each do |letter|
debugprint "letter: ", letter
case letter
when "r"
restsign = -1
when "w"
notetime += 4.0
debugprint "w ", notetime
when "h"
notetime += 2.0
debugprint "h ", notetim
when "q"
notetime += 1.0
debugprint "q ", notetime
when "e"
notetime += 0.5
debugprint "e ", notetime
when "s"
notetime += 0.25
debugprint "s ", notetime
when "d"
dots *= 1.5
debugprint "d ", dots
when "t"
triplet = 2.0 / 3
debugprint "t ", triplet
when /\d/, "."
thisnumber = thisnumber + letter
debugprint "thisnumber: " + thisnumber
else
debugprint "garbage letter, ignoring"
end #case letter
end #each letter
notetime *= restsign
debugprint "notetime: " , notetime
debugprint "thisnumber: " , thisnumber
debugprint "triplet: " ,triplet
debugprint "dots: ", dots
thisnumber = ( thisnumber == "" ? 1 : thisnumber.to_f )
notetime *= dots * triplet * thisnumber
debugprint "final notetime: " , notetime
mylist << notetime
debugprint "mylist: ", mylist
end #each item
period = mylist.ring
debugprint "period: ", period
else
debugprint "period is not a string"
period = [period].flatten.ring
end #if period is a string
debugprint "period: ", period
if gutter.is_a? String
debugprint "gutter is a string"
mylist = []
gutter.split(",").each do |item|
mylist << item.to_f
end #each item
gutter = mylist.ring
else
debugprint "gutter is not a string"
gutter = [gutter].flatten.ring
end #if span a string
debugprint "gutter ", gutter
if maxvol.is_a? String
debugprint "maxvol is string"
maxvol = maxvol.split(",").map do |x| x.to_f end
else
debugprint "maxvol not a string"
maxvol = maxvol.flatten.ring
end #if maxvols is string
debugprint "maxvol: ", maxvol
if minvol.is_a? String
debugprint "minvol is string"
minvol = minvol.split(",").map do |x| x.to_f end
else
debugprint "minvol not a string"
minvol = minvol.flatten.ring
end #if maxvols is string
debugprint "maxvol: ", maxvol
if lfotype.is_a? String
debugprint "lfotype is a string"
lfotype = lfotype.split(",").ring
else
debugprint "lfotype not a string"
lfotype = [lfotype].flatten.ring
end #if lfotype a string
debugprint "lfotype: ",lfotype
if curve.is_a? String
debugprint "curve is a string"
curve = curve.split(",").ring
else
debugprint "curve not a string"
curve = [curve].flatten.ring
end #if lfocurve a string
puts "curve " + curve.to_s
slideparam = "amp_slide"
shapeparam = "amp_shape"
curveparam = "amp_curve"
in_thread do
sleep delay
duration -= delay
while duration > 0 do
debugprint "top of while loop, duration ", duration
tick
if period.look > 0
case lfotype.look[0..2].downcase
when "tri"
debugprint "triangle"
shape = 1 #linear
when "saw"
debugprint "saw"
shape = 1 #linear
when "sin"
debugprint "sine"
shape = 3 #sine
when "smo"
debugprint "smooth random"
shape = 3 #sine
when "ran"
debugprint "random"
shape = 3 #sine
when "ste"
debugprint "step random"
shape = 0 #step
when "squ"
debugprint "square"
shape = 0 #step
when "cus"
puts "custom"
shape = 5 #custom
else
debugprint "garbage, defaulting to square"
shape = 0 #step
end
debugprint "maxvol: ", maxvol.look
debugprint "period: ", period.look
debugprint "gutter: ", gutter.look
debugprint "shape: ", shape
debugprint "curve: ", curve
cmd = "control handle, amp: " + maxvol.look.to_s + ", amp_slide: " + (period.look - gutter.look).to_s + ", amp_slide_shape: " + shape.to_s + ", amp_slide_curve: " + curve.look.to_s
debugprint "up cmd: ", cmd
eval cmd
sleep period.look - (gutter.look)
cmd = "control handle, amp: " + minvol.look.to_s + ", amp_slide: " + (gutter.look).to_s + ", amp_slide_shape: " + shape.to_s + ", amp_slide_curve: " + curve.look.to_s
debugprint "down cmd: ", cmd
eval cmd
sleep gutter.look
else
sleep period.look.abs
end #if period is positive
duration -= (period.look.abs + gutter.look)
debugprint "bottom of while loop"
debugprint "duration: ", duration
end #while
end #thread
end #define