Overlapping Samples Queue with Varying Wait Times


#1

Hey Sonic Pi Gurus!

Thanks in advance for your help!! I’m an experienced coder, but somewhat new to Sonic Pi (been coding with it for the last month or so on some personal projects). So far I’m loving it and have made some fun compositions! I’m working on an experimental project that reads a list of instructions from an external file (my own custom data format) and then plays samples from a folder based on those instructions.

I’m stumped on one part of my program – I would like to load in a VARIABLE amount of samples from the instructions list, each with their own Minimum Wait Time and Maximum Wait Time options. Then I would like to trigger ALL the sounds in this array of items, having each sample fire with it’s own sleep timing.

The goal is to be able to play concurrent samples:

Beat 1 - Folder A - Sample 1 (sleep 1) :::: Folder B - Sample 1 (sleep 3)
Beat 2 - Folder A - Sample 2 (sleep 1)
Beat 3 - Folder A - Sample 3 (sleep 1) :::: Folder B - Sample 2 (sleep 3)
Beat 4 - Folder A - Sample 4 (sleep 1)
Beat 5 - Folder A - Sample 5 (sleep 1) :::: Folder B - Sample 3 (sleep 3)
Beat 6 - Folder A - Sample 6 (sleep 1)
Beat 7 - Folder A - Sample 7 (sleep 1) :::: Folder B - Sample 4 (sleep 3)
Beat 8 - Folder A - Sample 8 (sleep 1)
Beat 9 - Folder A - Sample 9 (sleep 1) :::: Folder B - Sample 5 (sleep 3)

This is easy to do if I know how many folders I need to play, because then I can make a live_loop for each folder, but I need to be able to have a variable amount of folders/samples. My initial thought was to trigger all samples at once, and then give each sample it’s own delay time so that they could play concurrently.

I’ve been able to loop through my arrays to build all the necessary sample information, but I’m stumped on how to play the samples so that each one has it’s own timing and does not hold up the next sample in the queue.

Ideally, I would like to fire all samples at once, but have each sample pause for its designated time before playing. I’ve tried playing around with in_thread to allow each sample to have its own sleep timing, but I haven’t been able to get it to work as I’d hoped. Basically, in_thread seems to break my setup – I’m confused as to why.

Here’s the relevant part of my program:

# Setup
freeplaylist = []
playthislist = []
freeplaytrackcounter = []
freeplaywaittimes = []
freeplaylistcounter = 0

# Sample Playlist Arrays (Simplified -- To Be Replaced with Data-Loading Functions)
playthislist = sample_paths "/Folder A/"
freeplaylist.push(playthislist.to_a)
freeplaytrackcounter.push(0) # Add a Base Zero Counter For Each Element Added To Playlist Array
freeplaywaittimes.push(1) # Add 3 Second Wait Time For Each Playlist In This Array

playthislist = sample_paths "/Folder B/"
freeplaylist.push(playthislist.to_a)
freeplaytrackcounter.push(0) # Add a Base Zero Counter For Each Element Added To Playlist Array
freeplaywaittimes.push(3) # Add 3 Second Wait Time For Each Playlist In This Array

with_fx :reverb, room: 1, amp: 1, amp_slide: 10 do
  with_fx :echo, decay: 3, phase: 0.5, mix: 0.125 do
    live_loop :freeplay do

      freeplaylist.length.times do # Repeat For Each Playlists
        freeplaylist[freeplaylistcounter].length.times do # Repeat For Each Track in Playlist
          # Play Next Track In Playlist

          with_fx :panslicer, phase: 0.5, wave: 3, pan_min: -0.8, pan_max: 0.8, invert_wave: 1 do

            print "PLAYLIST: " + freeplaylistcounter.to_s
            print "TRACK: " + freeplaytrackcounter[freeplaylistcounter].to_s
            print "WAIT: " + freeplaywaittimes[freeplaylistcounter].to_s

            #in_thread do
            # sleep freeplaywaittimes[freeplaylistcounter]
            sample freeplaylist[freeplaylistcounter][freeplaytrackcounter[freeplaylistcounter]], attack: 0.2, release: 0.2
            #end
          end

          # Advance To Next Track In Playlist
          if freeplaytrackcounter[freeplaylistcounter] < freeplaylist[freeplaylistcounter].length-1
            freeplaytrackcounter[freeplaylistcounter] += 1
          else
            freeplaytrackcounter[freeplaylistcounter] = 0
          end
        end

        # Advance To Next Playlist
        if freeplaylistcounter < freeplaylist.length-1
          freeplaylistcounter += 1
        else
          freeplaylistcounter = 0
        end
      end

      # Minimum Wait Time
      # play 50, release: 0.1, amp: 0.25
      sleep 1
    end
  end
end

I’m open to a totally different approach, just want to be able to play a bunch of samples with their own timing. Thanks for taking a look! :slight_smile:


#2

I figured it out using dynamic live_loops generated from a custom function! And I was able to make my code much, much cleaner! Here’s my solution, for anybody else who is trying to achieve the same thing:

# Setup
freeplaylist = []
templist = []
freeplaywaittimes = []

# Sample Playlist Arrays
templist = sample_paths "/Folder A/"
freeplaylist.push(templist.to_a)
freeplaywaittimes.push(3) # Add 3 Second Wait Time For This Playlist

templist = sample_paths "/Folder B/"
freeplaylist.push(templist.to_a)
freeplaywaittimes.push(1) # Add 1 Second Wait Time For This Playlist

live_loop :testsync do
  sleep 1
end

# Create Dynamic Freeplaylist Loops
define :createloop do |loopname, looptiming, playlist|
  live_loop loopname.to_sym, sync: :testsync do
    tick_reset
    playlist.each do # Repeat For Each Track
      sample playlist[tick], attack: 0.2, release: 0.2
      sleep looptiming
    end
  end
end

freeplaylist.each do # Repeat For Each Playlists
  createloop "freeplay"+tick.to_s, freeplaywaittimes[look], freeplaylist[look]
end

Happy coding, friends! :slight_smile: