Ajuda com equações para envelope ADSR exponencial

11

Com o código do aplicativo, implementei um envelope ADSR linear para modelar a amplitude da saída de um oscilador. Os parâmetros para duração do ataque, decaimento e liberação, bem como o nível de sustentação, podem ser definidos no envelope e tudo funciona conforme o esperado.

No entanto, eu gostaria de ajustar as formas de rampa do envelope para algo que se assemelha ao que a maioria dos sintetizadores usa para uma resposta mais natural: exponencial inverso para o ataque e exponencial para a deterioração e liberação. Estou tendo problemas para acertar minhas fórmulas para calcular os valores de saída do envelope para esses tipos de formas de rampa. Para calcular as rampas lineares, estou usando o formulário de dois pontos, inserindo os valores / inicial / final derivados dos valores dos parâmetros de entrada de ataque / decaimento / sustentação / liberação. Não consigo encontrar a fórmula correta para rampas exponenciais (padrão e inversas) usando os mesmos valores de ponto inicial / final / .xyxy

Salvei uma sessão da Calculadora Gráfica Desmos que demonstra a abordagem para rampas lineares que descrevi acima.

Se alguém puder me ajudar a apontar na direção certa, seria muito apreciado.

Gary DeReese
fonte

Respostas:

10

Eu acho que o que o confunde é que um exponencial decrescente ( ) nunca chega a 0; portanto, um gerador ADSR com segmentos verdadeiramente exponenciais ficaria parado; porque nunca alcançaria o valor desejado. Por exemplo, se o gerador estiver no auge da fase de ataque (digamos, ) e precisar atingir um valor de sustentação em , ele não poderá ir para lá com um exponencial verdadeiro, porque o exponencial verdadeiro venceu ' t decair para 0,5, ele só vai assintoticamente para 0,5!exy=1y=0.5

Se você olhar para um gerador de envelope analógico (por exemplo, o circuito baseado em 7555 que todo mundo parece usar ), poderá ver que durante a fase de ataque, quando o capacitor está carregando, ele está "mirando mais alto" do que o limite usado para indicar o fim da fase de ataque. Em um circuito baseado em (7) 555, alimentado por + 15V, durante o estágio de ataque, o capacitor é carregado com uma etapa de + 15V, mas o estágio de ataque termina quando um limite de + 10V é atingido. Esta é uma escolha de design, embora 2/3 seja o "número mágico" encontrado em muitos geradores clássicos de envelopes, e talvez seja com isso que os músicos estejam familiarizados.

Algumas formas de ADSR resultantes de diferentes "taxas de mira" durante a carga do capacitor

Portanto, as funções com as quais você pode querer lidar não são exponenciais, mas versões alteradas / truncadas / dimensionadas, e você terá que fazer algumas escolhas sobre o quão "esmagado" deseja que elas sejam.

De qualquer forma, estou curioso para saber por que você está tentando obter essas fórmulas - talvez seja por causa dos limites da ferramenta que você está usando para síntese; mas se você estiver tentando implementar aqueles que usam uma linguagem de programação de uso geral (C, java, python) com algum código em execução para cada amostra do envelope e uma noção de "estado", continue lendo ... Porque é sempre mais fácil expressar coisas como "esse segmento passará de qualquer valor que acabou de chegar a 0".

Meus dois conselhos sobre a implementação de envelopes.

O primeiro não épara tentar dimensionar todas as inclinações / incrementos para que o envelope atinja exatamente os valores inicial e final. Por exemplo, você deseja um envelope que varia de 0,8 a 0,2 em 2 segundos, para que possa ser tentado a calcular um incremento de -0,3 / segundo. Não faça isso. Em vez disso, divida-o em duas etapas: obter uma rampa que varia de 0 a 1,0 em 2 segundos; e, em seguida, aplicando uma transformação linear que mapeia 0 a 0,8 e 1,0 a 0,2. Há duas vantagens em trabalhar dessa maneira - a primeira é que simplifica qualquer cálculo que você tenha em relação aos tempos de envelope para uma rampa de 0 a 1; a segunda é que, se você alterar os parâmetros do envelope (incrementos e horários de início / término) no meio do caminho, tudo permanecerá bem comportado. Bom se você estiver trabalhando em um sintetizador, já que as pessoas solicitarão parâmetros de tempo de envelope como destinos de modulação.

