Did a bit of music theory reading and learning about what’s possible. Made the following basic procedural sine-based string instrument specified by fundamental frequency and number of overtones as well as a permuted overtone version that lets you vary relative amplitude of overtones:
use_real_time
define :tone_sine do |frequency, amplitude, attack, sustain, release|
synth :sine, note: hz_to_midi(frequency), attack: attack, sustain: sustain, release: release, divisor: 1, depth: 1, amp: amplitude
end
# uses sum of first N integers as 0.5 * N * (N + 1) to compute a component tone's amplitude
# so that the sum of a group of tone's amplitudes is always 1.0
define :compute_amplitude do |whichFrequency, totalFrequencies|
whichFrequency = totalFrequencies - whichFrequency + 1
return (whichFrequency)/(0.5 * totalFrequencies * (totalFrequencies + 1))
end
# Plays overtones + 1 number of concurrent sine tones, the first being
# specified by fundamentalFrequency, and the rest being computed
# overtones relative to the fundamentalFrequency
#
# fundamentalFrequency: float representing fundamental frequency in hertz
# overtones: natural representing the number of overtones
# timbreAmplitude: float or positive integer representing the timbre's amplitude
define :sine_timbre do |fundamentalFrequency, overtones, timbreAmplitude, attack, sustain, release|
with_fx :level, amp: timbreAmplitude do
1.upto(overtones + 1) { |i|
tone_sine i * fundamentalFrequency, compute_amplitude(i, overtones), attack, sustain, release
}
end
end
# same as timbre but specifies a list of overtone permutations in range [1, overtones]
define :sine_timbre_permuted do |fundamentalFrequency, overtones, timbreAmplitude, amplitudePermutation, attack, sustain, release|
amplitudePermutation = amplitudePermutation.ring()
with_fx :level, amp: timbreAmplitude do
1.upto(overtones + 1) { |i|
tone_sine i * fundamentalFrequency, compute_amplitude(amplitudePermutation[i] + 1, overtones), attack, sustain, release
}
end
end
sine_timbre_permuted 200, 6, 1, [6, 5, 4, 3, 2, 1], 0, 0, 1
Obviously, however, this can be generalized to allow any permutation of one or more synths to be played instead simultaneously. Unfortunately, this still doesn’t really allow custom arithmetically defined timbres.
I suspect I need to learn how to make synths for Sonic Pi and make one that’s sufficiently general through its parameters to create arbitrary waveforms that way. Sounds quite awful frankly given how the syntax looks. It does not look very friendly (compared to C for arithmetic).