Here’s the entire script, fyi. NOT ready for prime time, but just to see about the editor bug.
##| arrange
##| allows user to arrange multiple samples to play in time with each other in a single function
##| uses a hashtable where the key is the sample and the value is a comma-delimited list of times.
##| w,h,q,e,s for time divisions, d for dotted, t for triplet. Supports any number of dots.
##| Supports either individual samples or lists of samples (rings or arrays). If a ring or array,
##| a sound is chosen randomly from the list.
##| kick = :bd_ada
##| snare = [:sn_dolf, :sn_dub, :sn_generic, :sn_zome]
##| hat = (ring :hat_bdu, :hat_cab, :hat_cats, :hat_gem, :hat_gnu)
##| use_bpm 120
##| arrangement = {kick =>"q,q,q,q", snare => "rq,h,q", hat => "qt,et,tq,te,qt,et,qt,et"}
##| 4.times do
##| arrangesamples arrangement
##| end
##| arrangement = {kick =>"qd,qd,q", snare => "rq,hs,de", hat => "e,e,e,e,e,e,e,e"}
##| 4.times do
##| arrangesamples arrangement
##| end
eval(File.open("C:\\Users\\harry\\Desktop\\Scripting\\SonicPi\\YummyFillings.rb", "r").read)
define :arrange do |arrangement, defaults, envelopes=nil, lfos=nil, trancegates=nil|
timeline = {} #for the timing of notes
melody = {} #for specified notes
melody[nil] = nil #initialize with stub to simplify code
settings = Hash.new #for instrument/sample-specific settings
envelopes = ( envelopes == nil ? {} : envelopes )
lfos = ( lfos == nil ? {} : lfos )
trancegates = (trancegates == nil ? {} : trancegates )
puts "envelopes"
puts envelopes
puts "lfos"
puts lfos
puts "trancegates"
puts trancegates
instruments = nil
arrangement.each do |synthorsample, instr_times|
puts " "
puts " "
puts "synthorsample " + synthorsample.to_s
puts "instr_times: " + instr_times.to_s
timetillnext = 0
thistime = 0
dots = 0
triplets = 1
takerest = false
tonelist = Array.new
thistone = nil
thischord = nil
thismode = nil
oldtone = nil
oldchord = nil
oldmode = nil
chordtone = nil
instr_times.split(",").each_with_index do |thisnote, howmanytimes| #w, h, q, e, s w/possible suffix of t or d
duration = ""
puts "thisnote: " + thisnote.to_s
puts "thistime: " + thistime.to_s
oldtone = thistone
oldchord = thischord
oldmode = thismode
thisdur, thistone, thischord, thismode = thisnote.split(" ")
if thischord == nil and oldmode != nil and ["arp", "asc", "des", "ran"].include? oldmode[0..2]
puts "grabbing old tone, chord, mode"
thistone = oldtone
thischord = oldchord
thismode = oldmode
end #if subbing in old chord and mode
tonemode = thistone != nil
chordmode = thischord != nil
modemode = thismode != nil
firsttone = nil
puts "thisdur: " + thisdur.to_s
puts "thistone: " + thistone.to_s
puts "thischord: " + thischord.to_s
puts "thismode: " + thismode.to_s
puts "tonemode: " + tonemode.to_s
puts "chordmode: " + chordmode.to_s
puts "modemode: " + modemode.to_s
thisdur.each_char do |letter|
puts letter
case letter.downcase
when "r"
takerest = true
when "w"
timetillnext += 4.0
when "h"
timetillnext += 2.0
when "q"
timetillnext += 1.0
when "e"
timetillnext += 0.5
when "s"
timetillnext += 0.25
when "d"
dots = dots + 1
when "t"
triplets = 2.0 / 3
when /\d/
puts "duration digit"
duration += letter
else
puts letter + " is garbage, ignored"
end #case letter
end #each letter
puts "raw duration: " + duration.to_s
if duration.length > 0
duration = duration.to_i
else
duration = 1
end #if duration length > 0
puts "cooked duration: " + duration.to_s
timetillnext = timetillnext * duration * triplets * (2 - (0.5 ** dots))
puts "timetillnext: " + timetillnext.to_s
if chordmode
puts "chordmode"
puts "thistone: " + thistone.to_s
if chordtone == nil
puts "first tone!"
chordtone = thistone
end #if firsttone nil
chordtone = ( chordtone[0] == ":" ? chordtone.delete_prefix(":").to_sym : chordtone.to_i )
puts "chordtone: " + chordtone.to_s
if chord_names.to_a.include? thischord
puts "chord"
thistone = chord chordtone, thischord
else
puts "scale"
thistone = scale chordtone, thischord
end #if chord or scale
puts "thistone: " + thistone.to_s
if modemode
puts "modemode"
puts "mode " + thismode[0..2]
case thismode[0..2].downcase
when "ran"
puts "random"
thistone = thistone.to_a.shuffle[howmanytimes]
when "asc"
puts "ascending arpeggio"
thistone = thistone[howmanytimes]
when "arp"
puts "ascending arpeggio"
thistone = thistone[howmanytimes]
when "des"
puts "descending arpeggio"
thistone = thistone.to_a.reverse[howmanytimes]
else
puts "chord mode, leave chord intact"
end #if in random mode
end #if mode mode
puts "thistone: " + thistone.to_s
end #if chordmode
if !takerest
puts "scheduling note"
if timeline.has_key?(thistime)
puts "found key " + thistime.to_s
timeline[thistime] << [synthorsample, timetillnext]
else
puts "did not find key " + thistime.to_s
timeline[thistime] = [[synthorsample, timetillnext]]
end #if haskey
puts "timeline: " + timeline.to_s
puts "thistone: " + thistone.to_s
if tonemode
tonelist << thistone
puts "tonelist: " + tonelist.to_s
tonelist.each_with_index do |x, i|
puts "tonelist[" + i.to_s + "]: " + x.to_s
end #each
else
puts "this is a rest, carry thistime to next item in loop to delay start of note"
end #if tonemode
end #if !takerest
thistime = thistime + timetillnext
timetillnext = 0
dots = 0
triplets = 1
takerest = false
end #each note for this instrument
if tonelist.length > 0
puts "got tones, adding to melody hash"
puts "melody: " + melody.to_s
puts "synthorsample: " + synthorsample.to_s
puts "tonelist: " + tonelist.to_s
melody[synthorsample] = tonelist
puts "melody: " + melody.to_s
end
end #each instrument
#now traverse keys of the timeline, which are times, in order, and play samples at that time_warp
puts " "
puts " "
puts " "
puts " "
puts "timeline keys sorted: " + timeline.keys.sort.to_s
puts "melody: " + melody.to_s
nextloop = 0
maxtime = 0
timeline.keys.sort.each do |thiskey|
puts "thiskey: " + thiskey.to_s
nextloop = nextloop + 1
puts "next loop: " + nextloop.to_s + ", timeline length: " + timeline.length.to_s
sleepytime = 0
if timeline.length > nextloop then
sleepytime = timeline.keys.sort[nextloop] - thiskey
puts "not last loop."
puts "next key: " + timeline.keys.sort[nextloop].to_s
puts "sleepytime: " + sleepytime.to_s
#the amount to rest is the duration between this time and next time
#this even accounts for triplets vs 4s vs dots
end #if
thistime = timeline[thiskey]
puts "thistime: " + thistime.to_s
maxtime = 0
thistime.each do |thissound|
puts "thissound: " + thissound[0].to_s
chosensound = thissound[0]
if chosensound.is_a? Enumerable or chosensound.is_a? SonicPi::Core::RingVector
chosensound = chosensound.choose
end #if ring or array
puts "chosensound: " + chosensound.to_s
handle = nil #init here so it's scoped to be visible where needed
if synth_names.to_a.include? chosensound
puts "synth mode, getting note to play"
thisnote = melody[thissound[0]]
puts "thisnote: " + thisnote.to_s
puts "melody[thissound[0]]: " + melody[thissound[0]].to_s
melody[thissound[0]] = melody[thissound[0]].rotate #rotate note just played
#need to add smarts for rings with fewer entries than notes played
puts "thisnote: " + thisnote.to_s
puts "thisnote[0]: " + thisnote[0].to_s
if thisnote[0].is_a? String
puts "thisnote[0] is a string"
if thisnote[0] =~ /:.*/
puts "got a colon, turning to symbol"
cooknote = thisnote[0].delete_prefix(":").to_sym
puts "thisnote: " + thisnote.to_s
else
puts "no colon, turning to int"
cooknote = thisnote[0].to_i
end
else
puts "thisnote[0] is a list/ring"
cooknote = thisnote[0]
end #if thisnote[0] is a string
puts "converted thisnote: " + thisnote.to_s
if defaults[thissound[0]] != nil
mycmd = "use_synth_defaults " + defaults[thissound[0]]
eval mycmd
end
with_synth chosensound do handle = play cooknote end
puts "testing for envelopes, lfos, trancegates"
puts "thissound: " + thissound.to_s
puts "chosensound: " + chosensound.to_s
puts "envelopes[thissound.to_sym]: "
puts envelopes[chosensound.to_sym]
puts "lfos[thissound.to_sym]: "
puts lfos[chosensound.to_sym]
puts "trancegates[thissound.to_sym]: "
puts trancegates[chosensound.to_sym]
if envelopes[chosensound.to_sym] != nil
puts "got an envelope for " + thissound.to_s
cmd = "env handle "
"param,attack,decay,sustain,release,startlevel,peaklevel,sustainlevel".split(",").each_with_index do |arg, i|
if envelopes[chosensound.to_sym].split(",")[i] != nil
cmd += ", " + envelopes[chosensound.to_sym].split(",")[i].to_s
end #if
end #each
puts cmd
eval cmd
end #if there's an envelope
if lfos[chosensound.to_sym] != nil
puts "got an lfo for " + thissound.to_s
cmd = "lfo handle "
"param,duration,period,span,lfotype, delay,rampupperiods,rampdowntime, lfocurve".split(",").each_with_index do |arg, i|
if lfos[chosensound.to_sym].split(",")[i] != nil
cmd += ", " + lfos[chosensound.to_sym].split(",")[i].to_s
end #if
end #each
puts cmd
eval cmd
end #if there's an lfo
if trancegates[chosensound.to_sym] != nil
puts "got a trancegate for " + thissound.to_s
cmd = "trancegate handle "
"duration,period,gutter,delay,maxvol,minvol,lfotype,curve".split(",").each_with_index do |arg, i|
if trancegates[chosensound.to_sym].split(",")[i] != nil
cmd += ", " + trancegates[chosensound.to_sym].split(",")[i].to_s
end #if
end #each
puts cmd
eval cmd
end #if there's a trancegate
else
puts "sample mode"
puts "melody: " + melody.to_s
puts "thissound: " + thissound.to_s
puts "melody[thissound]: " + melody[thissound[0]].to_s
if melody[thissound[0]] != nil
puts "got a melody, picking thisnote"
thisnote = melody[thissound[0]]
puts "thisnote: " + thisnote.to_s
puts "melody[thissound[0]]: " + melody[thissound[0]].to_s
melody[thissound[0]] = melody[thissound[0]].rotate #rotate note just played
#need to add smarts for rings with fewer entries than notes played
puts "thisnote: " + thisnote.to_s
puts "thisnote[0]: " + thisnote[0].to_s
if defaults[thisnote[0]] != nil
mycmd = "use_sample_defaults " + defaults[thisnote[0]]
eval mycmd
end
if thisnote[0].is_a? String
puts "thisnote[0] is a string"
if thisnote[0] =~ /:.*/
puts "got a colon, turning to symbol"
thisnote = thisnote[0].delete_prefix(":").to_sym
else
puts "no colon, turning to int"
thisnote = thisnote[0].to_i
end
pitch_stretch_ratio = midi_to_hz(60 + thisnote) / midi_to_hz(60)
sample chosensound, pitch_stretch: thissound[1].to_i * pitch_stretch_ratio, rpitch: thisnote
else
puts "thisnote[0] is a list/ring"
thisnote[0].each do |onenote|
puts "onenote: " + onenote.to_s
pitch_stretch_ratio = midi_to_hz(60 + onenote) / midi_to_hz(60)
sample chosensound, pitch_stretch: thissound[1].to_i * pitch_stretch_ratio, rpitch: onenote
end #each note
end #if thisnote[0] is a string
else
puts "no note, play sample raw"
#need to add code for pitch shifting samples
handle = sample chosensound
puts "testing for envelopes, lfos, trancegates"
puts "thissound: " + thissound.to_s
puts "chosensound: " + chosensound.to_s
puts "envelopes[thissound.to_sym]: "
puts envelopes[chosensound.to_sym]
puts "lfos[thissound.to_sym]: "
puts lfos[chosensound.to_sym]
puts "trancegates[thissound.to_sym]: "
puts trancegates[chosensound.to_sym]
if envelopes[chosensound.to_sym] != nil
puts "got an envelope for " + thissound.to_s
cmd = "env handle "
"param,attack,decay,sustain,release,startlevel,peaklevel,sustainlevel".split(",").each_with_index do |arg, i|
if envelopes[chosensound.to_sym].split(",")[i] != nil
cmd += ", " + envelopes[chosensound.to_sym].split(",")[i].to_s
end #if
end #each
puts cmd
eval cmd
end #if there's an envelope
if lfos[chosensound.to_sym] != nil
puts "got an lfo for " + thissound.to_s
cmd = "lfo handle "
"param,duration,period,span,lfotype, delay,rampupperiods,rampdowntime, lfocurve".split(",").each_with_index do |arg, i|
if lfos[chosensound.to_sym].split(",")[i] != nil
cmd += ", " + lfos[chosensound.to_sym].split(",")[i].to_s
end #if
end #each
puts cmd
eval cmd
end #if there's an lfo
end #if got a note
end #if melody note not nil
puts "this sound time: " + thissound[1].to_s
maxtime = [thissound[1], maxtime].max
puts "maxtime: " + maxtime.to_s
sleepytime = maxtime if timeline.length == nextloop
end #each sound
puts "sleepytime: " + sleepytime.to_s
sleep sleepytime
end #thistime
end #define
kick = :bd_ada
snare = [:sn_dolf, :sn_dub, :sn_generic, :sn_zome]
hat = (ring :hat_bdu, :hat_cab, :hat_cats, :hat_gem, :hat_gnu)
bass = :bass_foundation
test = :ambi_drone
use_bpm 120
##| arrangement = {bass => "q :e3 m7 asc,q,q,q", kick =>"q,q,q,q", snare => "rq,h,q", hat => "qt,et,tq,te,qt,et,qt,et"}
##| arrangement = {bass => "q :e3 m7 rand,q,q,q"}
arrangement = {test => "4w"}
defaults = {test => "cutoff: 70, release: 0.25, sustain: 4"}
envelopes = { test => "'amp',16" }
lfos = nil
trancegates = nil
arrange arrangement, defaults, envelopes, lfos, trancegates