Hilfe bei Gleichungen für die exponentielle ADSR-Hüllkurve


11

Mit Anwendungscode habe ich eine lineare ADSR-Hüllkurve implementiert, um die Amplitude des Oszillatorausgangs zu formen. Die Parameter für Attack-, Decay- und Release-Dauer sowie Sustain-Level können auf der Hüllkurve eingestellt werden und alles funktioniert wie erwartet.

Ich möchte jedoch die Rampenformen der Hüllkurve so anpassen, dass sie denen ähneln, die die meisten Synthesizer für eine natürlichere Reaktion verwenden: invers exponentiell für den Angriff und exponentiell für den Zerfall und die Freisetzung. Ich habe Probleme, meine Formeln für die Berechnung der Hüllkurvenausgabewerte für diese Arten von Rampenformen richtig zu machen. Um die linearen Rampen zu berechnen, verwende ich die Zweipunktform und stecke die Start / Ende- / y- Werte ein, die aus den Eingabeparameterwerten für Attack / Decay / Sustain / Release abgeleitet werden. Ich kann anscheinend nicht die richtige Formel für exponentielle (Standard- und inverse) Rampen mit denselben Start / Ende- x / y- Punktwerten erarbeiten.xyxy

Ich habe eine Desmos Graphing Calculator-Sitzung gespeichert , die den oben beschriebenen Ansatz für lineare Rampen demonstriert.

Wenn mir jemand helfen kann, in die richtige Richtung zu weisen, wäre er sehr dankbar.

Antworten:


10

Ich denke, was Sie verwirrt, ist, dass ein abnehmendes Exponential ( ) niemals 0 erreicht, so dass ein ADSR-Generator mit wirklich exponentiellen Segmenten stecken bleiben würde; weil es niemals den Zielwert erreichen würde. Befindet sich der Generator beispielsweise auf dem Höhepunkt der Angriffsphase (z. B. y = 1 ) und muss er bei y = 0,5 auf einem Sustain-Wert landen , kann er nicht mit einem echten Exponential dorthin gelangen, da das wahre Exponential gewonnen hat. ' t auf 0,5 abfallen, geht es nur asymptotisch auf 0,5!exy=1y=0.5

Wenn Sie sich einen analogen Hüllkurvengenerator ansehen (zum Beispiel die 7555-basierte Schaltung, die anscheinend jeder verwendet ), können Sie sehen, dass der Kondensator während der Angriffsphase beim Laden "höher zielt" als der Schwellenwert, der zur Anzeige des Endes verwendet wird der Angriffsphase. Auf einer (7) 555-basierten Schaltung, die mit +15 V betrieben wird, wird der Kondensator während der Angriffsphase mit einem + 15-V-Schritt aufgeladen, aber die Angriffsstufe endet, wenn ein Schwellenwert von +10 V erreicht wurde. Dies ist eine Design-Wahl, obwohl 2/3 die "magische Zahl" ist, die in vielen klassischen Hüllkurvengeneratoren zu finden ist, und dies könnte diejenige sein, mit der Musiker vertraut sind.

Einige ADSR-Formen resultieren aus unterschiedlichen "Zielverhältnissen" während der Kondensatorladung

Daher sind die Funktionen, mit denen Sie sich möglicherweise befassen möchten, keine Exponentialfunktionen, sondern verschobene / abgeschnittene / skalierte Versionen davon, und Sie müssen einige Entscheidungen treffen, wie "gequetscht" sie sein sollen.

Ich bin sowieso neugierig, warum Sie versuchen, solche Formeln zu erhalten - vielleicht liegt es an den Grenzen des Werkzeugs, das Sie für die Synthese verwenden; Wenn Sie jedoch versuchen, diese mithilfe einer universellen Programmiersprache (C, Java, Python) zu implementieren, wobei für jedes Beispiel des Umschlags Code ausgeführt wird und der Begriff "Status" verwendet wird, lesen Sie weiter ... Weil dies immer einfacher ist Drücken Sie Dinge aus wie "ein solches Segment wird von dem Wert, den es gerade erreicht hat, auf 0 gehen".

Meine zwei Ratschläge zur Implementierung von Umschlägen.

Der erste ist nichtum zu versuchen, alle Steigungen / Inkremente so zu skalieren, dass die Hüllkurve genau die Start- und Endwerte erreicht. Sie möchten beispielsweise eine Hüllkurve, die in 2 Sekunden von 0,8 auf 0,2 steigt, sodass Sie möglicherweise versucht sind, ein Inkrement von -0,3 / Sekunde zu berechnen. Tu das nicht. Teilen Sie es stattdessen in zwei Schritte auf: Erhalten einer Rampe, die in 2 Sekunden von 0 auf 1,0 geht; und dann Anwenden einer linearen Transformation, die 0 bis 0,8 und 1,0 bis 0,2 abbildet. Auf diese Weise gibt es zwei Vorteile: Der erste besteht darin, dass die Berechnung im Verhältnis zu den Hüllkurvenzeiten für eine Rampe von 0 auf 1 vereinfacht wird. Das zweite ist, dass, wenn Sie die Hüllkurvenparameter (Inkremente und Start- / Endzeiten) auf halbem Weg ändern, sich alles gut verhält. Gut, wenn Sie an einem Synthesizer arbeiten, da die Leute nach Hüllkurvenzeitparametern als Modulationsziele fragen.

Die zweite besteht darin, eine vorberechnete Nachschlagetabelle mit Umschlagformen zu verwenden. Es ist rechnerisch leichter, es entfernt viele schmutzige Details (zum Beispiel müssen Sie sich nicht mit einem Exponential herumschlagen, das nicht genau 0 erreicht - schneiden Sie es nach Lust und Laune ab und skalieren Sie es neu, damit es auf [0, 1] abgebildet wird). und es ist kinderleicht, für jede Stufe eine Option zum Ändern der Umschlagformen bereitzustellen.

Hier ist der Pseudocode für den Ansatz, den ich beschreibe.

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

Ich schien mein Problem zu lösen, indem ich meine lineare Skala / Zweipunktgleichung von y = ((y2 - y1) / (x2 - x1)) * (x - x1) + y1 nahm und sie durch Ersetzen der x-Variablen durch e umschrieb ^ x bis y = ((y2 - y1) / (e ^ x2 - e ^ x1)) * (e ^ x - e ^ x1) + y1. Meine Rechnersitzung unter Link veranschaulicht diesen Ansatz. Gibt es irgendwelche Fallstricke, die ich beachten sollte? Die Ergebnisse scheinen mir korrekt zu sein.
Gary DeReese

Dies ist keine Hüllkurvenform, die bei anderen Synthesizern zu finden ist. Abhängig von der Zeit / relativen Position des Start- und Endniveaus kann es sehr linear werden.
Pichenettes

@pichenettes, könnten Sie bereit sein, das Skript einzufügen, das diese Umschläge generiert hat?
P i

3

Dies ist eine ziemlich alte Frage, aber ich möchte nur einen Punkt in der Antwort von Pichenettes hervorheben:

Zum Beispiel möchten Sie einen Umschlag, der in 2 Sekunden von 0,8 auf 0,2 geht, [...] der in zwei Schritte unterteilt wird: Erhalten einer Rampe, die in 2 Sekunden von 0 auf 1,0 geht; und dann Anwenden einer linearen Transformation, die 0 bis 0,8 und 1,0 bis 0,2 abbildet.

Dieser Prozess wird manchmal als "Lockerung" bezeichnet und sieht aus wie

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

lu01f(x)xn01

f(x)

* Ich denke, das OP ist wahrscheinlich schon lange weg, aber vielleicht hilft das jemand anderem.


Vielen Dank dafür. Ich habe einen Sampler für eine DAW programmiert, für die ich Entwickler bin, und die in der Desmos-Sitzung bereitgestellten Formeln eingefügt, und sie haben perfekt funktioniert. Keine lahmen linearen Hüllkurven mehr! :)
Douglas

1

Über den Kommentar von Pichenettes: "Während der Angriffsphase wird der Kondensator mit einem + 15V-Schritt aufgeladen, aber die Angriffsphase endet, wenn ein Schwellenwert von +10 V erreicht wurde. Dies ist eine Entwurfswahl, obwohl 2/3 die" Magie "ist Nummer "in vielen klassischen Hüllkurvengeneratoren zu finden, und dies könnte diejenige sein, mit der Musiker vertraut sind.":

Jeder Umschlag, der für eine 15-V-Asymptote mit einem 10-V-Ziel schießt, erzeugt praktisch einen linearen Angriff. Es ist nur so, dass 15 V die höchste leicht verfügbare Asymptote ist und nahe genug an der Linearität liegt. Das heißt, es gibt nichts "Magisches" daran - sie streben nur so linear an, wie sie nur können.

Ich weiß nicht, wie viele klassische Synthesizer 15 V verwenden - ich würde vermuten, dass es oft ein oder zwei Diodenabfälle gibt. Mein altes Aries-Modul verwendet 13 V für eine 10-V-Hüllkurve, und ich habe gerade einen Curtis ADSR-Chip nachgeschlagen, der 6,5 V für eine 5-V-Hüllkurve verwendet.


1

Dieser Code sollte ähnliche Diagramme wie die von Pichenetten erzeugen:

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

Ich bin dankbar für Verbesserungen. Eine gute Idee ist es, die letzten drei Parameter (die die Steilheit jeder der drei Stufen bestimmen) zwischen 0 und 1 variieren zu lassen, wobei 0,5 eine gerade Linie wäre. Aber ich kann nicht ohne weiteres sehen, wie es geht.

Außerdem habe ich nicht alle Anwendungsfälle gründlich getestet, beispielsweise wenn eine Stufe eine Länge von Null hat.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.