Pomoc w równaniach wykładniczej obwiedni ADSR

11

Za pomocą kodu aplikacji zaimplementowałem liniową obwiednię ADSR do kształtowania amplitudy mocy wyjściowej oscylatora. Parametry czasu ataku, rozpadu i uwolnienia, a także poziom podtrzymania można ustawić na kopercie i wszystko działa zgodnie z oczekiwaniami.

Chciałbym jednak dostosować kształty rampy obwiedni do czegoś, co przypomina większość syntezatorów w celu uzyskania bardziej naturalnej odpowiedzi: odwrotność wykładnicza dla ataku i wykładnicza dla rozpadu i uwolnienia. Mam problem z dostosowaniem moich formuł do obliczania wartości wyjściowych obwiedni dla tego rodzaju kształtów rampy. Aby obliczyć liniowe rampy, używam dwupunktowej formy, wprowadzając wartości początkowe / końcowe / y , które są uzyskiwane z wartości wejściowych parametrów ataku / rozpadu / przedłużenia / zwolnienia. Nie mogę opracować poprawnej formuły dla wykładniczych (standardowych i odwrotnych) ramp przy użyciu tych samych wartości początkowych / końcowych x / y .xyxy

Mam zapisane sesję Informacje dla poszukujących Kalkulator graficzny , który pokazuje podejście do ramp liniowych, które opisałem powyżej.

Byłbym bardzo wdzięczny, gdyby ktoś pomógł skierować mnie we właściwym kierunku.

Gary DeReese
źródło

Odpowiedzi:

10

Myślę, że tym, co was myli, jest to, że malejący wykładniczy ( ) nigdy nie osiąga 0, więc generator ADSR z prawdziwie wykładniczymi segmentami utknąłby; ponieważ nigdy nie osiągnie wartości docelowej. Na przykład, jeśli generator znajduje się na wysokości fazy ataku (powiedzmy y = 1 ) i musi wylądować do wartości podtrzymania przy y = 0,5 , nie może tam dotrzeć z prawdziwym wykładniczym, ponieważ prawdziwy wykładniczy wygrał t rozpadnie się na 0,5, to tylko asymptotycznie przejdzie do 0,5!mi-xy=1y=0,5

Jeśli spojrzysz na analogowy generator obwiedni (na przykład obwód oparty na 7555, którego wszyscy używają ), możesz zauważyć, że podczas fazy ataku, gdy kondensator ładuje się, „celuje on wyżej” niż próg używany do wskazania końca fazy ataku. W obwodzie opartym na (7) 555 zasilanym + 15 V, podczas etapu ataku kondensator jest ładowany z krokiem + 15 V, ale etap ataku kończy się, gdy zostanie osiągnięty próg + 10 V. Jest to wybór projektowy, chociaż 2/3 to „magiczna liczba” znaleziona w wielu klasycznych generatorach obwiedni, i może być to jeden z muzyków, którego znają.

Niektóre kształty ADSR wynikające z różnych „współczynników celowania” podczas ładowania kondensatora

Zatem funkcje, z którymi możesz chcieć sobie poradzić, nie są wykładnicze, ale są wersjami przesuniętymi / obciętymi / skalowanymi i będziesz musiał dokonać pewnych wyborów co do tego, jak „zgniecione” mają być.

W każdym razie jestem ciekawy, dlaczego próbujesz uzyskać takie formuły - być może wynika to z ograniczeń narzędzia, którego używasz do syntezy; ale jeśli próbujesz zaimplementować te, które używają języka programowania ogólnego przeznaczenia (C, Java, Python) z jakimś kodem działającym dla każdej próbki koperty i pojęciem „stan”, czytaj dalej ... Ponieważ zawsze łatwiej jest wyrażaj rzeczy jako „taki segment przejdzie od wartości, którą właśnie osiągnął, do 0”.

Moje dwie porady dotyczące wdrażania kopert.

Pierwszy z nich to nieaby spróbować skalować wszystkie nachylenia / przyrosty, aby obwiednia dokładnie osiągnęła wartości początkową i końcową. Na przykład potrzebujesz koperty, która zmienia się od 0,8 do 0,2 w ciągu 2 sekund, więc możesz mieć ochotę obliczyć przyrost o -0,3 / sekundę. Nie rób tego Zamiast tego podziel go na dwa etapy: uzyskanie rampy, która przechodzi od 0 do 1,0 w ciągu 2 sekund; a następnie zastosowanie transformacji liniowej, która odwzorowuje 0 na 0,8 i 1,0 na 0,2. Istnieją dwie zalety pracy w ten sposób - pierwsza polega na tym, że upraszcza wszelkie obliczenia w stosunku do czasów obwiedni względem rampy od 0 do 1; po drugie, jeśli zmienisz parametry obwiedni (przyrosty i czasy rozpoczęcia / zakończenia) w połowie, wszystko pozostanie dobrze zachowane. Dobrze, jeśli pracujesz nad syntezatorem, ponieważ ludzie będą prosić o podanie parametrów czasu koperty jako miejsc docelowych modulacji.

