Independent amplitude envelopes on harmonics with a midi instrument

24 views
Skip to first unread message

kjel

unread,
Sep 9, 2022, 5:09:58 PM9/9/22
to pyo-discuss
Hi all,

I've been lurking and very much enjoying getting into Pyo. I've now hit a wall that I haven't been able to solve with the existant posts, and I thought I'd create my own to see if anyone has ideas. I'm trying to emulate the sound of a bourdon pipe organ stop. So far I have been doing this intuitively, but I know that if I want more precision I am going to have to have independent envelopes for different harmonics. Here is what have so far with one master envelope on all the harmonics :

from pyo import *

s = Server()
s.setMidiInputDevice(99)
s.boot()
t = HarmTable([1]+[0.07]+[0.01]+[0.003]+[0]*20, size=512)
t.autoNormalize(True)
t.view()
t.graph()
partialsInit = [1]
mul = [1]
note = Notein(poly=10, scale=0, first=0, last=127, channel=0, mul=1)
note.keyboard()

env = MidiAdsr(note['velocity'], attack=0.03, decay=0.1, sustain=0.8, release=0.5)
noiseEnv = MidiAdsr(note['velocity'], attack=0.001, decay=0.146, sustain=0.158, release=0.1)

freq = MToF(note['pitch'])
pitch = [(partial * freq) for partial in partialsInit]

noise = PinkNoise(5) * noiseEnv
noise = Reson(noise, freq=(freq*(20/4)), q=10)

sound = [Osc(t, freq=pit+Randi(-3.0, 3.0, 5), mul=amp*env) for pit, amp in zip(pitch, mul)]
sound = STRev(Mix(sound, 1) + noise, inpos=0.5, revtime=5, cutoff=4000, bal=0.15)
sound = ButLP(sound, freq=1650)

noise.ctrl(title="Noise")
env.ctrl(title="Harmonics Envelope")
noiseEnv.ctrl(title="Noise Envelope")

SL = sound.out()
SR = sound.out(1)

s.start()
s.gui(locals())

This works well enough, but when I try the following two solutions, I don't get the individual harmonic amplitude envelopes I'm looking for:

#first we need to add the amplitude envelopes for the first four harmonics
p1Env = MidiAdsr(note['velocity'], attack=0.1, decay=0.01, sustain=0.1, release=0.1)
p2Env = MidiAdsr(note['velocity'], attack=5, decay=0.01, sustain=0.2, release=0.1)
p3Env = MidiAdsr(note['velocity'], attack=5, decay=0.01, sustain=0.2, release=0.1)
p4Env = MidiAdsr(note['velocity'], attack=5, decay=0.01, sustain=0.2, release=0.1)
partEnvs = [p1Env, p2Env, p3Env, p4Env]

#option 1 with amp * an array of envelopes leads to a problem managing polyphony. Every fifth event sounds. 
sound = [Osc(t, freq=pit+Randi(-2.5, 2.5, 5), mul=amp*partEnvs) for pit, amp in zip(pitch, mul)]

#option 2 with the array of envelopes as amp. This avoids the polyphony problem but unfortunately it seems that only the first envelope in the array is used for all harmonics. We can see this by turning down the first harmonic and turning up the second and hearing that it doesn't have a slow fade in.
sound = [Osc(t, freq=pit+Randi(-3.0, 3.0, 5), mul=amp) for pit, amp in zip(pitch, partEnvs)]

Is it perhaps a problem with using HarmTable? It seems that the amplitude of individual harmonics is accessible by definition, but I'm not sure how to access them with envelopes. That being said I'm not sure if Osc(), when taking in the table, creates a new unit generator for every partial or somehow sums it into one. If it's just one unit generator then I can see how individual harmonic amplitude envelopes could be tricky outside the built in functionality. I do seem to have individual control over the frequency of each harmonic with pit+Randi(-2.5, 2.5, 5) however.

Let me know if anyone has any ideas on how to move forward with this, and if I've broken any egregious forum post formatting rules, let me know that as well!  

Take care and thanks in advance,

Kjel

kjel

unread,
Sep 14, 2022, 11:52:07 AM9/14/22
to pyo-discuss
Just as an addition, this doesn't work either:

p1Env = MidiAdsr(note['velocity'], attack=0.1, decay=0.01, sustain=0.1, release=0.1)
p2Env = MidiAdsr(note['velocity'], attack=5, decay=0.01, sustain=0.2, release=0.1)
t = HarmTable([1*p1Env]+[0.07*p2Env]+[0.01]+[0.003]+[0]*20, size=512)

