[Csnd] expexting different sounds

Dear community,
I would expect two different sound coulours in the below quoted code, the first one sharper, the second softer, but it doesn’t work.
They seem to be the identical , just a sinus sound. How could I achieve what I want?

-odac -m0d ; ==============================================

sr = 44100
;ksmps = 100
nchnls = 2
0dbfs = 1

giwave ftgen 0, 0, 2^16, 10, 1
;giwave ftgen 0,0,2^16,10, 1.0, 0.933, 0.896, 0.8706, 0.8513, 0.836, 0.8232, 0.8123, 0.8027, 0.7943, 0.7868, 0.78, 0.7738, 0.768, 0.7628, 0.7579

instr 1
icps = p4
iamp = ampdb(p5)
ipower = p6
iArr[] genarray 1,16
iArr[] = iArr^ipower
copya2ftab iArr, giwave
; envelope
iein = 0.005
idur = 7
irel = 0.1
aenv transegr 0,iein,(0),1,idur0.1,(-10),0.3,idur0.9,(-5),1/16,irel,(-5),0
a1 poscil aenv, icps, giwave
outs a1iamp, a1iamp
;iArr[] = 1/iArr^2

endin

; ============================================== i1 0 2 220 -6 -0.1 i1 3 2 220 -6 -2

Csound mailing list Csound@listserv.heanet.ie https://listserv.heanet.ie/cgi-bin/wa?A0=CSOUND Send bugs reports to https://github.com/csound/csound/issues Discussions of bugs and features can be posted here

Hi Stefan,
Aug 18 2023, Stefan Thomas has written:
...

I would expect two different sound coulours in the below quoted code,

...

giwave ftgen 0, 0, 2^16, 10, 1
;giwave ftgen 0,0,2^16,10, 1.0, 0.933, 0.896, 0.8706, 0.8513, 0.836,
0.8232, 0.8123, 0.8027, 0.7943, 0.7868, 0.78, 0.7738, 0.768, 0.7628, 0.7579

instr 1
icps = p4
iamp = ampdbp5
ipower = p6
iArr[] genarray 1,16
iArr[] = iArr^ipower
copya2ftab iArr, giwave

...

i1 0 2 220 -6 -0.1
i1 3 2 220 -6 -2

...
Assuming the above is the relevant code to generate different waveforms,
namely the copying of the iArr array to giWave, then it's no wonder you
almost get the same sound. You have a table with 2^16 32768 samples,
but you exchange the first 16 samples of that waveform, the difference
is minimal. If you want to write a new waveform to giWave using an
array, the array should also have 32768 entries, not just 16.

HTH.

Best wishes,

Jeanette

Hi Stefan and Jeanette,

If I understand correctly, you are trying to alter the partial values with each instance using the array values.

An easy way to modify your code is to not copy the array to an existing gi table but instead just use the array values to generate the ftable. In this case the table doesn’t need to be global unless you intend to use it in other instr. This might achieve what you want:

-odac -m0d ; ==============================================

sr = 44100
;ksmps = 100
nchnls = 2
0dbfs = 1

instr 1
icps = p4
iamp = ampdb(p5) * 2
ipower = p6
iArr[] genarray 1,16
iArr[] = iArr^ipower
iwave ftgen 0, 0, 2^16, 10,
iArr[0], iArr[1], iArr[3], iArr[4],
iArr[5], iArr[6], iArr[7], iArr[8],
iArr[9], iArr[10], iArr[11], iArr[12],
iArr[13], iArr[14], iArr[15]
iein = 0.005
idur = 7
irel = 0.1
aenv transegr 0,iein,(0),1,idur0.1,(-10),0.3,idur0.9,(-5),1/16,irel,(-5),0
a1 poscil aenv, icps, giwave
outs a1iamp, a1iamp
;iArr[] = 1/iArr^2

endin

; ============================================== i1 0 2 220 -6 -0.1 i1 3 2 220 -6 -2

Best,
Scott

P.S. I multiplied the amp value so I could hear it better & forgot to change that back, sorry.

Your subject line perfectly sums up my sound sythesis practice of the past 25 years.

Hi again Stefan,
I just wanted to mention that regardless of the p4 value used it seems no more that 7 partials are created. In effect the p4 values are “tilting” the partial spectrum of those 7 partials as you intended.

In case you’re not aware of them, the buzz & gbuzz opcodes will achieve essentially the same results but will allow creating any number of partials/harmonics you want, as well as allowing exponential tilt (see the kmul parameter for gbuzz).

Best,
Scott

Dear Jeanette,
thanks for Your answer, it’s convincing. I thought the array could define the relative strength of the first 16 partials, as one would normally do with gen10.

Is there another way to do this?

Oh sorry, I didn’t see the answers of ST Music. I will try it out!

Hi Stefan,
also try GEN33 and 34, they are the same as GEN09 and 10, but they take their parameters from another ftable. So you can do something like this
instr Test
   iParts init p4
   iParam = ftgenonce(0, 0, -iParts*3, 2, 0) ; an empty table
   ; fill an array or directly the ftable with values
   ; with an array of length iParts*3 you can use copya2ftable
   ; Now create your waveform:
   iWave = ftgenonce(0, 0, 2^16, 33, iParam, iParts, 1)
   ...
endin
The params table is iParts * 3, because partials take the same parameters as GEN09, so for every partial you have strength, partial number and phase.

There are differences between the two GENs.

Best wishes,

Jeanette

Dear Scott,
I’ve read a little on gbuzz. If I have understood it correctly, then one can create a time changing spectrum with this opcode, without using filters.
Would You recommend doing it without filters or do You think using a filter would be better? Or in which case would You use filters?

Dear Jeanette,
thanks for Your reply.
That sounds promising. I should find out about GEN33 and 34.

Another way of morphing spectra during performance is the use of the Chebyshev opcodes, see https://gogins.github.io/csound-extended-manual/examples/chebyshevpoly.csd.html. Increase the duration of the first note to 100 BEFORE you click “play” and you will hear more detail in what is happening.

=)
(and +1)

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

P.S. just realized I forgot to comment out the -o add_sub.wav in CsOptions

Dear community,
thank You for all Your great explanations and advices. I’m going to try them all!

Best,
Stefan