Hi Stefan.
It’s a somewhat difficult question & I’m no expert, but some personal perspectives:
Although the manual describes gbuzz as useful for subtractive synthesis, I often use it as a flexible additive opcode, especially when using less harmonics (knh), like 1 - 15, though sometimes more. And it very much depends on the “tilt” of the spectra and range of notes used. It’s interesting to compare it’s uses a additive or subtractive.
One example would be to look at a saw wave which contains all the odd & even harmonics. If you create say 15 harmonics with gbuzz, you can also get a very similar timbre by using a low pass filter to attenuate the upper harmonics of a saw wave.
In the code below the first two notes sound fairly similar. Not exact as the harmonics don’t decay at the same rate. Note that the examples are a little exaggerated for effect. You might also try them with different knh or modulating that.
So both additive & subtractive can achieve somewhat similiar timbres using different methods. But from there it can get more complex.
For example, what if you want to tilt the harmonics so the fundamental has a much lower amplitude compared to the 15th harmonic? In other words the amplitude of the harmonics increase exponentially instead of decreasing. And what if you want exactly 15 harmonics?
This can sound similar to high pass filtering a complex waveform. gbuzz does this very well & it can be fun territory to explore. After the highest harmonic it drops like a cliff which can be harder to accurately reproduce with filters.
You can hear this with the third note played. This can be achieved fairly easily with gbuzz by changing or modulating the kmul parameter (or using GEN10).
-odac
-o add_sub.wav
;==================================
sr = 44100
ksmps = 32
nchnls = 1
0dbfs = 1
seed 0
giCos ftgen 0, 0, 16384, 11, 1
instr 1, gbuzz
aEnv = adsr:a(.1, 0, 1, .1)
if p5 == 0 then
kNh = p6
else
kNh = line(1, p3, 20)
endif
if p7 == 0 then
kMul = line(p8, p3, p9)
elseif p6 == 1 then
kMul = linseg(p8, 2, p8, p3/2-1, p9, p3/2-1, p8)
else
kMul = rspline(p8, p9, .3, 3)
endif
aSig = gbuzz(p4, 220, kNh, 1, kMul, giCos)
out(aSig * aEnv)
endin
instr 2 ; saw with low pass filter
aEnv = adsr:a(.1, 0, 1, .1)
aSig = vco2(p4, 220) ; saw wave
aLPF = dcblock2(butlp(aSig, 600)) ; low pass filter
out(aLPF * aEnv)
endin
instr 3 ; saw with low & high pass filters
aEnv = adsr:a(.1, 0, 1, .1)
aSig = vco2(p4, 220) ; saw wave
aHPF = buthp(aSig, 10000) ; high pass filter
aLPF = butlp(aHPF, 1600)
out(aLPF * aEnv)
endin
;==================================
; p4 = amp, p6 = number of harmonics
; p5, p8 = if statement switches
; p8, p9 = variables for kmul
i1 0 4 .8 0 15 0 .5 .5
i2 5 4 .5
i1 10 4 .5 0 15 0 -1.2 -1.2
i3 15 4 30
i1 20 10 .7 0 15 0 -.5 1.2
i. 31 . . 0 . . .5 1.2
i. 42 10 .7 0 15 1 .001 1
i. 53 . . 0 . 1 . 1.2
i. 64 16 . 0 . 2 . 1.5
i. 81 10 .7 1 1 0 .7 2
If you listen to the fourth note (instr 3) you’ll notice that with the saw wave it requires 2 filters to attempt to replicate a similiar timbe, and then it isn’t very close at all. It might be possible with a lot of trial & error and choosing very specific high pass filters, much steeper ones. Then there is the issue that the filter cutoffs must accurately track note frequencies, and that for better or worse they can add their own character to the timbre.
If you render the examples to audio and look at the file in a spectrum analyzer, perhaps in a wave editor or the like, you can really see what’s happening although audibly it’s reasonably obvious.
So depending on what I want to achieve, I don’t filter gbuzz at all, especially if using it primarily for additive synthesis. It can generate partials that decay exponentially, have no decay (all are roughly equal in amplitude), or increase exponentially. But one can certainly use filters with it.
A good example case might be that while a saw wave has all of the higher harmonics, the strength of each partial decreases as you move above the fundamental. If you use gbuzz with high kmul values, especially above about .7, the upper harmonics start getting stronger than those generated by a sawtooth waveform, so filtering can give different results, especially resonant ones.
But this can potentially become more complicated depending on range of notes (frequencies) & number of harmonics. With gbuzz, if you’re using very high notes with many harmonics, you can potentially have aliasing issues. There are a few ways you can deal with this but one would be to use a very steep LPF to prevent that.
The last notes of the above example dynamically morph the spectra (the term Michael used and you called time changing spectrum), something that could also be achieved by morphing thru multiple tables (see ftmorf opcode for an example):
https://csound.com/docs/manual/ftmorf.html
or using chebyshevpoly etc. I think Michael and Jeanette’s suggestions are also quite interesting, well worth looking into.
Simply put, for me gbuzz can be useful for general creation of a specific number of harmonics. By modulating the kmul you can not only go from using only the fundamental or klh (lowest harmonic) to using all of the knum of partials (notes 7 & 8 in the above examples demonstrates this), you can also “tilt” the spectra very easily and create effects similiar to low or high pass filtering, among other things. This can also be beneficial as changing the knum of partials can cause clicks. Modulating the kmul somewhat solves this issue. While it’s easy to change the number of harmonics used (knum), it could possibly by safer to change at i-rate (using a static value instead of mudulating).
I suggested gbuzz because your code seemed to indicate you wanted an exponential relationship to the partials and gbuzz handles that specific case well, & quite simply.
While one could emulate aspects of gbuzz with chebyshevpoly or ftmorf and a bunch of GEN10 tables (or likely using other tables/opcodes), it might a little more inconvenient or difficult to easily achieve the same results. In the case of using say 15 harmonics with an exponential relationship that allowed tilting the spectra, with chebyshevpoly you would seem to need 15 parameters and 15 expseg opcodes I think, and alot of tweaking, but perhaps I’m not as familiar with it and all the available opcodes, and not very clever with math either.
If you want more precise control of the individual levels of harmonics, then using chebyshevpoly or GEN10 and ftmorpf, among other options, might allow much more detailed control of individual amplitudes (strengths) of each partial.
Here are links to a few examples, perhaps a little more musical & demonstrative than those in the manual:
https://github.com/ST-Music/csound-examples/blob/main/chebyshevpoly/chebyshevpoly_random.csd
https://github.com/ST-Music/csound-examples/blob/main/waveform_generation/GEN10/random_gen10_recurs_udo.csd
One example uses randomization and modulation of individual partials to create fairly rich pad like timbres using the chebyshevpoly opcode. You could say each note morphs or has time varying randomized dynamic spectrum. The other similar example will automatically create a “wavetable” of multiple GEN10 ftables with random partial strengths and randomly morph thru them using the ftmorf opcode. I find it’s response slightly more stable & predictable compared to the chebyshevpoly method but that’s a personal perspective & I have less experience using it.
If you have trouble downloading the files let me know.
That’s one of many things that are nice about Csound. There are often multiple ways to achieve similar results, each having their own benefits or drawbacks. The more you learn over time about each the easier it is to sculpt sounds you like or want to emulate, which can depend on personal preferences or your understanding of the many available opcodes. I often choose specific methods/opcodes on a case by case basis.
Sorry for the long reply, I’m a music teacher/performer by trade so I tend to possibly over-explain things.
Cheers,
Scott