Song: DataTropics

I decided to build something up out of free nature sound and some sample loops I had laying around. Also, tried building a Doppler-like effect which kind of works. All of the audio is being routed out of Sonic Pi and recorded into my DAW for mixing purposes. I only added one additional crunchy effect in-DAW, so 99% of what you hear is straight out of Sonic Pi.

Here’s a preview:

The code may also be useful but you’ll have to change the paths to your own samples and tinker with it. Sorry, there’s a bit of useless code in there that I was too lazy to remove, or may use as I keep experimenting.

# INITIALIZE MASTER VALUES
use_bpm 40
use_random_seed 0

# LOAD IN SAMPLE LIBRARIES
sampleLibA = "/Users/sheakennedy/Desktop/Elder Desktop/DESKTOP/Drum Kits _ Samples/QY100 Chops/"
sampleLibB = "/Users/sheakennedy/Desktop/Elder Desktop/DESKTOP/Drum Kits _ Samples/Waldo/Loops/"
hat = "/Users/sheakennedy/Desktop/Elder Desktop/DESKTOP/Drum Kits _ Samples/Waldo/Loops/", 1

# AUTOMATION
lfo0 = (line 70, 100, inclusive: true, steps: 16).mirror
lfo0r = (line 120, 70, inclusive: true, steps: 16).mirror
lfo1 = (line 0.6, 0.1, inclusive: true, steps: 8).mirror
lfo2 = (line -1, 1, inclusive: true, steps: 16).mirror
lfo3 = (line 70, 120, inclusive: true, steps: 8).mirror
lfo4 = (line 0, 0.9, inclusive: true, steps: 32).mirror.map { |v| v**2 }
lfo5 = (line 80, 120, inclusive: true, steps: 8).mirror
lfo5r = (line 120, 80, inclusive: true, steps: 8).mirror

# MODIFIERS
rate_mod = 1
pat_mod = 1

# LEVELS
inst0_lvl           = 0.8
inst1_lvl           = 1
inst2_lvl           = 1
inst3_lvl           = 1
inst4_lvl           = 1
inst5_lvl           = 0.8

live_loop :inst0 do
  puts tick
  with_fx :sound_out_stereo, output: 3 do  # ch 3-4
    if(inst0_lvl != 0)
      with_fx :level, amp: inst0_lvl  do
        with_fx :lpf, cutoff: lfo5.look  do
          sample sampleLibA, (ring 4, 0).look, rate: (ring 0.5).look, beat_stretch: 16*rate_mod, start: (ring 0.125, 0.5).look, finish: (ring 0.25, 0.625).look, release: 0.01
        end
      end
    end
  end
  sleep (ring 4).look
end

live_loop :inst1 do
  puts tick
  with_fx :sound_out_stereo, output: 5 do  # ch 5-6
    if(inst1_lvl != 0)
      with_fx :level, amp: inst1_lvl  do
        with_fx :ping_pong, feedback: 0.5, mix: lfo1.look do
          #with_fx :lpf, cutoff: lfo5r.look  do
          with_fx :lpf, cutoff: lfo0.look do
            sample sampleLibB, 7, rate: (ring 4, 8, 8, 8).look, beat_stretch: 32*rate_mod, onset: (ring 1,7,7,9,1,7,7,40).look,  release: 0.01
            #sample sampleLibB, 25, rate: (ring 4).look, beat_stretch: 16*rate_mod, onset: (ring 22, 29).look,  release: 0.01
          end
          #end
        end
      end
    end
  end
  sleep (ring 1*rate_mod, 0.25*rate_mod, 0.5*rate_mod, 0.25*rate_mod).look
end

