web-dev-qa-db-ja.com

wavファイルに音符を書く

音符(A、B、C#など)または和音(同時に複数の音符)を取得してwavファイルに書き込む方法に興味があります。

私が理解していることから、各音符には(絶対音感のために)特定の周波数が関連付けられています-たとえば、A4(真ん中のCの上のA)は440 Hzです(完全なリストは2/3下にあります このページ )。

私の理解が正しければ、このピッチは周波数領域にあるので、時間領域に相当するものを生成するために、逆高速フーリエ変換を適用する必要がありますか?

私が知りたいのは:

  • コードはどのように機能しますか?それらはピッチの平均ですか?
  • Wavファイルの内容が波形の場合、各音符を演奏する時間の長さはどのように指定されますか?
  • 複数の音符が逆FFTされて、wavファイルのデータを構成するバイトの配列に変換された結果はどうですか?
  • これに関連するその他の関連情報。

あなたが与えることができるどんな助けにも感謝します。コード例を示す場合、私はC#を使用しており、wavファイルの作成に現在使用しているコードは次のとおりです。

int channels = 1;
int bitsPerSample = 8;
//WaveFile is custom class to create a wav file.
WaveFile file = new WaveFile(channels, bitsPerSample, 11025);

int seconds = 60;
int samples = 11025 * seconds; //Create x seconds of audio

// Sound Data Size = Number Of Channels * Bits Per Sample * Samples

byte[] data = new byte[channels * bitsPerSample/8 * samples];

//Creates a Constant Sound
for(int i = 0; i < data.Length; i++)
{
    data[i] = (byte)(256 * Math.Sin(i));
}
file.SetData(data, samples);

これは(どういうわけか)一定の音を作成しますが、コードが結果とどのように相関するかを完全には理解していません。

49

あなたは正しい方向に進んでいます。

あなたの例を見てみましょう:

for(int i = 0; i < data.Length; i++)
  data[i] = (byte)(256 * Math.Sin(i));

OK、1秒あたり11025サンプルあります。 60秒相当のサンプルがあります。各サンプルは0から255までの数値であり、特定の時間における空間内のあるポイントでの気圧の小さな変化を表します。

ただし、少し待ってください。正弦は-1から1になり、サンプルは-256から+256になります。これはバイトの範囲よりも大きいため、ここで何かおかしなことが起こっています。サンプルが適切な範囲になるようにコードを作り直してみましょう。

for(int i = 0; i < data.Length; i++)
  data[i] = (byte)(128 + 127 * Math.Sin(i));

これで、1〜255の範囲でスムーズに変化するデータが得られたため、バイトの範囲内にあります。

それを試してみて、それがどのように聞こえるかを確認してください。それは多くの「スムーズ」に聞こえるはずです。

人間の耳は、空気圧の信じられないほど小さな変化を検出します。これらの変化が繰り返しパターンを形成する場合、パターンが繰り返される頻度は、耳の蝸牛によって特定のものとして解釈されます。トーン。圧力変化のサイズは、体積として解釈されます。

波形の長さは60秒です。変化は、最小の変化1から最大の変化255になります。ピークはどこにありますか?つまり、サンプルはどこで255の値に到達するのでしょうか、それともそれに近いのでしょうか。

さて、正弦はπ/ 2、5π/2、9π/2、13π/2などで1です。したがって、ピークは、私がそれらの1つに近いときはいつでもあります。つまり、2、8、14、20、...

それらは時間的にどれくらい離れていますか?各サンプルは1/11025秒であるため、ピークは約2π/ 11025 =各ピーク間の約570マイクロ秒です。 1秒あたりのピーク数はいくつですか? 11025 /2π= 1755Hz。 (ヘルツは周波数の尺度であり、1秒あたりのピーク数です)。 1760HzはA440より2オクターブ上なので、これはわずかにフラットなAトーンです。

コードはどのように機能しますか?それらはピッチの平均ですか?

いいえ。A440と1オクターブ上のコードであるA880は660Hzに相当しません。 平均ピッチはしません。あなたsum波形

気圧について考えてください。 1つの振動源が1秒間に440回上下に圧力をポンピングし、もう1つが1秒間に880回上下に圧力をポンピングする振動源がある場合、ネットは1秒間に660回の振動と同じではありません。これは、任意の時点での圧力の合計に等しくなります。 WAVファイルは次のとおりです:気圧変化の大きなリスト

サンプルの下に1オクターブを作成したいとします。頻度は?半分です。それで、それを半分の頻度で起こさせましょう:

for(int i = 0; i < data.Length; i++)
  data[i] = (byte)(128 + 127 * Math.Sin(i/2.0)); 

2ではなく2.0である必要があることに注意してください。整数の丸めは必要ありません。 2.0は、結果を整数ではなく浮動小数点にすることをコンパイラーに通知します。

これを行うと、ピークの頻度が半分になります。i= 4、16、28 ...であるため、トーンは1オクターブ低くなります。 (すべてのオクターブダウン半分周波数;すべてのオクターブアップ2倍それ。)

それを試してみて、同じトーンが1オクターブ低くなる方法を確認してください。

次に、それらを一緒に追加します。

for(int i = 0; i < data.Length; i++)
  data[i] = (byte)(128 + 127 * Math.Sin(i)) + 
            (byte)(128 + 127 * Math.Sin(i/2.0)); 

それはおそらくがらくたのように聞こえました。どうした? 再びオーバーフローしました;合計は多くの点で256より大きかった。 両方の波の体積を半分にします

for(int i = 0; i < data.Length; i++)
  data[i] = (byte)(128 + (63 * Math.Sin(i/2.0) + 63 * Math.Sin(i))); 

より良い。 「63sinx + 63 sin y」は-126〜 + 126であるため、バイトをオーバーフローさせることはできません。

(つまり、平均です:基本的に、平均ではなく、各トーンの圧力への寄与の平均を取ります周波数の)

演奏する場合は、両方の音を同時に取得する必要があります。一方は他方より1オクターブ高くなります。

その最後の表現は複雑で読みにくいです。読みやすいコードに分解してみましょう。しかし、最初に、これまでの話を要約してください。

  • 128は、低圧(0)と高圧(255)の中間です。
  • トーンの音量は、波によって達成される最大圧力です。
  • トーンは特定の周波数の正弦波です
  • hz単位の周波数は、サンプル周波数(11025)を2πで割ったものです。

それでは、まとめましょう。

double sampleFrequency = 11025.0;
double multiplier = 2.0 * Math.PI / sampleFrequency;
int volume = 20;

// initialize the data to "flat", no change in pressure, in the middle:
for(int i = 0; i < data.Length; i++)
  data[i] = 128;

// Add on a change in pressure equal to A440:
for(int i = 0; i < data.Length; i++)
  data[i] = (byte)(data[i] + volume * Math.Sin(i * multiplier * 440.0))); 

