Spiegel im Spiegel in Ruby
Arvo Pärt's minimalist piece, reimplemented in Sonic Pi using loops, counters, and conditionals.
About the piece
Spiegel im Spiegel ("Mirror in the Mirror") is a 1978 composition by Arvo Pärt for piano and violin. Its structure is deceptively simple: a single melodic line expands outward and contracts inward in a mirror pattern. I wrote a Ruby program that models this structure algorithmically and plays it through Sonic Pi, a live-coding music environment built on Ruby.
To run this yourself, paste the code into Sonic Pi (free download) and press Run.
Recording
Source code
F_MAJ = [48, 50, 52, 53, 55, 57, 58, 60, 62, 64, 65, 67, 69, 70, 72, 74, 76, 77, 79, 81, 82, 84, 86, 88, 89, 91, 93, 94, 96, 98, 100, 101, 103, 105]
CHORD_SHAPE_1 = [5, 8]
CHORD_SHAPE_2 = [5, 9]
CHORD_SHAPE_3 = [3, 8]
CHORD_SHAPE_4 = [7, 9]
MIDDLE_C_IDX = 14
C_F_A = F_MAJ.select { |n| (n % 12 == 0) || (n % 9 == 0) || (n % 5 == 0) }
def chord_pattern(start_note, intervals, idx, second)
play start_note
if idx && !second
play F_MAJ[idx - 2]
elsif idx && second
play C_F_A.select { |n| n.between?((start_note + intervals[1]), (start_note + intervals[1] + 8)) }.sample
end
sleep 0.65
play start_note + intervals[0]
sleep 0.65
play start_note + intervals[1]
sleep 0.65
end
def play_bar(high, branch_length_idx, bar)
number = if high
idx = MIDDLE_C_IDX + branch_length_idx
F_MAJ[idx]
else
idx = MIDDLE_C_IDX - branch_length_idx
F_MAJ[idx]
end
second = false
chord_parser(number, idx, second)
second = true
chord_parser(number, idx, second)
bar += 1
return bar
end
def chord_parser(number, idx, second)
remainder = number % 12
if [2, 4].include?(remainder) # D, E
chord_pattern(number, CHORD_SHAPE_1, idx, second)
elsif [5, 7].include?(remainder) # F, G
chord_pattern(number, CHORD_SHAPE_2, idx, second)
elsif remainder == 0 # C
chord_pattern(number, CHORD_SHAPE_2, nil, second)
elsif remainder == 9 # A
chord_pattern(number, CHORD_SHAPE_3, idx, second)
elsif remainder == 10 # Bb
chord_pattern(number, CHORD_SHAPE_4, idx, second)
end
end
def opening_bars(high, bar)
bar = play_bar(high, 0, bar)
low_fs
bar = play_bar(high, 0, bar)
return bar
end
def main_section(bar, high, branch_length_start, going_outwards)
loop do
bar = middle_chord(bar) if bar > 3
if high
high = false
branch_length_start += 1
going_outwards ? going_outwards = false : going_outwards = true
else
high = true
end
branch_length_idx = branch_length_start
high_branch_length_idx = 1
return bar if branch_length_start > 8
loop do
if going_outwards
bar = play_bar(high, high_branch_length_idx, bar)
break if branch_length_idx == high_branch_length_idx
high_branch_length_idx += 1
else
bar = play_bar(high, branch_length_idx, bar)
branch_length_idx -= 1
break if branch_length_idx == 0
end
end
end
end
def closing_bars(bar, high)
2.times { bar = play_bar(high, 0, bar) }
play F_MAJ[MIDDLE_C_IDX]
return bar
end
def plink_or_bong
if Random.new.rand(3) == 2
high_cs
else
low_fs
end
end
def middle_chord(bar)
high = true
play F_MAJ[MIDDLE_C_IDX - 2]
bar = play_bar(high, 0, bar)
plink_or_bong
bar = play_bar(high, 0, bar)
return bar
end
def low_fs
with_fx :reverb do
play F_MAJ[10]
end
end
def high_cs
with_fx :reverb do
play F_MAJ[21]
end
end
def play_spiegel_im_spiegel
high = true
going_outwards = false
branch_length_start = 0
bar = 1
bar = opening_bars(high, bar)
bar = main_section(bar, high, branch_length_start, going_outwards)
bar = closing_bars(bar, high)
end
play_spiegel_im_spiegel