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.