Cara membuat generator gelombang sinus yang dapat dengan lancar transisi antar frekuensi

27

Saya dapat menulis generator gelombang sinus dasar untuk audio, tetapi saya ingin dapat dengan lancar beralih dari satu frekuensi ke yang lain. Jika saya berhenti menghasilkan satu frekuensi dan segera beralih ke yang lain akan ada diskontinuitas dalam sinyal dan "klik" akan terdengar.

Pertanyaan saya adalah, apa algoritma yang baik untuk menghasilkan gelombang yang dimulai pada, katakanlah 250Hz, dan kemudian transisi ke 300Hz, tanpa memperkenalkan klik apa pun. Jika algoritma menyertakan waktu glide / portamento opsional, maka jauh lebih baik.

Saya dapat memikirkan beberapa pendekatan yang mungkin seperti oversampling diikuti oleh low pass filter, atau mungkin menggunakan wavetable, tapi saya yakin ini adalah masalah yang cukup umum sehingga ada cara standar untuk mengatasinya.

Mark Heath
sumber
2
Mengapa Anda tidak menggunakan transisi frekuensi linier selama periode transisi. Misalnya, Anda perlu transit dari frekuensi f0 pada waktu t0 ke frekuensi f1 pada waktu t1, lalu mengapa tidak memperkenalkan frekuensi transisi f (t) = f0 * (1-q) + f1 * q, di mana q = (t -t0) / (t1-t0), lalu menghasilkan sinyal A (t) = sin (2 * Pi * f (t) * t)?
mbaitoff

Jawaban:

24

Salah satu pendekatan yang saya gunakan di masa lalu adalah untuk mempertahankan akumulator fase yang digunakan sebagai indeks ke tabel pencarian gelombang. Nilai delta fase ditambahkan ke akumulator pada setiap interval sampel:

phase_index += phase_delta

Untuk mengubah frekuensi Anda mengubah delta fase yang ditambahkan ke akumulator fase di setiap sampel, misalnya

phase_delta = N * f / Fs

dimana:

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

Ini menjamin bahwa bentuk gelombang output kontinu bahkan jika Anda mengubah fase_delta secara dinamis, misalnya untuk perubahan frekuensi, FM, dll.

Untuk perubahan frekuensi (portamento) yang lebih lancar, Anda dapat meningkatkan nilai phase_delta antara nilai lamanya dan nilai baru di atas jumlah interval sampel yang sesuai daripada hanya mengubahnya secara instan.

Perhatikan bahwa phase_indexdan phase_deltakeduanya memiliki bilangan bulat dan komponen fraksional, yaitu mereka harus floating point atau titik tetap. Bagian integer dari phase_index (ukuran tabel modulo) digunakan sebagai indeks ke dalam LUT gelombang, dan bagian fraksional dapat secara opsional digunakan untuk interpolasi antara nilai LUT yang berdekatan untuk output kualitas yang lebih tinggi dan / atau ukuran LUT yang lebih kecil.

Paul R
sumber
terima kasih, saya berharap bahwa jawabannya mungkin melibatkan LUT. Saya sedang berpikir untuk pergi dengan LUT yang berisi satu bentuk gelombang pada 1Hz (yaitu entri Fs). Apakah ada aturan praktis yang mengatur ukuran optimal LUT?
4
Itu tergantung pada berbagai faktor: apa SNR yang Anda cari, apakah itu gelombang sinus murni atau bentuk gelombang yang lebih kompleks, apakah Anda berencana untuk menyisipkan di antara entri LUT yang berdekatan atau hanya memotong, dll. Itu juga tergantung pada apakah Anda hanya akan memiliki tabel kuadran tunggal dan menangani aritmatika pengindeksan dan menandatangani inversi sendiri, atau memiliki tabel empat kuadran penuh. Secara pribadi saya akan mulai dengan poin 1024 (NB: 2 ^ N baik untuk pengindeksan modulo) empat tabel kuadran tanpa interpolasi karena ini sangat sederhana dan harus memberikan hasil yang baik untuk misalnya 16 bit "konsumen" audio.
Paul R
1
Jawaban yang bagus, Paul. Ada juga pertanyaan serupa tentang topik yang telah diposting beberapa waktu lalu; info lebih lanjut selalu membantu.
Jason R
4
Cara lain untuk melihat pendekatan ini adalah emulasi osilator yang dikendalikan tegangan (VCO). Frekuensi output dari VCO tergantung pada tegangan input (biasanya fungsi linear dari tegangan input) tetapi sinyal output memiliki fase kontinu bahkan jika tegangan input beralih secara instan. Outputnya adalah mana ϕ ( t ) adalah kontinu
sin(ϕ(t))=sin(0tω0+kx(τ)dτ)
ϕ(t)fungsi waktu, sedangkan frekuensi output adalah turunan dari fase, dan sama dengan mana ω 0 adalah frekuensi diam.
ω0+kx(t)
ω0
Dilip Sarwate
1
Saya memiliki masalah yang sama, terima kasih atas ide akumulator (saya menggunakan perhitungan langsung, yang tidak berfungsi karena perkiraan): jsfiddle.net/sebpiq/p3ND5/12
sebpiq
12