// Add on a change in pressure equal to A880:

for(int i = 0; i < data.Length; i++)
  data[i] = (byte)(data[i] + volume * Math.Sin(i * multiplier * 880.0))); 

そして、そこに行きます。これで、任意の周波数と音量の任意のトーンを生成できます。コードを作成するには、それらを足し合わせて、音量が大きくなりすぎてバイトがオーバーフローしないようにします。

A220、A440、A880など以外の音の周波数をどのように知っていますか?各半音は、前の周波数に2の12乗根を掛けます。したがって、2の12乗根を計算し、それを440で掛けると、それがA#になります。 A#に2の12乗根、つまりBを掛けます。Bに2の12乗根を掛けると、C、次にC#というようになります。これを12回実行すると、2の12乗根であるため、最初の2倍の880が得られます。

Wavファイルの内容が波形の場合、各音符を演奏する時間の長さはどのように指定されますか?

トーンが鳴っているサンプルスペースに入力するだけです。 A440を30秒間再生してから、A880を30秒間再生するとします。

// initialize the data to "flat", no change in pressure, in the middle:
for(int i = 0; i < data.Length; i++)
  data[i] = 128;

// Add on a change in pressure equal to A440 for 30 seconds:
for(int i = 0; i < data.Length / 2; i++)
  data[i] = (data[i] + volume * Math.Sin(i * multiplier * 440.0))); 

// Add on a change in pressure equal to A880 for the other 30 seconds:

for(int i = data.Length / 2; i < data.Length; i++)
  data[i] = (byte)(data[i] + volume * Math.Sin(i * multiplier * 880.0))); 

複数の音符が逆FFTされて、wavファイルのデータを構成するバイトの配列に変換された結果はどうですか?

逆FFTは、ここで行っているように、正弦波を作成してそれらを加算するだけです。それだけです!

これに関連する他の関連情報はありますか?

このテーマに関する私の記事を参照してください。

http://blogs.msdn.com/b/ericlippert/archive/tags/music/

パート1から3は、ピアノが1オクターブあたり12音である理由を説明しています。

パート4はあなたの質問に関連しています。ここで、WAVファイルを最初から作成します。

私の例では、11025ではなく44100サンプル/秒を使用しており、0〜255の範囲の8ビットサンプルではなく、-16000〜 + 16000の範囲の16ビットサンプルを使用していることに注意してください。ただし、これらの詳細を除けば、基本的にあなたと同じです。

