Cabbage Logo
Back to Cabbage Site

Filter Cutoff Envelope Implementation

Hello,

I’m struggling a bit with implementing an envelope for the filter cutoff frequency that also allows the user to control the cutoff frequency at full range during performance with the mod wheel. The way I have it right now ties the maximum frequency that can be reached during performance to the sustain value.

Here’s my Csound code, the filter is between lines 269 - 289. There’s must be a less convoluted way to implement what I want but I’ve been butting my head against this wall for a while. Any help is very much appreciated!

Granulera_v0.8.csd (18.1 KB)

It looks like you are multiplying the output of the expsegr by the mod wheel? Is that right? I don’t get what you are doing with this line:

kFilterEnv1 = kFilterEnv1 - 0.01

or as long as the note is help kFilterEnv1 will decrease in value by .1 for every k-cycle. I can’t think why you might want to do that. I think if you want the mod wheel to control the range of a filter envelop you probably have to do it like this. expsegr take i-rate values so you can’t slot it in there. I think this looks Ok. Does it do what you want it to?

Thanks for pointing that out, I took that line from the FLOSS manual cause it says exp envelopes can’t be assigned a value of 0 and showed how you can assign an offset value and then subtract from it but didn’t stop to think that they showed that happening on i-rate and I’m doing it on k-rate, so yeah, that’s a mistake on my part.

Yeah, the envelope technically works alright but the way it is right now the mod wheel value is relative to the sustain value, so if I have a sustain value less than 1, the mod wheel can’t fully open the envelope after initialization. The biggest roadblock I’m finding for this is that all envelope opcodes I’m aware of take i-rate arguments while the modwheel is k-rate.

i-rate parameters for envelopes makes sense, but I do understand the problem. Would the scale opcode help at all?

Thanks! I think that might be it but I’m not being able to figure out how to best go about it.

My current train of thought is to scale the filter envelope between the cc1 value and 0 but I’m finding that if I just plug the values in like that it doesn’t do anything different. I’m trying then to multiply the modwheel value (by arbitrary numbers for now) but that eliminates a lot of nuance from the modwheel and still doesn’t give the modwheel the ability to close or open completely the filter during the sustain period

The addition is on line 266:
Granulera_v0.8.csd (17.8 KB)

Also, is there a way I could make it so the modwheel actually controls the values in the filter frequency channel (altering the GUI slider) but still allows the user to set the value with the mouse/DAW automation at k-rate?

It would be much easier for me to help if I had access to a MIDI keyboard with a mod wheel. Actually, would you mind preparing a simplified example that clearly shows the issue. It would be much easier to debug I think. I have to run to class now, but I will check in again later…

Of course, here’s a simplified version!

FilterEnvelopeExample.csd (1.1 KB)

Even though the linseg representing the mod wheel goes up to 1, the filter doesn’t open all the way during the sustain phase and I want to make it so I can have an envelope and open and close it at will during the sustain phase.

Just so I’m clear, you want to mod wheel to be able to override the adsr at any point? So if the adsr is currently outputting .5 and the user pushes the mod wheel to max, you want the mod wheel value to override the adsr?

Yes, that’s it! But ideally the the envelope would still release as set by the user (in a MIDI context)

I made a test earlier with a simple if statement that seemed to do the trick, but… one would need to take into account the release. I’m not in the office now, but I can send you a proof of concept tomorrow maybe. That’s if you don’t figure it out yourself by then…

If this example I continuously check if the mod wheel is greater than the env value. If it is, I switch to it, otherwise stick with the ADSR. When the instrument enters the release stage I switch back to the ADSR. During the switch back I use a tonek to smoothen the transition. portk might work better. I’m not sure if this is what you are after? I’ve attached the full .csd below. Note I’m just controlling a simple frequency value rather than a filter.

instr 1
    kModWheel linsegr 0, 3, 1, 1, .00001
    kEnv madsr 1, .2, .5, 1
    kFilter init 0  
    
    if kModWheel > kEnv then                ;if mod wheel is greater than current ADSR, use it...
        kFilter = kModWheel
    else                                    ;else use adsr
        kFilter = kEnv
    endif
    
    if release() == 1 then
        kFilter = kEnv
        kFilter = tonek(kFilter, 20)        ;smooth signal when it enters release phase
    endif

    a1 oscili 1, kFilter*1000               ;using signal to control frequency for now..
    outs a1, a1
endin

FilterEnvelopeExample.csd (893 Bytes)

