How to make an ADSR?

edited April 2016 in Library Questions

Hi,

As part of a bigger sketch, where some values are affected by incoming MIDI, I want to be able to make an ADSR (Attack, Decay, Sustain, Release) -envelope.

  1. Midi value comes in. (0-127)
  2. ADSR envelopes (mapped to 0-1)
  3. ADSR value is used in several parts of the sketch.

How would you go about this?

Best, hjesper

Tagged:

Answers

  • Do you just mean using the map() function?

    output = map(value, minInput, maxInput, minOutput, maxOutput)
    

    How are you using the ADSR? What does it control?

  • Hi, thanks for your answer!

    It isn't map I am looking for. I know that one well.

    I was thinking about how to translate the midi information that goes from 0 to 127 in a millisecond to something that mimics what an ADSR on a synthesizer does.

    For example when midi comes in you would let the value gradually increase from 0 to 127 in 5 miliseconds, and then when midi is released the value would decrease to 0 over a period of 50 miliseconds.

    It could basically control everything, but I imagine it controlling the size of a shape.

  • That sounds interesting. I think you're saying to convert incoming MIDI data to a variable which you can then use for other things. You could use either MIDI cc, or note value, and extract this. How far have you got with reading incoming MIDI data? You'll need eg 6N137 optocoupler, and then read from a GPIO pin - once you have the MIDI data it's easy.

  • edited April 2016

    Sorry, read this again & worked out what you mean this time. How about this code? On pressing the mouse, value increases linearly to reach sustainLevel after attackTime ms. On releasing, it works out current value in case attack phase not reached maximum, then decays back to zero over decayTime.

    int attackTime = 1000;
    int decayTime = 2000;
    int sustainLevel = 500;
    
    //working values
    float value; // output value
    float valueStartDecay;
    long startTime;
    long attackEndTime;
    long decayEndTime;
    
    boolean newTrigger = false;
    boolean startDecay = false;
    
    void setup() {
    
    }
    
    void draw() {
      if (newTrigger) {
        startTime = millis();
        attackEndTime = startTime + attackTime;
        newTrigger = false;
      }
      if (millis() < attackEndTime) {
      value = map(millis(), startTime, attackEndTime, 0, sustainLevel); 
      println(value);
      }
      if (startDecay) {
        startDecay = false;
        valueStartDecay = value; // find out current value, as might release before end of attack phase
        startTime = millis(); // reuse this for start of decay phase
        attackEndTime = startTime; // set to now to finish attack phase
        decayEndTime = startTime + decayTime;
      }
      if (millis() < decayEndTime) {
      value = map(millis(), startTime, decayEndTime, valueStartDecay, 0); 
      println(value);
      }
    }
    
    void mousePressed() {
      newTrigger = true;  
    }
    
    void mouseReleased() {
      startDecay = true;  
    }
    
  • that looks perfect!! psyched to try it out! thanks a lot

  • I tested, and the behaviour of it has got some problems.

    If you let "value" decay all the way to its finishing value it doesn't go all the way to 0 again.

    If you restart the attack cycle, while "value" is still decaying, it results in jumpy values.

    I added a circle here to illustrate the point.

    int attackTime = 1000;
    int decayTime = 2000;
    int sustainLevel = 100;
    
    //working values
    float value; // output value
    float valueStartDecay;
    long startTime;
    long attackEndTime;
    long decayEndTime;
    
    boolean newTrigger = false;
    boolean startDecay = false;
    
    void setup() {
      size(500,500);
    
    }
    
    void draw() {
      background(0);
      if (newTrigger) {
        startTime = millis();
        attackEndTime = startTime + attackTime;
        newTrigger = false;
      }
      if (millis() < attackEndTime) {
      value = map(millis(), startTime, attackEndTime, 0, sustainLevel); 
      println(value);
      }
      if (startDecay) {
        startDecay = false;
        valueStartDecay = value; // find out current value, as might release before end of attack phase
        startTime = millis(); // reuse this for start of decay phase
        attackEndTime = startTime; // set to now to finish attack phase
        decayEndTime = startTime + decayTime;
      }
      if (millis() < decayEndTime) {
      value = map(millis(), startTime, decayEndTime, valueStartDecay, 0); 
      println(value);
      }
      ellipse(width/2,height/2, 50+10*value, 50+10*value); // using "value" to change size of ellipse
    }
    
    void mousePressed() {
      newTrigger = true;  
    }
    
    void mouseReleased() {
      startDecay = true;  
    }
    
  • I'll have a play this evening - was going to have another go anyway to try & get it to draw on the screen, as that would show it better. Good point about the attack restarting - would need to copy the valueStartDecay = value; into the attack portion, so it gets the current value when the attack restarts, that's straightforward to do. I might try do a whole ADSR envelope if I get a chance - would you use that?

  • Thanks! I am also trying to figure it out, but I am mostly just making it stop working. Haha (new to coding)

    I am mostly using the attack/sustain/release, so I don't have an urgent need for a full ADSR. But I am definitely curios of how you would do it!

  • This looks much better. Much more hectic to implement a full ADSR compared to an ASR, so I gave up on that - code gets too complex & much harder to understand. Have tried to comment it properly.

    int attackTime = 1000; // milliseconds
    int sustainLevel = 100; // percent
    int releaseTime = 3000; // ms
    int screenLength = 10000; // ms between screen refreshes
    
    //working values
    float value; // output value
    float currentValue = 0; // value at start of each segment
    long startTime;
    long attackEndTime;
    long releaseEndTime;
    int x = 0;
    long startXaxis;
    
    boolean newTrigger = false;
    boolean releaseTrigger = false;
    
    void setup() {
      size(800, 600);
      background(100);
      screenLength = 10000; // let's make X axis of screen represent 10 secs
      startXaxis = millis();
      stroke(255);
    }
    
    void draw() {
      if (newTrigger) {
        startTime = millis();
        releaseEndTime = startTime; // turn off release phase
        attackEndTime = startTime + attackTime;
        currentValue = value; // find out current level
        newTrigger = false;
      }
      if (millis() < attackEndTime) {
        value = map(millis(), startTime, attackEndTime, currentValue, sustainLevel); 
        println(value);
      } 
    
      if (releaseTrigger) {
        releaseTrigger = false;
        currentValue = value; // find out current value, as might release before end of attack phase
        startTime = millis(); // reuse this for start of release phase
        attackEndTime = startTime; // to turn off attack
        releaseEndTime = startTime + releaseTime; 
      }
      if (millis() < releaseEndTime) {
        value = map(millis(), startTime, releaseEndTime, currentValue, 0); 
        println(value);
      }
      x = (int)((millis() - startXaxis) * width / screenLength);
      if (x > width) {
        startXaxis = millis();
        x = 0; // start at L side again
        background(100); // comment this out to stop refreshing screen
      }
      ellipse(x, height - (height * value / 100), 2, 2); 
    }
    
    void mousePressed() {
      newTrigger = true;
      // attackTime = mouseX * 3; // uncomment these so mouse position varies the attack and release rates
      // releaseTime = mouseY * 5;
    }
    
    void mouseReleased() {
      releaseTrigger = true;
    }
    
  • Wow thats perfect!! Very responsive too. Thanks a lot for your help!

    My next task would be to put all this in a class (at least I think that is what it is called). So I have the ASR in a different tab, and I can make new instances of it without making the sketch too messy.

    Stoked to use this!!

  • I worked out that night how to add the decay segment in - actually much easier that I thought, as just needs an extra if statement during the attack phase. Classes are trickier, I struggle with the syntax of OOP, but useful if you're making multiple copies. Putting the code into a function is easiest - you just move the relevant code into a function like

    void updateADSR() { 
      // move code into here
    }
    

    and call it from the draw() loop:

    updateADSR();

    Then the function can stay at the bottom of the sketch & you don't need to look at it again. To have it in a separate tab would mean making a library, & you can then use it in any future sketches. I tend to take the lazy way & copy the code into a function.

  • edited April 2016

    Like this. I've put the code into updateADSR() & drawADSR() so it doesn't clutter the main draw() loop. I've added a decay part as well, set decayTime = 0 to not use this. It looks cooler if the colours change each time, and we don't redraw the screen. I'll put any changes in future on github as this post is getting long. https://github.com/fuzzySi/processing/blob/master/ADSR

    I'd be really interested to see what you come up with.

        // processing sketch which simulates the 4 phases of an ADSR envelope, as used in synthesisers (atttack, decay, sustain, release)
    
        // try altering these values
        int attackTime = 1000; // milliseconds
        int peakLevel = 100; // percent of height of screen
        int decayTime = 500;  // ms
        int sustainLevel = 80; // percent
        int releaseTime = 3000; // ms
        int screenLength = 5000; // ms between screen refreshes
    
        //working values
        float value; // output value
        float currentValue = 0; // value at start of each segment
        long startTime, attackEndTime, decayStartTime, releaseEndTime;
        float x = 0.0;
        float y = 0.0;
        long startXaxis;
        boolean newTrigger = false;
        boolean releaseTrigger = false;
    
        void setup() {
          size(800, 600);
          background(100);
          startXaxis = millis();
          stroke(255);
        }
    
        void draw() {
          y = checkADSR(); // looks at ADSR function which returns y value
          drawADSR(y); // sends y value to draw function
        }
    
        void mousePressed() {
          newTrigger = true;
          // attackTime = mouseX * 3; // uncomment these so mouse position varies the attack and release rates
          // releaseTime = mouseY * 5;
        }
    
        void mouseReleased() {
          releaseTrigger = true;
        }
    
        void drawADSR(float yValue) {
          x = (millis() - startXaxis) * width / screenLength;
          if (x > width) {
            startXaxis = millis();
            x = 0.0; // start at L side again
            stroke(random(255), random(255), random(255)); // random colour each time
            // background(100); // uncomment this out to refresh screen
          }
          ellipse(x, height - (height * yValue / 100), 2, 2);
          println(x);
        }
    
        float checkADSR() {
          if (newTrigger) {
            startTime = millis();
            releaseEndTime = startTime; // turn off release phase
            attackEndTime = startTime + attackTime + decayTime;
            decayStartTime = startTime + attackTime;
            currentValue = value; // find out current level
            newTrigger = false;
          }
          if (millis() < attackEndTime) {
            if (millis() < decayStartTime) {
              value = map(millis(), startTime, decayStartTime, currentValue, peakLevel);
            } else {
              value = map(millis(), decayStartTime, attackEndTime, peakLevel, sustainLevel);
            }
          } 
    
          if (releaseTrigger) {
            releaseTrigger = false;
            currentValue = value; // find out current value, as might release before end of attack phase
            startTime = millis(); // reuse this for start of release phase
            attackEndTime = startTime; // to turn off attack
            releaseEndTime = startTime + releaseTime;
          }
          if (millis() < releaseEndTime) {
            value = map(millis(), startTime, releaseEndTime, currentValue, 0); 
          }
          return value;
        }
    
Sign In or Register to comment.