Я думаю, що те, що вас бентежить, полягає в тому, що зменшувальна експоненція ( ) ніколи не досягає 0, тому генератор ADSR з по-справжньому експоненціальними сегментами залишився б застряг; тому що вона ніколи не досягне цільового значення. Наприклад, якщо генератор знаходиться на висоті фази атаки (скажімо, ) і повинен приземлитися до стійкого значення при , він не може перейти туди з справжньою експоненцією, тому що справжня експоненція виграла ' t занепаде до 0,5, це лише асимптотично перейде до 0,5! y = 1 y = 0,5е- хy=1y=0.5
Якщо ви подивитеся на аналоговий генератор конвертів (наприклад, схема на базі 7555, яку, здається, використовують усі ), ви можете бачити, що під час фази атаки, коли конденсатор заряджається, він "націлений вище", ніж поріг, який використовується для позначення кінця фази атаки. У схемі на базі (7) 555, що живиться від + 15 В, під час стадії атаки конденсатор заряджається кроком + 15 В, але етап атаки закінчується, коли досягається поріг + 10 В. Це вибір дизайну, хоча 2/3 - це "магічне число", яке зустрічається у багатьох класичних генераторах конвертів, і це може бути той, хто знайомий з музикантами.
Таким чином, функції, з якими ви хочете мати справу, - це не експоненціали, а зміщені / усічені / масштабні його версії, і вам доведеться зробити певний вибір щодо того, наскільки "розчавленими" ви хочете, щоб вони були.
Мені все одно цікаво, чому ви намагаєтеся отримати такі формули - можливо, це через обмеження інструменту, який ви використовуєте для синтезу; але якщо ви намагаєтесь реалізувати ті, хто використовує мову програмування загального призначення (C, java, python) з деяким кодом, що працює для кожного зразка конверта, та поняттям "стан", читайте далі ... Тому що це завжди простіше висловіть речі як "такий сегмент буде переходити від будь-якого значення, яке він щойно досяг до 0".
Мої дві поради щодо реалізації конвертів.
Перший - ніспробувати масштабувати всі нахили / кроки, щоб конверт точно досягнув початкових та кінцевих значень. Наприклад, ви хочете, щоб конверт переходив від 0,8 до 0,2 за 2 секунди, тож, можливо, ви захочете обчислити приріст в -0,3 / секунду. Не робіть цього. Замість цього розбийте його на два етапи: отримання пандуса, який проходить від 0 до 1,0 за 2 секунди; а потім застосувати лінійне перетворення, яке відображає 0 до 0,8 і 1,0 до 0,2. У цьому способі є дві переваги - перша полягає в тому, що це спрощує будь-які обчислення, які ви матимете щодо часу конверта до рампи від 0 до 1; по-друге, якщо змінити параметри конверта (з кроком та часом початку / закінчення), усередині залишатиметься належним чином. Добре, якщо ви працюєте на синтезаторі, оскільки люди попросять мати параметри часу конвертів як пункти призначення модуляції.
Друга - використовувати попередньо обчислену таблицю пошуку з формами конвертів. Він обчислювально легший, він виймає багато брудних деталей (наприклад, вам не доведеться турбуватися, коли показник не досягає точно 0 - обрізати його за вашим примхом і змінити масштаб так, щоб він відобразився до [0, 1]), і мертво легко запропонувати можливість змінити конвертні форми для кожного етапу.
Ось псевдокод для описаного нами підходу.
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