Comment créer un générateur d'ondes sinusoïdales qui peut passer en douceur entre les fréquences

27

Je suis capable d'écrire un générateur d'onde sinusoïdale de base pour l'audio, mais je veux qu'il puisse passer en douceur d'une fréquence à l'autre. Si je cesse de générer une fréquence et que je passe immédiatement à une autre, il y aura une discontinuité dans le signal et un "clic" se fera entendre.

Ma question est de savoir quel est un bon algorithme pour générer une onde qui commence à, disons, 250 Hz, puis passe à 300 Hz, sans introduire de clics. Si l'algorithme comprend un temps de glissement / portamento en option, tant mieux.

Je peux penser à quelques approches possibles telles que le suréchantillonnage suivi d'un filtre passe-bas, ou peut-être en utilisant une table d'ondes, mais je suis sûr que c'est un problème suffisamment courant pour qu'il existe un moyen standard de le résoudre.

Mark Heath
la source
2
Pourquoi n'avez-vous pas simplement utilisé la transition de fréquence linéaire sur la période de transition. Par exemple, vous devez passer de la fréquence f0 à l'instant t0 à la fréquence f1 à l'instant t1, alors pourquoi ne pas simplement introduire une fréquence de transition f (t) = f0 * (1-q) + f1 * q, où q = (t -t0) / (t1-t0), puis produire un signal A (t) = sin (2 * Pi * f (t) * t)?
mbaitoff

Réponses:

24

Une approche que j'ai utilisée dans le passé est de maintenir un accumulateur de phase qui est utilisé comme index dans une table de recherche de forme d'onde. Une valeur de delta de phase est ajoutée à l'accumulateur à chaque intervalle d'échantillonnage:

phase_index += phase_delta

Pour changer la fréquence, vous changez le delta de phase qui est ajouté à l'accumulateur de phase à chaque échantillon, par exemple

phase_delta = N * f / Fs

où:

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

Cela garantit que la forme d'onde de sortie est continue même si vous modifiez dynamiquement phase_delta, par exemple pour les changements de fréquence, FM, etc.

Pour des changements de fréquence plus fluides (portamento), vous pouvez augmenter la valeur phase_delta entre son ancienne valeur et sa nouvelle valeur sur un nombre approprié d'intervalles d'échantillons plutôt que de simplement la changer instantanément.

Notez que phase_indexet les phase_deltadeux ont un entier et un composant fractionnel, à savoir qu'ils doivent être en virgule flottante ou point fixe. La partie entière de phase_index (taille de table modulo) est utilisée comme index dans la LUT de forme d'onde, et la partie fractionnaire peut éventuellement être utilisée pour l'interpolation entre les valeurs LUT adjacentes pour une sortie de meilleure qualité et / ou une taille LUT plus petite.

Paul R
la source
merci, je m'attendais à ce que la réponse puisse impliquer des LUT. Je pensais aller avec une LUT qui contient une forme d'onde à 1 Hz (c'est-à-dire les entrées Fs). Existe-t-il une règle empirique régissant la taille optimale de la LUT?
4
Cela dépend de divers facteurs: quel SNR vous recherchez, que ce soit une onde sinusoïdale pure ou une forme d'onde plus complexe, si vous prévoyez d'interpoler entre des entrées LUT adjacentes ou simplement de tronquer, etc. Cela dépend également de si vous allez simplement avoir une seule table de quadrants et gérer vous-même l'arithmétique d'indexation et l'inversion de signe, ou avoir une table complète de quatre quadrants. Personnellement, je commencerais par une table de quatre quadrants de 1024 points (NB: 2 ^ N est bon pour l'indexation modulo) sans interpolation car cela est très simple et devrait donner de bons résultats, par exemple pour l'audio "grand public" 16 bits.
Paul R
1
Bonne réponse, Paul. Il y a aussi une question similaire sur le sujet qui a été publiée il y a quelque temps; plus d'informations aide toujours.
Jason R
4
Une autre façon d'envisager cette approche est l'émulation d'un oscillateur commandé en tension (VCO). La fréquence de sortie d'un VCO dépend de la tension d'entrée (généralement une fonction linéaire de la tension d'entrée) mais le signal de sortie a une phase continue même si la tension d'entrée change instantanément. La sortie est où est une fonction continue du temps, tandis que la fréquence de sortie est la dérivée de la phase, et est égale à où est la fréquence de repos.
sin(ϕ(t))=sin(0tω0+kx(τ)dτ)
ϕ(t)
ω0+kx(t)
ω0
Dilip Sarwate
1
J'ai eu le même problème, merci pour l'idée de l'accumulateur (j'utilisais le calcul direct, qui n'a pas fonctionné à cause des approximations): jsfiddle.net/sebpiq/p3ND5/12
sebpiq
12

L'une des meilleures façons de créer une onde sinusoïdale est d'utiliser un phaseur complexe avec une mise à jour récursive. C'est à dire

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

