So erstellen Sie einen Sinusgenerator, der reibungslos zwischen den Frequenzen wechseln kann


27

Ich kann einen einfachen Sinusgenerator für Audio schreiben, aber ich möchte, dass er reibungslos von einer Frequenz zur anderen wechselt. Wenn ich einfach aufhöre, eine Frequenz zu erzeugen, und sofort auf eine andere umschalte, kommt es zu einer Unterbrechung des Signals, und ein "Klicken" ist zu hören.

Meine Frage ist, was ist ein guter Algorithmus, um eine Welle zu erzeugen, die bei etwa 250 Hz beginnt und dann auf 300 Hz übergeht, ohne dass es zu Klicks kommt. Wenn der Algorithmus eine optionale Gleit- / Portamentozeit enthält, ist dies umso besser.

Ich kann mir ein paar mögliche Ansätze vorstellen, wie Überabtastung, gefolgt von einem Tiefpassfilter, oder vielleicht die Verwendung einer Wavetable, aber ich bin sicher, dass dies ein häufig genug auftretendes Problem ist, dass es eine Standardmethode gibt, um es anzugehen.


2
Warum haben Sie nicht einfach den linearen Frequenzübergang über die Übergangsperiode verwendet? Zum Beispiel müssen Sie von der Frequenz f0 zur Zeit t0 zur Frequenz f1 zur Zeit t1 übergehen, warum dann nicht einfach eine Übergangsfrequenz f (t) = f0 * (1-q) + f1 * q einführen, wobei q = (t) -t0) / (t1-t0), dann ein Signal A (t) = sin (2 · Pi · f (t) · t) erzeugen?
mbaitoff

Antworten:


24

Ein Ansatz, den ich in der Vergangenheit verwendet habe, besteht darin, einen Phasenakkumulator beizubehalten, der als Index für eine Wellenform-Nachschlagetabelle verwendet wird. In jedem Abtastintervall wird ein Phasendeltawert zum Akkumulator addiert:

phase_index += phase_delta

Um die Frequenz zu ändern, ändern Sie das Phasendelta, das dem Phasenakkumulator bei jedem Sample hinzugefügt wird, z

phase_delta = N * f / Fs

woher:

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

Dies garantiert, dass die Ausgangswellenform kontinuierlich ist, auch wenn Sie phase_delta dynamisch ändern, z. B. für Frequenzänderungen, FM usw.

Für weichere Frequenzänderungen (Portamento) können Sie den phase_delta-Wert über eine geeignete Anzahl von Abtastintervallen zwischen seinem alten Wert und seinem neuen Wert ändern, anstatt ihn sofort zu ändern.

Beachten Sie, dass phase_indexund phase_deltabeide eine Ganzzahl- und eine Bruchkomponente haben, dh sie müssen Gleitkomma- oder Festkommazahlen sein. Der ganzzahlige Teil von phase_index (Modulotabellengröße) wird als Index für die Wellenform-LUT verwendet, und der Bruchteil kann optional zur Interpolation zwischen benachbarten LUT-Werten für eine Ausgabe höherer Qualität und / oder eine kleinere LUT-Größe verwendet werden.


danke, ich hatte erwartet, dass die Antwort LUTs beinhalten könnte. Ich habe darüber nachgedacht, mit einer LUT zu arbeiten, die eine Wellenform mit 1 Hz enthält (dh Fs-Einträge). Gibt es eine Faustregel für die optimale Größe der LUT?

4
Dies hängt von verschiedenen Faktoren ab: Welches SNR Sie suchen, ob es sich um eine reine Sinuswelle oder eine komplexere Wellenform handelt, ob Sie zwischen benachbarten LUT-Einträgen interpolieren oder nur abschneiden möchten usw. Es hängt auch davon ab, ob Sie gerade dabei sind Haben Sie eine einzelne Quadrantentabelle und behandeln Sie die Indizierungsarithmetik und die Vorzeichenumkehrung selbst, oder haben Sie eine vollständige Vier-Quadrantentabelle. Persönlich würde ich mit 1024 Punkten beginnen (Anmerkung: 2 ^ N ist gut für die Modulo-Indizierung). Vier-Quadranten-Tabelle ohne Interpolation, da dies sehr einfach ist und gute Ergebnisse für z. B. 16-Bit- "Consumer" -Audio liefern sollte.
Paul R

1
Gute Antwort, Paul. Es gibt auch eine ähnliche Frage zum Thema, die vor einiger Zeit gepostet wurde; mehr info hilft immer.
Jason R

4
Eine andere Sichtweise auf diesen Ansatz ist die Emulation eines spannungsgesteuerten Oszillators (VCO). Die Ausgangsfrequenz eines VCO hängt von der Eingangsspannung ab (normalerweise eine lineare Funktion der Eingangsspannung), aber das Ausgangssignal hat eine kontinuierliche Phase, auch wenn die Eingangsspannung sofort umschaltet. Die Ausgabe ist wobei ist eine kontinuierliche Funktion der Zeit, während die Ausgangsfrequenz die Ableitung der Phase ist und gleich wobei die Ruhefrequenz ist. φ ( t ) ω 0 + k x ( t ) ω 0
sin(ϕ(t))=sin(0tω0+kx(τ)dτ)
ϕ(t)
ω0+kx(t)
ω0
Dilip Sarwate

1
Ich hatte das gleiche Problem, danke für die Akkumulator-Idee (ich habe direkt gerechnet, was aufgrund von Näherungen nicht funktioniert hat): jsfiddle.net/sebpiq/p3ND5/12
sebpiq

12

Eine der besten Möglichkeiten, eine Sinuswelle zu erstellen, ist die Verwendung eines komplexen Zeigers mit rekursiver Aktualisierung. Dh

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

Ω=exp(jω)ωnz[n]z[n]=ein+jbz[n]

einein+bb=1

So können wir von Zeit zu Zeit prüfen, ob dies noch der Fall ist und entsprechend korrigieren. Die genaue Korrektur wäre

z[n]=z[n]einein+bb

einein+bb1/xx=1

1x3-x2

so vereinfacht sich die korrektur zu

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

Wenn Sie diese einfache Korrektur alle paar hundert Samples anwenden, bleibt der Oszillator für immer stabil.

Um die Frequenz kontinuierlich zu variieren, muss der Multiplikator W entsprechend aktualisiert werden. Selbst eine nicht kontinuierliche Änderung des Multiplikators behält eine kontinuierliche Oszillatorfunktion bei. Wenn eine Frequenzerhöhung erforderlich ist, kann die Aktualisierung entweder in einige Schritte unterteilt werden, oder Sie können denselben Oszillatoralgorithmus verwenden, um den Multiplikator selbst zu aktualisieren (da er auch ein komplexer Zeiger mit Einheitsverstärkung ist).


Vielen Dank für diese Antwort. Ich werde wahrscheinlich eine Weile brauchen, bis ich mich gut genug verstanden habe, um mich in einen Code der realen Welt zu verwandeln, aber es scheint eine interessante Alternative zu sein, um es zu versuchen.
Mark Heath

2
Ich implementierte diese Lösung in Golang als Referenz: github.com/rmichela/Acoustico/blob/…
Ryan Michela

Dies ist eine schöne Lösung, die leider nur dann gut funktioniert, wenn eine konstante Zeitbasis verwendet wird. Wenn nicht, müssen Sie eine Sünde und ein cos berechnen, um die korrekte komplexe Rotation zu berechnen.
Cameron Tacklind

2

Von dieser Seite :

Um einen reibungslosen Übergang von einer Frequenz zu einer anderen oder einer Amplitude zu einer anderen zu erzeugen, muss eine unvollständige Sinuswelle mit einem angehängten Abschnitt so modifiziert werden, dass die resultierende Welle nach jeder Iteration der while-Schleife an der x-Achse endet.

Klingt so, als sollte es funktionieren.

(Wenn beide beim Übergang auf der x-Achse synchronisiert sind, ist vermutlich kein schrittweiser Übergang erforderlich.)


1
ω00ω10

2

Ich stimme den bisherigen Vorschlägen zur Verwendung eines Phasenakkumulators zu. Im Wesentlichen ist der Steuereingang der Betrag des Phasenvorschubs pro Schritt oder pro Taktperiode (oder pro Unterbrechung oder was auch immer), so dass das Ändern dieses Werts die Frequenz ohne Unterbrechung in der Phase ändert. Die Wellenamplitude wird dann aus dem akkumulierten Phasenwert entweder über eine LUT oder nur über die Berechnung von sin (Theta) oder cos (Theta) bestimmt.

Dies ist im Wesentlichen das, was allgemein als numerisch gesteuerter Oszillator (NCO) oder direkter digitaler Synthesizer (DDS) bekannt ist. Wenn Sie eine Websuche nach diesen Begriffen durchführen, erhalten Sie wahrscheinlich mehr als Sie in Theorie und Praxis wissen möchten, damit sie gut funktionieren.

Das Hinzufügen eines zusätzlichen Akkumulators kann nahtlose Übergänge zwischen Frequenzen ermöglichen, wie Sie vorgeschlagen haben, wenn dies ebenfalls gewünscht wird, indem die Änderungsrate des Phasenvorschubwerts gesteuert wird. Dies wird manchmal als Digital Differential Analyzer oder DDA bezeichnet.


Gute Zusatzinformationen. Freut mich, dich hier zu sehen, Eric; Wir könnten einen Minister für Algorithmen gebrauchen.
Jason R

1

1. Ordnung, Sie sollten die Startphase der neuen Frequenz-Sinuskurve so einstellen, dass sie mit der Phase der vorherigen Sinuskurve am 1. Übergang-Abtastpunkt übereinstimmt. Berechnen Sie die erste Frequenz und verwenden Sie deren Phase für die zweite Frequenz.

Die zweite Möglichkeit könnte darin bestehen, d_phase über mehrere Abtastwerte von einer Frequenz zur nächsten zu rampen. Dadurch wird die Kontinuität der 1. Ableitung aufgeräumt und ein Gleiten ermöglicht.

Die dritte Option könnte darin bestehen, ein Glättungsfenster wie einen Raised-Cosine für die d_phase-Anstiegsrate zu verwenden.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.