Como criar um gerador de ondas senoidais que possa fazer a transição suave entre frequências

27

Sou capaz de escrever um gerador básico de ondas senoidais para áudio, mas quero que seja capaz de fazer a transição sem problemas de uma frequência para outra. Se eu parar de gerar uma frequência e mudar imediatamente para outra, haverá uma descontinuidade no sinal e um "clique" será ouvido.

Minha pergunta é: qual é um bom algoritmo para gerar uma onda que começa em, digamos, 250Hz e depois transita para 300Hz, sem introduzir cliques. Se o algoritmo incluir um tempo opcional de deslizamento / portamento, tanto melhor.

Posso pensar em algumas abordagens possíveis, como a sobre amostragem seguida por um filtro passa-baixo ou talvez usando uma tabela de ondas, mas tenho certeza de que esse é um problema bastante comum e que existe uma maneira padrão de resolvê-la.

Mark Heath
fonte
2
Por que você não usou a transição de frequência linear durante o período de transição? Por exemplo, você precisa transitar da frequência f0 no tempo t0 para a frequência f1 no tempo t1, por que não introduzir apenas uma frequência de transição f (t) = f0 * (1-q) + f1 * q, onde q = (t -t0) / (t1-t0), então produz um sinal A (t) = sin (2 * Pi * f (t) * t)?
mbaitoff

Respostas:

24

Uma abordagem que usei no passado é manter um acumulador de fase que é usado como índice em uma tabela de pesquisa de forma de onda. Um valor delta de fase é adicionado ao acumulador a cada intervalo de amostra:

phase_index += phase_delta

Para alterar a frequência, você altera o delta de fase que é adicionado ao acumulador de fase em cada amostra, por exemplo

phase_delta = N * f / Fs

Onde:

phase_delta is the number of LUT samples to increment
freq is the desired output frequency
Fs is the sample rate

Isso garante que a forma de onda de saída seja contínua, mesmo se você mudar phase_delta dinamicamente, por exemplo, para mudanças de frequência, FM, etc.

Para alterações mais suaves na frequência (portamento), é possível aumentar o valor de phase_delta entre seu valor antigo e o novo valor em um número adequado de intervalos de amostras, em vez de apenas alterá-lo instantaneamente.

Note-se que phase_indexe phase_deltaambos têm um número inteiro e um componente fraccionada, ou seja, que necessitam de ser ponto ou ponto fixo flutuante. A parte inteira de phase_index (tamanho da tabela de módulo) é usada como um índice na forma de onda LUT, e a parte fracionária pode opcionalmente ser usada para interpolação entre valores LUT adjacentes para obter saída de maior qualidade e / ou tamanho LUT menor.

Paul R
fonte
obrigado, eu esperava que a resposta envolvesse LUTs. Eu estava pensando em ir com um LUT que contém uma forma de onda em 1Hz (ou seja, entradas Fs). Existe uma regra prática que rege o tamanho ideal da LUT?
4
Depende de vários fatores: o SNR que você está procurando, se é uma onda senoidal pura ou uma forma de onda mais complexa, se planeja interpolar entre entradas LUT adjacentes ou apenas truncar etc. Isso também depende se você está apenas indo para tenha uma única tabela de quadrante e lide com a aritmética de indexação e faça a inversão de sinal, ou tenha uma tabela de quatro quadras completa. Pessoalmente, eu começaria com uma tabela de quatro quadrantes de 1024 pontos (NB: 2 ^ N é boa para indexação de módulos) sem interpolação, pois isso é muito simples e deve fornecer bons resultados, por exemplo, para áudio "consumidor" de 16 bits.
Paul R
11
Boa resposta, Paul. Há também uma pergunta semelhante sobre o tópico que foi postado há algum tempo; mais informações sempre ajudam.
Jason R
4
Outra maneira de olhar para essa abordagem é a emulação de um oscilador controlado por tensão (VCO). A frequência de saída de um VCO depende da tensão de entrada (geralmente uma função linear da tensão de entrada), mas o sinal de saída tem fase contínua , mesmo que a tensão de entrada mude instantaneamente. A saída é que é uma função contínua do tempo, enquanto a frequência de saída é a derivada da fase e é igual a onde é a frequência quiescente. φ ( t ) ω 0 + k x ( t ) ω 0
pecado(ϕ(t))=pecado(0 0tω0 0+kx(τ)dτ)
ϕ(t)
ω0 0+kx(t)
ω0 0
precisa
11
Eu tive o mesmo problema, obrigado pela ideia do acumulador (eu estava usando cálculo direto, o que não funcionou devido a aproximações): jsfiddle.net/sebpiq/p3ND5/12
sebpiq 31/12
12

Uma das melhores maneiras de criar uma onda senoidal é usar um fasor complexo com atualização recursiva. Ou seja,

z[n+1 1]=z[n]Ω