複雑な波形を作成する場合は、ビットレートを高くすることをお勧めします。 1秒あたり11Kサンプルの8ビットは、複雑な波形ではひどい音になります。 1秒あたり44Kサンプルの16ビット/サンプルはCD品質です。

そして率直に言って、符号なしバイトではなく符号付きショートで計算を行う方がはるかに簡単です。

第5部では、錯覚の興味深い例を示します。

また、Windows MediaPlayerの「スコープ」視覚化で波形を見てみてください。それはあなたに実際に何が起こっているのかについての良い考えを与えるでしょう。

更新:

2つの音符を一緒に追加すると、2つの波形間の遷移が鋭すぎるために、ポップノイズが発生する可能性があることに気付きました(たとえば、1つの波形の上部で終了し、次の波形の下部で開始します)。この問題をどのように克服できますか?

優れたフォローアップの質問。

基本的に、ここで起こっていることは、(たとえば)高圧から低圧への瞬間的な遷移があり、これは「ポップ」として聞こえます。これに対処する方法はいくつかあります。

テクニック1:位相シフト

1つの方法は、後続のトーンの開始値と前のトーンの終了値の差が生じるように、後続のトーンを少しだけ「位相シフト」することです。次のような位相シフト項を追加できます。

  data[i] = (data[i] + volume * Math.Sin(phaseshift + i * multiplier * 440.0))); 

位相シフトがゼロの場合、明らかにそれは変化ではありません。 sinの周期は2πであるため、2π(またはπの倍数)の位相シフトも変化しません。 0から2πの間のすべての値は、トーンが波に沿って少しだけ「始まる」場所でシフトします。

正しい位相シフトが何であるかを正確に理解するのは少し難しいかもしれません。 「継続的に下降する」シェパード錯視トーンの生成に関する私の記事を読むと、ポップなしですべてが継続的に変化することを確認するために、いくつかの簡単な計算を使用したことがわかります。同様の手法を使用して、ポップを非表示にするための正しいシフトが何であるかを理解できます。

位相シフト値を生成する方法を見つけようとしています。 「ArcSin(((新しいノートの最初のデータサンプル)-(前のノートの最後のデータサンプル))/ noteVolume)」は正しいですか?

さて、最初に気付くのは、「正しい値」がbeないかもしれないということです。終了音が非常に大きく、ピークで終了し、開始音が非常に静かな場合、古い音の値と一致する新しい音のポイントがない可能性があります。

解決策があると仮定すると、それは何ですか?終了サンプルがあり、それをyと呼び、次のような位相シフトxを見つけたいと考えています。

y = v * sin(x + i * freq)

iがゼロのとき。だから〜だ

x = arcsin(y / v)

しかし、それは完全に正しくないかもしれません!あなたが持っているとしましょう

sine wave 1

そしてあなたは追加したい

sine wave 2

2つの可能な位相シフトがあります:

sine wave 3

そして

Sine wave 4

どちらが良い音かについては、大げさな推測をしてください。 :-)

波の「アップストローク」と「ダウンストローク」のどちらを使用しているかを判断するのは少し難しい場合があります。実際の計算を実行したくない場合は、「遷移時に連続するデータポイント間の差の符号が変化したか」など、いくつかの簡単なヒューリスティックを実行できます。

テクニック2:ADSRエンベロープ

実際の楽器のように聞こえるはずの何かをモデリングしている場合は、次のように音量を変更することで良い結果を得ることができます。

あなたがしたいのは、アタック、ディケイ、サステイン、リリースと呼ばれる、ノートごとに4つの異なるセクションを持つことです。楽器で演奏される音の音量は、次のようにモデル化できます。

     /\
    /  \__________
   /              \
  /                \
   A  D   S       R

ボリュームはゼロから始まります。次に、攻撃が発生します。サウンドは、ピークボリュームまですばやく上昇します。その後、わずかに減衰してサステインレベルになります。その後、そのレベルに留まり、ノートの再生中にゆっくりと減少し、その後ゼロに戻ります。

これを行うと、各ノートの開始と終了の音量がゼロになるため、ポップは発生しません。リリースはそれを保証します。

楽器が異なれば、「エンベロープ」も異なります。たとえば、パイプオルガンの攻撃、減衰、解放は非常に短いです。それはすべてサステインであり、サステインは無限です。既存のコードはパイプオルガンのようなものです。たとえば、ピアノと比較してください。繰り返しますが、短いアタック、短いディケイ、短いリリースですが、サステイン中にサウンドは徐々に静かになります。

アタック、ディケイ、リリースのセクションは非常に短く、短すぎて聞こえませんが、ポップを防ぐのに十分な長さです。ノートの再生中に音量を変更してみて、何が起こるかを確認してください。

115
Eric Lippert