live_loop :inst2 do
  puts tick
  with_fx :sound_out_stereo, output: 7 do  # ch 7-8
    if(inst2_lvl != 0)
      with_fx :level, amp: inst2_lvl  do
        with_fx :pan, pan: rrand(-0.08, 0.08) do
          with_fx :bitcrusher do
            sample sampleLibB, 7, rate: (ring choose([4,-4]), 8, 8, 8).look, beat_stretch: 32, onset: (ring 1,7,7,9,1,77,40).look,  release: 0.01
          end
          
          #sample sampleLibB, 9, rate: (ring 4, 8, 8, 8).look, beat_stretch: 32, onset: (ring 1,7,7,9,1,7,7,40).look,  release: 0.01
          sample sampleLibB, 19, rate: (ring 1, 2).look, beat_stretch: 16, onset: (ring 7).look,  release: 0.01
        end
      end
    end
  end
  
  sleep (ring 1, 0.25, 0.5, 0.25, 1, 0.25, 0.5, 0.25, 1, 0.25, 0.5, 0.25, 1.25, 0.25, 0.25, 0.25).look
end


live_loop :inst3 do
  puts tick
  with_fx :sound_out_stereo, output: 9 do  # ch 9-10
    if(inst3_lvl != 0)
      # === Settings ===
      sample_path = "/Users/sheakennedy/Desktop/Elder Desktop/DESKTOP/Drum Kits _ Samples/Freesound/CC0/Bird/", choose([1,6,2])
      total_length = 14.0                        # Length of the sample in seconds
      section_length = 4 * (60.0 / current_bpm)  # 1 measure of 4/4
      fade_time = section_length / 4.0           # 1/4 of section for smooth fade
      buffer = 1                                  # Minimum distance from previous section
      sections = (0..(total_length - section_length)).step(section_length).to_a
      
      # === Last Index Tracking ===
      if !$last_index.is_a?(Integer)
        $last_index = -10
      end
      last_index = $last_index
      
      # === Choose New Section (not same or neighbor) ===
      valid_indices = (0...sections.length).to_a.select do |i|
        (i - last_index).abs > buffer
      end
      
      if valid_indices.empty?
        valid_indices = (0...sections.length).to_a
      end
      
      new_index = valid_indices.choose
      $last_index = new_index
      
      # Calculate Start/Finish Positions (as fractions of sample)
      start_sec = sections[new_index]
      finish_sec = start_sec + section_length
      start_ratio = start_sec / total_length
      finish_ratio = finish_sec / total_length
      
      # FX Randomization
      random_pan = rrand(-0.5, 0.5)
      strong_pingpong = one_in(4)
      delay_mix = strong_pingpong ? 0.6 : 0.2
      delay_phase = strong_pingpong ? 0.375 : 0.25
      
      # Play the Main Sample with Ping Pong and Pan
      with_fx :ping_pong, mix: delay_mix, phase: delay_phase do
        with_fx :pan, pan: random_pan do
          sample sample_path,
            start: start_ratio,
            finish: finish_ratio,
            amp: 1,
            attack: fade_time,
            release: fade_time
        end
      end
      
      # Bitcrushed Slice with HPF
      if one_in(5)
        crush_start_ratio = rrand(start_ratio, finish_ratio - 0.05)
        crush_finish_ratio = crush_start_ratio + 0.05  # Small slice ~5% of sample
        
        with_fx :hpf, cutoff: rrand(90, 131) do
          with_fx :bitcrusher, bits: 7, sample_rate: 8000 do
            sample sample_path,
              start: crush_start_ratio,
              finish: crush_finish_ratio,
              amp: 0.5,
              attack: 0.2,
              release: 0.2,
              pan: rrand(-0.4, 0.4)
          end
        end
      end
    end
  end
  
  sleep 16
end

define :trap_hats do |smp, div, s_rate, e_rate, pan = 0|
  with_sample_defaults sustain: 0.015, release: 0.015 do
    rate_div = (s_rate - e_rate) / div.to_f
    div.times do
      sample smp, sustain: 0.02, amp: rrand(0.5, 0.6), rate: s_rate, pan: pan
      sleep 0.5/div
      s_rate -= rate_div
    end
  end
end