onde z [n] é a fasor, , com sendo a frequência angular do oscilador em radianos e o índice da amostra. Tanto a parte real quanto a imaginária de são ondas senoidais, estão 90 graus fora de fase. Muito conveniente se você precisar de seno e cosseno. Um cálculo de amostra única requer apenas quatro múltiplos e quatro adições e é MUITO mais barato do que qualquer coisa que contenha sin () cos () ou tabelas de pesquisa. O problema potencial é que a amplitude pode sofrer variações ao longo do tempo devido a problemas de precisão numérica. No entanto, existe um processo bastante simples de reparar isso. Digamos que . Sabemos que deve ter magnitude de unidade, ou seja, ω n z [ n ] z [ n ] = um + j b z [ n ]Ω=exp(jω)ωnz[n]z[n]=uma+jbz[n]

umauma+bb=1 1

Assim, podemos verificar de vez em quando se esse ainda é o caso e corrigir de acordo. A correção exata seria

z[n]=z[n]umauma+bb

Esse é um cálculo complicado, mas como está muito próximo da unidade, você pode aproximar os termos com uma expansão de Taylor em torno de e obtemos1 / umauma+bb x=11 1/xx=1 1

1 1x3-x2

então a correção simplifica para

z[n]=z[n]3-uma2-b22

A aplicação dessa correção simples a cada poucas centenas de amostras manterá o oscilador estável para sempre.

Para variar a frequência continuamente, o multiplicador W precisa ser atualizado de acordo. Mesmo uma mudança não contínua no multiplicador manterá uma função de oscilador contínuo. Se for necessário aumentar a frequência, a atualização pode ser dividida em algumas etapas ou você pode usar o mesmo algoritmo do oscilador para atualizar o próprio multiplicador (já que também é um fasor complexo de ganho de unidade).

Hilmar
fonte
obrigado por esta resposta, provavelmente levará um pouco de tempo para entender o suficiente para me transformar em algum código do mundo real, mas parece uma alternativa interessante para tentar.
Mark Heath
2
I implementado esta solução em golang para referência: github.com/rmichela/Acoustico/blob/...
Ryan Michela
Esta é uma solução bonita que, infelizmente, só funciona bem se estiver usando uma base de tempo constante. Caso contrário, você precisa calcular um pecado e um cos para calcular a rotação complexa correta.
Cameron Tacklind 16/11
2

A partir deste site :

Para criar uma transição suave de uma frequência para outra ou de uma amplitude para outra, uma onda senoidal incompleta deve ser modificada com uma seção anexada para que a onda resultante após cada iteração do loop while termine no eixo x.

Parece que deve funcionar.

(Na verdade, se ambos forem sincronizados no eixo x após a transição, suponho que não seja necessária uma transição gradual.)


fonte
11
Isso significa que, aguarde o sinusóide atual na frequência concluir um ciclo e chegar a e depois mude para o outro sinusóide na frequência . Isso efetivamente mantém a continuidade da fase e provavelmente é bom para aplicativos de áudio nos quais os poucos milissegundos ou microssegundos atrasam entre o tempo de comutação desejado (agora) e o tempo de comutação implementado (quando meu sinusóide completa um ciclo) é inconseqüente. No entanto, a diferença pode causar problemas em outros aplicativos. Lembre-se de que um senoide é duas vezes em um ciclo e não se esqueça de escolher o caminho certo! ω0 00 0ω1 10 0
Dilip Sarwate
2

Eu concordo com as sugestões anteriores de usar um acumulador de fase. Essencialmente, a entrada de controle é a quantidade de avanço de fase por etapa ou por período de relógio (ou por interrupção ou o que for), de modo que a alteração desse valor altera a frequência sem uma descontinuidade na fase. A amplitude da onda é então determinada a partir do valor da fase acumulada, via LUT ou apenas computação do pecado (teta) ou cos (teta).

Isso é essencialmente o que é comumente conhecido como oscilador controlado numericamente (NCO) ou sintetizador digital direto (DDS). Fazer uma pesquisa na Web nesses termos provavelmente renderá mais do que você deseja saber sobre a teoria e a prática de fazê-las funcionar bem.

A adição de um acumulador adicional pode permitir transições contínuas entre frequências, como você sugeriu, se desejado, controlando a taxa de variação do valor do avanço de fase. Às vezes, isso é chamado de Digital Differential Analyzer, ou DDA.

Eric Jacobsen
fonte
Boas informações adicionais. Fico feliz em vê-lo por aqui, Eric; nós poderíamos usar um ministro de algoritmos.
Jason R
1

Na 1ª ordem, você deve ajustar a fase inicial do novo sinusóide de frequência para que seja a mesma que seria a fase do sinusóide anterior no primeiro ponto de amostra de transição. Calcule a primeira frequência e use sua fase para a segunda frequência.

A segunda opção pode ser aumentar a d_phase de uma frequência para a seguinte em várias amostras. Isso limpará a continuidade do 1º derivado e proporcionará um deslize.

A terceira opção pode ser usar uma janela de suavização, como um cosseno elevado, na taxa de rampa d_phase.

hotpaw2
fonte