Sound and Music Computing
Dr Charles Martin
vcf~, bob~, filter math (light)Let’s take a complex sound and remove some content.

Subtractive synthesis is often used in analogue synth designs, particular with those associated with Bob Moog (famous synth designer).
E.g.,:
It’s good for analogue designs because you can get a lot of timbral variation out of few (2 or 3) basic oscillators.


Here’s a basic design for an analogue synthesiser with two sawtooth oscillators.
bob~ object. Similar to vcf~ but modelled on actual Moog filter designs.N.B.: the synthesis part here is quite simple, but processing note information is tricky and requires lots of supporting objects.
We can create a similar synth in Strudel by using the sawtooth oscillator sound, and applying low pass filter and envelope effects.
note("<[c2 c3]*4 [f2 f3]*4>".mul("[1,1.01]"))
.gain(0.5)
.sound("sawtooth") // also try "supersaw"
.lpf(300).lpq(20)
.lpa(.5).lpr(.5)
.lpenv("4")
.lpf and .lpq are the cutoff frequency and resonance of the low-pass filter effect.
.lpenv, .lpa and `.lpr are envelope parameters for the filter frequency.
Remember that all the audio effects like .lpf, .lpq, lpenv can and should be sequenced to modulate them in interesting ways.
The previous example showed how to use some of the audio effects built into Strudel.
In Strudel’s functional design, effects are implemented by adding function calls to your pattern.
Some effects modify the sound of a pattern and others modify the sequence. It’s not always obvious which (e.g., does rev mean reverb or reverse the sequence?)
Note! I’m not going to explain the whole API here, you have to use Strudel’s documentation and the “reference” tab in the REPL to understand what is available.
We introduced Strudel’s filters already, but let’s look at the other typical effects. E.g., gain and pan:
s("hh").gain("0.4|0.5|0.6").pan("0|.5|1").fast(16)
delay, delaytime and delayfeedback (N.B. shortcut in mininotation to all params)
note("c a f e").delay(0.65).delaytime(0.27).delayfeedback(0.8)
note("c a f e").delay("0.65:0.27:0.8")
Reverb room (reverb level) and rsize (“room” size), also with mininotation shortcuts.
s("bd sd [bd bd] [sd bd], [hh*8]").room(.8).rsize(4)
s("bd sd [bd bd] [sd bd], [hh*8]").room(".8:4")
These effects modify your pattern of notes giving you lots of options for variation and algorithmic composition, e.g., rev plays a pattern backwards:
n("0 1 2 3 4 5 6 7").scale("C:minor").rev()
n("0 1 2 3 4 5 6 7").scale("C:minor").jux(rev) // left: forward, right: backward
There are arithmetic effects add, sub, mul, div: (Note .add is effecting the mininotation!)
n("0 1 2 3".add("<0 2 5 3>")).scale("C:minor")
Changing speed with fast or slow
n("0 1 2 3 4 5 6 7").scale("C:minor").slow("1,1.5,4.5")
Signal patterns are waveforms that you can use to modulate or generate patterns.
Use as a low frequency oscillator, e.g., tri controlling a lowpass filter cutoff frequency:
note("c3*16").s("sawtooth")
.lpf(tri.range(100,8000).slow(2))
Use for note patterns, n creates a pattern of indices interpreted by scale, e.g.:
n(saw.range(0,15).segment(16)).scale("C:minor")
n(tri.range(0,15).euclid(7,16)).scale("C:scriabin")
In the above, segment and euclid turn the signal into a discrete sequence.
There’s (at least) two kinds of useful randomness paradigms in Strudel: random signals, and random modifiers. The signals work similarly to the regular signals:
n(irand(16).euclid(7,16)).scale("C:scriabin")
n(perlin.range(-7,24).segment(16)).scale("C:scriabin").s("kawai")
Random modifiers like sometimes, almostNever, and often take an action sometimes.
note("c2".sometimesBy(.2, x=>x.add("12"))).fast(16)
Note the arrow function in there. Nice.
Don’t forget: you can still have randomness in mininotation with | and ?

We introduced FM synthesis earlier in the course as a way to make interesting sounds with just two oscillators.
This fmsynth.pd patch has been used a lot!
$1 is harmonicity (modulation frequency divided by carrier frequency)$2 is the index (modulation depth divided by modulation frequency)This allows us to create a consistent timbre for any frequency input. Can we do more with more oscillators?

Let’s just revise how “frequency modulation” works.
phasor~ and cos~ objects.
We can take the concept of a phase-modulation oscillator and abstract to a reuseable unit: an FM operator.
Combining multiple oscillators allows lots of sounds to work together. Typical FM synths will have 4 or 6 operators.
In FM lingo, the wiring diagram between operators is called an algorithm.

Each operator needs:
This gets complicated quickly…
Volca FM has 23 parameters per operator, and 16 global parameters, that’s 154 params for one patch!

And here’s how you could wire them together…
r and route to send freq scaling and envelope parametersthrow~ and catch~ for the feedback loop on operator 6.This is a fixed configuration, could you design a way to control the FM algorithm with parameters?

Op-based FM is very popular. The Yamaha DX7 was the first successful digital synthesiser in 1983 and Yamaha’s FM sound chips were found in computers and video game consoles throughout the late 80s and 90s.
See dexed for free FM synth fun.
Not terribly easily (6-op synths are complicated) and Strudel itself isn’t designed for creating new synths on the fly.
For any “new” synth, i.e., where you want to build the signal graph yourself like in Pd, the two options would be:
Option 2 would be likely to be easier but involves learning another programming language.
CSound is among the oldest computer music languages (first released in 1985) and it’s still used today. Although you would typically run CSound as a regular application, there’s now an implementation on the web.
Here’s a simple CSound synth in Strudel:
await loadCsound`
instr charlessynth
iduration = p3
ifreq = p4
igain = p5
kenv = linen(igain, 0.1, iduration, 0.2)
asig = vco2(kenv, ifreq, 4, .5)
out(asig,asig)
endin`
"0 3 1 2 5@4"
.scale('D minor')
.note()
.csound('charlessynth')
There’s some weird stuff about CSound. It’s original syntax looks like assembly and types of variables are determined by the first letter of their names.
instr charlessynth
iduration = p3 ; CSound has special parameter variables, p3 is duration
ifreq = p4 ; p4 is frequency
igain = p5 ; p5 is volume
kenv = linen(igain, 0.1, iduration, 0.2) ; linen is a basic envelope
asig = poscil(kenv, ifreq) ; poscil is sine tone
out(asig,asig) ; output the signal
endin
There’s two good resources for learning CSound, the reference manual and the flossmanual
It might seem a bit unhinged to introduce a new complete computer music system at this stage of the course.
The point is: I want you to have the tools to explore computer music deeply.
CSound in Strudel gives you a very good balance of convenience to depth for defining complex instruments in a live coding environment.
There’s a learning curve to understanding the syntax and getting a feel for the system, but it’s not that hard now that you know the concepts.
tl;dr: to do feedback phase-modulation synthesis in Strudel, you should use CSound, and see this paper
await loadCSound`
giSine ftgen 0, 0, 8192, 10, 1 ; makes a sine tone table
instr feedback_PM
kCarFreq = p4
kFeedbackAmountEnv linseg 0, 2, 0.2, 0.1, 0.3, 0.8, 0.2, 1.5, 0
aAmpEnv expseg .001, 0.001, 1, 0.3, 0.5, 8.5, .001
aPhase phasor kCarFreq
aCarrier init 0 ; init for feedback
aCarrier tablei aPhase+(aCarrier*kFeedbackAmountEnv), giSine, 1, 0, 1
outs aCarrier*aAmpEnv, aCarrier*aAmpEnv
endin`
n(sine.range(0,15).segment(8).slow(16)).scale("C:minor")
.csound('feedback_PM')
This is just one operator with feedback controlled by an envelope (example adapted from the CSound flossmanual)
Karplus-Strong string synthesis is a famous algorithm for creating a string-like sound with a noise source, a delay and a filter.
It can be considered a special case of digital waveguide synthesis, used for string, tube, and membrane sounds.
K-S synthesis is quite common in digital synthesisers (e.g., Arturia Microfreak) and it’s fun to do in Pd.

Here’s a simple Karplus-Strong implementation.
noise~
vline~ controls noise entering the delay loopdelread~ and delwrite~ define the delay looplop~, and the loop has feedback of 0.999
await loadCSound`
instr kstest
ifreq = p4
iamp = p5
ipluck = 0.6 ; pluck position (0-1)
idamp = 0.999 ; damping factor (0-1)
idelaytime = 1 / ifreq
anoise rand iamp
aexcite = anoise * ipluck
kenv linseg 1, 0.01, 0, 0.01, 0
ainitial = aexcite * kenv
afeedback init 0
adelay delay ainitial + afeedback, idelaytime
afilt = (adelay + delay1(adelay)) * 0.5 * idamp
afeedback = afilt
out afilt,afilt
endin`
n(sine.range(0,15).segment(8)).scale("C:minor")
.csound('kstest')
Note the simple LPF: (adelay + delay1(adelay)) * 0.5
Physical modelling synthesis is an interesting area with lots of possibilities and challenges.
Have a look at Julius O Smith’s Stanford Courses (Music 420A) to learn more.
Note that you can do a lot of awesome physical modelling synthesis in CSound. Take a look at the book.

One form of synthesis that is usually limited to computers involves modifying the frequency domain of a sound and recreating new versions.
Spectral manipulation is something that Pd is quite good at doing!
We return a bit to the Fourier transform mentioned early in the course.
For the full story, see Fourier Analysis and Resynthesis in Miller Puckette’s book.
Remember the Fourier transform? This allowed us to extract the amplitude each frequency component of a sound.
In practice, the FT can’t be used as it requires an infinite input (who has time for that).
We can use a similar construction called: Short-Time Discrete Fourier Transform (STDFT):
short-time: operates on a finite-length signal instead of an infinite signal
discrete: operates on sampled information
We often refer to STDFT as FFT, or “fast Fourier transform” (e.g., the fft~ object in Pd).
FFT is actually a clever algorithm for accomplishing a DSTFT quickly, so it’s ok to use the acronyms interchangeably.
For a signal with $N$ samples, the STDFT equations providing the sine and cosine amplitudes at certain frequencies are:
$R_k = \sum\limits_{i=0}^{N-1} x_i cos(2\pi k i \ N)$
$X_k = - \sum\limits_{i=0}^{N-1} x_i sin(2\pi k i \ N)$
The STDFT also only focuses on $N$ frequencies that we call “bins” between 0 and the sampling frequency.
N.B.: the frequency “resolution” is limited by the length of the signal we are analysing!

Typically we want to apply FFT to a “chunk” of a signal rather than the whole thing.
Usually we can set the length of the chunk (window length) and an envelope function.
The usual choice for envelope function is the Hann function, it looks kind of like a Gaussian distribution.
The window length is flexible, but will determine the number of frequency bins that can be analysed! Usually the window length is required to be a power of 2 (e.g., 512 or 2048)
The FFT procedure actually also works backwards!
The exclamation mark is doing a lot of work here! Imagine what we can do with full control over a spectrum!!
In fact, the IFFT algorithm is almost identical to the FFT algorithm
It is important when doing an FFT to cope with the sine and cosine elements (or the real and complex outputs of a frequency. These interact in a certain way to make the ouput sound “work”.)
Pure Data can perform a STDFT with the fft~ object (yes it’s that easy).
The two outputs give you the real and imaginary part of the signal. You can also do rfft~ just to get the real output (saves CPU).
Similarly, you can do an inverse FFT with rifft~ and ifft~.
In Pd, N is the same as the “block size” (number of samples processed at once), so Pd patches often adjust block size just for the FFT patch to get the right number of FFT bins.

Here’s a fun way to modify the spectrum of “noise”.


The “timbre stamp” algorithm modulates a signal by the spectral envelope of another sound.
see I06.timbre.stamp.pd

The “phase vocoder” is an algorithm for stretching or compressing the time and frequency axes of a recorded sound.
see I07.phase.vocoder.pd
You can do similar procedures in CSound as outlined in the manual