O segundo é usar a tabela de pesquisa pré-calculada com formas de envelope. É computacionalmente mais leve, remove muitos detalhes sujos (por exemplo, você não precisa se preocupar com um exponencial que não atinja exatamente 0 - truncá-lo à sua vontade e redimensioná-lo para que seja mapeado para [0, 1]), e é fácil fornecer uma opção para alterar as formas dos envelopes, para cada estágio.

Aqui está o pseudo-código da abordagem que descrevo.

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
fonte
Eu parecia resolver meu problema tomando minha escala linear / equação de dois pontos de y = ((y2 - y1) / (x2 - x1)) * (x - x1) + y1, reescrevendo-o substituindo as variáveis ​​x por e ^ x a y = ((y2 - y1) / (e ^ x2 - e ^ x1)) * (e ^ x - e ^ x1) + y1. Minha sessão de calculadora no link ilustra essa abordagem. Há algum problema nisso que eu deveria estar ciente? Os resultados parecem corretos para mim.
Gary DeReese
Esta não é a forma de envelope encontrada em outros sintetizadores. Dependendo do tempo / posição relativa do nível inicial e final, ele pode se tornar muito linear.
Pichenettes
@ pichenettes, você pode colar o script que gerou esses envelopes?
1
3

Essa é uma pergunta bastante antiga, mas eu só quero destacar um ponto na resposta das pichenettes:

Por exemplo, você deseja um envelope que varia de 0,8 a 0,2 em 2 segundos [...] quebre em duas etapas: obter uma rampa que varia de 0 a 1,0 em 2 segundos; e, em seguida, aplicando uma transformação linear que mapeia 0 a 0,8 e 1,0 a 0,2.

Esse processo às vezes é conhecido como "flexibilização" e parece

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

onde e são o limite inferior e superior (os valores possíveis são , e o nível de sustentação) é algo como . Observe que você não precisa disso para a fase de ataque, pois ela já varia de a .u 0 1 f ( x ) x n 0 1lu01f(x)xn01

Aqui está a sessão original do Desmos, atualizada para usar essa abordagem. Eu usei uma forma cúbica aqui, mas você * pode usar a forma que quiser, desde que produza saídas que variam de zero a uma dada entrada que varia de zero a uma.f(x)

* Acho que o OP provavelmente já está longe, mas talvez isso ajude outra pessoa.

Hóspede
fonte
Obrigado por isso, eu estava programando um amostrador para um DAW para o qual sou desenvolvedor e plugei as fórmulas fornecidas na sessão Desmos e elas funcionaram perfeitamente. Chega de envelopes lineares coxos! :)
Douglas
1

Sobre o comentário dos pichenettes, "Durante o estágio de ataque, o capacitor é carregado com uma etapa de + 15V, mas o estágio de ataque termina quando um limite de + 10V é atingido. Essa é uma opção de design, embora 2/3 seja a" mágica número "encontrado em muitos geradores clássicos de envelopes, e é com isso que os músicos estão familiarizados".

Qualquer envelope que esteja disparando para uma assíntota de 15v com um alvo de 10v está praticamente criando um ataque linear. É apenas que 15v é a maior assíntota disponível com facilidade, e é perto o suficiente para linear. Ou seja, não há nada de "mágico" nisso - eles estão indo tão linearmente quanto possível.

Não sei quantos sintetizadores clássicos usam 15v - eu suspeitaria que geralmente há uma queda de diodo ou dois. Meu antigo módulo de Áries usa 13v para um envelope de 10v, e acabei de procurar no chip ADSR da Curtis que usa, equivalentemente, 6,5v para um envelope de 5v.

Nigel Redmon
fonte
1

Esse código deve gerar gráficos semelhantes aos das 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

Sou grato por quaisquer melhorias, uma coisa que pode ser uma boa idéia é permitir que os três últimos parâmetros (que determinam a inclinação de cada um dos três estágios) variem entre 0 e 1, onde 0,5 seria uma linha reta. Mas não consigo ver de imediato como fazê-lo.

Também não testei completamente todos os casos de uso, por exemplo, se um estágio tem tamanho zero.

P i
fonte