# How do I synthesize arbitrary timbres arithmetically?

I came to this hoping this would work out of the box, but it would seem as though I need our good friend Fourier and loops to generate arbitrary waveforms procedurally if I don’t just create a sample using external software (defeating the point of Sonic Pi in this case) and use that instead for synthesis.

If anything, only thing I want to know is what arithmetic capacities the language has, and how I can easily translate that into waveforms presumably by combining synths and FX. I tried making a procedural timbre using halves, but I seem to lack the knowledge of how to use a list of frequencies to generate a sequence of unique synth function invocations. Ideally, though, there’d be an existing synth that lets me provide a waveform perhaps as one of the available data structures with index related to frequency, and value as the relative amplitude.

Then with that in mind as a concept for generating arbitrary waveforms procedurally, I could sample an arbitrary mathematical expression for an arbitrary range of values and then process it accordingly for use with said procedural timbre generator.

1 Like

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).

Hello! Yes, I think you’re right that this is best done by building a new SynthDef in SuperCollider. My rough heuristic is that Sonic Pi is more geared toward sequencing than synthesis per se. While I haven’t actually worked through it myself, people seem to like this tutorial: TheManual | A manual for learning to write Supercollider synths for use in Sonic Pi

Fortunately, the syntax of SuperCollider makes it pretty easy to generate large numbers of oscillators. You might find this example helpful: Recreating the THX Deep Note · Earslap

Or this relatively terse one: Additive Synthesis Basics on SuperCollider · GitHub

Do you have any ideas of specific sounds you’d like to create, or functions you’d like to sonify?

Sure would seem to be geared towards sequencing, but it would seem that even though it’s been out for like a decade, it somehow missed the devs that the note values are completely off? I’m not getting linear changes in note going from things like :c4 to :c5 in various places. I had to correct them myself with my own symbols (also, typing : all the time is annoying). How in the world do you screw up that badly?

That major inconvenience aside, no. I want to make all sounds. My expectations with this software were a means to be absolutely unhinged in capacity to create timbres, but I deceived myself it seems. Basically make arbitrary waveforms like a synthesizer with it, and then also do some sequencing with what I come up with as well. Starting to look more like I should just deal with SuperCollider itself for everything as it seems to be more competent. Like if I wrap every single play in an effect, and then try to play them all, SuperCollider says “ran out of time”… on a desktop PC no less, not a Raspberry Pi, but not if I just use one effect on top of all of them. What the heck, bro? That’s not funny… that’s the most frustrating thing in the world having to deal with working around software defects so that I can get what I want accomplished…

While I’m at it, getting timings right has been a hassle already, and it would seem that pressing play multiple times seems to introduce some kind of distortion into the sound because reasons, while also experiencing some kind of noticeable lag despite having the 2000s equivalent of a supercomputer? And then there’s trying to analyze the waveform itself in the waveform output, but it’s at some sort of awkward refresh rate, so I have to mess with the actual choice of note in hertz to get some sort of idea of what the waveform looks like such that it’s virtually at a stand-still, otherwise all I see is a stuttery mess.

So far, this is just frustration and nothing more. I am so ready to just quit this altogether and wait to make my own synthesis and sequencing software. It’s this nonsense that is exactly why I (inevitably and eventually) will make my own.

Could you say what you mean by “linear changes in note”—what you’re expecting to see, and what you see instead? I’ve been using Sonic Pi for a while and have found the devs to be gracious and extremely helpful.

Sorry you’ve found this frustrating. In my experience, Sonic Pi is optimized for exactly the sort of musically expressive live coding that I found difficult to do in SuperCollider, where the syntax and basic libraries are geared much more toward synthesis than sequencing. Not to dissuade you from trying to code your own engine; I just can’t imagine that would be easier than learning some basic SuperCollider, then importing your SynthDefs into Sonic Pi.

Or maybe you’d be happier with something like CSound, which has integrated, complementary systems for the “orchestra” and “score”? I knew some people who used that pretty heavily; they all moved to SuperCollider, but it’s still around.

Could you say what you mean by “linear changes in note”

Linearity is marked by a constant measure for all given changes, and a note is one of ABCDEFG relative to an octave with the smallest constant measure of change of note being 1.

SuperCollider, then importing your SynthDefs into Sonic Pi.

The DSL (domain-specific language) is ugly. No need to reinvent the wheel. C and Lua exist with better polymorphic and abstractive capacities while remaining simple. One of the documentation websites I found for Ruby is also apparently opinionated as it calls for-loops something along the lines of archaic and useless compared to “X.upto(Y)” iterating syntax despite no change in semantics from for loops. That’s about as useful as calling a programming language diverse and inclusive. I don’t think machines care. Neither do I when I’m trying to read documentation so I can write code. Speaking of documentation, it may as well not exist if I can’t find what I need, or have to rely on forum browsing to use Sonic Pi. Documentation should let me use a system without needing to ask for help! A massive list of procedures and whatnot that I can use is quite obviously terrible UX, at least as a default!

Or maybe you’d be happier with something like CSound

I took one look at its language and its basic “Get Started 1” demo, then noped out because it’s garbage to code in. That is maybe the one good thing about Sonic Pi, except I still (in case this rant wasn’t making it clear) can’t quite figure out in a straightforward manner how to do just what I want and write code because it’s 2024 and being straightforward in code is illegal now unless you write C like me (for now). We do not live in the Fortran era where abbreviations and the like are necessary (B and C could take some notes for their standard libraries), yet the CSound demo is littered with this unreadable garbage!

Yep. Everything sucks. I’m making my own. I don’t have time for dealing with other people’s failures and poorly-thought-out designs. (You really just couldn’t let me use math procedures within Sonic Pi so I can code them myself in the most annoying way possible such as a LUT for floored log2 even though it’s two instructions on x86?) People need to stop settling for good enough and “it gets the job done” because this is just inexcusable. It’s pretty sad that it takes milliseconds to print hello world in ECMAScript when aircraft simulations from decades ago run in the same amount of time. Remind me again how old the “echo” command is in the Windows shell (cmd.exe) and when it was coded and for what hardware, and the time it took on that hardware to execute.

Thank you for taking the time to reply to me, but I’m out.