Play along the Jig Saw Puzzle Blues

This is a piece to play along with the guitar (sorry, no live-coding here :upside_down_face:).

Link to a take with guitar

The architecture is such that a central control loop is orchestrating 3 instruments. Just like a band on stage with a band leader.

I found it a bit tricky to use both sleep and sync in a live_loop and avoid race conditions. Maybe I overlooked a simpler solution here?

### Jigsaw Puzzle Blues
### Fleetwood Mac / Peter Green / Danny Kirwan / Joe Venuti and Adrian Rollini

use_debug false

load_samples [:drum_cymbal_open, :drum_cymbal_closed, :perc_snap,
              :drum_heavy_kick, :drum_snare_hard]

use_bpm 100

###############
# control loop

live_loop :control, auto_cue: false, delay: 0.2 do
  t = tick
  case t
  when 0
    set :run, 1
    set :s_drums, 1
    set :drum_pattern, 3
  when 1
    set :drum_pattern, 1
    set :s_piano, 1
    set :s_bass, 1
  when 26
    set :drum_pattern, 0
  when 27
    set :drum_pattern, 1
  when 41
    set :drum_pattern, 2
    set :run, 0  # send stop signal to all loops
  when 42
    stop
  end
  
  # sync other loops
  cue :bar, t
  sleep 4
end


###################
# instrument loops

# drum loop
live_loop :my_drums, auto_cue: false, sync: :s_drums do
  sync :bar
  drum_kit(get :drum_pattern)
  stop if (get :run)  == 0
end

# bass loop
my_bass_line = []  # definition see below
with_synth :fm do
  use_synth_defaults attack: 0.01, sustain: 0.5, release: 0.1, divisor: 2, amp: 0.9
  
  live_loop :my_bass, auto_cue: false, sync: :s_bass do
    m = my_bass_line.tick
    rep = m[:rep] ? m[:rep] : 1
    rep.times do
      sync :bar
      my_play_pattern(m)
    end
    stop if (get :run)  == 0
  end
  
end

# piano loop
my_chords = []  # definition see below
with_synth :piano do
  use_synth_defaults amp: 1.5, hard: 0.6, release: 1.2
  
  live_loop :my_piano, auto_cue: false, sync: :s_piano do
    m = my_chords.tick
    rep = m[:rep] ? m[:rep] : 1
    rep.times do
      sync :bar
      my_play_pattern(m)
    end
    stop if (get :run)  == 0
  end
  
end

##############
# definitions

define :drum_kit do |pattern|
  # 3.99 beats per pattern
  # ensures drum loop is always slightly ahead of cue :bar
  case pattern
  when 0
    sleep 3.99
  when 1
    sample :drum_heavy_kick, amp: 2
    sleep 0.5
    sample :drum_cymbal_closed
    sleep 0.5
    sample :drum_cymbal_closed
    sleep 0.5
    sample :drum_cymbal_closed
    sleep 0.5
    sample :drum_snare_hard
    sleep 0.5
    sample :drum_cymbal_closed
    sleep 0.5
    sample :drum_cymbal_closed
    sleep 0.5
    sample :drum_cymbal_open, attack: 0.01, sustain: 0.1, release: 0.6, amp: 0.7
    sleep 0.49
  when 2
    sample :drum_splash_soft, attack: 0.05, release: 4, amp: 0.6
    sleep 3.99
  when 3
    sample :perc_snap, amp: 0.4
    sleep 1
    sample :perc_snap, amp: 0.4
    sleep 1
    sample :perc_snap, amp: 0.4
    sleep 1
    sample :perc_snap, amp: 0.4
    sleep 0.99
  end
end

define :my_play_pattern do |m|
  # plays pattern m with total length slightly less than 4 beats
  tick_reset(:notes)
  max_t = m[:n].length - 1
  
  m[:n].each do |n|
    t = tick(:notes)
    
    rel = m[:rel] ? m[:rel][t] : nil
    sus = m[:sus] ? m[:sus][t] : nil
    
    play n, sustain: sus, release: rel
    
    if t == max_t
      # last note/chord has been played
      # ensures instrument loops are always slightly ahead of cue :bar
      sleep 0.99*m[:t][t]
    else
      sleep m[:t][t]
    end
  end
end

