Come creare un generatore di onde sinusoidali in grado di passare agevolmente da una frequenza all'altra


27

Sono in grado di scrivere un generatore di onde sinusoidali di base per l'audio, ma voglio che sia in grado di passare agevolmente da una frequenza all'altra. Se smetto di generare una frequenza e passo immediatamente a un'altra, ci sarà una discontinuità nel segnale e si sentirà un "clic".

La mia domanda è: qual è un buon algoritmo per generare un'onda che inizia a, diciamo 250Hz, e poi passa a 300Hz, senza introdurre alcun clic. Se l'algoritmo include un tempo di scorrimento / portamento opzionale, tanto meglio.

Riesco a pensare ad alcuni possibili approcci come il sovracampionamento seguito da un filtro passa basso, o forse usando un wavetable, ma sono sicuro che questo è un problema abbastanza comune che esiste un modo standard per affrontarlo.


2
Perché non hai semplicemente usato la transizione di frequenza lineare durante il periodo di transizione. Ad esempio, è necessario passare dalla frequenza f0 al tempo t0 alla frequenza f1 al tempo t1, quindi perché non introdurre semplicemente una frequenza di transizione f (t) = f0 * (1-q) + f1 * q, dove q = (t -t0) / (t1-t0), quindi produce un segnale A (t) = sin (2 * Pi * f (t) * t)?
mbaitoff,

Risposte:


24

Un approccio che ho usato in passato è quello di mantenere un accumulatore di fase che viene utilizzato come indice in una tabella di ricerca della forma d'onda. Un valore delta di fase viene aggiunto all'accumulatore ad ogni intervallo di campionamento:

phase_index += phase_delta

Per cambiare frequenza, si modifica il delta di fase che viene aggiunto all'accumulatore di fase in ciascun campione, ad es

phase_delta = N * f / Fs

dove:

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

Ciò garantisce che la forma d'onda di uscita sia continua anche se si cambia phase_delta in modo dinamico, ad es. Per cambi di frequenza, FM, ecc.

Per variazioni più regolari della frequenza (portamento) è possibile aumentare il valore di phase_delta tra il suo vecchio valore e il nuovo valore su un numero adeguato di intervalli di campionamento piuttosto che cambiarlo istantaneamente.

Si noti che phase_indexed phase_deltaentrambi hanno un numero intero e un componente frazionario, cioè devono essere in virgola mobile o fissa. La parte intera di phase_index (dimensione tabella modulo) viene utilizzata come indice nella LUT della forma d'onda e la parte frazionaria può essere facoltativamente utilizzata per l'interpolazione tra valori LUT adiacenti per un output di qualità superiore e / o dimensioni LUT più piccole.


grazie, mi aspettavo che la risposta potesse coinvolgere i LUT. Stavo pensando di andare con un LUT che contiene una forma d'onda a 1Hz (cioè voci Fs). Esiste una regola empirica che regola la dimensione ottimale del LUT?

