Here’s the Sonic Pi code
# Gradle music
use_osc_logging false
use_debug false
use_cue_logging false
use_midi_logging false
use_bpm 90
use_timing_guarantees true
use_osc "localhost", 4560
#########################################################
# INIT: when the Gradle builds emits an init event
#########################################################
define :initStarted do |live_loop_name, args|
loop_kill_switch = loop_kill_switch(live_loop_name).to_sym
live_loop live_loop_name.to_sym do
maybeKillLiveLoop(live_loop_name, loop_kill_switch)
use_synth :fm
use_octave -2
3.times do
play c[0]
sleep 1
end
play c[2]
sleep 0.5
play c[1]
sleep 0.5
c = chords.tick
end
end
define :initFinished do |live_loop_name, args|
setKillLiveLoopSwitch(live_loop_name)
end
#########################################################
# BUILD : when the Gradle builds emits an build event
#########################################################
define :buildStarted do |live_loop_name, args|
loop1 = live_loop_name + "_blade"
loop_1_kill_switch = loop_kill_switch(loop1).to_sym
live_loop (loop1).to_sym do
maybeKillLiveLoop(live_loop_name, loop_1_kill_switch)
use_synth :blade
play c
sleep 2
end
end
define :buildFinished do |live_loop_name, args|
setKillLiveLoopSwitch(live_loop_name + "_blade")
end
#########################################################
# TASK: : when the Gradle builds emits an task event
#########################################################
define :taskStarted do |live_loop_name, args|
loop1 = live_loop_name + "_blade"
loop_1_kill_switch = loop_kill_switch(loop1).to_sym
live_loop (loop1).to_sym do
maybeKillLiveLoop(live_loop_name, loop_1_kill_switch)
use_synth :blade
r = [0.25, 0.25, 0.5, 1].choose
play c.choose, attack: 0, release: r
sleep r
end
end
define :taskFinished do |live_loop_name, args|
setKillLiveLoopSwitch(live_loop_name + "_blade")
end
#########################################################
# Helper methods
#########################################################
# OSC bus live loop
# Receives OSC events and trigger live_loop creation or stopping based on the passed data
live_loop :osc_bus do
use_real_time
# Wait on an OSC event at that address
args = sync "/osc*/event/**"
input_event = parse_sync_address "/osc*/event/**"
puts "Received event: " + input_event.to_s
type = input_event[2]
unique_id = input_event[3]
action = input_event[4]
loopName = loop_name(type, unique_id)
case action
when "started"
event_started(type, loopName, args)
when "finished"
event_finished(type, loopName, args)
else
puts "Unknown action: " + action
stop
end
# Ping back that we are ready to receive a new OSC event
# The Gradle builds waits for this 'ack' OSC message before moving on
osc_send "localhost", 57110, "/ack"
end
# Creates a thread creating a live_loop which is monitoring when it should stop itself
define :event_started do |type, live_loop_name, args|
in_thread(name: (live_loop_name + "_createthread").to_sym) do
case type
when "init"
initStarted(live_loop_name, args)
when "build"
buildStarted(live_loop_name, args)
when "task"
taskStarted(live_loop_name, args)
else
puts "Unknown type: " + type
stop
end
end
end
# Creates a thread that is eventually going to stop a live_loop after some sleep
define :event_finished do |type, live_loop_name, args|
in_thread(name: (live_loop_name + "_killthread").to_sym) do
case type
when "init"
initFinished(live_loop_name, args)
when "build"
buildFinished(live_loop_name, args)
when "task"
taskFinished(live_loop_name, args)
else
puts "Unknown type: " + type
stop
end
end
end
define :parse_sync_address do |address|
# puts address #-> ""/osc*/event/**""
# puts get_event(address).to_s.split(",")[6] #-> " \"/osc:127.0.0.1:4560/event/project/0/started/bd_haus\""
v = get_event(address).to_s.split(",")[6]
if v != nil
# puts v[3..-2] #-> "osc:127.0.0.1:4560/event/project/0/started/bd_haus"
puts v[3..-2].split("/") #-> "["osc:127.0.0.1:4560", "event", "project", "0", "started", "bd_haus"]"
return v[3..-2].split("/")
else
puts "Error in :parse_sync_address: v=nil"
stop
end
end
# function to define a unique name based on the type and unique id
define :loop_name do |type, unique_id|
return type + "_" + unique_id
end
# function to define the unique live_loop kill switch
define :loop_kill_switch do |live_loop_name|
return live_loop_name + "_kill"
end
# function to define the unique live_loop kill switch
define :loop_kill_delta do |live_loop_name|
return live_loop_name + "_delta"
end
define :setKillLiveLoopSwitch do |live_loop_name|
loop_kill_switch = loop_kill_switch(live_loop_name).to_sym
set loop_kill_switch, 1
end
define :maybeKillLiveLoop do |live_loop_name, loop_kill_switch|
if get(loop_kill_switch) == 1
set loop_kill_switch, 0
stop
end
end
The entry point is the :osc_bus live_loop, that is going to wait for an OSC message, like
osc:127.0.0.1:4560/event/init/0/started
osc:127.0.0.1:4560/event/project/1/started
osc:127.0.0.1:4560/event/task/2/started
osc:127.0.0.1:4560/event/task/2/finished
osc:127.0.0.1:4560/event/task/3/started
osc:127.0.0.1:4560/event/task/3/finished
osc:127.0.0.1:4560/event/task/4/started
osc:127.0.0.1:4560/event/task/4/finished
osc:127.0.0.1:4560/event/project/1/finished
osc:127.0.0.1:4560/event/init/0/finished
This sequence above is very simple (init, project, then 3 tasks, then project finished, init finished).
You can imagine having way more task events sent, very close to each others (only a few millis apart), and overlapping (i.e. several ‘started’ tasks before some ‘finished’ tasks, due to the Gradle build’s parallelism)
When the action is ‘started’, the :event_started function is called, that starts a thread.
When the action is ‘finished’, the :event_finished function is called, that starts another thread.
I’m using threads here to unblock as soon as possible the main thread (the one of the :osc_bus live_loop), so that it can answer with ‘ack’ as soon as possible and be ready to accept a new incoming OSC message.
The :event_started thread calls initStarted, buildStarted or taskStarted based on the action grabbed from the OSC message. Those functions define what melody to play based on the kind of event received.
The :event_finished thread will essentially set some global variable in TimeState to stop the created live_loop previously created for that event.
Hope this helps you figure out what the problem might be.