my_chords = [
  # part 1
  {n: (knit chord(:D3, :m, invert: 1), 4), t: [1, 1, 1, 1]},
  {n: (knit chord(:Bb2, :dom7), 2, chord(:A2, :dom7), 2), t: [1, 1, 1, 1]},
  {n: (knit chord(:D3, :m, invert: 1), 4), t: [1, 1, 1, 1]},
  {n: (knit chord(:Bb2, :dom7), 2, chord(:A2, :dom7), 2), t: [1, 1, 1, 1]},
  {n: (knit chord(:D3, :m, invert: 1), 4), t: [1, 1, 1, 1]},
  {n: (knit chord(:Bb2, :dom7), 2, chord(:A2, :dom7), 2), t: [1, 1, 1, 1]},
  {n: (knit chord(:D3, :m, invert: 1), 4), t: [1, 1, 1, 1]},
  {n: [chord(:Eb3, :M), chord(:E3, :M)], t: [2, 2], rel: [2, 2]},
  # part 2 (t=7)
  {n: (knit chord(:A2, :M, invert: 1), 4), t: [1, 1, 1, 1], rep: 3},
  {n: (knit chord(:A2, :M, invert: 1), 3), t: [1, 0.5, 2.5], rel: [1, 0.5, 2.5]},
  # part 3 (t=9)
  {n: (knit chord(:D3, '9'), 4), t: [1, 1, 1, 1], rep: 2},
  {n: (knit chord(:A2, :M, invert: 1), 4), t: [1, 1, 1, 1], rep: 2},
  {n: (knit chord(:E3, '9'), 4), t: [1, 1, 1, 1]},
  {n: (knit chord(:F3, '9'), 1, chord(:E3, '9'), 2), t: [2, 1, 1], rel: [2, 1.2, 1.2]},
  {n: [chord(:A2, :M), chord(:A2, :dom7, invert: 2), chord(:D3, :M) ,chord(:D3, :m, invert: 2)], t: [1, 1, 1, 1]},
  {n: [chord(:A3, :M), chord(:B3, :M)], t: [2, 2], rel: [2, 2]},
  # part 4 (t=15)
  {n: (knit chord(:E3, :M, invert: 1), 4), t: [1, 1, 1, 1], rep: 3},
  {n: (knit chord(:E3, :M, invert: 1), 3), t: [1, 0.5, 2.5], rel: [1, 0.5, 2.5]},
  # part 5 (t=17)
  {n: (knit chord(:A2, :M, invert: 1), 4), t: [1, 1, 1, 1]},
  {n: (knit chord(:Bb2, :dim7, num_octaves: 2), 2), t: [2, 2], rel: [2, 2]},
  {n: (knit chord(:E3, :M, invert: 1), 4), t: [1, 1, 1, 1], rep: 2},
  {n: (knit chord(:B2, '9', invert: 2), 4), t: [1, 1, 1, 1]},
  {n: (knit chord(:C3, '9', invert: 2), 2, chord(:B2, '9', invert: 2), 2), t: [1, 1, 1, 1]},
  {n: [chord(:E3, :M), chord(:E3, :dom7), chord(:A3, :M) ,chord(:A3, :m)], t: [1, 1, 1, 1]},
  {n: (knit chord(:E3, :M), 2, chord(:Fs3, :M), 2), t: [1, 1, 1, 1]},
  # part 6 (t=24)
  {n: (knit chord(:B2, :m, invert: 2), 4), t: [1, 1, 1, 1]},
  {n: (knit chord(:G2, :dom7), 2, chord(:Fs2, :dom7), 2), t: [1, 1, 1, 1]},
  {n: (knit chord(:B2, :m, invert: 2), 4), t: [1, 1, 1, 1]},
  {n: (knit chord(:G2, :dom7), 2, chord(:Fs2, :dom7), 2), t: [1, 1, 1, 1]},
  {n: (knit chord(:B2, :m, invert: 2), 4), t: [1, 1, 1, 1]},
  {n: (knit chord(:G2, :dom7), 2, chord(:Fs2, :dom7), 2), t: [1, 1, 1, 1]},
  {n: (knit chord(:B2, :m, invert: 2), 4), t: [1, 1, 1, 1]},
  {n: [chord(:G2, :dom7), chord(:Fs2, :dom7)], t: [2, 2], rel: [2, 2]},
  {n: [chord(:B2, :m, invert: 2)], t: [4], rel: [4]},
]