Salah satu cara terbaik untuk membuat gelombang sinus adalah dengan menggunakan fasor kompleks dengan pembaruan rekursif. Yaitu

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

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

aa+bb=1

Jadi kita dapat memeriksa sesekali jika itu masih terjadi dan memperbaikinya. Koreksi yang tepat adalah

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

aa+bb1/xx=1

1x3x2

jadi koreksi disederhanakan menjadi

z[n]=z[n]3a2b22

Menerapkan koreksi sederhana ini setiap beberapa ratus sampel akan membuat osilator tetap stabil selamanya.

Untuk memvariasikan frekuensi secara terus menerus, pengali W perlu diperbarui. Bahkan perubahan non-kontinu pada pengali akan mempertahankan fungsi osilator kontinu. Jika peningkatan frekuensi diperlukan, pembaruan dapat dipecah menjadi beberapa langkah atau Anda dapat menggunakan algoritma osilator yang sama untuk memperbarui pengali itu sendiri (karena itu juga merupakan phasor kompleks gain gain).

Hilmar
sumber
terima kasih atas jawaban ini, mungkin akan membutuhkan sedikit waktu untuk mengerti dengan cukup baik untuk berubah menjadi beberapa kode dunia nyata, tetapi sepertinya alternatif yang menarik untuk dicoba.
Mark Heath
2
Saya menerapkan solusi ini di golang untuk referensi: github.com/rmichela/Acoustico/blob/…
Ryan Michela
Ini adalah solusi indah yang, sayangnya, hanya berfungsi dengan baik jika menggunakan basis waktu yang konstan. Jika tidak, Anda perlu menghitung dosa dan cos untuk menghitung rotasi kompleks yang benar.
Cameron Tacklind
2

Dari situs ini :

Untuk membuat transisi yang mulus dari satu frekuensi ke frekuensi lain atau satu amplitudo ke yang lain, gelombang sinus yang tidak lengkap harus dimodifikasi dengan bagian yang ditambahkan sehingga gelombang yang dihasilkan setelah setiap iterasi dari loop sementara berakhir pada sumbu x.

Kedengarannya seperti itu seharusnya bekerja.

(Sebenarnya, jika keduanya disinkronkan pada sumbu x pada transisi, saya kira transisi bertahap tidak diperlukan.)


sumber
1
ω00ω10
2

Saya setuju dengan saran sebelumnya menggunakan akumulator fase. Pada dasarnya input kontrol adalah jumlah gerak maju per langkah atau per periode jam (atau per interupsi atau apa pun), sehingga mengubah nilai tersebut mengubah frekuensi tanpa diskontinuitas dalam fase. Amplitudo gelombang kemudian ditentukan dari nilai fase terakumulasi baik melalui LUT atau hanya perhitungan dosa (theta) atau cos (theta).

Ini pada dasarnya adalah apa yang umumnya dikenal sebagai Oscillator Controlled Oscillator (NCO) atau Direct Digital Synthesizer (DDS). Melakukan pencarian web pada istilah-istilah itu mungkin akan menghasilkan lebih dari yang Anda ingin tahu tentang teori dan praktik membuatnya bekerja dengan baik.

Menambahkan akumulator tambahan dapat memungkinkan transisi yang mulus antara frekuensi, seperti yang Anda sarankan, jika itu diinginkan juga, dengan mengendalikan laju perubahan nilai kenaikan fasa. Ini kadang-kadang disebut Penganalisis Diferensial Digital, atau DDA.

Eric Jacobsen
sumber
Informasi tambahan yang bagus. Senang melihatmu di sini, Eric; kita bisa menggunakan Menteri Algoritma.
Jason R
1

Urutan pertama, Anda harus menyesuaikan fase awal sinusoid frekuensi baru sehingga sama dengan fase sinusoid sebelumnya pada titik sampel transisi pertama. Hitung frekuensi pertama dan gunakan fase untuk frekuensi kedua.

Opsi 2 mungkin untuk meningkatkan d_phase dari satu frekuensi ke yang berikutnya melalui beberapa sampel. Ini akan membersihkan kontinuitas turunan pertama dan memberikan luncuran.

Pilihan ke-3 mungkin menggunakan jendela penghalusan, seperti peningkatan-cosinus, pada tingkat ramping d_phase.

hotpaw2
sumber