Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Suggestion for ADSR (and other) envelopes #97

Open
dfkettle opened this issue Apr 29, 2024 · 0 comments
Open

Suggestion for ADSR (and other) envelopes #97

dfkettle opened this issue Apr 29, 2024 · 0 comments

Comments

@dfkettle
Copy link

dfkettle commented Apr 29, 2024

I'm still working my way through your book, so maybe I missed it, but looking through the source code for ThinkDSP, I couldn't find any way to apply an ADSR envelope to a 'Wave' object. So I wrote this as a starting point. It could probably be improved (there isn't much validation being done), but if you think it might be useful for musical applications, feel free to include it.

Two classes are defined, Envelope, and a sub-class, ADSR. The envelope class could be used for evelopes that don't fit the classic ADSR pattern. Instances of either class can then be multiplied with a Wave object, returning another Wave object.

For example:

import thinkdsp
import envelope

cos_sig = thinkdsp.CosSignal(freq=440, amp=1.0, offset=0)
wave = cos_sig.make_wave(duration=1.0, start=0, framerate=11025)
env = envelope.ADSR([1000,500,0.5,4000],11025)
new = env * wave
new.play()

Here's the source code:

"""@author: David Kettle.

Copyright 2024 David Kettle
License: MIT License (https://opensource.org/licenses/MIT)
"""

from thinkdsp import Wave
import numpy as np

class Envelope(Wave):
    """Represents an amplitude envelope."""
    
    def __init__(self, ys):
        """Initialize the envelope.
        
        ys: array of amplitudes
        """
        Wave.__init__(self,ys)

    def __mul__(self, other):
        """Multiply an envelope with a wave elementwise.
        
        other: Wave
        returns: new Wave
        """
        # the envelope and the wave have to have the same duration
        assert len(self) == len(other)
        
        ys = self.ys * other.ys
        return Wave(ys, other.ts, other.framerate)

    __rmul__ = __mul__

class ADSR(Envelope):
    """Represents an attack, decay, sustain and release envelope.
    
    adsr: array or tuple representing attack (# of frames), decay (# of frames),
          sustain (amplitude level) and release (# of frames)
    duration: total duration of envelope (# of frames)
    returns: new Wave
    """
    
    def __init__(self, adsr, duration):
        """Initialize the envelope."""
        assert len(adsr) == 4
        (att_dur,dec_dur,sus_lvl,rel_dur) = adsr
        assert sus_lvl >= 0 and sus_lvl <= 1
        sus_dur = duration - (att_dur + dec_dur + rel_dur)
        if sus_dur < 0:
            sus_dur = 0
        
        attack = np.linspace(0,1,int(att_dur))
        decay = np.linspace(1,sus_lvl,int(dec_dur))
        sustain = np.linspace(sus_lvl,sus_lvl,int(sus_dur))
        release = np.linspace(sus_lvl,0,int(rel_dur))
        
        ys = np.concatenate((attack,decay,sustain,release))[0:int(duration)]
        Envelope.__init__(self,ys)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant