Skip to content

Commit

Permalink
Lang - teach live_loop to move like live_audio
Browse files Browse the repository at this point in the history
This touches in_thread, with_fx, live_loop and the threading infrastructure - so is highly likely to have associated bugs.

I'm committing it with all the logging statements in place incase these are needed for debugging purposes in the future - I'll remove them in the next commit, but they'll be here in the history.

This was extremely tricky code to write which is why it's taken me so long to get round to implementing such an obvious fix to the language.
  • Loading branch information
samaaron committed Oct 15, 2024
1 parent d2c183c commit 06fe62a
Show file tree
Hide file tree
Showing 6 changed files with 287 additions and 36 deletions.
162 changes: 150 additions & 12 deletions app/server/ruby/lib/sonicpi/lang/core.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
require_relative "../version"
require_relative "../util"
require_relative "../runtime"
require_relative "../promise"
require_relative "western_theory"

require 'active_support/inflector'
Expand Down Expand Up @@ -2298,22 +2299,159 @@ def live_loop(name=nil, *args, &block)
raise ArgumentError, "Live loop block must only accept 0 or 1 args"
end

in_thread(name: ll_name, delay: delay, sync: sync_sym, sync_bpm: sync_bpm_sym) do
__system_thread_locals.set_local :sonic_pi_local_live_loop_auto_cue, auto_cue
if args_h.has_key?(:init)
res = args_h[:init]
else
res = 0
end
use_random_seed args_h[:seed] if args_h[:seed]
loop do
__live_loop_cue name if __system_thread_locals.get :sonic_pi_local_live_loop_auto_cue
res = send(ll_name, res)
start_live_loop = Promise.new
parent_thread = Thread.current
# lg ""
# lg "live_loop creation w/: #{Thread.current.object_id} -> #{__system_thread_locals.get(:sonic_pi_local_parent_thread).object_id}"
# lg "======================"
mk_live_loop = lambda do
in_thread(name: ll_name, delay: delay, sync: sync_sym, sync_bpm: sync_bpm_sym) do

start_live_loop.get
__system_thread_locals.set_local :sonic_pi_local_live_loop_auto_cue, auto_cue
if args_h.has_key?(:init)
res = args_h[:init]
else
res = 0
end
lg "LL #{name} - started - [#{Thread.current.object_id}] -> #{__system_thread_locals.get(:sonic_pi_local_parent_thread).object_id}"
use_random_seed args_h[:seed] if args_h[:seed]

loop do
new_bus = nil
new_group = nil
new_parent_t = nil
completed_prom = nil
new_thread_moved_prom = nil
new_thread_moved_ack_prom = nil
__system_thread_locals.get(:sonic_pi_local_spider_thread_move_mut).synchronize do
new_bus, new_group, new_parent_t, completed_prom, new_thread_moved_prom, new_thread_moved_ack_prom = __system_thread_locals.get :sonic_pi_local_live_loop_move_to_bus_and_parent_t

# immediately reset for the next move
__system_thread_locals.set_local :sonic_pi_local_live_loop_move_to_bus_and_parent_t, nil
end
if new_bus
moved_prom = __system_thread_locals.get :sonic_pi_local_spider_thread_moved
ack_prom = __system_thread_locals.get :sonic_pi_local_spider_thread_moved_ack
# update the context
# reset tracker and send old tracker with ack
tracker = __current_tracker
__system_thread_locals.set_local(:sonic_pi_local_tracker, nil)
old_bus = __system_thread_locals.get :sonic_pi_mod_sound_synth_out_bus
__system_thread_locals.set(:sonic_pi_mod_sound_synth_out_bus, new_bus)
__system_thread_locals.set(:sonic_pi_mod_sound_job_group, new_group)

lg ""
lg "live_loop #{Thread.current.object_id} "
lg " - parent: #{__system_thread_locals.get(:sonic_pi_local_parent_thread).object_id}"
lg " - sending moved signal"
lg " - moved bus from #{old_bus} -> #{new_bus}"
lg " - move completed ack: #{ack_prom.object_id}"
lg ""
moved_prom.deliver! tracker
lg "....waiting for ack"
ack_prom.get
lg "....got ack"
__system_thread_locals.set_local :sonic_pi_local_spider_thread_moved, new_thread_moved_prom
__system_thread_locals.set_local :sonic_pi_local_spider_thread_moved_ack, new_thread_moved_ack_prom
__system_thread_locals.set_local :sonic_pi_local_live_loop_move_to_bus_and_parent_t, nil
new_job_id = __system_thread_locals(new_parent_t).get(:sonic_pi_spider_job_id)
job_subthread_move_named(Thread.current, new_job_id, ll_name)
__remove_thread_from_parent_subthreads!(Thread.current)
__move_thread_to_new_parent!(Thread.current, new_parent_t)
lg ""
lg "live_loop #{Thread.current.object_id} "
lg " - parent: #{__system_thread_locals.get(:sonic_pi_local_parent_thread).object_id}"
lg " - moved to bus: #{current_out_bus}"

