周波数間をスムーズに移行できる正弦波ジェネレーターを作成する方法


27

オーディオ用の基本的な正弦波ジェネレーターを作成できますが、ある周波数から別の周波数にスムーズに移行できるようにしたいと考えています。1つの周波数の生成を停止し、すぐに別の周波数に切り替えると、信号に不連続が発生し、「クリック音」が聞こえます。

私の質問は、クリックを導入することなく、たとえば250Hzで始まり、300Hzに移行する波を生成するための優れたアルゴリズムとは何ですか。アルゴリズムにオプションのグライド/ポルタメント時間が含まれている場合は、はるかに優れています。

オーバーサンプリングに続いてローパスフィルターを使用する、またはウェーブテーブルを使用するなど、いくつかの可能なアプローチを考えることができますが、これは標準的な方法で対処できるほど一般的な問題であると確信しています。


2
なぜ移行期間にわたって線形周波数移行を使用しなかったのですか。たとえば、時刻t0の周波数f0から時刻t1の周波数f1に遷移する必要がある場合、なぜ遷移周波数f(t)= f0 *(1-q)+ f1 * qを導入しないのか、ここでq =(t -t0)/(t1-t0)、次に信号A(t)= sin(2 * Pi * f(t)* t)を生成しますか?
mbaitoff

回答:


24

過去に使用したアプローチの1つは、波形ルックアップテーブルのインデックスとして使用される位相アキュムレータを維持することです。各サンプル間隔で位相デルタ値がアキュムレータに追加されます。

phase_index += phase_delta

周波数を変更するには、各サンプルで位相アキュムレータに追加される位相デルタを変更します。たとえば、

phase_delta = N * f / Fs

ここで:

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

これにより、たとえば周波数の変更、FMなどのためにphase_deltaを動的に変更しても、出力波形が連続することが保証されます。

周波数のよりスムーズな変更(ポルタメント)を行うには、瞬時に変更するのではなく、適切なサンプル間隔で古い値と新しい値の間でphase_delta値を傾斜させることができます。

phase_indexとのphase_delta両方が整数と小数成分を持っていることに注意してください。つまり、浮動小数点または固定小数点である必要があります。phase_indexの整数部(モジュロテーブルサイズ)は、波形LUTのインデックスとして使用され、小数部は、高品質の出力やより小さいLUTサイズの隣接LUT値間の補間にオプションで使用できます。


おかげで、私は答えがLUTを含むかもしれないと期待していました。1Hzの1つの波形(つまりFsエントリ)を含むLUTを使用することを考えていました。LUTの最適サイズを支配する経験則はありますか?

4
それはさまざまな要因に依存します:探しているSNR、純粋な正弦波またはより複雑な波形、隣接するLUTエントリ間で補間するか、単に切り捨てるかなどです。単一の象限表を持ち、インデックスの算術演算と符号反転を自分で処理するか、完全な4象限表を持ちます。個人的には、1024ポイント(NB:2 ^ Nはモジュロインデックス作成に適しています)から始まる4象限テーブルは非常に単純であり、たとえば16ビットの「コンシューマ」オーディオに適した結果になるためです。
ポールR

1
良い答えです、ポール。しばらく前に投稿されたトピックに関する同様の質問もあります。より多くの情報が常に役立ちます。
ジェイソンR

4
このアプローチの別の見方は、電圧制御発振器(VCO)のエミュレーションです。VCOの出力周波数は入力電圧(通常は入力電圧の線形関数)に依存しますが、入力電圧が瞬時に切り替わっても出力信号は連続した位相を持ちます。出力は、ここで、φ T であり、連続的な
ϕt=0tω0+kバツτdτ
ϕt時間の関数、出力周波数は、位相の誘導体であり、そして等しいながらω 0は静止周波数です。
ω0+kバツt
ω0
ディリップサルワテ

1
アキュムレーターのアイデアのおかげで、同じ問題がありました(直接計算を使用していましたが、近似のために機能しませんでした):jsfiddle.net/sebpiq/p3ND5/12
sebpiq

12

サイン波を作成する最良の方法の1つは、再帰的な更新で複雑なフェーザーを使用することです。すなわち

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

Ω=expjωωnz[n]z[n]=a+jbz[n]

aa+bb=1

そのため、それがまだ当てはまる場合は時々チェックし、それに応じて修正することができます。正確な修正は

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

aa+bb1/バツバツ=1

1バツ3バツ2

そのため、補正は

z[n]=z[n]3a2b22

この単純な補正を数百サンプルごとに適用すると、発振器が永久に安定します。

周波数を連続的に変更するには、それに応じて乗数Wを更新する必要があります。乗算器の非連続的な変化でさえ、連続的な発振器機能を維持します。周波数のランピングが必要な場合、更新をいくつかのステップに分割するか、同じオシレータアルゴリズムを使用して乗算器自体を更新できます(ユニティゲインの複素フェーザーでもあるため)。


この答えのおかげで、おそらく実際のコードに変換するのに十分理解するには少し時間がかかりますが、試してみるのは興味深い代替手段のようです。
マークヒース

2
私は参考のためにgolangにこのソリューションを実装:github.com/rmichela/Acoustico/blob/...
ライアンのMichela

これは美しいソリューションであり、残念ながら、一定のタイムベースを使用している場合にのみうまく機能します。そうでない場合は、sinとcosを計算して正しい複素回転を計算する必要があります。
キャメロンタックリンド

2

このサイトから:

ある周波数から別の周波数、またはある振幅から別の振幅へのスムーズな遷移を作成するには、whileループの各反復後の結果の波がx軸で終了するように、不完全な正弦波を追加セクションで修正する必要があります。

動作するはずです。

(実際、両方が移行時にx軸で同期される場合、段階的な移行は必要ないと思われます。)


1
ω00ω10

2

位相アキュムレータの使用に関する以前の提案に同意します。基本的に、制御入力はステップごとまたはクロック周期ごと(または割り込みごとなど)の位相進みの量であるため、その値を変更すると、位相が途切れることなく周波数が変化します。次に、LUTを介して、または単にsin(theta)またはcos(theta)の計算を介して、累積位相値から波の振幅が決定されます。

これは基本的に、数値制御発振器(NCO)またはDirect Digital Synthesizer(DDS)として一般に知られているものです。それらの用語でウェブ検索を行うと、おそらくそれらをうまく機能させるための理論と実践について知りたい以上のものが得られるでしょう。

追加のアキュムレータを追加すると、位相アドバンス値の変化率を制御することで、必要に応じて、周波数間のシームレスな移行を可能にすることができます。これは、デジタル微分アナライザー(DDA)と呼ばれることもあります。


良い追加情報。エリック、この辺でお会いできてうれしいです。アルゴリズム大臣を使用できます。
ジェイソンR

1

1次では、新しい周波数の正弦波の開始位相を調整して、最初の遷移サンプルポイントでの前の正弦波の位相と同じになるようにします。最初の周波数を計算し、その位相を2番目の周波数に使用します。

2番目のオプションは、複数のサンプルでd_phaseをある周波数から次の周波数に傾斜させることです。これにより、1次導関数の連続性がクリーンアップされ、グライドが提供されます。

3番目のオプションは、d_phaseランピングレートでレイズドコサインなどの平滑化ウィンドウを使用することです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.