my_bass_line = [
  {n: [:D3, :D3, :D3, :D3], t: [1, 1, 1, 1]},
  {n: [:Bb2, :Bb2, :A2, :A2], t: [1, 1, 1, 1]},
  {n: [:D3, :D3, :D3, :D3], t: [1, 1, 1, 1]},
  {n: [:Bb2, :Bb2, :A2, :A2], t: [1, 1, 1, 1]},
  {n: [:D3, :D3, :D3, :D3], t: [1, 1, 1, 1]},
  {n: [:Bb2, :Bb2, :A2, :A2], t: [1, 1, 1, 1]},
  {n: [:D3, :D3, :D3, :D3], t: [1, 1, 1, 1]},
  {n: [:Eb3, :E3], t: [2, 2], sus: [1.5, 1.5], rel: [0.5, 0.5]},
  #
  {n: [:A2, :Fs2, :E2, :E2], t: [1.5, 0.5, 1.5, 0.5], sus: [nil, 0.2, nil, 0.2], rel: [1, 0.05, 1, 0.05]},
  {n: [:A2, :Fs2, :E2, :Fs2], t: [1.5, 0.5, 1.5, 0.5], sus: [nil, 0.2, nil, 0.2], rel: [1, 0.05, 1, 0.05]},
  {n: [:A2, :B2, :C3, :Db3], t: [1, 1, 1, 1]},
  {n: [:D3, :Eb3, :A3], t: [1, 0.5, 2.5], sus: [nil, 0.3, nil], rel: [1, 0.2, 2.5]},
  #
  {n: [:D3, :D3, :D3, :D3], t: [1, 1, 1, 1], rep: 2},
  {n: [:A2, :Fs2, :E2, :E2], t: [1.5, 0.5, 1.5, 0.5], sus: [nil, 0.2, nil, 0.2], rel: [1, 0.05, 1, 0.05]},
  {n: [:A2, :Fs2, :E2, :A2], t: [1.5, 0.5, 1.5, 0.5], sus: [nil, 0.2, nil, 0.2], rel: [1, 0.05, 1, 0.05]},
  {n: [:E2, :E2, :E2, :E2], t: [1.5, 0.5, 1.5, 0.5], sus: [nil, 0.2, nil, 0.2], rel: [1, 0.05, 1, 0.05]},
  {n: [:F2, :F2, :E2, :E2], t: [1.5, 0.5, 1.5, 0.5], sus: [nil, 0.2, nil, 0.2], rel: [1, 0.05, 1, 0.05]},
  {n: [:A2, :Db3, :D3, :Eb3], t: [1, 1, 1, 1]},
  {n: [:E3, :B2], t: [2, 2], sus: [1.5, 1.5], rel: [0.5, 0.5]},
  #
  {n: [:E3, :Db3, :B2, :Db3], t: [1.5, 0.5, 1.5, 0.5], sus: [nil, 0.2, nil, 0.2], rel: [1, 0.05, 1, 0.05]},
  {n: [:E3, :Db3, :B2, :Db3], t: [1.5, 0.5, 1.5, 0.5], sus: [nil, 0.2, nil, 0.2], rel: [1, 0.05, 1, 0.05]},
  {n: [:E2, :Gb2, :G2, :Ab2], t: [1, 1, 1, 1]},
  {n: [:A2, :Bb2, :E3], t: [1, 0.5, 2.5], sus: [nil, 0.3, nil], rel: [1, 0.2, 2.5]},
  #
  {n: [:A2, :Fs2, :E2, :E2], t: [1.5, 0.5, 1.5, 0.5], sus: [nil, 0.2, nil, 0.2], rel: [1, 0.05, 1, 0.05]},
  {n: [:Ab2, :Ab2, :Bb2, :B2], t: [1.5, 0.5, 1.5, 0.5], sus: [nil, 0.2, nil, 0.2], rel: [1, 0.05, 1, 0.05]},
  {n: [:E3, :Db3, :B2, :Db3], t: [1.5, 0.5, 1.5, 0.5], sus: [nil, 0.2, nil, 0.2], rel: [1, 0.05, 1, 0.05]},
  {n: [:E3, :Db3, :B2, :Db3], t: [1.5, 0.5, 1.5, 0.5], sus: [nil, 0.2, nil, 0.2], rel: [1, 0.05, 1, 0.05]},
  {n: [:B2, :B2, :B2, :B2], t: [1.5, 0.5, 1.5, 0.5], sus: [nil, 0.2, nil, 0.2], rel: [1, 0.05, 1, 0.05]},
  {n: [:C3, :C3, :B2, :B2], t: [1.5, 0.5, 1.5, 0.5], sus: [nil, 0.2, nil, 0.2], rel: [1, 0.05, 1, 0.05]},
  {n: [:E2, :Ab2, :A2, :Bb2], t: [1, 1, 1, 1]},
  {n: [:B2, :r, :Gb2], t: [1, 1.5, 1.5], rel: [1, 1.5, 1.5]},
  #
  {n: [:B2, :B2, :B2, :B2], t: [1, 1, 1, 1]},
  {n: [:G2, :G2, :Gb2, :Gb2], t: [1, 1, 1, 1]},
  {n: [:B2, :B2, :B2, :B2], t: [1, 1, 1, 1]},
  {n: [:G2, :G2, :Gb2, :Gb2], t: [1, 1, 1, 1]},
  {n: [:B2, :B2, :B2, :B2], t: [1, 1, 1, 1]},
  {n: [:G2, :G2, :Gb2, :Gb2], t: [1, 1, 1, 1]},
  {n: [:B2, :B2, :B2, :B2], t: [1, 1, 1, 1]},
  {n: [:G2, :Gb2], t: [2, 2], sus: [1.5, 1.5], rel: [0.5, 0.5]},
  {n: [:B2], t: [4], sus: [2], rel: [1.5]},
]
2 Likes

Sounds very nice! I think you’ve got the fm synth set up nicely for your bass line.
Great debut post to in-thread!
EDIT
I’ve used that short time in a loop to make sure it catches the sync properly. It works well.

Wouldn’t it be a good idea if SP could handle the synchronization in the core? Just like this:

  1. If set/cue :key is called, the time of this call is stored in a variable together with the name of the :key (system time in milliseconds)
  2. if a sync :key is called, the last cue/set time is checked. If the time distance is some milliseconds, the sync call is considered successful

This would allow for some glitch between the sync and the cue/set.