Aiuto con le equazioni per l'inviluppo esponenziale dell'ADSR


11

Con il codice dell'applicazione, ho implementato un inviluppo ADSR lineare per modellare l'ampiezza dell'uscita di un oscillatore. I parametri per l'attacco, il decadimento e la durata del rilascio nonché il livello di sustain possono essere impostati sull'inviluppo e tutto funziona come previsto.

Tuttavia, vorrei modificare le forme di rampa dell'involucro in qualcosa che assomigli a quello che la maggior parte dei sintetizzatori usa per una risposta più naturale: esponenziale inversa per l'attacco ed esponenziale per il decadimento e il rilascio. Ho difficoltà a ottenere le formule giuste per il calcolo dei valori di output dell'inviluppo per questi tipi di forme di rampa. Per calcolare le rampe lineari, sto usando la forma a due punti, inserendo i valori di inizio / fine / y derivati ​​dai valori dei parametri di input di attacco / decadimento / sustain / rilascio. Non riesco a elaborare la formula corretta per le rampe esponenziali (standard e inverse) usando gli stessi valori di punto iniziale / finale x / y .xyxy

Ho salvato una sessione di Desmos Graphing Calculator che dimostra l'approccio per rampe lineari che ho descritto sopra.

Se qualcuno può aiutarmi a indicarmi la giusta direzione, sarebbe molto apprezzato.

Risposte:


10

exy=1y=0.5

Se guardi un generatore di inviluppo analogico (ad esempio il circuito basato su 7555 che tutti sembrano usare ), puoi vedere che durante la fase di attacco, quando il condensatore si sta caricando, "punta più in alto" della soglia utilizzata per indicare la fine della fase di attacco. Su un circuito (7) basato su 555 alimentato da + 15 V, durante la fase di attacco, il condensatore viene caricato con una fase di + 15 V, ma la fase di attacco termina quando viene raggiunta una soglia di + 10 V. Questa è una scelta di design, sebbene 2/3 sia il "numero magico" che si trova in molti generatori di buste classiche, e questo potrebbe essere quello con cui i musicisti hanno familiarità.

Alcune forme ADSR risultanti da diversi "goal ratio" durante la carica del condensatore

Pertanto, le funzioni che potresti voler trattare non sono esponenziali, ma versioni spostate / troncate / ridimensionate di esso, e dovrai fare alcune scelte su come "schiacciate" che vuoi che siano.

Sono comunque curioso di sapere perché stai cercando di ottenere tali formule - forse è a causa dei limiti dello strumento che stai usando per la sintesi; ma se stai cercando di implementare quelli che usano un linguaggio di programmazione generico (C, java, python) con del codice in esecuzione per ogni campione della busta e una nozione di "stato", continua a leggere ... Perché è sempre più facile esprimere cose come "tale segmento andrà da qualunque valore abbia appena raggiunto a 0".

I miei due consigli sull'implementazione delle buste.

Il primo noper provare a ridimensionare tutte le pendenze / incrementi in modo che l'inviluppo raggiunga esattamente i valori di inizio e fine. Ad esempio, vuoi un inviluppo che va da 0,8 a 0,2 in 2 secondi, quindi potresti essere tentato di calcolare un incremento di -0,3 / secondo. Non farlo. Invece, suddividilo in due passaggi: ottenere una rampa che va da 0 a 1,0 in 2 secondi; e quindi applicare una trasformazione lineare che mappa da 0 a 0,8 e da 1,0 a 0,2. Ci sono due vantaggi nel lavorare in questo modo: il primo è che semplifica qualsiasi calcolo che avrai rispetto ai tempi di inviluppo a una rampa da 0 a 1; il secondo è che se si cambiano i parametri di inviluppo (incrementi e tempi di inizio / fine) a metà tutto rimarrà ben educato. Buono se stai lavorando su un synth, poiché le persone chiederanno di avere i parametri del tempo di inviluppo come destinazioni di modulazione.

Il secondo consiste nell'utilizzare una tabella di ricerca pre-calcolata con forme di inviluppo. È più leggero dal punto di vista computazionale, elimina molti dettagli sporchi (ad esempio non devi preoccuparti di un esponenziale che non raggiunge esattamente 0 - troncalo a tuo capriccio e ridimensionalo in modo che sia mappato su [0, 1]), ed è facile fornire un'opzione per modificare le forme delle buste, per ogni fase.

Ecco lo pseudo-codice per l'approccio che descrivo.

render:
  counter += increment[stage]
  if counter > 1.0:
    stage = stage + 1
    start_value = value
    counter = 0
  position = interpolated_lookup(envelope_shape[stage], counter)
  value = start_value + (target_level[stage] - start_value) * position

trigger(state):
  if state = ON:
    stage = ATTACK
    value = 0  # for mono-style envelopes that are reset to 0 on new notes
    counter = 0
  else:
    counter = 0
    stage = RELEASE