Thank you so much, this is exactly what I’m looking to do!

The if statement for the modwheel is working perfectly for me but checking if release() == 1 by itself was creating a pop (like it was rearticulating the filter from the sustain value). I condensed the code into this and it fixed the rearticulating issue, the only problem now is that the release time for the filter is sounding much shorter than set by the user through the Cabbage UI

EDIT: just realized the release isn’t actually shorter, it’s just releasing from the actual sustain value rather than the modwheel’s
EDIT 2: I’m listening in headphones now and realized this doesn’t remove the rearticulation issue, it just makes a low frequency pop instead

if kcc1 > kFilterEnv && release() ==0 then
    kFilterFreq = kcc1
else
    kFilterFreq = kFilterEnv
endif  
kFilterFreq portk kFilterFreq, 0.02

Ended up arranging the code like this and for some reason it removed the popping issue?

if release() == 0 then
    if kcc1 > kFilterEnv then
        kFilterFreqSum = kcc1
    else
        kFilterFreqSum = kFilterEnv
    endif  
else
    kFilterFreqSum = kFilterEnv
    ;kFilterFreq portk kFilterFreq, 0.1
    kFilterFreq tonek kFilterEnv, 20
endif

I’m not quite sure why your change fixes it, but we should never question the Csound Gods!

Yes, I noticed that rearranging the code would yield different types of issues so I just fumbled my way into a solution!

Just to complicate my life a bit, I realized I also want the possibility to lower the cutoff frequency if the filter is sustaining at full range, so I converted the previous code to the code below.

The idea is if the user touches the modwheel during performance it completely overrides the envelope until the release stage, so I’m storing the changed value in another variable that won’t revert its value.

The problem I’m having with it is that the kcc1Trig variable starts with a value of 1 when the instrument is initialized despite the modwheel remaining at the same value. Do you know why that happens and what could be a fix for it? I’m including the whole .csd if more context is needed (the code below starts on line 309
Granulera_v0.9.csd (21.6 KB)

kcc1Trig changed kcc1
if kcc1Trig == 1 then
    kcc1Trig2 = 1
endif
printk 0.2, kcc1Trig

if release() == 0 then
    if kcc1Trig2 == 1 then 
        kFilterFreqSum = kcc1
    else
        kFilterFreqSum = kFilterEnv
    endif  
else
    kFilterFreqSum = kFilterEnv
    kFilterFreq port kFilterFreq, 0.1
endif

I think changed2 could be your friend here :+1:

That opcode is indeed my friend here, thank you!

I’m just having trouble making it work while using port in the modwheel value: if the modwheel value is higher than its minimum, every time I trigger a MIDI note it ports it from 0 to the assigned value, triggering the changed opcode. If I remove the port line, it works as intended but introduces noise if the modwheel is moved to quickly.

I think this is why I put the port/tonek into the release() block in my first attempt. Let me revisit that one and see if I can get rid of the pop…

When I put in a vco and a filter I get no pops with the first instrument:

instr 1

    kModWheel linsegr 0, 3, 1, 1, .00001
    kEnv madsr 1, .2, .5, 1
    kFilter init 0

    if kModWheel > kEnv then                ;if mod wheel is greater than current ADSR, use it...
        kFilter = kModWheel
    else                                    ;else use adsr
        kFilter = kEnv
    endif
    
    if release() == 1 then
        kFilter = kEnv
        kFilter = tonek(kFilter, 20)        ;smooth signal when it enters release phase
    endif


    aSrc vco2 1, 200
    
    a1 moogladder aSrc, kFilter*5000, 0               ;using signal to control frequency for now..
    outs a1, a1
    
endin

Oh, sorry about the confusion, that part of the code is working fine.

What I meant is that changed2 is working as expected but if I include a line to use port on the modwheel value to reduce noise while moving the modwheel fast (kcc1 port kcc1, 0.02) and leave the modwheel at a non zero value (let’s say 1), the opcode actually smooths the modwheel value from 0 to 1 as soon as the note starts, which triggers the change2 opcode and overrides the attack of the filter even though the modwheel wasn’t touched.

If I comment out the kcc1 port kcc1, 0.02 line, it works as intended but moving the modwheel produces noise

EDIT: Ok, I think I fixed it. I just used the port opcode on the entire envelope after all the if statements instead of porting the cc1 and the release individually
EDIT 2: Yeah, I think it’s fixed now, thank you so much again for your help, Rory!