Recursive 'melodic' thread creation

Hi there,

I’d like to share with you one of the first things I did in Sonic Pi. It’ll play for about 5 minutes before it stops. Not very engaging music, but nice in the background.

# Made 2025-03-09

define :play_complex do |n, v|
  return if n == -1

  weight_on_lower = (0.8)**((n-69)/12)  # extra weight on duration(release) and velocity in lower octaves
  release = weight_on_lower*1.66

  vel = v+0.1*rrand(0, 1)
  amp1 = 0.1*weight_on_lower*(0.5+vel)
  amp2 = amp1*(0.1+0.7*vel)
  amp3 = amp2*(0.1+0.5*vel)

  # each note is made into a complex: base-note and octave doubled with added octaves and higher 5ths
  notes = [n-0.05, n+0.05, n+12+0.02, n+12-0.02, n+19, n+24, n+36, n+43]
  amps = [amp1, amp1, amp2, amp2, amp3, amp3*0.5, 0.05*amp1*(0.7+vel), 0.02*amp1*(0.1+vel)]
  rel_release = [1, 1, 0.8, 0.8, 0.5, 0.4, 0.1, 0.02]
  pans = [-1, 1, -1, 1, -0.5, 0.5, -0.2, 0.2]

  notes.each_with_index do |note, i|
    play note, amp: amps[i], pan: pans[i], release: rel_release[i]*release
  end
end

define :play_and_sprout do |s, n, v, r|
  return if v < 0.012

  in_thread do
    play_complex n, v

    # more rythmic timing
    sleep (1+rand_i(3))*0.5*s
    # more dreamy timing
    # sleep (1+rrand(0, 2)*0.5)*s

    offspring = dice(6) < 6 ? 1 : 2  # sometimes sprout two notes
    offspring.times do
      next_chord_note = (chord r+12*[0,0,0,0,0,0,1,1,2].choose, :major).choose
      next_alt_note = (chord n + 12 * 12 * [0, 0, 0, 1].choose, :major).choose
      n_next = dice(12) < 12 ? next_chord_note : next_alt_note

      v_next = dice(10) < 10 ? 0.5*v : 1.4*v
      v_next = 0.9 if v_next > 0.9

      play_and_sprout s, n_next, v_next, r
    end
  end
end

chords = [:c2, :c3, :g3, :f3, :c3, :f2, :g2, :c3]
song_arc = [0.0, 0.2, 0.5, 0.7, 0.8, 0.7, 0.9, 0.3, 0.1, 0.0]
with_fx :gverb, room: 30 do
  use_bpm 96
  loop do
    bars_per_chord = 2
    root = chords.ring[tick/bars_per_chord]
    pt = chords.length*bars_per_chord  # define tempo of progression for calculating progression in arc
    arc_vel = (song_arc[look/pt]*(pt-look%pt) + song_arc[look/pt+1]*(look%pt))/pt  # no ring. song will end automatically

    play_and_sprout 1, root, 0.4*arc_vel, root
    sleep 2
    play_and_sprout 0.5, root+7, 0.33*arc_vel, root
    sleep 2
  end
end

I like how it is both random, as well as adhering to an idea of what the song should be like.

When it plays, some dissonant notes appear. No doubt because of the way the source for next_alt_note is picked. One can make the creation of this more consonant, but I quite like the fact some of these weird notes pop up.

The play_complex-function was inspired by a tune on dittytoy.net (the site that set me onto Sonic Pi).

As a programmer I notice that there are a lot of parameters, magic values if you like.

Love to hear you feedback

Recording on Soundcloud