Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 21 of 21

Thread: Adjustable envelope code for the 4.x

  1. #1
    Senior Member
    Join Date
    Mar 2013
    Location
    Austin, TX
    Posts
    106

    Adjustable envelope code for the 4.x

    I finally fixed up my adjustable envelope object I've been messing around with forever. You can get it here.

    Click image for larger version. 

Name:	envelope-examples.jpg 
Views:	14 
Size:	17.6 KB 
ID:	24086

    Shape is adjustabe from a sharp exponential curve, 1.0 is a very chonky log for each ADSR stage.
    Lengths of the envelope stages and amplitude of sustain is unaffected by shape change.

    It can also be triggered works like a standard slope generator. Triggering it will produce the attack and decay stages and then end.

    This works well on the teensy 4.x and only takes about 2% processor on the 4.0 but on the 3.2 itís over 100% due to all the powf use. Each update of envelope has two calls to fscale and then interpolates between them for the other values in the 8 output batch.
    If there's demand I'll make a LUT version of it for the 3.x

    Let me know what bugs you find!

  2. #2
    Senior Member
    Join Date
    Apr 2019
    Posts
    144
    Hello I'm testing it on TSynth using a T4.1. Global variable usage on compile has gone from 279828 bytes (53%) to 312596 bytes (59%) just by including the two files. Using it on both the 12 amp envelopes and 12 filter envelopes, CPU is higher and can hit over 100 when playing a lot of notes rapidly, plus I'm using MarkT's band-limited waveforms too. AudioMemory is still fine at 96. Using the library on just the amp envelopes improves the CPU load and this is how I would use it.

    The smoother release is much better. Actually I don't care about attack and decay as these aren't audibly jarring when linear and I want attacks to be fast when set to minimum time. If memory and CPU load improvements can be made by only using it on the release stage, I would choose that, as no one will notice the attack or decay being linear.

    I'm getting a frequent click when releasing a note exactly when it's released, which is what would prevent me from implementing it at the moment. You can hear it in this audio file: MoogBass Otherwise, this could be a keeper. Thanks.

  3. #3
    Senior Member
    Join Date
    Mar 2013
    Location
    Austin, TX
    Posts
    106
    What is the processor usage of the TSynth usually?

    I can't replicate the click. Is it happening in other modes? Is it the resonance of the filter clipping?

  4. #4
    Senior Member
    Join Date
    Apr 2019
    Posts
    144
    It usually varies between 35 and 65%. I don't know what you mean by modes. I'll have a further play. It is intermittent as the sample shows.

    WCalvert says in another post It does not have the feature to prevent clicks if noteOff() is triggered before reaching sustain.
    Could this be it?

  5. #5
    Senior Member
    Join Date
    Apr 2019
    Posts
    144
    It would be great to add this to the AudioSynthWaveformDc object too, so that better portamento glides can be made.

  6. #6
    Senior Member
    Join Date
    Jul 2020
    Posts
    986
    I think the DC object should be for DC really - perhaps there's a need for a single-shot capable sweep generator class
    for portamento and suchlike - in fact such a class could be a superclass for an envelope class.

  7. #7
    Senior Member
    Join Date
    Mar 2013
    Location
    Austin, TX
    Posts
    106
    Quote Originally Posted by UHF View Post
    WCalvert says in another post It does not have the feature to prevent clicks if noteOff() is triggered before reaching sustain.
    Could this be it?
    This adjustable envelop can have note off occur at any time and fade out from there. I tested it a bit to make sure there wasn't an odd state where this would happen but of course I could still be missing something.


    Quote Originally Posted by UHF View Post
    It would be great to add this to the AudioSynthWaveformDc object too, so that better portamento glides can be made.
    This object already does that. It has a second output that is just the level of the envelope. Only issue is that it needs something coming into the input for the CV out to work. This is something I need to fix.

  8. #8
    Senior Member
    Join Date
    Apr 2019
    Posts
    144
    I'm using AudioSynthWaveformDc for portamento but an exponential response instead of a linear one, that mimics capacitor charge/discharge would be preferable.

  9. #9
    Senior Member
    Join Date
    Feb 2015
    Posts
    247
    Quote Originally Posted by UHF View Post
    WCalvert says in another post It does not have the feature to prevent clicks if noteOff() is triggered before reaching sustain.
    Could this be it?
    That was for my envelope code, which has nothing to do with john-mike's code.

  10. #10
    Senior Member
    Join Date
    Apr 2019
    Posts
    144
    Yes, but it could be a similar problem. Just a thought.
    Last edited by UHF; 03-19-2021 at 07:29 AM.

  11. #11
    Senior Member
    Join Date
    Mar 2013
    Location
    Austin, TX
    Posts
    106
    I still haven't been able to replicate the issue.
    Is anyone having it?
    Is it happening with other presets, UHF?

  12. #12
    Senior Member
    Join Date
    Apr 2019
    Posts
    144
    It seems intermittent. I can't see a pattern. When Decay level is set to minimum (1ms) and Sustain to zero, I also get a high-pitched artifact too.
    Someone else needs to test. Here's a sound file demonstrating this:

    https://1drv.ms/u/s!Avbtf5PcDsB0i-YN...J8uZQ?e=Vc4CL7

  13. #13
    Senior Member Rolfdegen's Avatar
    Join Date
    Sep 2020
    Location
    Germany
    Posts
    184
    LUT version is a good suggestion. This is done in the 8Bit Shruthi Synthesizer from Mutable instruments.


    Code:
    // Copyright 2009 Olivier Gillet.
    //
    // Author: Olivier Gillet (ol.gillet@gmail.com)
    //
    // This program is free software: you can redistribute it and/or modify
    // it under the terms of the GNU General Public License as published by
    // the Free Software Foundation, either version 3 of the License, or
    // (at your option) any later version.
    // This program is distributed in the hope that it will be useful,
    // but WITHOUT ANY WARRANTY; without even the implied warranty of
    // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    // GNU General Public License for more details.
    // You should have received a copy of the GNU General Public License
    // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    //
    // -----------------------------------------------------------------------------
    //
    // Envelopes.
    
    
    #ifndef SHRUTHI_ENVELOPE_H_
    #define SHRUTHI_ENVELOPE_H_
    
    
    #include "avrlib/base.h"
    #include "shruthi/patch.h"
    #include "shruthi/resources.h"
    #include "shruthi/shruthi.h"
    #include "avrlib/op.h"
    
    
    using namespace avrlib;
    
    
    namespace shruthi {
    
    
    enum EnvelopeStage {
      ATTACK = 0,
      DECAY = 1,
      SUSTAIN = 2,
      RELEASE = 3,
      DEAD = 4,
      NUM_SEGMENTS,
    };
    
    
    
    
    class Envelope {
     public:
      Envelope() { }
    
    
      void Init() {
        stage_target_[ATTACK] = 255;
        stage_target_[RELEASE] = 0;
        stage_target_[DEAD] = 0;
        stage_phase_increment_[SUSTAIN] = 0;
        stage_phase_increment_[DEAD] = 0;
      }
    
    
      uint8_t stage() { return stage_; }
      uint16_t value() { return value_; }
    
    
      void Trigger(uint8_t stage) {
        if (stage == DEAD) {
          value_ = 0;
        }
        a_ = value_ >> 8;
        b_ = stage_target_[stage];
        stage_ = stage;
        phase_ = 0;
        phase_increment_ = stage_phase_increment_[stage];
      }
    
    
      inline void UpdateAttack(uint8_t attack) {
        stage_phase_increment_[ATTACK] = ResourcesManager::Lookup<
            uint16_t, uint8_t>(lut_res_env_portamento_increments, attack);
        phase_increment_ = stage_phase_increment_[stage_];
      }
    
    
      inline void Update(
          uint8_t attack,
          uint8_t decay,
          uint8_t sustain,
          uint8_t release) {
        stage_phase_increment_[ATTACK] = ResourcesManager::Lookup<
            uint16_t, uint8_t>(lut_res_env_portamento_increments, attack);
        stage_phase_increment_[DECAY] = ResourcesManager::Lookup<
            uint16_t, uint8_t>(lut_res_env_portamento_increments, decay);
        stage_phase_increment_[RELEASE] = ResourcesManager::Lookup<
            uint16_t, uint8_t>(lut_res_env_portamento_increments, release);
        stage_target_[DECAY] = sustain << 1;
        stage_target_[SUSTAIN] = stage_target_[DECAY];
      }
    
    
      uint8_t Render() {
        phase_ += phase_increment_;
        if (phase_ < phase_increment_) {
          value_ = U8MixU16(a_, b_, 255);
          Trigger(++stage_);
        }
        if (phase_increment_) {
          uint8_t step = InterpolateSample(wav_res_env_expo, phase_);
          value_ = U8MixU16(a_, b_, step);
        }
        return stage_ == SUSTAIN ? stage_target_[DECAY] : value_ >> 8;
      }
    
    
     private:
      // Phase increments for each stage.
      uint16_t stage_phase_increment_[NUM_SEGMENTS];
      // Value that needs to be reached at the end of each stage.
      uint8_t stage_target_[NUM_SEGMENTS];
      // Current stage.
      uint8_t stage_;
    
    
      // Start and end value of the current segment.
      uint8_t a_;
      uint8_t b_;
    
    
      // Phase and phase increment.
      uint16_t phase_increment_;
      uint16_t phase_;
    
    
      // Current value of the envelope.
      uint16_t value_;
    
    
      DISALLOW_COPY_AND_ASSIGN(Envelope);
    };
    
    
    }  // namespace shruthi
    
    
    #endif // SHRUTHI_ENVELOPE_H_
    
    
    
    
    const prog_uint8_t wav_res_env_expo[] PROGMEM = {
           0,      4,      9,     14,     19,     23,     28,     32,
          37,     41,     45,     49,     53,     57,     61,     65,
          68,     72,     76,     79,     83,     86,     89,     92,
          96,     99,    102,    105,    108,    111,    113,    116,
         119,    121,    124,    127,    129,    132,    134,    136,
         139,    141,    143,    145,    148,    150,    152,    154,
         156,    158,    160,    161,    163,    165,    167,    169,
         170,    172,    174,    175,    177,    178,    180,    181,
         183,    184,    186,    187,    188,    190,    191,    192,
         193,    195,    196,    197,    198,    199,    200,    201,
         202,    203,    205,    206,    206,    207,    208,    209,
         210,    211,    212,    213,    214,    215,    215,    216,
         217,    218,    218,    219,    220,    221,    221,    222,
         223,    223,    224,    225,    225,    226,    226,    227,
         227,    228,    229,    229,    230,    230,    231,    231,
         232,    232,    233,    233,    233,    234,    234,    235,
         235,    236,    236,    236,    237,    237,    238,    238,
         238,    239,    239,    239,    240,    240,    240,    241,
         241,    241,    241,    242,    242,    242,    243,    243,
         243,    243,    244,    244,    244,    244,    245,    245,
         245,    245,    245,    246,    246,    246,    246,    246,
         247,    247,    247,    247,    247,    248,    248,    248,
         248,    248,    248,    248,    249,    249,    249,    249,
         249,    249,    249,    250,    250,    250,    250,    250,
         250,    250,    250,    251,    251,    251,    251,    251,
         251,    251,    251,    251,    251,    252,    252,    252,
         252,    252,    252,    252,    252,    252,    252,    252,
         252,    253,    253,    253,    253,    253,    253,    253,
         253,    253,    253,    253,    253,    253,    253,    253,
         253,    254,    254,    254,    254,    254,    254,    254,
         254,    254,    254,    254,    254,    254,    254,    254,
         254,    254,    254,    254,    254,    254,    254,    255,
         255,
    };
    Last edited by Rolfdegen; 03-23-2021 at 01:16 PM.

  14. #14
    Senior Member
    Join Date
    Mar 2013
    Location
    Austin, TX
    Posts
    106
    Ok I've updated it to fix lots of little things and have added several LUTs. You can still set arbitrary shapes and it interpolates between them.
    This should also fix the issues UHF was having
    https://github.com/BleepLabs/adjusta...velope_example

  15. #15
    Senior Member Rolfdegen's Avatar
    Join Date
    Sep 2020
    Location
    Germany
    Posts
    184
    Hallo John..

    Thank you for your development work. I will test it in my synthesizer.

    Greetings from germany. Rolf

  16. #16
    Member
    Join Date
    Apr 2021
    Location
    Cambridgeshire, UK
    Posts
    52
    Be interesting to know how the CPU load and memory footprint compare to my ExpEnvelope variant, to be found at https://github.com/h4yn0nnym0u5e/Aud...es/expEnvelope, and discussed in this thread: https://forum.pjrc.com/threads/66898...nerator-object. Mine doesn't have LUTs, so on-the-fly calculates an (acceptable, I believe) integer approximation to the exponential RC circuit found on most analogue synths: this seems to double the load you get from the baseline linear envelope object. But it can't do a logarithmic attack How does yours cope with a change of parameter during a particular state, e.g. lowering sustain when decay has already started?

    Cheers

    Jonathan

  17. #17
    Senior Member
    Join Date
    Apr 2019
    Posts
    144
    Yes, it does the job. No noises. CPU usage on a T4.1 is up a bit.

    Testing with patch 1 Solina with current linear envelope and this adjustable one:

    Code:
                 resting   1 note   4 notes    unison
    Linear       41         42          45          49
    Expo        44         45          47          56
    This is just with the amp envelopes. The filters are still linear.

    I've noticed when attack shape is positive and attack time is long (seconds) the initial start, jumps. Negative values are fine.
    Click image for larger version. 