4
Dipende da vari fattori: quale SNR stai cercando, che si tratti di un'onda sinusoidale pura o di una forma d'onda più complessa, che tu abbia intenzione di interpolare tra voci LUT adiacenti o semplicemente troncare, ecc. Dipende anche dal fatto che stai per avere una tabella a quadrante singolo e gestire l'aritmetica di indicizzazione e firmare l'inversione da soli, oppure avere una tabella a quattro quadranti completa. Personalmente inizierei con un punto 1024 (NB: 2 ^ N è buono per l'indicizzazione dei moduli) tabella a quattro quadranti senza interpolazione in quanto è molto semplice e dovrebbe dare buoni risultati, ad esempio per l'audio "consumer" a 16 bit.
Paolo R

1
Buona risposta, Paul. C'è anche una domanda simile sull'argomento che è stato pubblicato qualche tempo fa; più informazioni aiutano sempre.
Jason R,

4
Un altro modo di guardare a questo approccio è l'emulazione di un oscillatore controllato in tensione (VCO). La frequenza di uscita di un VCO dipende dalla tensione di ingresso (generalmente una funzione lineare della tensione di ingresso) ma il segnale di uscita ha una fase continua anche se la tensione di ingresso cambia istantaneamente. L'output è dove ϕ ( t ) è un continuo
peccato(φ(t))=peccato(0tω0+KX(τ)dτ)
φ(t)funzione del tempo, mentre la frequenza di uscita è la derivata della fase ed è uguale a dove ω 0 è la frequenza di riposo.
ω0+KX(t)
ω0
Dilip Sarwate,

1
Ho avuto lo stesso problema, grazie per l'idea dell'accumulatore (stavo usando il calcolo diretto, che non ha funzionato a causa delle approssimazioni): jsfiddle.net/sebpiq/p3ND5/12
sebpiq

12

Uno dei modi migliori per creare un'onda sinusoidale è utilizzare un phasor complesso con aggiornamento ricorsivo. ie

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

dove z [n] è il fasore, , con ω essendo la frequenza angolare dell'oscillatore in radianti e n l'indice di campionamento. Sia la parte reale che quella immaginaria di z [ n ] sono onde sinusoidali, sfasate di 90 gradi. Molto conveniente se hai bisogno sia di seno che di coseno. Un singolo calcolo di esempio richiede solo 4 multipli e 4 aggiunte ed è MOLTO più economico di qualsiasi cosa contenga tabelle sin () cos () o di ricerca. Il potenziale problema è che l'ampiezza può spostarsi nel tempo a causa di problemi di precisione numerica. Tuttavia c'è un modo abbastanza semplice per ripararlo. Diciamo che z [ nΩ=exp(jω)ωnz[n] . Sappiamo che z [ n ] dovrebbe avere unità magnitudo, cioè z[n]=un'+jBz[n]

un'un'+BB=1

Quindi possiamo controllare di tanto in tanto se è ancora così e correggere di conseguenza. La correzione esatta sarebbe

z'[n]=z[n]un'un'+BB

Questo è un calcolo imbarazzante ma poiché è molto vicino all'unità puoi approssimare 1 / un'un'+BB1/XX=1

1X3-X2

quindi la correzione si semplifica

z'[n]=z[n]3-un'2-B22

L'applicazione di questa semplice correzione ogni poche centinaia di campioni manterrà l'oscillatore stabile per sempre.

Per variare continuamente la frequenza, il moltiplicatore W deve essere aggiornato di conseguenza. Anche una variazione non continua del moltiplicatore manterrà una funzione di oscillatore continuo. Se è necessario lo speed ramping, l'aggiornamento può essere suddiviso in pochi passaggi oppure è possibile utilizzare lo stesso algoritmo oscillatore per aggiornare il moltiplicatore stesso (poiché è anche un phasor complesso con guadagno unitario).


grazie per questa risposta, probabilmente mi ci vorrà un po 'di tempo per capire abbastanza bene da trasformarmi in qualche codice del mondo reale, ma sembra un'alternativa interessante da provare.
Mark Heath,

2
Ho implementato questa soluzione in Golang come riferimento: github.com/rmichela/Acoustico/blob/…
Ryan Michela

Questa è una bella soluzione che, sfortunatamente, funziona bene solo se si utilizza una base temporale costante. In caso contrario, è necessario calcolare un peccato e un cos per calcolare la rotazione complessa corretta.
Cameron Tacklind il

2

Da questo sito :

Per creare una transizione graduale da una frequenza a un'altra o da un'ampiezza a un'altra, un'onda sinusoidale incompleta deve essere modificata con una sezione aggiunta in modo che l'onda risultante dopo ogni iterazione del ciclo while finisca sull'asse x.

Sembra che dovrebbe funzionare.

(In realtà, se entrambi sono sincronizzati sull'asse x al momento della transizione, suppongo che una transizione graduale non sia necessaria.)


1
ω00ω10

2

Sono d'accordo con i precedenti suggerimenti sull'utilizzo di un accumulatore di fase. In sostanza, l'ingresso di controllo è la quantità di avanzamento di fase per fase o periodo di clock (o per interruzione o altro), in modo che la modifica di quel valore cambi la frequenza senza discontinuità nella fase. L'ampiezza dell'onda viene quindi determinata dal valore della fase accumulata tramite una LUT o solo il calcolo di sin (theta) o cos (theta).

Questo è essenzialmente ciò che è comunemente noto come oscillatore a controllo numerico (NCO) o sintetizzatore digitale diretto (DDS). Fare una ricerca web su quei termini probabilmente produrrà più di quanto tu voglia sapere sulla teoria e la pratica per farli funzionare bene.

L'aggiunta di un accumulatore aggiuntivo può consentire transizioni senza soluzione di continuità tra le frequenze, come suggerito, se desiderato, controllando la velocità di variazione del valore di avanzamento di fase. Questo a volte viene chiamato Analizzatore differenziale digitale o DDA.


Buone informazioni aggiuntive. Sono contento di vederti qui intorno, Eric; potremmo usare un ministro degli algoritmi.
Jason R

1

1 ° ordine, è necessario regolare la fase iniziale della nuova sinusoide di frequenza in modo che sia la stessa di quella che sarebbe la fase della sinusoide precedente nel 1 ° punto del campione di transizione. Calcola la prima frequenza e usa la sua fase per la seconda frequenza.

La seconda opzione potrebbe essere quella di aumentare d_phase da quella di una frequenza alla successiva su diversi campioni. Questo pulirà la continuità del primo derivato e fornirà una planata.

La terza opzione potrebbe essere quella di utilizzare una finestra di livellamento, come un coseno rialzato, sulla velocità di rampa d_phase.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.