Skip to content

Instantly share code, notes, and snippets.

@shawngraham
Created January 16, 2024 17:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save shawngraham/ac6602b0a228d5b1c967d6983f98a82b to your computer and use it in GitHub Desktop.
Save shawngraham/ac6602b0a228d5b1c967d6983f98a82b to your computer and use it in GitHub Desktop.
1384: Letters from Barcelona. An experiment sonifying the metadata in an archive. Listen at https://soundcloud.com/fhg1711/1384-letters-from-barcelona-sonification-version
# 1384: Letters from Barcelona - sonification version
# Data is travel time from Barcelona to Pisa and Barcelona to Avignon for letters (piano: to Pisa; string pluck: to Avignon). Duration is a function of the travel time scaled to 4 beats = 2 weeks. Notes are scored to correspond with the start date; pitch is a function of duration it took for the letter to reach its destination, so silences are meaningful. Longer journeys have higher and longer tones. The percussion plays the beat so that we can hear the progression of time; there is a background drone to indicate the passage of months.
# this code was rubber-ducked using GPT4-preview
require 'date'
ba_data = [9,9,8,7,9,8,8,8,12,9,8,9,10,8,8,8,8,11] #barcelona - avignon
ba_data_trip_start_date = ["1384-05-02","1384-05-09","1384-05-12","1384-05-20","1384-05-23","1384-05-26","1384-06-03","1384-06-15","1384-06-23","1384-07-11","1384-08-02","1384-08-06","1384-08-12","1384-09-01","1384-09-06","1384-09-06","1384-09-12","1384-09-19"]
bp_data = [26,22,34,23,23,21,30,26,24,14,30,19,7,28,25,22,48,22,23,29,16,33,31,44,41,10] #barcelona - pisa
bp_data_trip_start_date = ["1384-01-04","1384-01-22","1384-01-28","1384-02-08","1384-02-11","1384-02-13","1384-02-15","1384-02-19","1384-02-24","1384-03-11","1384-04-05","1384-04-29","1384-04-30","1384-05-09","1384-05-17","1384-05-26","1384-06-23","1384-07-02","1384-07-11","1384-08-06","1384-09-26","1384-11-11","1384-12-13","1384-12-19","1384-12-22","1384-12-29"]
# Trigger date to start playing notes from
trigger_date = "1384-01-01"
# 4 beats = 2 week
days_per_four_beats = 14
define :median do |values|
sorted = values.sort
len = sorted.length
(sorted[(len - 1) / 2] + sorted[len / 2]) / 2.0
end
define :map_to_scale do |value, data, octave|
median_value = median(data)
scale_size = scale(octave, :minor_pentatonic).size
middle_index = scale_size / 2
index_offset = (value - median_value).round
middle_index + index_offset
end
define :time_to_beats do |time_in_days|
beats_per_day = 4.0 / days_per_four_beats
time_in_days * beats_per_day
end
define :date_to_days do |start_date, date|
(Date.parse(date) - Date.parse(start_date)).to_i
end
define :schedule_note do |zipped_data, synth_type, synth_opts|
use_bpm 240 # Marching tempo
in_thread(name: synth_opts[:name]) do
zipped_data.each do |value, date|
days_since_trigger = date_to_days(trigger_date, date)
sleep days_since_trigger * (4.0 / days_per_four_beats) if days_since_trigger > 0 # Convert days since trigger to beats
note_duration = time_to_beats(value)
scale_notes = scale(:eb3, :minor_pentatonic)
note_index = map_to_scale(value, zipped_data.map(&:first), :eb3)
note_index %= scale_notes.size
note = scale_notes[note_index]
synth synth_type, {note: note, sustain: note_duration, release: 0.1}.merge(synth_opts)
sleep note_duration
end
end
end
define :play_monthly_voice do |trigger_date, scale_notes, synth_opts|
use_bpm 240 # Set BPM to make a beat equal to a day
beats_per_month = 16 # 4 weeks per month, 4 beats per week
in_thread(name: synth_opts[:name]) do
# Calculate total number of months based on the max 'days_since_trigger' for all trips
max_days_since_trigger = [bp_data_trip_start_date, ba_data_trip_start_date].flatten.max_by { |date| date_to_days(trigger_date, date) }
total_months = (date_to_days(trigger_date, max_days_since_trigger) / 30.0).ceil
# Play a note starting at the bottom of the scale and moving up in thirds every month
total_months.times do |month|
note_index = month * 1 # 2 Moving up by thirds in a scale is equivalent to skipping one note each time
note = scale_notes[note_index % scale_notes.size] # Ensure we loop back to the start of the scale if we run out of notes
octave_adjustment = (note_index / scale_notes.size) * 12 # Adjust the octave if we've looped the scale
adjusted_note = note + octave_adjustment
synth :winwood_lead, {note: adjusted_note, sustain: beats_per_month - 1, release: 1}.merge(synth_opts) # Play the note with the passed synth options
sleep beats_per_month # Wait for the next month
end
end
end
scale_notes = scale(:eb2, :minor_pentatonic) # Start at the bottom of the scale
play_monthly_voice(trigger_date, scale_notes, {name: :monthly_voice, amp: 0.1})
# Precompute zipped data for Barcelona-Pisa
bp_zipped_data = bp_data.zip(bp_data_trip_start_date)
bp_zipped_data_sorted = bp_zipped_data.sort_by { |_, date| Date.parse(date) }
# Precompute zipped data for Barcelona-Avignon
ba_zipped_data = ba_data.zip(ba_data_trip_start_date)
ba_zipped_data_sorted = ba_zipped_data.sort_by { |_, date| Date.parse(date) }
# Schedule the voices using precomputed zipped data
schedule_note(bp_zipped_data_sorted, :piano, {name: :piano_voice, amp: 0.9, pan: -0.5, release: 0.3})
schedule_note(ba_zipped_data_sorted, :pluck, {name: :pluck_voice, amp: 0.9, pan: 0.5, release: 0.3})
# Background sostenuto voices using precomputed zipped data
schedule_note(bp_zipped_data_sorted, :prophet, {name: :prophet_voice, amp: 0.2, pan: -0.5, attack: 1, sustain: 4})
schedule_note(ba_zipped_data_sorted, :saw, {name: :saw_voice, amp: 0.2, pan: 0.5, attack: 1, sustain: 4})
# percussion # straight march
live_loop :march_drumming do
sample :bd_ada, amp: 0.5
sleep 1
sample :drum_bass_soft, amp: 0.5
sleep 1
sample :bd_ada, amp: 0.3
sleep 1
sample :drum_snare_hard, amp: 0.5
sleep 1
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment