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

Thread: Teensy 3.1 Audio Library - Envelope limited to 1000 ms?

  1. #1

    Teensy 3.1 Audio Library - Envelope limited to 1000 ms?

    I've been testing the Teensy 3.1 audio library and find that the envelope object doesn't seem to respond to A, D, or R values greater than 1000 ms, perhaps even a bit shorter.

    In my example code below, envelope1 is after the filter and envelope2 feeds a DC signal into the frequency control of the filter. Envelope1 just has a sustain level of 1.0, so basically it's not there. Envelope2 should produce a sound with a quick attack and long release, but it's more like a quick "wow".

    I wait 3000 ms before calling noteOff, so there should be plenty of time for phase D to complete.

    Bug, or am I doing it wrong?

    Code:
    #include <Audio.h>
    #include <Wire.h>
    #include <SPI.h>
    #include <SD.h>
    
    // GUItool: begin automatically generated code
    AudioSynthWaveformDc     dc1;            //xy=57,359
    AudioSynthWaveform       waveform1;      //xy=86,216
    AudioEffectEnvelope      envelope2;      //xy=165,283
    AudioFilterStateVariable filter1;        //xy=367,277
    AudioEffectEnvelope      envelope1;      //xy=554,266
    AudioOutputI2S           i2s1;           //xy=570,411
    AudioConnection          patchCord1(dc1, envelope2);
    AudioConnection          patchCord2(waveform1, 0, filter1, 0);
    AudioConnection          patchCord3(envelope2, 0, filter1, 1);
    AudioConnection          patchCord4(filter1, 0, envelope1, 0);
    AudioConnection          patchCord5(envelope1, 0, i2s1, 0);
    AudioConnection          patchCord6(envelope1, 0, i2s1, 1);
    AudioControlSGTL5000     audioShield;     //xy=569,528
    // GUItool: end automatically generated code
    
    void setup(void)
    {
    
      // Set up
      AudioMemory(8);
      audioShield.enable();
      audioShield.volume(0.45);
    
      // audio waveform
      waveform1.pulseWidth(0.5);
      waveform1.begin(0.2, 100, WAVEFORM_PULSE);
    
      // EG for VCA
      envelope1.attack(1);
      envelope1.decay(1);
      envelope1.sustain(1.0);
      envelope1.release(0.0);
    
      // EG for VCF
      envelope2.attack(100);
      envelope2.decay(2800);
      envelope2.sustain(0.0);
      envelope2.release(0.0);
      
      // DC input to EG for VCF
      dc1.amplitude(0.3);
      
      // VCF setup
      // corner frequency when control is zero
      filter1.frequency(0);
      // resonance
      filter1.resonance(5.0);
      // filter range
      filter1.octaveControl(7);
    }
    
    void loop() {
      
      float w;
      float v = 0.3;
      for (uint32_t i =1; i<20; i++) {
        w = i / 20.0;
        waveform1.pulseWidth(w);
        envelope1.noteOn();
        envelope2.noteOn();
        delay(3000);
        envelope1.noteOff();  
        envelope2.noteOff();
        delay(100);
        v = v + 0.1;
        if ( v > 1.0 )
        {
          v = 0.3;
        }
        dc1.amplitude(v);
      }
    }

  2. #2
    Senior Member
    Join Date
    Nov 2012
    Location
    Boston, MA, USA
    Posts
    1,108
    Quote Originally Posted by quarterturn View Post
    Envelope2 should produce a sound with a quick attack and long release, but it's more like a quick "wow".
    (Assuming you mean long decay, since you set release to 0.0 and talk about "phase D" later on)

    Quote Originally Posted by quarterturn View Post
    I wait 3000 ms before calling noteOff, so there should be plenty of time for phase D to complete.
    From the documentation
    http://www.pjrc.com/teensy/gui/?info...EffectEnvelope
    The recommended range for each of the 5 timing inputs is 0 to 50 milliseconds. Up to 200 ms can be used, with somewhat reduced accuracy
    looking in effect_envelope.h the DAHD&R vlues in milliseconds go to a function milliseconds2count defined as

    Code:
    	uint16_t milliseconds2count(float milliseconds) {
    		if (milliseconds < 0.0) milliseconds = 0.0;
    		uint32_t c = ((uint32_t)(milliseconds*SAMPLES_PER_MSEC)+7)>>3;
    		if (c > 1103) return 1103; // allow up to 200 ms
    		return c;
    	}
    so any value > 200ms is clamped.

    This object is not really a generic envelope. Its a combined envelope and VCA aimed specifically at note on/off articulation. You would probably want a different envelope object for controlling VCF, LFOs and so on over longer time scales.

    I'm curious if the decision to clamp values to 200ms was made for performance reasons or whether it was just assumed that no-one would need longer values.

    There is a fade object whose times are not clamped;
    http://www.pjrc.com/teensy/gui/?info=AudioEffectFade

  3. #3
    Ah, I didn't see the last part of the documentation - there it is!

    I'll look the the source some more and try to see why it's limited to 200 ms.

    The fade stuff is not desirable for an ADSR, since you'd have to wrap your own timing around things to know when in and out were completed. The envelope function saves a ton of work tracking and timing envelope states.

  4. #4
    examine the attack phase:
    attack_count gets set in void AudioEffectEnvelope::noteOn(void)

    defined in effect_envelope.h:
    void attack(float milliseconds) {
    attack_count = milliseconds2count(milliseconds);
    }

    calculated as:
    private:
    uint16_t milliseconds2count(float milliseconds) {
    if (milliseconds < 0.0) milliseconds = 0.0;
    uint32_t c = ((uint32_t)(milliseconds*SAMPLES_PER_MSEC)+7)>>3;
    if (c > 1103) return 1103; // allow up to 200 ms
    return c;
    }

    So, attack_count is limited to 1103 by the above.
    In this case, inc = 65536 / count >> 3, which is 7

    I think there's a duplication of rightwards bit shifting, because you want to ensure you increment in the attack phase by at least 1, which would be the case at about 1600 ms (if you don't shift 3 bits rightwards). I must be missing something though.

  5. #5
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    21,138
    Yup, it's a numerical precision issue.

    For version 1.0, I wanted to limit the times to a range where the code can always be very accurate. It's much easier to widen the allowed range in future versions than it is to later restrict it when/if it turns out the numerical precision is a problem.

    You can experiment with longer times by just editing the code to allow them. But the increment become small and the round-off to the nearest integer becomes a larger error. Please let me know how it actually works, if you try this.

  6. #6
    Quote Originally Posted by PaulStoffregen View Post
    Yup, it's a numerical precision issue.

    For version 1.0, I wanted to limit the times to a range where the code can always be very accurate. It's much easier to widen the allowed range in future versions than it is to later restrict it when/if it turns out the numerical precision is a problem.

    You can experiment with longer times by just editing the code to allow them. But the increment become small and the round-off to the nearest integer becomes a larger error. Please let me know how it actually works, if you try this.
    Thanks for the reply!

    I made the following change to effect_envelope.h:
    Code:
    private:
    	uint16_t milliseconds2count(float milliseconds) {
    		if (milliseconds < 0.0) milliseconds = 0.0;
    		uint32_t c = ((uint32_t)(milliseconds*SAMPLES_PER_MSEC)+7);
    		if (c > 8800) return 8800; // allow up to 1600 ms
    		return c;
    	}
    I tested with a decay value of 1600 ms and it works fine and sounds good too.

    Having this much extra envelope phase time will make for nice synth sounds.

  7. #7
    I've played with this some more, it's not right.

    In noteOn, the attack increment is calculated as:
    inc = (0x10000 / count ) >> 3;

    so, milliseconds2count can't ever return more than 8192, since 65536/8192 = 8, and 8 >> 3 is 1.

    But, nonwithstanding, attack is still not working right. Very small values of A produce a long attack, eg. 90 ms sound like 1 second. Much more than that and it's obviously not increasing the level properly as you hear a ramp up until time runs out for the phase, at which point it falls from the max level in phase D.

    My guess is that other bitshifting in effect_envelope.cpp needs adjusting.

  8. #8
    Good grief! I lost a left-shift! effec_envelope.h private milliseconds2count needs to look like this:

    private:
    uint16_t milliseconds2count(float milliseconds) {
    if (milliseconds < 0.0) milliseconds = 0.0;
    uint32_t c = ((uint32_t)(milliseconds*SAMPLES_PER_MSEC)+7) >> 3;
    if (c > 8192) return 8192; // allow up to 1600 ms
    return c;
    }

  9. #9
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    21,138
    The potential trouble with allowing count to go all the way to 8192 is the increment becomes a very small number integer. If the increment should have been 1.49, it gets rounded down to 1.0, or if it should have been 1.5 is gets rounded up to 2.0, for about 50% error.

    That's why I limited the version 1.0 code to 1103. That means the increment is always 7 or higher. Integer round off can cause no more than 7% error.

    Admittedly, that's probably more conservative than it needs to be. Especially with public APIs, where restricting options means breaking programs people have written, I tend to be pretty conservative. It's much easier to add to an API than to take something away or change it in incompatible ways.

    I'm happy to increase the range in the official code, but allowing so long a duration that range that the rate of change can be off by 50% worries me.

    The real question is how much error in the increment is acceptable?
    Last edited by PaulStoffregen; 12-13-2014 at 03:17 AM.

  10. #10
    I should note that a count of 8192 is actually 1489 ms, not 1600 ms. A tenth of a second difference is not perceptible.

    I appreciate your involvement in minor details like this. The library, the audio shield and the Teensy 3.1 are a great combo.

    Quote Originally Posted by PaulStoffregen View Post
    The potential trouble with allowing count to go all the way to 8192 is the increment becomes a very small number integer. If the increment should have been 1.49, it gets rounded down to 1.0, or if it should have been 1.5 is gets rounded up to 2.0, for about 50% error.

    That's why I limited the version 1.0 code to 1103. That means the increment is always 7 or higher. Integer round off can cause no more than 7% error.

    Admittedly, that's probably more conservative than it needs to be. Especially with public APIs, where restricting options means breaking programs people have written, I tend to be pretty conservative. It's much easier to add to an API than to take something away or change it in incompatible ways.

    I'm happy to increase the range in the official code, but allowing so long a duration that range that the rate of change can be off by 50% worries me.

    The real question is how much error in the increment is acceptable?

  11. #11
    Senior Member
    Join Date
    Nov 2012
    Location
    Boston, MA, USA
    Posts
    1,108
    Quote Originally Posted by PaulStoffregen View Post
    I'm happy to increase the range in the official code, but allowing so long a duration that range that the rate of change can be off by 50% worries me.

    The real question is how much error in the increment is acceptable?
    I think a good way to handle this is to retain an indication of the intended value, to avoid cumulative error. So one time round the loop the intended increment is 1.49 and gets rounded down to 1; but the next time around the intended total increment is 2.98, the actual current value is 1.0 so the actual increment is 2.0. This avoids multiplyimg up the roundoff error.

  12. #12
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    21,138
    That's a great idea Nantonos!

    I can think of a couple ways this might be implemented. Probably the simplest would be adding an integer post-increment number, which is simply added after the 8 normal ones, in each iteration of the loop. That should cut the cumulative error by a factor of 8.

    A more accurate approach would probably increment a 32 bit integer, carrying 16 fractional bits. That should reduce error to pretty much zero, but perhaps it's too much complexity? Or maybe not?


    Edit: I've added it to the issue list, so this won't be forgotten months from now, when I will be working on the code again.

    https://github.com/PaulStoffregen/Audio/issues/102
    Last edited by PaulStoffregen; 12-13-2014 at 11:54 PM.

  13. #13
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    21,138
    If anyone's still watching this old thread, I've *finally* fixed this long-standing limitation in the envelope effect. The fix is on github now, and will be released in Teensyduino 1.37 in June-July 2017 time frame.

    https://github.com/PaulStoffregen/Au...eca40131281014

    I increased the internal numerical resolution for gain adjustment from 16 to 30 bits. The prior limit of 0.2 seconds for attack, hold, decay & release is now increased to 11.88 seconds.

    I also tried to fix the pop that could occur if you call noteOff() before the sustain phase. Could really use some feedback on the effectiveness of this fix, if anyone is willing to try the latest from github...

  14. #14
    Quote Originally Posted by PaulStoffregen View Post
    If anyone's still watching this old thread, I've *finally* fixed this long-standing limitation in the envelope effect. The fix is on github now, and will be released in Teensyduino 1.37 in June-July 2017 time frame.

    https://github.com/PaulStoffregen/Au...eca40131281014

    I increased the internal numerical resolution for gain adjustment from 16 to 30 bits. The prior limit of 0.2 seconds for attack, hold, decay & release is now increased to 11.88 seconds.

    I also tried to fix the pop that could occur if you call noteOff() before the sustain phase. Could really use some feedback on the effectiveness of this fix, if anyone is willing to try the latest from github...
    I'll see if I can re-assemble my Teensy 3.x MIDI setup and try this.

    Did anything get updated since with the lowpass filter behavior? I see to recall there was an issue with high cutoff frequency and lowest resonance - it breaks out into LOUD white noise, maybe some sort of chaotic behavior in the feedback loop? Also, can the filter yet self-oscillate?

    Thanks!

  15. #15
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    21,138
    Quote Originally Posted by quarterturn View Post
    Did anything get updated since with the lowpass filter behavior?
    No, not yet. I have this thread on my bug list:

    https://forum.pjrc.com/threads/41314...edge-of-filter

    If you have a sketch you'd like me to specifically test when I do work on that problem, please post it on that thread.

  16. #16
    Quote Originally Posted by PaulStoffregen View Post
    If anyone's still watching this old thread, I've *finally* fixed this long-standing limitation in the envelope effect. The fix is on github now, and will be released in Teensyduino 1.37 in June-July 2017 time frame.

    https://github.com/PaulStoffregen/Au...eca40131281014

    I increased the internal numerical resolution for gain adjustment from 16 to 30 bits. The prior limit of 0.2 seconds for attack, hold, decay & release is now increased to 11.88 seconds.

    I also tried to fix the pop that could occur if you call noteOff() before the sustain phase. Could really use some feedback on the effectiveness of this fix, if anyone is willing to try the latest from github...
    I just updated to this new envelope library and so far it is brilliant, so much more useful than the old envelope effect :-)

    What I'd like to see happen now is a modulation input on the waveform module for vibrato effects, and a modulation input on the delay / delay-ext effects for modulated echo effects, then we can build some amazing digital synthesisers !

    Some simple switches would be useful too - for routing signals throughout the synth.

  17. #17
    Quote Originally Posted by PaulStoffregen View Post
    If anyone's still watching this old thread, I've *finally* fixed this long-standing limitation in the envelope effect. The fix is on github now, and will be released in Teensyduino 1.37 in June-July 2017 time frame.

    https://github.com/PaulStoffregen/Au...eca40131281014

    I increased the internal numerical resolution for gain adjustment from 16 to 30 bits. The prior limit of 0.2 seconds for attack, hold, decay & release is now increased to 11.88 seconds.

    I also tried to fix the pop that could occur if you call noteOff() before the sustain phase. Could really use some feedback on the effectiveness of this fix, if anyone is willing to try the latest from github...
    The click/pop issue still seems to be there, unfortunately. A click will likely occur where there is a sudden discontinuity in the waveform. Many synths over the years have suffered with this problem! If that is what it is, then one way around it may be to not allow it to snap back to zero quite so quickly, with a quick ramp down. If it were fast enough (say 0.5-1ms), it shouldn't affect the snappiness of the envelope too much. If it had to be longer, then making it switchable would still allow for snappy drums. Just a thought.

  18. #18
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    21,138
    Quote Originally Posted by DirtBox_Rich View Post
    The click/pop issue still seems to be there, unfortunately.
    Any chance you could give me a simple sketch that makes this sound?

  19. #19
    Do you have a MIDI keyboard to hand?

  20. #20
    Quote Originally Posted by PaulStoffregen View Post
    Any chance you could give me a simple sketch that makes this sound?
    If you have a USB or MIDI keyboard, then this demonstrates it.

    Code:
    #include <Audio.h>
    #include <Wire.h>
    #include <SPI.h>
    #include <SD.h>
    #include <SerialFlash.h>
    #include <MIDI.h>
    
    AudioSynthWaveformSine   sine1;          //xy=226,136
    AudioEffectEnvelope      envelope1;      //xy=377,136
    AudioMixer4              mixer1;         //xy=533,139
    AudioOutputI2S           i2s1;           //xy=727,116
    AudioConnection          patchCord1(sine1, envelope1);
    AudioConnection          patchCord2(envelope1, 0, mixer1, 0);
    AudioConnection          patchCord3(mixer1, 0, i2s1, 0);
    AudioConnection          patchCord4(mixer1, 0, i2s1, 1);
    AudioControlSGTL5000     sgtl5000_1;     //xy=557,203
    
    void setup() {
      MIDI.begin(MIDI_CHANNEL_OMNI);
      MIDI.setHandleNoteOff(noteOffReceived);
      MIDI.setHandleNoteOn(noteOnReceived);
      usbMIDI.begin();
      usbMIDI.setHandleNoteOff(noteOffReceived);
      usbMIDI.setHandleNoteOn(noteOnReceived);
    
      AudioMemory(10);        
      sgtl5000_1.enable();
      sgtl5000_1.volume(0.5);
    
      mixer1.gain(0, 0.5);
      mixer1.gain(1, 0.0);
      mixer1.gain(2, 0.0);
      mixer1.gain(3, 0.0);
    
      envelope1.delay(0.0);
      envelope1.attack(2000.0);
      envelope1.hold(0.0);
      envelope1.decay(0.0);
      envelope1.sustain(1.0);
      envelope1.release(1000.0);
    }
    
    void loop() {
      MIDI.read();
      usbMIDI.read();
    }
    
    void noteOnReceived(byte channel, byte note, byte velocity) {
      if (velocity == 0) {
        envelope1.noteOff();
        return;
      }
      sine1.amplitude(0.5);
      sine1.frequency(440.0);
      envelope1.noteOn();
    }
    
    void noteOffReceived(byte channel, byte note, byte velocity) {
      envelope1.noteOff();
    }
    Last edited by DirtBox_Rich; 05-06-2017 at 05:06 PM.

  21. #21
    Quote Originally Posted by PaulStoffregen View Post
    Any chance you could give me a simple sketch that makes this sound?
    and here's one if you don't have a keyboard...

    The clicks are more subtle like this, but they are still there for me.
    Interestingly they don't happen every time.

    Code:
    #include <Audio.h>
    #include <Wire.h>
    #include <SPI.h>
    #include <SD.h>
    #include <SerialFlash.h>
    
    AudioSynthWaveformSine   sine1;          //xy=226,136
    AudioEffectEnvelope      envelope1;      //xy=377,136
    AudioMixer4              mixer1;         //xy=533,139
    AudioOutputI2S           i2s1;           //xy=727,116
    AudioConnection          patchCord1(sine1, envelope1);
    AudioConnection          patchCord2(envelope1, 0, mixer1, 0);
    AudioConnection          patchCord3(mixer1, 0, i2s1, 0);
    AudioConnection          patchCord4(mixer1, 0, i2s1, 1);
    AudioControlSGTL5000     sgtl5000_1;     //xy=557,203
    
    void setup() {
      
      AudioMemory(10);         
      sgtl5000_1.enable();
      sgtl5000_1.volume(0.5);
    
      mixer1.gain(0, 0.5);
      mixer1.gain(1, 0.0);
      mixer1.gain(2, 0.0);
      mixer1.gain(3, 0.0);
    
      envelope1.delay(0.0);
      envelope1.attack(2000.0);
      envelope1.hold(0.0);
      envelope1.decay(0.0);
      envelope1.sustain(1.0);
      envelope1.release(1000.0);
    
      sine1.amplitude(0.5);
      sine1.frequency(440.0);
    
    }
    
    void loop() {
      envelope1.noteOn();
      delay(100.0);
      envelope1.noteOff();
      delay(500.0);
    }

  22. #22
    Another way to possibly fix the issue, would be to ensure that the waveform starts and ends exactly at a zero-crossing (amplitude=0). But I'm guessing that that may be more tricky as the audio is always delivered in 128 sample packets? If so, the last few samples could be faded, perhaps? .... I'm sure you've tried various methods of this!!
    - great library, by the way

  23. #23
    Member
    Join Date
    Nov 2012
    Location
    Santa Fe, New Mexico, USA
    Posts
    30
    When this came up in my audio processing class I think the answer was that the corners of the envelope are always smoothed so there are no corners. Discontinuities in the rate of change make clicks.

    So convert the trapezoidal envelope into a smooth curve of samples at instantiation, and simply convolve the envelope samples with the audio stream at run time.

    But I see the envelope that DirtBox_Rich gave you also has some 0 duration elements in the middle, those might be even more discontinuous.

  24. #24
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    21,138
    Do you think this could be accomplished by adding 1, 2 or even 3 more linear slopes for short durations to smooth the transition?

    It's easy to say things like "simply convolve the envelope samples", but do actually do them requires quite a bit of memory and processor time. I'm planning to add a piecewise control waveform synthesis object, which will allow exactly this sort of thing when used as an input to the multiplier object. But doing an actual exponential waveform and then the multiply step is much, much more CPU and memory intensive than the envelope's linear ramping.

  25. #25
    Junior Member
    Join Date
    May 2014
    Posts
    13
    Hi, I've tried the last update,
    I have very low knowledge of programming, but if it helps, I hear that the problem is in the transition to sustain, if the sustain is not in a value 1 there are always clicks
    * I can not contribute more ... I'm sorry
    Thanks for this great library. I'm having a great time.

Posting Permissions

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