I think this probably cuts to the heart of the issue which is that (correct me if I'm wrong), these tables are arrays, and we can't assign a dynamic value like an envelope to array addresses. In this case, I think the way to go seems to be to create a bank of sine waves each assigned their own envelope, or if that's too processing intensive, to create a bank of harmonic tables that are stacked or latticed somehow. 

TLDR is: can we assign independent operators to the partials of HarmTable. It seems like the answer is no. 

This seems to be working for four partials:

from pyo import *

s = Server()
s.setMidiInputDevice(99)
s.boot()

randDev = Sig(1)

note = Notein(poly=10, scale=0, first=0, last=127, channel=0, mul=1)
note.keyboard()

p1Env = MidiAdsr(note['velocity'], attack=0.1, decay=0.02, sustain=0.3, release=0.1, mul=1)
p2Env = MidiAdsr(note['velocity'], attack=0.2, decay=0.04, sustain=0.2, release=0.1, mul=1)
p3Env = MidiAdsr(note['velocity'], attack=0.05, decay=0.01, sustain=0.1, release=0.1, mul=1)
p4Env = MidiAdsr(note['velocity'], attack=0.07, decay=0.008, sustain=0.05, release=0.1, mul=1)

noiseEnv = MidiAdsr(note['velocity'], attack=0.001, decay=0.146, sustain=0.158, release=0.1)

freq = MToF(note['pitch'])

noise = PinkNoise(5) * noiseEnv
noise = Reson(noise, freq=(freq*(20/4)), q=10)

p1 = Sine(freq=freq+Randi(-randDev, randDev, 5), mul=p1Env)
p2= Sine(freq=(freq*2)+Randi(-randDev, randDev, 5), mul=p2Env)
p3 = Sine(freq=(freq*3)+Randi(-randDev, randDev, 5), mul=p3Env)
p4 = Sine(freq=(freq*4)+Randi(-randDev, randDev, 5), mul=p4Env)

sound = Mix(p1+p2+p3+p4+noise, 1)

p1Env.ctrl(title="p1Env")
p2Env.ctrl(title="p2Env")
p3Env.ctrl(title="p3Env")
p4Env.ctrl(title="p4Env")
noise.ctrl(title="Noise")

noiseEnv.ctrl(title="Noise Envelope")

SL = sound.out()
SR = sound.out(1)

s.amp = 0.3

s.start()
s.gui(locals())

But I'm not sure why this doesn't seem to work with multichannel expansion:

#putting all our frequencies and partials in arrays
freq = MToF(note['pitch'])
pEnvs = [p1Env, p2Env, p3Env, p4Env]
freqs = [freq, freq*2, freq*3, freq*4]

#and then instantiating a Sine object with multichannel expansion
sound = Sine(freq=freqs, mul=pEnvs)
sound = Mix(sound+noise, 1)

I tried this to no avail:

freqs = [freq.value, freq.value*2, freq.value*3, freq.value*4]

and even:

freqs = [MToF(note['pitch']), MToF(note['pitch'])*2, MToF(note['pitch'])*3, MToF(note['pitch'])*4]

I'm also not sure how to control the overall gain of each partial with a slider. The .ctrl method for MidiAdsr does not have a mul slider, and if I multiply the MidiAdsr by Sig(1), it tells me there are no controls for a dummy object.

Thanks for your patience!

Kjel

kjel

unread,
Oct 24, 2022, 9:01:31 PM10/24/22
to pyo-discuss
For the record, this is my solution. It seems that HarmTable doesn't work for this but we can use Sine instead. Here we should hear the third harmonic (the octave and a fifth above the fundamental), fade in gradually whereas the fundamental comes in right away. I got rid of things that were extraneous to the question so it would be more clear.

from pyo import *

s = Server()
s.setMidiInputDevice(99)
s.boot()

#create harmonic series frequency ratios
partials = list(range(1, 4, 1))
#scale amplitudes of each harmonic
muls = [1, 0, 0.6]
#define attacks and releases of each envelope
attacks = [0.1, 0.1, 4]
releases = [0.1, 0.1, 4]

note = Notein(poly=10, scale=0, first=0, last=127, channel=0, mul=1)
note.keyboard()
freq = MToF(note['pitch'])
pitch = [(partial * freq) for partial in partials]

#You have to zip every parameter that you want individual control of. This doesn't seem to work with HarmTable but does with Sine. You could also zip a list of MidiAdsr objects and that would work
sound = [Sine(freq=pit, mul=amp*MidiAdsr(note['velocity'], attack=attacks, decay=0, sustain=1, release=releases)) for pit, amp, attacks, releases in zip(pitch, muls, attacks, releases)]

SL = Mix(sound, 1).out()
SR = Mix(sound, 1).out(1)

s.start()
s.gui(locals())

Olivier Bélanger

unread,
Oct 30, 2022, 5:15:53 PM10/30/22
to pyo-d...@googlegroups.com
Hi,

Tables can, but are not meant to be changed in real time (that would not be very efficient). Using independent oscillators is, IMO, the way to go!

And yes, to avoid polyphony problems, you should always mix your oscillator voices to mono (each note should be on the same channel in the end), and then, dispatch these mono signals as you wish!

Olivier


--
You received this message because you are subscribed to the Google Groups "pyo-discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to pyo-discuss...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/pyo-discuss/22f8546d-4270-495b-a56f-f10f2fa9543bn%40googlegroups.com.

kjel sidloski

unread,
Oct 31, 2022, 6:48:02 AM10/31/22
to pyo-d...@googlegroups.com
Thanks so much for the reply Olivier! This is great to know.

Take care,
Kjel

On Oct 30, 2022, at 5:15 PM, Olivier Bélanger <bela...@gmail.com> wrote:


You received this message because you are subscribed to a topic in the Google Groups "pyo-discuss" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/pyo-discuss/hZSTcAkbhAw/unsubscribe.
To unsubscribe from this group and all its topics, send an email to pyo-discuss...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/pyo-discuss/CAMXBGhQa4jWd02BBcSN9WCUPwLDEK08DKPAXbthAXxYWHUr_6A%40mail.gmail.com.
Reply all
Reply to author
Forward
0 new messages