Skip to content

Instantly share code, notes, and snippets.

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 xavriley/fec3da354d726779fcbb2fecc46a19ec to your computer and use it in GitHub Desktop.
Save xavriley/fec3da354d726779fcbb2fecc46a19ec to your computer and use it in GitHub Desktop.
cymbal synthesis
This is Google's cache of http://www.mcld.co.uk/cymbalsynthesis/. It is a snapshot of the page as it appeared on 4 May 2020 03:03:17 GMT. The current page could have changed in the meantime. Learn more.
Full versionText-only versionView source
Tip: To quickly find your search term on this page, press Ctrl+F or ⌘-F (Mac) and use the find bar.
Cymbal synthesis tutorial
Dan Stowell
This tutorial describes an approach to synthesising decent-sounding cymbals, in a way that is efficient enough for a real-time synthesis system.
It doesn't aim to emulate the cymbals from the classic drum machines (see the excellent Sound On Sound Synth Secrets tutorials for some of that). It is not a physical modelling approach either. Although physical models have been used to simulate cymbals, they're not my area of expertise, and they also tend to be fairly computationally heavy-duty so less suited to real-time synthesis. Instead, we approximate the main features of real cymbal sounds using relatively simple signal processing units. We create a generic cymbal sound which can be tweaked to sound more like a ride, crash, splash, or gong.
The examples in this tutorial use SuperCollider. You can save this HTML document to disk, open it in SuperCollider and then run the examples directly from the page.
Staring at spectrograms
Although spectrograms don't really show you the perceptually important aspects of a sound, they can sometimes be a starting point in thinking about how to recreate at least the gross aspects of a sound. Here's a spectrogram of a crash cymbal being struck once:
attachments/cymbals/cymbalspecgram.png
One of the most obvious features is the presence of many horizontal lines, unevenly distributed in frequency, representing resonances of the cymbal. They all fade out over time (i.e. left to right) but at different rates — the lower-pitched resonances tend to last the longest. At the very start of the recording there is very broad-band energy, i.e. plenty of energy in all frequency bins, which fits pretty well with experience: the first moments of a loud cymbal crash tend to sound a lot like white noise.
The physical explanation for this behaviour involves nonlinearities in the cymbal's many resonant modes. If we tap a cymbal very gently then it tends to sound less noisy and more bell-like, because the cymbal is not being deformed very much by its vibration, meaning the resonances behave approximately like ordinary linear resonators. So with a gentle tap, or in the dying stages of a "normal" strike like the one above, the resonant behaviour can be modeled quite well with a simple collection of linear resonators.
But in the early stages of a normal-velocity strike, the cymbal deforms as it vibrates, and the deformations alter way the energy propagates around the cymbal; the resonance is strongly non-linear and doesn't produce simple horizontal lines on a spectrogram, rather it produces something more like broadband noise. Notice, though, that even at the start of the above spectrogram the horizontal lines appear to be present, so the sound isn't purely white.
First steps: linear resonances
First of all let's create a bank of resonators to approximate the main resonances present in the cymbal sound. The spectrogram above shows lines of different thicknesses and darknesses, hinting that our resonators should have different characteristics. At first I tried to replicate this complexity, but in the end I found that a bank of simple resonators, differing only in their resonant frequency, gave a decent first pass at the sound:
s.boot; // If you haven't already booted
x = { Ringz.ar(PinkNoise.ar(0.1), {exprand(300, 20000)}.dup(100)).mean }.play;
x.free; // To stop
In the code above, I'm using SuperCollider's array expansion to create a hundred parallel filters. The code {exprand(300, 20000)}.dup(100) generates a hundred numbers, exponentially distributed from 300 to 20000; then passing this array as an argument to Ringz creates a single Ringz resonator at each of those frequencies. The pink noise is just a simple driver for the resonators, and will change later. Note that since the code involves some randomness, you'll get a different timbre each time you run it.
Why use exponentially-distributed frequencies? The lines in the spectrogram above don't really look to be exponentially distributed, although you can see that there's a higher density lower down, and the density tails off towards the higher frequencies. (The spectrogram goes from 0 to 16 kHz.) An exponential distribution turns out to approximate this pattern reasonably well. Try replacing "exprand" with "rand" in the above — you'll get a surprisingly different result.
The next thing we might like to do is simulate the dying away of these resonances, which occurs faster at higher frequency. We might suspect that the higher-pitched resonators should be given shorter resonance times, but instead of doing that we can modulate the frequency content of the driver by filtering it.
The following code may look different to the above, but that's mainly because I've spaced it out onto multiple lines. The only real addition is to replace the pink noise with white noise which is being pushed through a low-pass filter (whose cutoff frequency decreases over time):
(
x = {
var driver, cutoffenv, freqs, res;
cutoffenv = EnvGen.ar(Env.perc(0, 5)) * 20000 + 10;
driver = LPF.ar(WhiteNoise.ar(0.1), cutoffenv);
freqs = {exprand(300, 20000)}.dup(100);
res = Ringz.ar(driver, freqs).mean.dup
}.play;
)
x.free;
This already gives an interesting impression of decay although it's still very simple. Of course it doesn't yet sound realistic enough. The rate of decay is definitely wrong, and the attack sounds extremely unnatural.
Bwoosh
How can we improve the attack? Think of what people say when they're doing an impression of a cymbal — often something like "bwoosh". The "bw" part of that sound is a bit like a low-pass filtered sound being swept from a low to a high cutoff. This suggests that the higher-pitched resonances of the cymbal don't fill out immediately, but instead take a fraction of a second to start sounding.
Note that this wasn't particuarly obvious from the spectrogram — it's easier to hear than to see. Most of the energy hitting a cymbal first activates the lower resonances, and this energy is then transferred from the lower to the higher ones by coupling between the modes. This must depend on the nature of the strike as well as on the cymbal, but the unnaturalness of the immediate onset (in the code above) suggests that there must be an element of this in all cymbal sounds.
To add a bit of "bw" to the sound produced by the code above, change the filter position's attack time (in the "Env.perc") from 0 to 1. The attack is still simplistic but much improved.
More shimmer
The brightness of the sound we've created tails off smoothly and it gives a pretty good impression of the energy dying away, but it's still a little artificial. Part of the artificialness is because the brightness falls off too perfectly: once the cutoff frequency drops below a certain level, certain high-frequency resonators essentially stop sounding completely. But real cymbals retain a bit more "shimmer" than that, presumably because of interactions between the resonant modes which continue to feed a little bit of energy back from the lower resonances to the higher ones.
To produce such an effect let's add a little bit of high-pass-filtered noise to the driving signal. We'll do it similarly to the low-pass-filtered noise, but also adding an amplitude envelope:
(
x = {
var lodriver, locutoffenv, hidriver, hicutoffenv, freqs, res;
locutoffenv = EnvGen.ar(Env.perc(0.25, 5)) * 20000 + 10;
lodriver = LPF.ar(WhiteNoise.ar(0.1), locutoffenv);
hicutoffenv = 10001 - (EnvGen.ar(Env.perc(1, 3)) * 10000);
hidriver = HPF.ar(WhiteNoise.ar(0.1), hicutoffenv);
hidriver = hidriver * EnvGen.ar(Env.perc(1, 2, 0.25));
freqs = {exprand(300, 20000)}.dup(100);
res = Ringz.ar(lodriver + hidriver, freqs).mean.dup
}.play;
)
x.free;
The amplitude envelope makes sure the high-pass noise fits the general shape of the cymbal sound's envelope, but also makes it tail off a little before the main sound tails off (after about 60% of the time), which to me gives the most suitable sound.
More noise
If we are outputting simply the sound of the resonators then we're ignoring the wideband noise that should be at the start of the sound. A simple way to include this aspect is to mix some of the low-pass-filtered driving signal into the output, supplementing the fixed-frequency resonators:
(
x = {
var lodriver, locutoffenv, hidriver, hicutoffenv, freqs, res;
locutoffenv = EnvGen.ar(Env.perc(0.5, 5)) * 20000 + 10;
lodriver = LPF.ar(WhiteNoise.ar(0.1), locutoffenv);
hicutoffenv = 10001 - (EnvGen.ar(Env.perc(1, 3)) * 10000);
hidriver = HPF.ar(WhiteNoise.ar(0.1), hicutoffenv);
hidriver = hidriver * EnvGen.ar(Env.perc(1, 2, 0.25));
freqs = {exprand(300, 20000)}.dup(100);
res = Ringz.ar(lodriver + hidriver, freqs).mean;
((res * 1) + (lodriver * 2)).dup;
}.play;
)
x.free;
To my ears this improves the beginning of the sound, making it sound slightly less "MP3ised" (i.e. slightly less distractingly ringy).
Add a stick
The sound so far is OK. You'll notice that if anything, it sounds like it's being struck with a soft mallet. That's because a soft mallet doesn't create much sound of its own upon impact, rather it gives the cymbal a "push", and the resultant sound is pretty much completely created by the way the resonating cymbal dissipates that energy.
Let's instead make it sound more like a strike with a drumstick. The impact of wooden drumstick on metal cymbal makes an impulsive sound, whose character is defined by the stick and the metal. But we'll use a crude approximation, a simple impulsive envelope that lasts a couple of milliseconds.
In the following example we've added this (the "thwack" variable). Note that we use it in two ways: (1) as input to the resonators, and (2) mixed directly into the output. This roughly simulates the impact sound of (1) the cymbal and (2) the stick.
(
x = {
var lodriver, locutoffenv, hidriver, hicutoffenv, freqs, res, thwack;
locutoffenv = EnvGen.ar(Env.perc(0.5, 5)) * 20000 + 10;
lodriver = LPF.ar(WhiteNoise.ar(0.1), locutoffenv);
hicutoffenv = 10001 - (EnvGen.ar(Env.perc(1, 3)) * 10000);
hidriver = HPF.ar(WhiteNoise.ar(0.1), hicutoffenv);
hidriver = hidriver * EnvGen.ar(Env.perc(1, 2, 0.25));
thwack = EnvGen.ar(Env.perc(0.001,0.001,1));
freqs = {exprand(300, 20000)}.dup(100);
res = Ringz.ar(lodriver + hidriver + thwack, freqs).mean;
((res * 1) + (lodriver * 2) + thwack).dup;
}.play;
)
x.free;
You may wonder why we've kept the slowly-building aspect of the driving noise (the "bw"), when we've added the more-or-less instant impulsive onset. Depending on the way the cymbal is struck, there will often still be a brief moment before the high frequencies fill out (particularly with an edge-struck cymbal), so I find this combination of immediate impulse and slightly-delayed build sounds good. Try removing the "bw" aspect from the above patch and see what you think of the difference.
For the basic generic cymbal synthesis that's pretty much it. I tend to tweak the shape of the noise envelopes to change the decay slightly; I'll leave that as an exercise for you to do if you think it's needed. The patch can be "struck" again simply by retriggering the envelopes.
Results
Since we started with a spectrogram, here's a spectrogram of one sound generated by the above patch:
attachments/cymbals/synthcymspecgram.png
You can't tell from a spectrogram how well or badly we've done: your ears will be a much better judge. But you can probably spot most of the elements we've added to the patch. The exponentially-distributed stripes are the resonators, and imbetween you can see the low-pass-filtered noise — you can even spot the "bw" at the beginning, as the higher frequencies kick in slightly later.
You can also see some of what's "wrong" with our patch: for example, the resonances all have the same strength and sharpness. Feel free to modify your patch to alter this. In my tests I haven't found that makes much of a difference to the sound quality.
Audio samples
Here's a recording of my patch, using crash-cymbal settings: crash cymbals example (MP3)
I recorded a simple beat featuring a few of these cymbals, using a ride-like setting for the cymbals. It's here: MCLD Rides Again (MP3)
Different types of cymbal
The crash-like cymbal sound developed above can easily be tweaked to sound more like a ride, splash, or gong. The main differences are:
The frequency range of the resonators. The smaller the cymbal, the higher the pitch. So try a range of 3500-20000 Hz for a splash, 150-20000 Hz for a gong.
The duration of the "bw" build. A long build time (e.g. 2.5 seconds) sounds very typically like a gong. This duration is influenced by how the instrument is struck, as well as its intrinsic properties.
The overall duration of the sound. A splash may last around a second, while a gong around 20 seconds.
The number of resonators. The fewer resonators, the more "bell-like" the sound; the more resonators, the more "noise-like".
It takes a lot of tweaking to make a good splash-cymbal sound from the above, but it's fairly easy to change the crash to a ride or gong sound.
Different types of stick
The "thwack" which I added aimed to approximate a wooden drumstick. As already mentioned, a softer mallet makes little sound of its own so can be simulated by removing the thwack element altogether. (Or try replacing it with a slower impulse, around 100 ms.)
You can create the effect of brush drumsticks by replacing the single impulsive attack by a collection of smaller impulses, to simulate a small bundle of bristles all striking at the symbal at "roughly" the same time. I do this by creating an impulse of about 200 ms duration and multiplying that by a noisy random factor (a Dust and/or WhiteNoise). Note that the randomness introduced by the Dust/WhiteNoise generators improves the brush sound, since no two brush hits happen in exactly the same way, and the variation in the brush hits is just about perceivable.
The different beaters affect the sound beyond just the attack. You'll typically find that you need to modify the envelopes slightly too — slowing down the "bw" build slightly for the soft mallet, or shortening the overall duration for the brush.
Other controls
My full patch also includes a "velocity" parameter to allow for gentle as well as hard strikes. The velocity is directly linked to the amplitude of the "thwack" and the overall duration of the sound (gentle strikes don't last long); but also to the highest cutoff frequency reached by the low-pass filter, and the amplitude of the high-pass content (gentle strikes don't fill out the high frequencies so much).
You may also find it useful to add a "muffle" control, e.g. to replicate the type of strike where the drummer hits the crash cymbal but then immediately grabs it with her hand to stop it sounding. This can be done by adding a feature that quickly pulls down the low-pass filter's cutoff, as well as the amplitude of the high-pass content and and the decay time of the resonators.
Conclusion
With the approach given above you can simulate decent-sounding cymbals that are generated in real time, and have a natural variation in them (thanks to pseudo-random units used and to the patch's statefulness) which I find more pleasing than using a single repeated cymbal sample. The cymbal patch takes about 10% CPU on my PowerBook laptop so is easily useable in real time.
About the author
Dan Stowell is a researcher working on real-time timbral analysis and synthesis, as well as a SuperCollider developer and a musician. See www.mcld.co.uk for info. Email
Copyright info
Creative Commons License
Cymbal synthesis tutorial by Dan Stowell is licensed under a Creative Commons Attribution-Noncommercial 2.0 UK: England & Wales License.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment