Довідка з рівняннями для експоненціальної оболонки ADSR


11

За допомогою коду програми я реалізував лінійну оболонку ADSR для формування амплітуди виходу генератора. Параметри атаки, занепаду та тривалості випуску, а також рівень підтримки можуть бути встановлені на конверті, і все працює так, як очікувалося.

Однак я хотів би змінити форми рампи конверта на те, що нагадує те, що більшість синтезаторів використовують для більш природного реагування: обернену експоненцію для атаки та експонентність для занепаду та вивільнення. У мене виникають проблеми з правильними формулами для обчислення вихідних значень конвертів для цих типів форм рампи. Для обчислення лінійних пандусів я використовую двоточкову форму, підключаючи значення початку / кінця / які виводяться із значень вхідних параметрів атаки / розпаду / підтримки / випуску. Я не можу, здається, розробити правильну формулу для експоненціальних (стандартних та зворотних) пандусів, використовуючи однакові початкові / кінцеві значення / .y x yxyxy

Я зберег сесію графічного калькулятора Desmos, яка демонструє підхід до лінійних пандусів, який я описав вище.

Якщо хтось може допомогти мені вказати в правильному напрямку, це буде дуже вдячно.

Відповіді:


10

Я думаю, що те, що вас бентежить, полягає в тому, що зменшувальна експоненція ( ) ніколи не досягає 0, тому генератор ADSR з по-справжньому експоненціальними сегментами залишився б застряг; тому що вона ніколи не досягне цільового значення. Наприклад, якщо генератор знаходиться на висоті фази атаки (скажімо, ) і повинен приземлитися до стійкого значення при , він не може перейти туди з справжньою експоненцією, тому що справжня експоненція виграла ' t занепаде до 0,5, це лише асимптотично перейде до 0,5! y = 1 y = 0,5exy=1y=0.5

Якщо ви подивитеся на аналоговий генератор конвертів (наприклад, схема на базі 7555, яку, здається, використовують усі ), ви можете бачити, що під час фази атаки, коли конденсатор заряджається, він "націлений вище", ніж поріг, який використовується для позначення кінця фази атаки. У схемі на базі (7) 555, що живиться від + 15 В, під час стадії атаки конденсатор заряджається кроком + 15 В, але етап атаки закінчується, коли досягається поріг + 10 В. Це вибір дизайну, хоча 2/3 - це "магічне число", яке зустрічається у багатьох класичних генераторах конвертів, і це може бути той, хто знайомий з музикантами.

Деякі форми ADSR, отримані внаслідок різного "співвідношення цілі" під час заряду конденсатора

Таким чином, функції, з якими ви хочете мати справу, - це не експоненціали, а зміщені / усічені / масштабні його версії, і вам доведеться зробити певний вибір щодо того, наскільки "розчавленими" ви хочете, щоб вони були.

Мені все одно цікаво, чому ви намагаєтеся отримати такі формули - можливо, це через обмеження інструменту, який ви використовуєте для синтезу; але якщо ви намагаєтесь реалізувати ті, хто використовує мову програмування загального призначення (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

Я, здавалося, вирішив свою проблему, взявши моє лінійне масштаб / двоточне рівняння y = ((y2 - y1) / (x2 - x1)) * (x - x1) + y1, переписавши його, замінивши x змінних на e ^ x to y = ((y2 - y1) / (e ^ x2 - e ^ x1)) * (e ^ x - e ^ x1) + y1. Мій сеанс калькулятора за посиланням ілюструє такий підхід. Чи є у них якісь стосунки до цього, про що я повинен знати? Результати мені здаються правильними.
Gary DeReese

Це не конвертна форма, що зустрічається на інших синтезаторах. Залежно від часу / відносного положення початкового та кінцевого рівня, воно може стати дуже лінійним.
пікенети

@pichenettes, ви можете бути готовими вставити сценарій, який створив ці конверти?
П я

3

Це досить старе питання, але я просто хочу виділити крапку у відповіді з пікенет:

Наприклад, ви хочете, щоб конверт, який проходив від 0,8 до 0,2 за 2 секунди [...], розбити його на два етапи: отримання пандуса, який проходить від 0 до 1,0 за 2 секунди; а потім застосувати лінійне перетворення, яке відображає 0 до 0,8 і 1,0 до 0,2.

Цей процес іноді відомий як "ослаблення" і виглядає так

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

де і - нижня і верхня межа (можливі значення , і рівень підтримки), а - це щось на зразок . Зауважте, що це вам не потрібно для фази атаки, оскільки вона вже становить від до .u 0 1 f ( x ) x n 0 1lu01f(x)xn01

Ось оригінальний сеанс Desmos, оновлений для використання цього підходу. Я використовував кубічну форму тут, але ви можете * використовувати будь-яку форму, яка вам подобається, до тих пір, поки дає результати, починаючи від нуля до одного заданих входів, що варіюються від нуля до одного.f(x)

* Я думаю, що ОП, мабуть, давно минув, але, можливо, це допомагає комусь іншому.


Дякую за це, я програмував зразок для DAW, для якого я розробник, і підключав формули, надані в сесії Desmos, і вони працювали чудово. Більше не кульгавих лінійних конвертів! :)
Дуглас

1

Щодо коментаря пікенет: "Під час атаки конденсатор заряджається кроком + 15 В, але етап атаки закінчується, коли досягнуто порогу + 10 В. Це вибір дизайну, хоча 2/3 - це" магія номер "знайдено в багатьох класичних генераторах конвертів, і це може бути той, хто знайомий музикантам.":

Будь-який конверт, який стріляє за асимптотою 15 В з ціллю 10 В, практично створює лінійну атаку. Просто 15В - це найвищий асимптот, доступний легко, і він досить близький до лінійного. Тобто в цьому немає нічого "магії" - вони просто настільки лінійні, наскільки вони можуть отримати.

Я не знаю, скільки класичних синтезаторів використовують 15 В - я б підозрював, що часто є два або два краплі діода. У моєму старому модулі Овна використовується 13 В для конверта 10 В, і я щойно шукав чіп Curtis ADSR, який використовує 6,5 В для конверта 5 В.


1

Цей код повинен генерувати подібні сюжети до пікенетів:

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

Я вдячний за будь-які вдосконалення, одна річ, яка може бути хорошою ідеєю - це дозволяти останнім трьом параметрам (які визначають крутість кожного з трьох етапів) змінюватися між 0 і 1, де 0,5 буде прямолінійним. Але я не можу зрозуміти, як це зробити.

Також я не перевірив ретельно всіх випадків використання, наприклад, якщо одна стадія має нульову довжину.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.