I’ve been learning music theory and got to a section on harmonics. I wanted to play around with this in sonic pi. My first solution was based on some codes by martin:
def play_harmonic(fundamental, amps, *args)
use_synth :sine
partials = [fundamental,
fundamental * 2,
fundamental * 3,
fundamental * 4,
fundamental * 5,
fundamental * 6,
fundamental * 7,
fundamental * 8].map{|hz| hz_to_midi(hz) }
partials.each_with_index do |note, index|
play note, *args
end
end
sine = [1,0,0,0,0,0,0,0]
violin = [1,0.8,0.5,0.25,0.35,0.35,0.5,0.5]
flute = [1,0.5,0.8,0.35,0.25,0.1525,0.125,0.255]
trumpet = [0.96,0.94,1,0.75,0.73,0.75,0.48,0.5]
guitar = [1,0.75,0.65,0.65,0.7,0.75,0.6,0.8]
play_harmonic 100, flute, release: 3
However after hearing some delay with this implementation, and because I fancied playing around with some code, I decided to try to implement it in a similar way to how sonic-pi implements play_chord
(you can find this here). Here’s what I’ve ended up with:
def play_harmonic(fundamental, amps, *args)
sn = :sine.to_sym
info = Synths::SynthInfo.get_info(sn)
args_h = resolve_synth_opts_hash_or_array(args)
args_h = normalise_and_resolve_synth_args(args_h, info, true)
partials = [fundamental,
fundamental * 2,
fundamental * 3,
fundamental * 4,
fundamental * 5,
fundamental * 6,
fundamental * 7,
fundamental * 8].map{|hz| hz_to_midi(hz) }
unless __system_thread_locals.get(:sonic_pi_spider_real_time_mode)
if __thread_locals.get(:sonic_pi_mod_sound_timing_guarantees)
unless in_good_time?
__delayed_message "!! Out of time, skipping: synth #{sn.inspect}, #{arg_h_pp({note: notes}.merge(args_h))}"
return BlankNode.new(args_h)
end
end
end
unless __thread_locals.get(:sonic_pi_mod_sound_synth_silent)
__delayed_message "synth #{sn.inspect}, #{arg_h_pp({note: partials}.merge(args_h))}"
end
chord_group = @mod_sound_studio.new_group(:tail, current_group, "CHORD")
cg = ChordGroup.new(chord_group, partials, info)
nodes = []
amp = args_h[:amp] || 1.0
partials.each_with_index do |note, index|
if note
args_h[:note] = note
args_h[:amp] = amp * amps[index]
nodes << trigger_synth(:sine, args_h.dup, cg, info)
end
end
cg.sub_nodes = nodes
__thread_locals.set(:sonic_pi_local_last_triggered_node, cg)
cg
end
sine = [1,0,0,0,0,0,0,0]
violin = [1,0.8,0.5,0.25,0.35,0.35,0.5,0.5]
flute = [1,0.5,0.8,0.35,0.25,0.1525,0.125,0.255]
trumpet = [0.96,0.94,1,0.75,0.73,0.75,0.48,0.5]
guitar = [1,0.75,0.65,0.65,0.7,0.75,0.6,0.8]
play_harmonic 100, flute, release: 3
There is a potential here for a bit of further abstraction here too. Maybe a trigger_amped_chords
that allows the playing of midi/notes simultaneously but at different volumes.
Figured I’d share, could be interesting for others