lg ""
completed_prom.deliver! :moved
end
__live_loop_cue name if __system_thread_locals.get :sonic_pi_local_live_loop_auto_cue
res = send(ll_name, res)
end
end
end

live_loop_thread = mk_live_loop.call
st = sthread(ll_name)
__system_thread_locals(st).set_local :sonic_pi_local_live_loop_auto_cue, auto_cue if st
if live_loop_thread.alive?

# live loop was created - this was the first time
# let it start normally
start_live_loop.deliver! :go
else
new_out_bus = current_out_bus
new_synth_group = current_group
in_thread do
new_thread_moved_prom = __system_thread_locals.get(:sonic_pi_local_spider_thread_moved)
new_thread_moved_ack_prom = __system_thread_locals.get(:sonic_pi_local_spider_thread_moved_ack)
move_holding_thread_prom = Promise.new
Thread.new do
__system_thread_locals.set_local :sonic_pi_local_thread_group, "ll swapper and holding thread for #{name} #{st}"
lg "LL[#{Thread.current.object_id}] #{name} - move - [#{st.object_id}] -> #{parent_thread.object_id} - ST:#{Thread.current.object_id}"
completed_prom = Promise.new


ct1 = Thread.new do
__system_thread_locals.set_local :sonic_pi_local_thread_group, "ll ct1 mv waiter for #{name} #{st.object_id} #{__system_thread_locals(st).get(:sonic_pi_local_thread_group)}"
__system_thread_locals(st).get(:sonic_pi_local_spider_thread_move_mut).synchronize do

_b, _g, t, p = __system_thread_locals(st).get(:sonic_pi_local_live_loop_move_to_bus_and_parent_t)
if p
lg "CLOBBERING #{t.object_id} #{__system_thread_locals(t).get(:sonic_pi_local_thread_group)} #{p.object_id}"
## another live loop already registered a move, but didn't manage to swap in time - so we're going to clobber it
p.deliver! :clobbered
end
__system_thread_locals(st).set_local :sonic_pi_local_live_loop_auto_cue, auto_cue if st
## register our move

__system_thread_locals(st).set_local :sonic_pi_local_live_loop_move_to_bus_and_parent_t, [new_out_bus, new_synth_group, parent_thread, completed_prom, new_thread_moved_prom, new_thread_moved_ack_prom]
end
end

ct2 = Thread.new do
__system_thread_locals.set_local :sonic_pi_local_thread_group, "ll ct2 join waiter for #{name} #{st.object_id} #{__system_thread_locals(st).get(:sonic_pi_local_thread_group)}"
st.join
completed_prom.deliver! :joined
end

moved_or_clobbered = completed_prom.get
ct1.kill
ct2.kill

if moved_or_clobbered == :moved
lg "LL[#{Thread.current.object_id}] #{name} - moved - [#{st.object_id}] - ST:#{Thread.current.object_id}"

st_joined_or_moved_again = Promise.new
mt1 = Thread.new do
__system_thread_locals.set_local :sonic_pi_local_thread_group, "ll join waiter for #{name} #{st.object_id} #{__system_thread_locals(st).get(:sonic_pi_local_thread_group)}"
st.join
st_joined_or_moved_again.deliver! true, false
end

mt2 = Thread.new do
__system_thread_locals.set_local :sonic_pi_local_thread_group, "ll move waiter for #{name} #{st.object_id} #{__system_thread_locals(st).get(:sonic_pi_local_thread_group)}"

