Ayuda con ecuaciones para envolvente ADSR exponencial

11

Con el código de la aplicación, he implementado un sobre ADSR lineal para dar forma a la amplitud de la salida de un oscilador. Los parámetros para la duración del ataque, la caída y la liberación, así como el nivel de sostenido, se pueden configurar en el sobre y todo funciona como se espera.

Sin embargo, me gustaría ajustar las formas de la rampa de la envoltura a algo que se asemeje a lo que la mayoría de los sintetizadores usan para una respuesta más natural: exponencial inversa para el ataque y exponencial para la descomposición y liberación. Tengo problemas para obtener las fórmulas correctas para calcular los valores de salida de envolvente para estos tipos de formas de rampa. Para calcular las rampas lineales, estoy usando la forma de dos puntos, conectando los valores de inicio / final / que se derivan de los valores de los parámetros de entrada de ataque / decadencia / sostenido / liberación. Parece que no puedo encontrar la fórmula correcta para las rampas exponenciales (estándar e inversas) usando los mismos valores de punto de inicio / fin / .y x yxyxy

He guardado una sesión de Desmos Graphing Calculator que demuestra el enfoque para las rampas lineales que describí anteriormente.

Si alguien puede ayudarme a orientarme en la dirección correcta, sería muy apreciado.

Gary DeReese
fuente

Respuestas:

10

Creo que lo que te confunde es que un exponencial decreciente ( ) nunca llega a 0, por lo que un generador ADSR con segmentos verdaderamente exponenciales se quedaría estancado; porque nunca alcanzaría el valor objetivo. Por ejemplo, si el generador está a la altura de la fase de ataque (digamos ) y tiene que aterrizar a un valor sostenido en , no puede ir allí con un exponencial verdadero, porque el exponencial verdadero ganó ' t decae a 0.5, ¡solo asintóticamente irá a 0.5! y = 1 y = 0.5exy=1y=0.5

Si observa un generador de envolvente analógico (por ejemplo, el circuito basado en 7555 que todo el mundo parece usar ), puede ver que durante la fase de ataque, cuando el condensador se está cargando, "apunta más alto" que el umbral utilizado para indicar el final de la fase de ataque. En un (7) circuito basado en 555 alimentado por + 15V, durante la etapa de ataque, el condensador se carga con un paso de + 15V, pero la etapa de ataque termina cuando se alcanza un umbral de + 10V. Esta es una opción de diseño, aunque 2/3 es el "número mágico" que se encuentra en muchos generadores de sobres clásicos, y este podría ser el único con el que los músicos están familiarizados.

Algunas formas ADSR resultantes de diferentes "relaciones de objetivo" durante la carga del condensador

Por lo tanto, las funciones con las que puede querer tratar no son exponenciales, sino versiones desplazadas / truncadas / escaladas, y tendrá que tomar algunas decisiones sobre qué tan "aplastadas" quiere que sean.

De todos modos, tengo curiosidad de por qué está tratando de obtener tales fórmulas, tal vez sea por los límites de la herramienta que está utilizando para la síntesis; pero si está tratando de implementar aquellos que usan un lenguaje de programación de propósito general (C, java, python) con algún código en ejecución para cada muestra del sobre y una noción de "estado", siga leyendo ... Porque siempre es más fácil expresa cosas como "dicho segmento pasará del valor que acaba de alcanzar a 0".

Mis dos consejos sobre la implementación de sobres.

El primero no espara intentar escalar todas las pendientes / incrementos para que la envolvente alcance exactamente los valores inicial y final. Por ejemplo, si desea un sobre que vaya de 0,8 a 0,2 en 2 segundos, por lo que podría verse tentado a calcular un incremento de -0.3 / segundo. No hagas eso. En su lugar, divídalo en dos pasos: obtener una rampa que va de 0 a 1.0 en 2 segundos; y luego aplicando una transformación lineal que asigna 0 a 0.8 y 1.0 a 0.2. Hay dos ventajas para trabajar de esta manera: la primera es que simplifica cualquier cálculo que tenga en relación con los tiempos de envolvente a una rampa de 0 a 1; el segundo es que si cambia los parámetros de envolvente (incrementos y tiempos de inicio / finalización) a mitad de camino, todo se comportará bien. Es bueno si está trabajando en un sintetizador, ya que las personas pedirán tener parámetros de tiempo de envolvente como destinos de modulación.

