How to start groups of threads in sequence?

I’m attempting to start a group of threads (that will run for an unknown amount of time), then when they’ve all finished start a second group of threads.
However in this simplified attempt I’ve made the second group of threads is raising an Runtime Error - "Timing Exception: thread got too far behind".

use_timing_guarantees false

def riff(pan)
  threads = [
    in_thread { play_pattern [:r, :r, :r, :r, :b, :b, :r], pan: pan *  1, release: 0.5 },
    in_thread { play_pattern [:d, :d, :a, :a, :r, :r, :a], pan: pan * -1, release: 0.5 }
  ]
  threads.map(&:join)
end

riff 1
puts "here0"
sleep 1
puts "here1"
riff -1

I was hoping that use_timing_guarantees false command might help me out - but no dice. :frowning:

Anyone got any ideas?

I would play one of the sequences directly, not in a thread. That would then set the duration for the sequence for you. Like this:

def riff(pan)
  in_thread { play_pattern [:r, :r, :r, :r, :b, :b, :r], pan: pan *  1, release: 0.5}
  play_pattern [:d, :d, :a, :a, :r, :r, :a], pan: pan * -1, release: 0.5
end

riff 1
puts "here0"
sleep 0.5 #allows for gap between riff calls
puts "here1"
riff -1

EDIT if the threads were not of the same duration, then “un thread” the longest one. You could have say 5 threads in there with the longest duration one playing directly, not in a thread.

2 Likes

Ahh… thanx @robin.newman !
Your suggestion works great. :heart_eyes:

That was the missing piece I needed for this guitar tab player:

def play_tabs(tabs, bpm: 480)
  use_bpm bpm
  in_thread do
    tabs.strip.split(/\n{2,}/).each do |tabs_row|
      play_string_patterns string_patterns(tabs_hash(tabs_row))
    end
  end
end

def play_string_patterns(string_patterns)
  string_patterns.each do |string_pattern|
    in_thread { play_pattern string_pattern }
  end
  play_pattern [:rest] * string_patterns.map(&:size).max # rest between thread groups
end

def string_patterns(tabs_hash)
  tabs_hash.map { |string, tab| tab2pattern(string, tab) }
end

def tabs_hash(tabs_row)
  YAML.load(tabs_row2yaml(tabs_row))
end