initialization:
  target_level[ATTACK] = 1.0
  target_level[RELEASE] = 0.0
  target_level[END_OF_RELEASE] = 0.0
  increment[SUSTAIN] = 0.0
  increment[END_OF_RELEASE] = 0.0

configuration:
  increment[ATTACK] = ...
  increment[DECAY] = ...
  target_level[DECAY] = target_level[SUSTAIN] = ...
  increment[RELEASE] = ...
  envelope_shape[ATTACK] = lookup_table_exponential
  envelope_shape[DECAY] = lookup_table_exponential
  envelope_shape[RELEASE] = lookup_table_exponential

Mi è sembrato di risolvere il mio problema prendendo la mia scala lineare / equazione a due punti di y = ((y2 - y1) / (x2 - x1)) * (x - x1) + y1, riscrivendolo sostituendo le variabili x con e ^ x to y = ((y2 - y1) / (e ^ x2 - e ^ x1)) * (e ^ x - e ^ x1) + y1. La mia sessione di calcolatrice al link illustra questo approccio. Ci sono dei trucchi per questo di cui dovrei essere a conoscenza? I risultati mi sembrano corretti.
Gary DeReese,

Questa non è una forma di inviluppo trovata su altri sintetizzatori. A seconda del tempo / posizione relativa del livello iniziale e finale, può diventare molto lineare.
Pichenettes,

@pichenettes, potresti essere disposto a incollare lo script che ha generato quelle buste?
P

3

Questa è una domanda piuttosto vecchia, ma voglio solo evidenziare un punto nella risposta delle pichenettes:

Ad esempio, vuoi un inviluppo che va da 0,8 a 0,2 in 2 secondi [...] suddividendolo in due passaggi: ottenere una rampa che va da 0 a 1,0 in 2 secondi; e quindi applicare una trasformazione lineare che mappa da 0 a 0,8 e da 1,0 a 0,2.

Questo processo è talvolta noto come "allentamento" e sembra

g(x,l,u)=f(xlul)(ul)+l

lu01f(x)xn01

f(x)

* Suppongo che l'OP sia probabilmente scomparso da tempo, ma forse questo aiuta qualcun altro.


Grazie per questo, stavo programmando un campionatore per un DAW di cui sono uno sviluppatore, e ho inserito le formule fornite nella sessione Desmos e hanno funzionato perfettamente. Niente più buste lineari sfigate! :)
Douglas,

1

A proposito del commento di Pichenettes, "Durante la fase di attacco, il condensatore viene caricato con un passo di + 15V, ma la fase di attacco termina quando viene raggiunta una soglia di + 10V. Questa è una scelta di progettazione, sebbene 2/3 sia la" magia numero "trovato in molti generatori di inviluppi classici, e questo potrebbe essere quello con cui i musicisti hanno familiarità.":

Qualsiasi busta che spara per un asintoto 15v con un bersaglio 10v sta praticamente creando un attacco lineare. È solo che 15v è il più alto asintoto disponibile facilmente ed è abbastanza vicino al lineare. Cioè, non c'è nulla di "magico" in ciò: stanno solo andando per il più lineare possibile.

Non so quanti sintetizzatori classici utilizzino 15v: sospetto che spesso ci sia una caduta di diodi o due. Il mio vecchio Aries modulare usa 13v per una busta da 10v, e ho appena cercato un chip ADSR Curtis che utilizza, equivalentemente, 6,5v per una busta da 5v.


1

Questo codice dovrebbe generare grafici simili a quelli delle pichenette:

def ASD_envelope( nSamps, tAttack, tRelease, susPlateau, kA, kS, kD ):
    # number of samples for each stage
    sA = int( nSamps * tAttack )
    sD = int( nSamps * (1.-tRelease) )
    sS = nSamps - sA - sD

    # 0 to 1 over N samples, weighted with w
    def weighted_exp( N, w ):
        t = np.linspace( 0, 1, N )
        E = np.exp( w * t ) - 1
        E /= max(E)
        return E

    A = weighted_exp( sA, kA )
    S = weighted_exp( sS, kS )
    D = weighted_exp( sD, kD )

    A = A[::-1]
    A = 1.-A

    S = S[::-1]
    S *= 1-susPlateau
    S += susPlateau

    D = D[::-1]
    D *= susPlateau

    env = np.concatenate( [A,S,D] )

    # plot
    tEnv = np.linspace( 0, nSamps, len(env) )
    plt.plot( tEnv, env )
    plt.savefig( "OUT/EnvASD.png" )
    plt.close()

    return env

Sono grato per eventuali miglioramenti, una cosa che potrebbe essere una buona idea è consentire agli ultimi tre parametri (che determinano la pendenza di ciascuna delle tre fasi) di variare tra 0 e 1, dove 0,5 sarebbe una linea retta. Ma non riesco a vedere con disinvoltura come farlo.

Inoltre non ho testato a fondo tutti i casi d'uso, ad esempio se uno stadio ha lunghezza zero.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.