El segundo es usar una tabla de búsqueda precalculada con formas de envolvente. Es computacionalmente más liviano, elimina muchos detalles sucios (por ejemplo, no tiene que molestarse con un exponencial que no alcanza exactamente 0 - truncarlo a su antojo y reescalarlo para que se asigne a [0, 1]), y es muy fácil proporcionar una opción para alterar las formas de los sobres, para cada etapa.

Aquí está el pseudocódigo para el enfoque que describo.

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
pichenettes
fuente
Parecía resolver mi problema tomando mi ecuación de escala lineal / dos puntos de y = ((y2 - y1) / (x2 - x1)) * (x - x1) + y1, reescribiéndola sustituyendo las variables x con e ^ x to y = ((y2 - y1) / (e ^ x2 - e ^ x1)) * (e ^ x - e ^ x1) + y1. Mi sesión de calculadora en el enlace ilustra este enfoque. ¿Hay alguna trampa en esto que debería tener en cuenta? Los resultados me parecen correctos.
Gary DeReese
Esta no es la forma de la envoltura que se encuentra en otros sintetizadores. Dependiendo del tiempo / posición relativa del nivel inicial y final, puede volverse muy lineal.
pichenettes
@pichenettes, ¿podría estar dispuesto a pegar el script que generó esos sobres?
P i
3

Esta es una pregunta bastante antigua, pero solo quiero resaltar un punto en la respuesta de pichenettes:

Por ejemplo, si desea un sobre que vaya de 0,8 a 0,2 en 2 segundos, [...] divídalo en dos pasos: obtener una rampa que vaya de 0 a 1.0 en 2 segundos; y luego aplicando una transformación lineal que asigna 0 a 0.8 y 1.0 a 0.2.

Este proceso a veces se conoce como "facilitación" y parece

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

donde y son el límite inferior y superior (los valores posibles son , y el nivel de sostenido) es algo así como . Tenga en cuenta que no necesita esto para la fase de ataque, ya que oscila entre y .u 0 1 f ( x ) x n 0 1lu01f(x)xn01

Aquí está la sesión original de Desmos, actualizada para usar este enfoque. Utilicé una forma cúbica aquí, pero usted * podría usar la forma que desee, siempre que produzca salidas que van de cero a una entrada dada que varía de cero a uno.f(x)

* Supongo que el OP probablemente ya no existe, pero quizás esto ayude a alguien más.

Huésped
fuente
Gracias por esto, estaba programando una muestra para un DAW para el que soy desarrollador, y conecté las fórmulas proporcionadas en la sesión de Desmos y funcionaron perfectamente. ¡No más sobres lineares cojos! :)
Douglas
1

Sobre el comentario de pichenettes, "Durante la etapa de ataque, el condensador se carga con un paso de + 15V, pero la etapa de ataque termina cuando se alcanza un umbral de + 10V. Esta es una opción de diseño, aunque 2/3 es la" magia " número "encontrado en muchos generadores de sobres clásicos, y este podría ser el único con el que los músicos están familiarizados":

Cualquier sobre que dispare para una asíntota de 15v con un objetivo de 10v está, prácticamente, creando un ataque lineal. Es solo que 15v es la asíntota más alta disponible fácilmente, y está lo suficientemente cerca de lineal. Es decir, no hay nada "mágico" al respecto, solo van por lo más lineal posible.

No sé cuántos sintetizadores clásicos usan 15v. Sospecho que a menudo hay una caída de diodos o dos. Mi antiguo módulo modular Aries usa 13v para un sobre de 10v, y acabo de buscar el chip Curtis ADSR que usa, de manera equivalente, 6.5v para un sobre de 5v.

Nigel Redmon
fuente
1

Este código debería generar tramas similares a las de las pichenettes:

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

Estoy agradecido por cualquier mejora, una cosa que puede ser una buena idea es permitir que los últimos tres parámetros (que determinan la inclinación de cada una de las tres etapas) varíen entre 0 y 1, donde 0.5 sería una línea recta. Pero no puedo ver de antemano cómo hacerlo.

Además, no he probado exhaustivamente todos los casos de uso, por ejemplo, si una etapa tiene una longitud cero.

Pi
fuente