ack_p = __system_thread_locals(st).get(:sonic_pi_local_spider_thread_moved_ack)
lg "LL[#{Thread.current.object_id}] #{name} - waiting for move again - [#{st.object_id}] - ack_p: #{ack_p.object_id}"
ack_p.get
st_joined_or_moved_again.deliver! true, false
end
lg "LL[#{Thread.current.object_id}] #{name} - KA - [#{st.object_id}] -> #{parent_thread.object_id} - ST:#{Thread.current.object_id}"
st_joined_or_moved_again.get
lg "LL[#{Thread.current.object_id}] #{name} - Done - [#{st.object_id}] -> #{parent_thread.object_id} - ST:#{Thread.current.object_id}"
mt1.kill
mt2.kill
else
lg "LL[#{Thread.current.object_id}] #{name} - clobbered - [#{st.object_id}] - ST:#{Thread.current.object_id}"
end
move_holding_thread_prom.deliver! true
end

move_holding_thread_prom.get
end
end
st
end
doc name: :live_loop,
Expand Down
49 changes: 43 additions & 6 deletions app/server/ruby/lib/sonicpi/lang/sound.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1814,19 +1814,54 @@ def with_fx(fx_name, *args, &block)
else
kill_delay = args_h[:kill_delay] || 1
end
subthreads.each do |st|
st.join
__system_thread_locals(st).get(:sonic_pi_local_spider_subthread_empty).get

lg "FX #{fx_name} - waiting #{subthreads.size} - #{new_bus}"

subthreads.map do |st|


Thread.new do
__system_thread_locals.set_local :sonic_pi_local_thread_group, "wfx subthread waiter for #{fx_name} #{new_bus}"
subthread_completed_or_moved = Promise.new
wt1 = Thread.new do
__system_thread_locals.set_local :sonic_pi_local_thread_group, "wfx join waiter for #{fx_name} #{new_bus}"
st.join
__system_thread_locals(st).get(:sonic_pi_local_spider_subthread_empty).get
lg " --> FX #{fx_name} - GC Joined - #{new_bus} - ST:#{st.object_id}"
subthread_completed_or_moved.deliver! true
end
wt2 = Thread.new do
__system_thread_locals.set_local :sonic_pi_local_thread_group, "wfx move waiter for #{fx_name} #{new_bus}"
p = __system_thread_locals(st).get(:sonic_pi_local_spider_thread_moved)
lg " --> FX #{fx_name} - GC Wait - #{new_bus} - ST:#{st.object_id} P:#{p.object_id} "
# lg "wfx [#{fx_name}] - waiting for thread moved #{st.class.inspect} - #{st.object_id}"
#
tracker_from_moved_thread = p.get
lg " --> wfx [#{fx_name}] - bus:#{new_bus.id} delivering prom:#{__system_thread_locals(st).get(:sonic_pi_local_spider_thread_moved_ack).object_id}"
__system_thread_locals(st).get(:sonic_pi_local_spider_thread_moved_ack).deliver! true, false
# lg "wfx [#{fx_name}] - waiting on tracker #{tracker.object_id}"
tracker_from_moved_thread.get
# lg "wfx [#{fx_name}] - tracker completed #{tracker.object_id}"
lg " --> FX #{fx_name} - GC Moved - #{new_bus} - #{st.object_id}"
subthread_completed_or_moved.deliver! true
end
subthread_completed_or_moved.get
wt1.kill
wt2.kill
end
end.each do |jt|
jt.join
end
lg "FX #{fx_name} - trk await - #{new_bus} - trk:#{tracker.object_id}"
tracker.block_until_finished
Kernel.sleep(kill_delay)
lg "FX #{fx_name} - completed - #{new_bus}"
lg "FX #{fx_name} - killing - #{new_bus}"
fx_container_group.kill(true)
end

gc_init_completed.deliver! true
end ## end gc collection thread definition


end

## Trigger new fx synth (placing it in the fx group) and
Expand All @@ -1851,6 +1886,8 @@ def with_fx(fx_name, *args, &block)
__system_thread_locals.set(:sonic_pi_mod_sound_synth_out_bus, new_bus)
__system_thread_locals.set_local(:sonic_pi_local_mod_fx_tracker, tracker)

lg "FX #{fx_name} - created - #{new_bus}"

begin
if block.arity == 0
args_h[:reps].times do
Expand Down Expand Up @@ -3544,7 +3581,7 @@ def trigger_inst(synth_name, args_h, info=nil, group=current_group)


unless __thread_locals.get(:sonic_pi_mod_sound_synth_silent)
__delayed_message "synth #{synth_name.inspect}, #{arg_h_pp(processed_args)}"
__delayed_message "[#{Thread.list.size}] synth #{synth_name.inspect}, #{arg_h_pp(processed_args)}"
end

add_arg_slide_times!(processed_args, info) if info
Expand Down
Loading

0 comments on commit 06fe62a

Please sign in to comment.