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ą.
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