# Copyright Kirk Rader 2023. All rights reserved.
# Farandole in Tempore Belli
# sleep duration for each beat
tempo = 0.2
# beats per measure
measure = 16
# drum rhythm
spread_lo = (spread 5, measure)
spread_mid = (spread 7, measure)
spread_hi = (spread 3, measure / 2 - 1)
spread_bass = (spread 1, measure)
# start of each measure
spread_measures = (spread 1, measure)
# arpeggio rhythms
spread_arp_1 = (spread measure / 3, measure / 2)
spread_arp_2 = (spread (measure / 2) + 1, measure)
chords = (ring
(chord :a4, :minor),
(chord :d4, :minor, invert: 2),
(chord :g4, :major),
(chord :c4, :major, invert: 2),
(chord :f4, :major),
(chord :b3, :diminished, invert: 2),
(chord :a3, :minor, invert: 2),
(chord :e4, :sus4),
(chord :e4, :major))
bass = (ring
(note :a2),
(note :d1),
(note :g1),
(note :c1),
(note :f2),
(note :b1),
(note :c2),
(note :e1),
(note :e1))
# duration of one measure
measure_hold = tempo * measure
# number of beats in a full phrase
phrase_length = chords.length * measure
# number of beats to repeat the full phrase 6 times
repeats = phrase_length * 6
# current beat number being played each time :master is cued
count = 0
# current phrase number being played each time :master is cued
phrase = 0
# stop threads next time :master is cued
terminate = false
# flags to allow different voices to be played out
# as separate tracks
enable_drums = true
enable_ambience = true
enable_chords = true
enable_arp_1 = true
enable_arp_2 = true
enable_bass = true
# flag to emit single-note voices (arp_1,
# arp_2 and bass) as midi events rather
# than playing audio output
enable_midi = false
# play arpeggio for 1 measure
define :play_arp do |notes, spread|
in_thread do
with_fx :reverb, room: 0.75 do
with_synth :chiplead do
measure.times do
stop if terminate
if spread.tick
if enable_midi
midi notes[0], sustain: tempo
else
play notes[0], sustain: tempo, amp: phrase * 0.1
end
notes = notes.rotate
end
sleep tempo
end
end
end
end
end
with_fx :reverb, room: 0.75 do
begin
# chords & arpeggios
in_thread do
with_random_source :perlin do
loop do
# await next beat
sync :master
stop if terminate
# only take action once per measure
if spread_measures.tick
# play current chord using ambient voice
with_synth :dark_ambience do
with_transpose -12 do
play chords[0], sustain: measure_hold, amp: 1.5 * phrase, pan: rrand(-0.5, 0.5) if enable_ambience && phrase > 0
end
end
# play current chord as arpeggio using first pattern
play_arp chords[0], spread_arp_1 if enable_arp_1 && phrase > 1
# play current chord as arpeggio using second pattern
play_arp chords[0], spread_arp_2 if enable_arp_2 && phrase > 2
# play current cord using melodic voice
with_synth :chipbass do
play chords[0], sustain: measure_hold * 0.8, amp: phrase * 0.1, pan: rrand(-0.5, 0.5) if enable_chords && phrase > 2
end
# play current note in bass line
with_synth :bass_foundation do
if enable_bass && phrase > 3
if enable_midi
midi bass[0], sustain: measure_hold
else
play bass[0], sustain: measure_hold, pan: rrand(-0.5, 0.5)
end
end
end
# advance to the next measure
chords = chords.rotate
bass = bass.rotate
end
end
end
end
# percussion
if enable_drums
in_thread do
with_random_source :white do
loop do
sync :master
stop if terminate
tick
sample :drum_tom_lo_hard, rate: rrand(0.75, 1.25), amp: rrand(0.2, 0.5), pan: -0.25 if spread_lo.look
sample :drum_tom_mid_hard, rate: rrand(0.75, 1.25), amp: rrand(0.2, 0.5), pan: 0.25 if spread_mid.look
sample :drum_tom_hi_hard, rate: rrand(0.75, 1.25), amp: rrand(0.2, 0.5) if spread_hi.look
sample :drum_bass_hard, rate: rrand(0.75, 1.25), amp: rrand(0.5, 0.8) if spread_bass.look
end
end
end
end
# clapper for syncing tracks in a DAW
with_synth :beep do
play :a4
midi :a4, sustain: 1
end
# give space after clapper
sleep measure_hold
# master clock for all threads
repeats.times do
cue :master
count = count + 1
phrase = phrase + 1 if count % phrase_length == 0
sleep tempo
end
ensure
terminate = true
sleep tempo * 2
cue :master
sleep tempo * 2
midi_all_notes_off if enable_midi
end
end