Name:	Screenshot 2021-04-28 184808.jpg 
Views:	7 
Size:	13.5 KB 
ID:	24629


    Also can you put
    Code:
     uint16_t lut[9][256]  = {...}
    into flash memory?

    And
    Code:
          float flev =  (powf(level, curve));
    is no longer used.
    Last edited by UHF; 04-28-2021 at 10:37 AM.

  18. #18
    Senior Member
    Join Date
    Mar 2013
    Location
    Austin, TX
    Posts
    106
    Quote Originally Posted by h4yn0nnym0u5e View Post
    e.g. lowering sustain when decay has already started?
    Yeah it seems to handle that kind of thing well as it's always calculating the offset and attenuation of the decay and release.

    I was using fscale before and interpolating between values but it was far too much work on the 3.x and even on the 4.x I needed to speed it up for an upcoming project that needs lots of voices.


    UHF:
    Yeah the log LUT has a very quick jump at the beginning that can cause a click. You can see it easily on this spreadsheet I used to generate them.
    I haven't seen it make a separate little artifact, which is what it look like there. Realistically log is not super useful over .3 I think but what you have there doesn't look that crazy. What setting sis you have it at?

    Yes it should be a constant! But that is harder than it seems on the 4.x. PROGMEM isn't for T4x and FLASHMEM and static const won't compile for me in the library. They work fine in the .ino but have no effect on memory usage....

    And I removed that other line.
    Thanks for catching these.
    Last edited by john-mike; 04-28-2021 at 07:32 PM.

  19. #19
    Member
    Join Date
    Apr 2021
    Location
    Cambridgeshire, UK
    Posts
    52
    Quote Originally Posted by john-mike View Post
    Yeah it seems to handle that kind of thing well as it's always calculating the offset and attenuation of the decay and release.
    Just out of interest, I took a look...
    • ADR phase [timing] changes don't seem to take effect in real time, but wait until the next envelope*
    • A sustain level change during decay results in a glitch
    • Note-on (with no intervening note-off) during hold re-starts attack immediately from zero (my preference for this is to do nothing)

    I didn't investigate curve changes during the relevant phases... CPU load seems to average about 7600 cycles, compared to the original linear envelope's 830 - that's based on my test harness which spends most of its time in the CPU-intensive ADR phases.

    *this looks benign for short envelopes, but I'd imagine it would be a pain if you set a long attack time and then turned the knob down, for example...

    Cheers

    Jonathan

  20. #20
    Senior Member
    Join Date
    Mar 2013
    Location
    Austin, TX
    Posts
    106
    Right you can't change the lengths of ADR yet. I'm still trying to find the best way to do that.

    I was thinking the interpolation for them would be tricky but I just did it to fix the decay glitch and it seems to work.
    The new version I just put up has an interpolated sustain level so changing it at any time shouldn't make any artifacts.

    I hadn't checked note on without a note off. It should probably gone it your right.

    Thanks for checking it out so thoroughly, h4yn0nnym0u5e!


    I realized the only way to keep the LUT in flash was to put it outside the library and pass it along to it.
    I haven't been able to find a way to pass multidirectional arrays so it's now just a 2d one but I changed the function to make it work. See the setup on how to use it.

    https://github.com/BleepLabs/adjusta...velope_example

  21. #21
    Member
    Join Date
    Apr 2021
    Location
    Cambridgeshire, UK
    Posts
    52
    Quote Originally Posted by john-mike View Post
    Right you can't change the lengths of ADR yet. I'm still trying to find the best way to do that.

    The new version I just put up has an interpolated sustain level so changing it at any time shouldn't make any artifacts.

    I hadn't checked note on without a note off. It should probably gone it your right.

    Thanks for checking it out so thoroughly, h4yn0nnym0u5e!
    Thanks for being so understanding - having posted I felt I might have come across as a bit over-critical! I think it's great that there's so many options for people to choose from, depending on their needs (efficiency vs. versatility etc.)...

    Changing the ADR lengths essentially makes a nonsense of the "count" value that gets pre-calculated - I dealt with it by keeping it as a safety net, but actually terminating the state at a target level: 1.0 for attack, <sustain> for decay, and 0.0 for release. Don't know if that would work for your code.

    At some point I should probably post my stress-testing harness, it helped a lot with my take on updated code: essentially I never start the audio engine, but call the update functions directly and use the AudioPlayQueue and AudioRecordQueue to insert known values and read the results out in a non-time-critical manner. I did two versions, one which does note on/off at different envelope states, and one which changes envelope parameters. Having trawled a few threads there seems to be a common issue of "X works great but creates glitches when I do Y" (sometimes without much clue as to what Y is...).

    Cheers

    Jonathan

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •