Jak stworzyć generator fali sinusoidalnej, który może płynnie przechodzić między częstotliwościami

27

Jestem w stanie napisać podstawowy generator fal sinusoidalnych dla audio, ale chcę, aby mógł płynnie przechodzić z jednej częstotliwości na drugą. Jeśli po prostu przestanę generować jedną częstotliwość i natychmiast przełączę się na inną, nastąpi nieciągłość w sygnale i usłyszysz „kliknięcie”.

Moje pytanie brzmi: co to jest dobry algorytm do generowania fali, która zaczyna się od, powiedzmy, 250 Hz, a następnie przechodzi do 300 Hz, bez wprowadzania żadnych kliknięć. Jeśli algorytm zawiera opcjonalny czas poślizg / portamento, to tym lepiej.

Mogę wymyślić kilka możliwych podejść, takich jak nadpróbkowanie, po którym następuje filtr dolnoprzepustowy, a może przy użyciu falowodu, ale jestem pewien, że jest to dość powszechny problem, że istnieje standardowy sposób radzenia sobie z nim.

Mark Heath
źródło
2
Dlaczego po prostu nie użyłeś liniowego przejścia częstotliwości w okresie przejściowym. Na przykład musisz przejść z częstotliwości f0 w czasie t0 do częstotliwości f1 w chwili t1, to dlaczego nie po prostu wprowadzić częstotliwość przejściową f (t) = f0 * (1-q) + f1 * q, gdzie q = (t -t0) / (t1-t0), a następnie wytworzyć sygnał A (t) = sin (2 * Pi * f (t) * t)?
mbaitoff,

Odpowiedzi:

24

Jednym z podejść, które stosowałem w przeszłości, jest utrzymywanie akumulatora fazowego, który jest używany jako indeks do tabeli przeglądów przebiegów. Wartość delta fazowa jest dodawana do akumulatora przy każdym okresie próbkowania:

phase_index += phase_delta

Aby zmienić częstotliwość, należy zmienić deltę fazową dodawaną do akumulatora fazowego przy każdej próbce, np

phase_delta = N * f / Fs

gdzie:

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

To gwarantuje, że fala wyjściowa jest ciągła, nawet jeśli zmienisz dynamicznie fazę_delta, np. Przy zmianach częstotliwości, FM itp.

Aby uzyskać płynniejsze zmiany częstotliwości (portamento), możesz zwiększyć wartość Phase_Delta między jej starą a nową wartością w odpowiedniej liczbie przedziałów próbkowania zamiast zmieniać ją natychmiast.

Należy pamiętać, że phase_indexi phase_deltaobie mają liczbę całkowitą i ułamkową składnik, czyli muszą być ustalony punkt lub zmiennoprzecinkowych. Część całkowitą indeksu fazy (wielkość tabeli modulo) stosuje się jako indeks do kształtu fali LUT, a część ułamkową można opcjonalnie zastosować do interpolacji między sąsiednimi wartościami LUT dla uzyskania wyższej jakości wyjściowej i / lub mniejszego rozmiaru LUT.

Paul R.
źródło
dzięki, spodziewałem się, że odpowiedź może dotyczyć LUT. Myślałem o wyborze LUT, który zawiera jeden przebieg przy 1 Hz (tzn. Wpisy Fs). Czy istnieje zasada praktyczna rządząca optymalnym rozmiarem LUT?
4
Zależy to od różnych czynników: jakiego SNR szukasz, czy jest to czysta fala sinusoidalna, czy bardziej złożony kształt, czy planujesz interpolować między sąsiednimi wpisami LUT, czy po prostu obciąć itp. Zależy to również od tego, czy po prostu zamierzasz mieć jedną tabelę kwadrantową i samodzielnie obsłużyć arytmetykę indeksowania i odwrócić znak lub mieć pełną tabelę czterech kwadrantów. Osobiście zacznę od 1024 punktów (NB: 2 ^ N jest dobre do indeksowania modulo) cztery kwadranty bez interpolacji, ponieważ jest to bardzo proste i powinno dawać dobre wyniki np. Dla 16-bitowego „konsumenckiego” dźwięku.
Paul R
1
Dobra odpowiedź, Paul. Jest też podobne pytanie na ten temat, które zostało opublikowane jakiś czas temu; więcej informacji zawsze pomaga.
Jason R
4
Innym sposobem spojrzenia na to podejście jest emulacja oscylatora sterowanego napięciem (VCO). Częstotliwość wyjściowa VCO zależy od napięcia wejściowego (zwykle liniowej funkcji napięcia wejściowego), ale sygnał wyjściowy ma ciągłą fazę, nawet jeśli napięcie wejściowe natychmiast się zmienia. Dane wyjściowe to gdzie to ciągłej funkcji czasu, a częstotliwość wyjściowa jest pochodną fazy i równa gdzie się częstotliwości spoczynkowej.
sin(ϕ(t))=sin(0tω0+kx(τ)dτ)
ϕ(t)
ω0+kx(t)
ω0
Dilip Sarwate,
1
Miałem ten sam problem, dzięki za pomysł na akumulator (korzystałem z bezpośrednich obliczeń, które nie działały z powodu przybliżeń): jsfiddle.net/sebpiq/p3ND5/12
sebpiq
12

Jednym z najlepszych sposobów na utworzenie fali sinusoidalnej jest użycie złożonego fazora z aktualizacją rekurencyjną. To znaczy

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

gdzie z [n] jest wskaźnikiem, , gdzie jest częstotliwością kątową oscylatora w radianach, a wskaźnikiem próby. Zarówno rzeczywista, jak i wyobrażona część są falami sinusoidalnymi, są o 90 stopni poza fazą. Bardzo wygodne, jeśli potrzebujesz zarówno sinusa, jak i cosinusa. Obliczenie pojedynczej próbki wymaga tylko 4 wielokrotności i 4 dodań i jest DUŻO tańsze niż cokolwiek zawierającego sin () cos () lub tabele odnośników. Potencjalnym problemem jest to, że amplituda może zmieniać się w czasie z powodu problemów z dokładnością numeryczną. Jednak naprawa tego jest dość prosta. Powiedzmy, że . Wiemy, że powinien mieć wielkość jedności, tj Ω=exp(jω)ωnz[n]z[n]=a+jbz[n]

aa+bb=1

Możemy więc od czasu do czasu sprawdzać, czy nadal tak jest, i odpowiednio korygować. Dokładna korekta byłaby

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

Jest to niezręczne obliczenie, ale ponieważ jest bardzo zbliżone do jedności, możesz przybliżać warunki z rozszerzeniem Taylora wokół i otrzymujemy1 / aa+bb x=11/xx=1

1x3x2

więc korekta upraszcza się

z[n]=z[n]3a2b22

Zastosowanie tej prostej korekty co kilkaset próbek zapewni stabilność oscylatora na zawsze.

Aby ciągle zmieniać częstotliwość, mnożnik W musi zostać odpowiednio zaktualizowany. Nawet nieciągła zmiana mnożnika utrzyma funkcję ciągłego oscylatora. Jeśli wymagane jest zwiększenie częstotliwości, aktualizacja może zostać podzielona na kilka kroków lub można użyć tego samego algorytmu oscylatora, aby zaktualizować sam mnożnik (ponieważ jest to również wskaźnik fazowania zespolonego wzmocnienia jedności).

Hilmar
źródło
dziękuję za tę odpowiedź, prawdopodobnie zajmie mi trochę czasu, aby zrozumieć wystarczająco dobrze, aby zmienić się w jakiś kod z prawdziwego świata, ale wygląda to na ciekawą alternatywę do wypróbowania.
Mark Heath
2
Zaimplementowałem to rozwiązanie w golang w celach informacyjnych: github.com/rmichela/Acoustico/blob/…
Ryan Michela
To piękne rozwiązanie, które niestety działa dobrze tylko przy użyciu stałej podstawy czasu. Jeśli nie, musisz obliczyć grzech i cos, aby obliczyć prawidłową złożoną rotację.
Cameron Tacklind
2

Z tej strony :

Aby stworzyć płynne przejście z jednej częstotliwości na drugą lub z jednej amplitudy na drugą, niepełną falę sinusoidalną należy zmodyfikować za pomocą dołączonego odcinka, aby fala wynikowa po każdej iteracji pętli while kończyła się na osi x.

Wygląda na to, że powinno działać.

(W rzeczywistości, jeśli oba są zsynchronizowane na osi x po przejściu, przypuszczam, że stopniowe przejście nie jest konieczne.)


źródło
1
To znaczy, poczekaj, aż aktualny sinusoida na częstotliwości zakończy jeden cykl i osiągnie a następnie przełączy się na drugą sinusoidę na częstotliwości . To skutecznie utrzymuje ciągłość fazy i prawdopodobnie będzie OK dla aplikacji audio, w których kilka milisekund lub mikrosekund opóźnienia między pożądanym czasem przełączenia (teraz) a zrealizowanym czasem przełączenia (kiedy moja sinusoida zakończy cykl) jest nieistotne. Różnica może jednak powodować problemy w innych aplikacjach. Pamiętaj tylko, że sinusoida ma wartość dwa razy w jednym cyklu i koniecznie wybierz właściwy! 0 ω 1 0ω00ω10
Dilip Sarwate,
2

Zgadzam się z poprzednimi sugestiami użycia akumulatora fazowego. Zasadniczo wejściem sterującym jest wielkość przesunięcia fazy na krok lub na okres zegara (lub na przerwanie lub cokolwiek innego), tak że zmiana tej wartości zmienia częstotliwość bez nieciągłości w fazie. Amplituda fali jest następnie określana na podstawie skumulowanej wartości fazy albo za pomocą LUT, albo po prostu obliczenia sin (theta) lub cos (theta).

Zasadniczo jest to powszechnie znane jako oscylator sterowany numerycznie (NCO) lub bezpośredni syntezator cyfrowy (DDS). Przeszukanie tych terminów przyniesie prawdopodobnie więcej, niż chcesz wiedzieć na temat teorii i praktyki, aby działały dobrze.

Dodanie dodatkowego akumulatora może umożliwić płynne przejścia między częstotliwościami, jak zasugerowałeś, jeśli jest to również pożądane, poprzez kontrolowanie szybkości zmiany wartości przesunięcia fazowego. Jest to czasami nazywane cyfrowym analizatorem różnicowym lub DDA.

Eric Jacobsen
źródło
Dobra dodatkowa informacja. Cieszę się, że cię tu widzę, Eric; moglibyśmy skorzystać z Ministra Algorytmów.
Jason R
1

1. rzędu, należy wyregulować fazę początkową sinusoidy nowej częstotliwości, tak aby była taka sama, jak faza poprzedniej sinusoidy w punkcie pierwszej próby przejścia. Oblicz pierwszą częstotliwość i użyj jej fazy dla drugiej częstotliwości.

Drugą opcją może być zwiększenie fazy d_fazy od częstotliwości jednej częstotliwości do następnej w kilku próbkach. Oczyści to ciągłość pierwszej pochodnej i zapewni poślizg.

Trzecią opcją może być użycie okna wygładzającego, takiego jak podwyższony cosinus, na szybkość narastania fazy d_phase.

hotpaw2
źródło