où z [n] est le phaseur, , avec étant la fréquence angulaire de l'oscillateur en radians et l'indice de l'échantillon. Les parties réelle et imaginaire de sont des ondes sinusoïdales, elles sont déphasées de 90 degrés. Très pratique si vous avez besoin à la fois de sinus et de cosinus. Un seul calcul d'échantillon ne nécessite que 4 multiples et 4 ajouts et est BEAUCOUP moins cher que tout ce qui contient sin () cos () ou tables de recherche. Le problème potentiel est que l'amplitude peut dériver dans le temps en raison de problèmes de précision numérique. Cependant, il est assez simple de réparer cela. Disons que . Nous savons que doit avoir une grandeur unitaire, c'est-à-dire Ω=exp(jω)ωnz[n]z[n]=a+jbz[n]

aa+bb=1

Nous pouvons donc vérifier de temps en temps si c'est toujours le cas et corriger en conséquence. La correction exacte serait

z[n]=z[n]aa+bb

C'est un calcul gênant mais comme est très proche de l'unité, vous pouvez approximer les termes avec une expansion de Taylor autour de et nous obtenonsaa+bb1/xx=1

1x3x2

de sorte que la correction se simplifie

z[n]=z[n]3a2b22

L'application de cette simple correction toutes les quelques centaines d'échantillons maintiendra l'oscillateur stable pour toujours.

Pour faire varier la fréquence en continu, le multiplicateur W doit être mis à jour en conséquence. Même un changement non continu du multiplicateur maintiendra une fonction d'oscillateur continue. Si une augmentation de fréquence est nécessaire, la mise à jour peut être décomposée en quelques étapes ou vous pouvez utiliser le même algorithme d'oscillateur pour mettre à jour le multiplicateur lui-même (car il s'agit également d'un phaseur complexe à gain unitaire).

Hilmar
la source
merci pour cette réponse, cela me prendra probablement un peu de temps pour comprendre assez bien pour me transformer en un vrai code du monde, mais cela ressemble à une alternative intéressante à essayer.
Mark Heath
2
J'ai implémenté cette solution dans golang pour référence: github.com/rmichela/Acoustico/blob/…
Ryan Michela
Il s'agit d'une belle solution qui, malheureusement, ne fonctionne bien que si vous utilisez une base de temps constante. Sinon, vous devez calculer un sin et un cos pour calculer la rotation complexe correcte.
Cameron Tacklind
2

De ce site :

Afin de créer une transition en douceur d'une fréquence à une autre ou d'une amplitude à une autre, une onde sinusoïdale incomplète doit être modifiée avec une section ajoutée de sorte que l'onde résultante après chaque itération de la boucle while se termine sur l'axe des x.

On dirait que cela devrait fonctionner.

(En fait, s'ils sont tous deux synchronisés sur l'axe des x lors de la transition, je suppose qu'une transition progressive n'est pas nécessaire.)


la source
1
Cela dit, attendez que la sinusoïde actuelle à la fréquence termine un cycle et arrive à , puis passez à l'autre sinusoïde à la fréquence . Cela maintient efficacement la continuité de la phase et est susceptible d'être OK pour les applications audio dans lesquelles les quelques millisecondes ou microsecondes retardent entre le temps de commutation souhaité (maintenant) et le temps de commutation mis en œuvre (lorsque ma sinusoïde termine un cycle) est sans conséquence. Cependant, la différence peut poser problème dans d'autres applications. N'oubliez pas qu'une sinusoïde est deux fois en un cycle et assurez-vous de choisir la bonne! 0 ω 1 0ω00ω10
Dilip Sarwate
2

Je suis d'accord avec les suggestions précédentes d'utiliser un accumulateur de phase. L'entrée de commande est essentiellement la quantité d'avance de phase par étape ou par période d'horloge (ou par interruption ou autre), de sorte que la modification de cette valeur modifie la fréquence sans discontinuité dans la phase. L'amplitude de l'onde est ensuite déterminée à partir de la valeur de phase accumulée soit via une LUT, soit simplement par le calcul de sin (thêta) ou cos (thêta).

Il s'agit essentiellement de ce que l'on appelle communément un oscillateur à commande numérique (NCO) ou un synthétiseur numérique direct (DDS). Faire une recherche sur le Web sur ces termes donnera probablement plus que vous ne voulez en savoir sur la théorie et la pratique de leur bon fonctionnement.

L'ajout d'un accumulateur supplémentaire peut permettre des transitions transparentes entre les fréquences, comme vous l'avez suggéré, si vous le souhaitez également, en contrôlant le taux de changement de la valeur d'avance de phase. Ceci est parfois appelé un analyseur différentiel numérique, ou DDA.

Eric Jacobsen
la source
Bonnes informations supplémentaires. Heureux de vous voir ici, Eric; nous pourrions utiliser un ministre des algorithmes.
Jason R
1

De premier ordre, vous devez ajuster la phase de démarrage de la nouvelle sinusoïde de fréquence afin qu'elle soit la même que celle qui serait la phase de la sinusoïde précédente au premier point d'échantillonnage de transition. Calculez la première fréquence et utilisez sa phase pour la deuxième fréquence.

La deuxième option pourrait consister à faire passer d_phase de celle d'une fréquence à la suivante sur plusieurs échantillons. Cela nettoiera la continuité du 1er dérivé et fournira une glisse.

La troisième option pourrait être d'utiliser une fenêtre de lissage, comme un cosinus surélevé, sur le taux de rampe d_phase.

hotpaw2
la source