あなたは正しい方向に進んでいます。 :)

オーディオ信号

逆FFTを実行する必要はありません(可能ですが、そのためのlibを見つけるか実装する必要があり、さらに入力として信号を生成する必要があります)。与えられた周波数の正弦波信号であるIFFTから期待する結果を直接生成する方がはるかに簡単です。

サインの引数は、生成するノートと、生成するウェーブファイルの サンプリング周波数 の両方に依存します(多くの場合、44100Hzに等しく、この例では11025Hzを使用しています)。

1 Hzのトーンの場合、1秒に等しい1周期の正弦波信号が必要です。 44100 Hzの場合、1秒あたり44100サンプルがあります。つまり、1周期が44100サンプルの正弦波信号が必要です。サインの周期は Ta (2 * Pi)に等しいので、次のようになります。

sin(44100*f) = sin(tau)
44100*f = tau
f = tau / 44100 = 2*pi / 44100

440 Hzの場合、次のようになります。

sin(44100*f) = sin(440*tau)
44100*f = 440*tau
f = 440 * tau / 44100 = 440 * 2 * pi / 44100

C#では、これは次のようになります。

double toneFreq = 440d;
double f = toneFreq * 2d * Math.PI / 44100d;
for (int i = 0; i<data.Length; i++)
    data[i] = (byte)(128 + 127*Math.Sin(f*i));

注:コードの正確さを検証するためにこれをテストしていません。私はそれをして、間違いを訂正しようとします。 更新:コードを機能するものに更新しました。耳を痛めてすみません;-)

コード

コードは音符の組み合わせです(たとえば、 ウィキペディアのマイナーコード を参照)。したがって、信号は異なる周波数の正弦波の組み合わせ(合計)になります。

純音

ただし、従来の楽器は単一周波数の音を演奏しないため、これらの音や和音は自然に聞こえません。代わりに、A4を演奏すると、周波数の分布が広くなり、濃度は約440Hzになります。たとえば、 Timbre を参照してください。

5
Markus Johnsson

KarplusStrong撥弦楽器アルゴリズムについてはまだ誰も言及していません。

Karplus–Strong string Synthesis これはリアルな撥弦楽器の音を生成するための非常に簡単な方法です。私はこれを使ってポリフォニック楽器/リアルタイムMIDIプレーヤーを書きました。

あなたはこのようにします:

まず、どの周波数をシミュレートしますか?コンサートピッチA = 440Hzとしましょう

サンプルレートが44.1kHzであるとすると、つまり44100/440 = 100.25サンプル/波長です。

これを最も近い整数100に丸めて、循環バッファ長100を作成しましょう。

したがって、周波数が約440Hzの定在波を1つ保持します(正確ではないことに注意してください。これを回避する方法があります)。

-1から+1の間のランダムな静的でそれを埋め、そして:

DECAY = 0.99
while( n < 99999 )
    outbuf[n++] = buf[k]

    newVal = DECAY  *  ( buf[k] + buf_prev ) / 2

    buf_prev = buf[k]
    buf[k] = newVal

    k = (k+1) % 100

それはとてもシンプルで超音を生成するので、それは素晴らしいアルゴリズムです。

何が起こっているのかを理解する最良の方法は、時間領域のランダムな静的ノイズがホワイトノイズであることを理解することです。周波数領域でのランダム静的。あなたはそれを異なる(ランダムな)周波数の多くの波の合成物として想像することができます。

440Hz(または2 * 440Hz、3 * 440Hzなど)に近い周波数は、リングを何度も通過するときに、それら自体と建設的な干渉を引き起こします。したがって、それらは保存されます。他の周波数は破壊的に干渉します。

さらに、平均化はローパスフィルターとして機能します-シーケンスが+1 -1 +1 -1 +1 -1であると想像してください。ペアを平均化する場合、各平均は0として出力されます。ただし、0のような遅い波がある場合0.2 0.3 0.33 0.3 0.2 ...その後、平均化しても波が発生します。波が長ければ長いほど、そのエネルギーはより多く保存されます。つまり、平均化によって減衰が少なくなります。

したがって、平均化は非常に単純なローパスフィルターと考えることができます。

もちろん、複雑な問題もあります。整数のバッファ長を選択する必要があるため、可能な周波数が強制的に量子化され、ピアノの上部に向かって目立つようになります。すべてが乗り越えられますが、それは難しくなります!

リンク:

Delicious Max/MSPチュートリアル1:Karplus-Strong

Karplus-Strongアルゴリズム

私が見る限り、JOSは合成音の生成において世界をリードする権威であり、すべての道は彼のWebサイトに戻っています。しかし、注意してください、それは非常に速くトリッキーになり、大学レベルの数学を必要とします。

2
P i