GUITAR_STRINGS = { E: :E2, A: :A2, D: :D3, G: :G3, B: :B3, e: :e4 }
def tabs_row2yaml(tabs_row)
  GUITAR_STRINGS.inject(tabs_row) do |yaml, (string_name, note_name)|
    yaml.sub(/^\s*(#{string_name})\s*[|]/, ":#{note_name}: ")
  end
end

def tab2pattern(string, tab)
  tab.scan(/[-0-9]/).map { |char| char2note(string, char) }
end

def char2note(string, char)
  char == '-' ? :rest : string + (char.to_i)
end



stairway_to_heaven = """
e|-------5-7-----7-|-8-----8-2-----2-|-0---------0-----|-----------------|
B|-----5-----5-----|---5-------3-----|---1---1-----1---|-0-1-1-----------|
G|---5---------5---|-----5-------2---|-----2---------2-|-0-2-2-----------|
D|-7-------6-------|-5-------4-------|-3---------------|-----------------|
A|-----------------|-----------------|-----------------|-2-0-0---0--/8-7-|
E|-----------------|-----------------|-----------------|-----------------|

e|---------7-----7-|-8-----8-2-----2-|-0---------0-----|-----------------|
B|-------5---5-----|---5-------3-----|---1---1-----1---|-0-1-1-----------|
G|-----5-------5---|-----5-------2---|-----2---------2-|-0-2-2-----------|
D|---7-----6-------|-5-------4-------|-3---------------|-----------------|
A|-0---------------|-----------------|-----------------|-2-0-0-------0-2-|
E|-----------------|-----------------|-----------------|-----------------|

e|-------0-2-----2-|-0-----0----------|---------3-----3-|-3^2-2-2---------|
B|-----------3-----|---1-----0h1------|-1-----1---0-----|-----3-3---------|
G|-----0-------2---|-----2-------2----|---0---------0---|-----------------|
D|---2-----0-------|-3----------------|-----2-----------|-0---0-0---------|
A|-3---------------|---------0----0-2-|-3---------------|-------------0-2-|
E|-----------------|------------------|---------3-------|-----------------|
"""


play_tabs stairway_to_heaven
2 Likes

Hi Yertto,

I’ve always thought they should have used a flute for the begining,
so I made a slight change to your code, hope you dont mind

Edit: Hmmm - the :pluck version isn’t so bad either, still needs
a little tweaking though… I also dropped the bpm to 300.

def play_tabs(tabs, bpm: 300)

  use_synth :pluck
  use_synth_defaults divisor: 0.5, depth: 4, attack: 0.01, sustain: 3,
    release: 1, amp: 2

Eli…

def play_tabs(tabs, bpm: 400)
  use_bpm bpm
  tabs.strip.split(/\n{2,}/).each do |tabs_row|
    play_string_patterns string_patterns(tabs_hash(tabs_row))
  end
end

def play_string_patterns(string_patterns)
  with_fx :reverb, room: 0.75, damp: 0.25 do
    string_patterns.each do |string_pattern|
      use_synth :fm
      use_synth_defaults divisor: 0.5, depth: 4, attack: 0.5, sustain: 0.95,
        release: 0.4, amp: 3
      
      in_thread { play_pattern string_pattern }
    end
  end
  play_pattern [:rest] * string_patterns.map(&:size).max # rest between thread groups
end

Wow - sounds great @Eli - thank you. :smiling_face_with_three_hearts:

As for changing my code - sure feel free!

And hope you don’t mind I’ve changed your code to pull the changes out into wrapper functions that could be played around with - even combined…

def with_guitar(amp: 1, fx: :none, &block)
  use_synth :pluck
  use_synth_defaults divisor: 0.5, depth: 4, attack: 0.01, sustain: 3, release: 1, amp: amp
  
  with_fx(*fx) { yield }
end

def with_flute(amp: 1, fx: [:reverb, room: 0.75, damp: 0.25], &block)
  use_synth :fm
  use_synth_defaults divisor: 0.5, depth: 4, attack: 0.5, sustain: 0.95, release: 0.4, amp: amp
  
  with_fx(*fx) { yield }
end

def with_instruments(instruments, &block)
  instruments.each do |instrument, opts|
    send("with_#{instrument}", opts, &block)
  end
end


with_flute { play :c }
sleep 3

with_guitar amp: 5, fx: :whammy do
  play :c
end
sleep 3

with_instruments guitar: { amp: 1, fx: :wobble }, flute: { amp: 3 } do
  play_tabs stairway_to_heaven, bpm: 400
end

EDIT: I’ve modifed the play_tabs above and put it in its own thread so it behaves the same way as play, then all the instruments can be played together.
(BTW is there some “Instrument Library” that I’ve missed somewhere in Sonic-Pi, or am I just “doing it wrong”:tm: ?)

HI there,

FIrst time i saw { used in sonic pi. What is this syntax ?

Hi @nlb - :wave:
{ } can be used as a shorthand for one line blocks in ruby.
( see https://www.rubyguides.com/2016/02/ruby-procs-and-lambdas/ )

ok it was easy to guess :slight_smile:
so it’s a “ruby syntax” way.

define :play_note do
  play :c4
  sleep 0.125
end

4.times { play_note }

Also used in the ‘at’ command methinks.

use_bpm 120
live_loop :beat4 do
  sleep 4
end

define :hat do
  at [0, 2, 3],
  [{:amp=>0.3}, {:amp=> 1}, {:amp=>0.5}] do |p|
    sample :drum_cymbal_pedal, p
  end
end

define :drum do
  at [1, rrand_i(2, 3)],
  [{:amp=>1}] do |p|
    sample [:drum_bass_hard, :drum_bass_soft].choose, p
  end
end

live_loop :do_hat do
  sync :beat4
  hat
  sleep 1
end

live_loop :do_drum do
  sync :beat4
  drum
  sleep 1
end

Eli…