Audio lib envelope.sustain

Rolfdegen

Well-known member
I am unable to change the `envelope.sustain` in Paul's audio library in real time. The AI suggested this solution to me, but unfortunately, it doesn't work. The only thing that works is a brief `envelope.noteOff` followed by `envelope.noteOn`. However, that causes clicking sounds :unsure:

C:
if (abs(currentSustain - lastSustainLevel) > 0.01) {

  envelope1.sustain(currentSustain);

 
  // If the note is sustained, briefly force it into an ultra-short decay phase

  if (envelope1.isSustain()) {

    envelope1.decay(1);  // Switches immediately and seamlessly to the new level

  }
 
  lastSustainLevel = currentSustain;

}

Envelope sustain phase not realtime
 
Last edited:
Hi Rolf,

I had assumed that you used other envelopes since you had the nice slopes etc.

I have been wondering about updating the current active voices envelopes and was about to go on this journey myself. For me it was the existing voices release I wanted to control.

For me the solution is to change the envelope source code. Would be good to have these changes applied to the main library as it’s a common feature along with slopes.

The main issue is the sustain multiplier is only calculated at transition from decay.

C:
else if (state == STATE_DECAY) {
                state = STATE_SUSTAIN;
                count = 0xFFFF;
                mult_hires = sustain_mult;
                inc_hires = 0;
            } else if (state == STATE_SUSTAIN) {
                count = 0xFFFF;

Updating will allow for calculations during the update.

I’ve taken a copy and plant to implement the slopes etc. this is maybe the only 3rd file
I’ve had to update to get what I need. But I really don’t like breaking from the library
 
I would like to use one linear and one exponential envelope. Paul's envelope library is linear. The envelope by Jonathan Oakley (h4yn0nnym0u5e)

How did you modify `effect_envelope.cpp` and `effect_envelope.h` to get the sustain update working?
 
Last edited:
I used Claude to examine the source code and then asked it to return a version that I could use but added options to change the linearity and be allowed to change all values whilst active. So at one point it’s perfectly linear and other values will be exp or log. But I haven’t used it yet because I messed up my code branch in git hub and spent last night recovering 🙊

I’ll likely look at h4yn0nnym0u5e version as well.

Changing


C++:
void sustain(float n) {
    if (n < 0) n = 0;
    else if (n > 1.0) n = 1.0;
    sustain_mult = n * 1073741824;
}

To

C++:
void sustain(float n) {
    if (n < 0) n = 0;
    else if (n > 1.0) n = 1.0;
    sustain_mult = n * 1073741824;
    if (state == STATE_SUSTAIN) mult_hires = sustain_mult;
}

Is the minimal fix

But I want to be able to set all values for the active voice. I can share code once I test. I do not trust any AI code without testing as I’ve been bitten too many times
 
With my code you can adjust the curvature of the attack, decay and release using a second optional parameter (target_factor). The usable range is 0.9999 to 0.5, defaulting to 0.95. If you set 0.5 it just uses the first half of the exponential curve, so not quite linear but pretty close.

All values should update in real time, though of course if you adjust, say, attack after that phase is over, then you won’t hear any effect.

On inspection, I’m not quite happy with how a live sustain level change looks as if it’s working, though it’s been a long time since I wrote the code. I should probably take a look at it sometime, but it’s not likely to be soon…
 
@h4yn0nnym0u5e
I used your code in my first project, `Jeannie_1`. You can only set the Attack phase from linear to exponential; Decay and Release are always exponential.

 
Last edited:
I used Claude to examine the source code and then asked it to return a version that I could use but added options to change the linearity and be allowed to change all values whilst active. So at one point it’s perfectly linear and other values will be exp or log. But I haven’t used it yet because I messed up my code branch in git hub and spent last night recovering 🙊

I’ll likely look at h4yn0nnym0u5e version as well.

Changing


C++:
void sustain(float n) {
    if (n < 0) n = 0;
    else if (n > 1.0) n = 1.0;
    sustain_mult = n * 1073741824;
}

To

C++:
void sustain(float n) {
    if (n < 0) n = 0;
    else if (n > 1.0) n = 1.0;
    sustain_mult = n * 1073741824;
    if (state == STATE_SUSTAIN) mult_hires = sustain_mult;
}

Is the minimal fix

But I want to be able to set all values for the active voice. I can share code once I test. I do not trust any AI code without testing as I’ve been bitten too many times

Thank you very much. I have modified `effect_envelope.h`. I can now change the sustain while holding the MIDI note.

 
Last edited:
You can only set the Attack phase from linear to exponential; Decay and Release are always exponential.
I assume that's a limitation of your Jeannie 1 project - with my code you could always set all of attack, decay and release to near-linear.

However, you couldn't change the sustain level live; I've pushed up some changes to allow this, using the decay settings to provide an exponential transition between the old and new levels, which should reduce the severity of any clicks (I'm slightly surprised not to hear any in your demo...). I've also permitted a target factor down to 0.25 so you can get closer to linear. There's a trade-off in the calculation accuracy, but it doesn't seem to be audible.
 
I have errors :unsure:

Screenshot 2026-05-20 194050.png
 
You probably need to import just the exponential envelope files. I merged in the most recent master Audio from Paul’s repo, but as he doesn’t see fit to use branches for development it’s polluted with Teensyduino 1.61 code, which also needs updated cores files.
 
I think I've found the problem. The DAC chip (PCM5102A) features a soft-mute function that can be activated via the XSMT I/O pin. This function causes a small transient spike when the DAC chip enters mute mode. I will set the pin to High.

1779470597154.png


DAC Chip PCM

9.3.2.3 Zero Data Detect
The PCM510xA has a zero-data detect function. When the device detects continuous zero data, it enters a full
analog mute condition. The PCM510xA counts zero data over 1024 LRCKs (21ms @ 48kHz) before setting
analog mute.
In Hardware mode, the device uses default values. By default, Both L-ch and R-ch have to be zero data for zero
data detection to begin the muting process etc.
9.3.3 XSMT Pin (Soft Mute / Soft Un-Mute)
An external digital host controls the PCM510xA soft mute function by driving the XSMT pin with a specific
minimum rise time (tr) and fall time (tf) for soft mute and soft un-mute. The PCM510xA requires tr and tf times of
less than 20ns. In the majority of applications, this is no problem, however, traces with high capacitance may
have issues.
When the XSMT pin is shifted from high to low (3.3 V to 0 V), a soft digital attenuation ramp begins. –1-dB
attenuation is then applied every sample time from 0 dBFS to –∞. The soft attenuation ramp takes 104 samples.
When the XSMT pin is shifted from low to high (0 V to 3.3 V), a soft digital “un-mute” is started. 1-dB gain steps
are applied every sample time from –∞ to 0 dBFS. The un-mute takes 104 samples.
In systems where XSMT is not required, it can be directly connected to AVDD.
 
I was mistaken. The XSMT pin controls the mute function. When the pin is set from Low to High, the chip enters unmute mode.
The disturbance pulse is then generated by switching off the envelopes :unsure:
 
Thanks. I am aware of the issue regarding the incorrect labeling. There are PCM5102A modules where Low = High and High = Low.
 
I tested both AudioEffectEnvelope and AudioEffectExpEnvelope. Both behave identically and generate a small impulse at the end. However, this could also be caused by the input on my mixing console with 12 Channels fom Behringer.

Perhaps someone with a different audio mixing console—or something similar—could verify this :unsure:
 
I tested it with another synthesizer. The noise pulses are similar. On the DeepMind 6, there is too much noise, but it is still discernible.

Another Teensy Synthesizer
Screenshot 2026-05-23 105810.png




DeepMind6 Synthesizer

DM6.png
 
Perhaps it is possible to trick the DAC chip and inject a small amount of noise (one bit) at the output. This would prevent analog muting within the DAC chip. :unsure:

PCM5102A analog mute function
'The PCM510xA has a zero-data detect function. When the device detects continuous zero data, it enters a full
analog mute condition. The PCM510xA counts zero data over 1024 LRCKs (21ms @ 48kHz) before setting
analog mute'
 
Last edited:
Yes. The trick with the noise worked. I added an AudioSynthNoiseWhite to the output mixer with the lowest amplitude of (1.0f/32768.0f)
Screenshot 2026-05-23 140530.png



Lin envelope
LinEnvNoise.png


Exp envelope
ExpEnvNoise.png
 
Last edited:
Perhaps it is possible to trick the DAC chip and inject a small amount of noise (one bit) at the output. This would prevent analog muting within the DAC chip. :unsure:
…is pretty much what was suggested in the post I linked to back in #18 above. I think they used a one bit DC offset.
 
Back
Top