Drugim jest użycie wstępnie obliczonej tabeli odnośników z kształtami kopert. Jest obliczeniowo lżejszy, usuwa wiele nieprzyzwoitych szczegółów (na przykład nie musisz zawracać sobie głowy wykładnikiem, który nie osiąga dokładnie 0 - obetnij go według własnego uznania i przeskaluj, aby został zamapowany na [0, 1]), i bardzo trudno jest zapewnić opcję zmiany kształtów obwiedni dla każdego etapu.

Oto pseudo-kod opisanego przeze mnie podejścia.

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
fenenety
źródło
Wydawało mi się, że rozwiązałem mój problem, biorąc moje liniowe równanie / dwupunktowe równanie y = ((y2 - y1) / (x2 - x1)) * (x - x1) + y1, przepisując go, zastępując zmienne x e ^ x do y = ((y2 - y1) / (e ^ x2 - e ^ x1)) * (e ^ x - e ^ x1) + y1. Moja sesja kalkulatora pod linkiem ilustruje to podejście. Czy są jakieś wady, o których powinienem wiedzieć? Wyniki wydają mi się poprawne.
Gary DeReese
To nie jest kształt koperty znaleziony w innych syntezatorach. W zależności od czasu / względnej pozycji poziomu początkowego i końcowego może stać się bardzo liniowy.
pikenety
@pichenettes, czy mógłbyś chcieć wkleić skrypt, który wygenerował te koperty?
P i
3

To dość stare pytanie, ale chcę tylko podkreślić punkt w odpowiedzi z fenenet:

Na przykład chcesz obwiednię, która zmienia się z 0,8 na 0,2 w 2 sekundy [...] rozbicie jej na dwa etapy: uzyskanie rampy, która przechodzi od 0 do 1,0 w 2 sekundy; a następnie zastosowanie transformacji liniowej, która odwzorowuje 0 na 0,8 i 1,0 na 0,2.

Ten proces jest czasem nazywany „łagodzeniem” i wygląda jak

sol(x,l,u)=fa(x-lu-l)(u-l)+l

lu01fa(x)xn01

fa(x)

* Myślę, że OP już dawno już nie ma, ale może to pomaga komuś innemu.

Gość
źródło
Dziękuję za to, programowałem sampler dla DAW, dla którego jestem programistą, i podłączyłem formuły podane w sesji Desmos i działały idealnie. Nigdy więcej kulawych liniowych kopert! :)
Douglas,
1

O komentarzu z fenomenetami: „Podczas etapu ataku kondensator jest ładowany krokiem + 15 V, ale etap ataku kończy się, gdy zostanie osiągnięty próg + 10 V. Jest to wybór projektowy, chociaż 2/3 to„ magia ” numer „znaleziony w wielu klasycznych generatorach obwiedni, i to może być ten, który znają muzycy.”:

Każda koperta, która strzela do asymptoty 15 V z celem 10 V, praktycznie tworzy atak liniowy. Tyle tylko, że 15v jest najwyższą dostępną asymptotą łatwo dostępną i jest wystarczająco zbliżona do liniowej. Oznacza to, że nie ma w tym nic „magicznego” - po prostu są tak liniowe, jak to tylko możliwe.

Nie wiem, ile klasycznych syntezatorów wykorzystuje 15 V - podejrzewam, że często występuje spadek diody lub dwa. Mój stary modułowy Baran używa 13 V dla koperty 10 V, a ja właśnie spojrzałem na układ Curtis ADSR, który używa odpowiednio 6,5 V dla koperty 5 V.

Nigel Redmon
źródło
1

Ten kod powinien generować wykresy podobne do wykresów fenenetowych:

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

Jestem wdzięczny za wszelkie ulepszenia, jedną rzeczą, która może być dobrym pomysłem, jest umożliwienie, aby ostatnie trzy parametry (które określają nachylenie każdego z trzech etapów) zmieniały się między 0 a 1, gdzie 0,5 byłoby linią prostą. Ale nie widzę od razu, jak to zrobić.

Nie przetestowałem też dokładnie wszystkich przypadków użycia, na przykład jeśli jeden stopień ma zerową długość.

Liczba Pi
źródło