live_loop :inst4 do
  puts tick
  with_fx :sound_out_stereo, output: 11 do  # ch 11-12
    if (inst4_lvl != 0)
      with_fx :lpf, cutoff: lfo5.look  do
        with_fx :echo, phase: choose([0.125]), mix: lfo4.look do
          #with_fx :lpf, cutoff: lfo5.look do
          with_fx :slicer do
            trap_hats(
              hat,
              [1, 4, 32, 6, 128].choose,
              rrand(0, 55),
              rrand(55, 0),
              [-0.7, -0.5, -0.25, 0, 0.25, 0.5, 0.7].choose  # Pan alternates on each call
            )
            #end
          end
        end
        
        trap_hats(
          hat,
          [1, 4, 32, 6, 128].choose,
          rrand(0, 55),
          rrand(55, 0),
          [-0.7, -0.5, -0.25, 0, 0.25, 0.5, 0.7].choose
        )
      end
    else
      sleep 1
    end
  end
end

live_loop :inst5 do
  with_fx :sound_out_stereo, output: 13 do  # ch 13-14
    if(inst5_lvl != 0)
      with_fx :level, amp: inst5_lvl  do
        in_thread do
          doppler_effect
        end
      end
    end
  end
  sleep 8
end

define :doppler_effect do
  sample_path = "/Users/sheakennedy/Desktop/Elder Desktop/DESKTOP/Drum Kits _ Samples/Waldo/One Shots/", 6
  duration = 3.8  # Now matches loop trigger time
  steps = 128
  
  approach_rates = (1.5..1.0).step((1.0 - 1.5) / steps)
  recede_rates = (1.0..0.7).step((0.7 - 1.0) / steps)
  approach_amps = (0.2..1.0).step((1.0 - 0.2) / steps)
  recede_amps = (1.0..0.2).step((0.2 - 1.0) / steps)
  
  pan_start = rrand(-1.0, 1.0)
  pan_end = -pan_start
  pan_vals = (pan_start..pan_end).step((pan_end - pan_start) / (2 * steps))
  
  with_fx :rbpf, cutoff: 100, mix: 0.9  do
    with_fx :slicer, wave: 1, phase: 1, invert_wave: 1, mix: 0.97 do
      with_fx :krush, cutoff: 100, mix: 0.2 do
        (0...steps).each do |i|
          sample sample_path,
            rate: approach_rates.to_a[i],
            amp: approach_amps.to_a[i],
            pan: pan_vals.to_a[i]
          sleep duration / (2 * steps.to_f)
        end
        
        (0...steps).each do |i|
          sample sample_path,
            rate: recede_rates.to_a[i],
            amp: recede_amps.to_a[i],
            pan: pan_vals.to_a[steps + i]
          sleep duration / (2 * steps.to_f)
        end
      end
    end
  end
end

Also, shout out to @mrbombmusic for that Spanish Trap code. I keep coming back to it. It’s a very versatile bit of code. I made some new modifications to it.

2 Likes

Hi
I like the Doppler effect, but I’ll have to dissect your code to fully understand how it works, especially the approach_rate etc.

PD-Pi

I really dig the sound of this. It’s abstract, yet catchy.

There’s one part of the code that I don’t quite grok. Can you explain in English what’s going on here?

The intention with that was that I have a nature (bird sound) sample,14 seconds in length, and I want to keep chopping different sections of it out of the sample to play back. However, I found that doing this randomly would too often result in the same section being played two or three times in a row which didn’t sound natural.

So, I tried to make a code that keeps track of the index of the section in array, then plays it. That way when the loop happens again it selects an index which is not the same, or neighbouring the previous index, so no obvious repeats are heard. Buffer is 1, so there is a buffered distance of at least 1 section between the last section and the new section. This could be increased to force the next section to be further away.

The sample is relatively short, which is why I did this.

I was thinking it could be cool to try the opposite and get an animal rustling through bushes sample, but make the sound continuously pan to a location neighbouring the postions of the last loop, so you can track the animal’s movement throughout the composition.

OK, thanks, I get it now. Nuance.