音声再生速度変換プログラムいままで考えていたことは、どうも適切な方法ではないようですが、試して見たことをまとめておきます。 1.リアルタイム・音声ゆっくりプログラム(SlowVoice)マイクで拾った音を、倍の長さに伸張してリアルタイムで再生するプログラムを作って見ました。そのうち、WaveIO2.dllのサンプルプログラムに追加します。 方法は、周波数を倍にして、再生のサンプリングレートを半分にすると言うものです。 前述の「音の高さと再生速度」の章で、周波数を上げると時間も半分になることと、FFTで周波数を上げるのはサンプルを間引くことと同じことを知りました。(合成の元になる波形を倍の周期にすると、合成した結果は1/2の時間の2回の繰り返しになる。サンプリングレート半分にして収集した結果は、元のサンプリングデータの偶数、あるいは奇数と同じ。サンプリングレート半分にして収集した結果を、本来のサンプリングレートで見れば周波数が倍になる。) このプログラムは、サンプリングデータの偶数だけを使うことで、波形が半分になり、後半分に何を詰めるかが要点になると思います。FFTと逆変換だと、同じ波形の繰り返しになりますが、半分ずつの接続点は、不整合になっているのだと思います。前半の最後は、次のフレームに接続する状態で、後半の先頭は、前のフレームに接続するものです。
前半と後半を同じ内容にすると、十分短い時間なら、目的に近く、長いとルルルといった感じなります。さらに長いと、ゆっくり感は無くなり、同じ音を繰り返しているように聞こえます。 1.1.リアルタイム処理音をリアルタイムで処理することを考えて見ました。入力の2倍の時間で再生するので、入力データが溜まって行きます。この対策として、1)無音の区間はスキップする。2)一定量を超えたら一旦全て破棄する。ことにしました。入力は無音であっても、一定間隔で一定量のPCMデータが受信されます。これに対して、無音の場合の出力は、無音を表すPCMデータを送ることも、データを送らないこともできます。今回は、後者で、再生動作を保留する仕組みを試して見ました。 また、入出力の単位は、リアルタイム性を考えるとある程度小さい必要があります。このサイズは、変換単位にもなる(続きを待つ訳には行かない)ので、変換に最適なサイズを設定できるように変更しました。(今までは、固定長の大きなバッファを少数使うようになっていた。) 1.2.無音検出単にゼロ交差回数で判断しました。一回の受信データ中で、符号反転回数をカウントします。したがって、サンプリングレート、受信サイズの設定に依存します。 1.2.出力バッファの保留通常は、切れ目無く再生するために、1バッファの再生済を知らせる、システム側からのコールバックで、バッファにデータを詰めます。今回は、この通知を受けたバッファを記憶して、受信によって再生が必要になるまで保留するようにして見ました。 1.3.バッファサイズなどの計算サンプリングレートは、入力が44100Hz、出力が22050Hzとして考えて見ます。 あまり根拠はないのですが、15msのPCMデータを変換するものとして見ます。 一回の受信サンプル数は約660個です。15msごとに受信されることになります。 15msと言う時間は、短すぎます。15msはOSのタイムスライスより短い値であり、この時間で常時動作することはできないものと思います。そこで、受信は4単位まとめて行うことにして、60msに1回処理します。(受信キューに、一回に4個いれます。) 出力も、最後のバッファが空になる前に、次のバッファを与えないと連続的に再生されませんが、今回は、出力が遅いことが分かっているので、出力キューがこの問題を解決します。 60msの間に、200Hzぐらいの信号なら12回程度ゼロ交差することになります。 6秒以上のデータが溜まってしまったら、古いデータを破棄するとするとして、15ms分を400個までキューイングします。 1.4.改善 サンプルプログラムなので複雑にするのは本意ではないのですが、工夫をして見ました。 無音をスキップしているので、処理しているブロックが連続したものかどうか分からずに、ブロック単位に処理をしていました。受信ブロックに連番を付与して前回との連続性が分かるようにしました。連続している場合は、前回のブロックとの合成を使うようにしました。周波数を上げる(偶数サンプルのみにする)処理の結果、データ量が半分になりますが、この出力に先立って、前回の入力との合成を出力します。このサイズも半分なので、合せて、入力と同じサンプル数になります。 左図では、2回の入力分(15ms、660個が2回分)を書いてあります。箱は、1回分の半分です。 合成は、0-1-0の直線の傾斜を乗じて、加算しています。始め1-0-1になっていた(時間の新しいものを優先すると考えていた)のですが、この方がきれいに聞こえました。 あえて理屈を付けると、変換前の2回分の接続位置に当たるところを優先すると言うことかと思います。
2.WAVファイルの再生速度を変える(WavSpeedCCF)WAVファイルの再生速度を変えて、新しいWAVファイルに出力します。そのうち、WaveIO2.dllのサンプルプログラムに追加します。 速度の指定は、0.5から2の範囲で指定します。0.5を指定すると、音声データが1/2になります。2だと2倍になります。再生時間の倍率と考えてください。 使うあてがあって作ったのではないですが、遅いです。246MB(25分ぐらい)を変換するのに数分かかりました。 WAVは16ビットのRAW PCMに限ります。ステレオとモノが扱えます。 SlowVoiceでは、無条件にバッファサイズを伸張しています。これを、相関関係の高い区間を合成によって、伸張、短縮するものです。サンプリングレートは変えません。 2.1.ccfデジタル化された信号(時系列データ)の類似度を判定するのに、相互相関関数と言うのを使うことを知りました。 R言語で、help(ccf)として、ヘルプを見るとccf()は「cross correlation function」のようです。 これを使って、作成したWavSpeedCCFの検証しようとしたのですが、上手く行かなかったと言うお話をします。 WavSpeedCCFで、合成対象と判断した区間のデータを、Visual Studioのデバッグ窓に書き、切り貼りしてテキスト・ファイルしました。これを、R言語でx[],y[]に読み込んで、 ccf(x,y) とすると、図の表示になりました。赤表示は除いて見てください。赤表示は、後で説明します。 X軸は何でしょう?なぜ、-26から26なのでしょう? と、言うわけで、この図の見方を勉強して見ました。
データx[],y[]は、いずれも960個です。WavSpeedCCFでは、x[]の先頭から固定の長さで、y[]中の一致度の高いところを探しています。ずらしもy[]を一方向にずらす以外は考えていませんでした。 fcc()で指定できるのは、lag.maxで、ずらす回数が指定できます。このデフォルト値がデータの個数から計算され、X軸が-26から26なっているようです。これ以外に、関連しそうな引き数はなさそうです。 64個のサインとコサインのサンプルを作って全域を表示すると、両端に向かって値が小さくなっていきます。このことから、ccf()は、データをすべて計算する関数だと気が付きました。すべて同じ個数で計算することしか考えていませんでしたが、この関数は、ズラシが大きくなって、重なった場所が短くなっても計算を続けていくようです。x[],y[]とも同じ長さなので、lagがゼロは、データの中央だと解釈します。長さが異なる場合も、それぞれの中央を重ねてからスタートするのだと思います。 lagが負、遅れが負と言うことで方向が方向はどうなっているのでしょうか。前述の、図の赤い部分は下記のスクリプトで計算した結果を重ねたものです。(実は、結果に17を乗じてスケールを合わせました。なぜ17を乗ずる必要があるのかは私には分かりません。) result<-matrix(0,26*2+1) #-26-0-26の結果を格納 iy <- 1 for(i in 1:27){ yy<-y[iy:length(y)] iy= iy+1 xx<-x[1:length(yy)] xx<-xx/32768 yy<-yy/32768 result[28-i]<-sum(xx*yy)/length(yy) } ix<-1 for(i in 1:26){ xx<-x[ix:length(x)] ix=ix+1 yy<-y[1:length(xx)] xx<-xx/32768 yy<-yy/32768 result[27+i]<-sum(xx*yy)/length(xx) } 
同じような計算が2段になっているのは、lagのゼロを挟んでどちら向きにずらすかで分かれています。図の左端に書かれる、result[1]には、x[1]とy[27]の組が入ります。図の右端のx[53]には、x[27]とy[1]の組が入ります。 このことから、左図の中断が、lagの負の方向で、下の図が、正の方向のようです。 WavSpeedCCFでは、決まった長さで和を取るので、終端までの和を取るccf()とは結果が一致しませんでした。 計算方法が、外れていないことは確認できたと思います。 2.2.処理概要 定数 | compareSize | 相関の高い位置の検索に使用するサンプル数。5ms分のサンプル数をサンプリングレートから計算する。 | ccfSize | 相関の高い位置を計算する範囲。compareSizeの4倍のサイズとしている。 | ccfInterval | 相関の高い位置の検索をするとき、検索位置を、この値分進めた位置から始める。compareSizeと同じ値にしてある。 | | durationMultiplier | 伸縮率。0.5から2.0で、伸縮なしに相当する1は指定されないものとする。再生時間に、この値を乗じたものが、新たな再生時間になる。 |
変数 | si | ソースデータのインデクス。WAVファイルのデータ部分のオフセット。単位は、サンプルで、チャネル数が考慮されている。 | next | 相関の高い位置の検索結果。siからの相対。 | | noc | 伸張の場合:next * (durationMultiplier – 1)nextサンプル出力後に、このサイズを出力することで伸張する。 | 短縮の場合:next / durationMultiplier - nextnextサンプル出力するが、このうち後半のnoc個が、続くnocとの合成になる。次の開始位置をnoc個スキップすることで短縮を行う。 |
next(短縮では、next+noc)ずつ進めながら、入力ファイルを処理していきます。一回のnext(あるいは、next+noc)の処理ごとに、バッファの先頭からデータを読み込んでいます。このサイズは、(ccfInterval + ccfSize) * 2 です。 検索は、バッファの先頭のcompareSize分に対して、バッファのccfIntervalから初めてccfInterval + ccfSize -1 までで、相関度が最も高い位置を探します。nextには、バッファの先頭からの相関度が最も高い位置を返します。 nextの最大は、ccfInterval + ccfSize -1で、この範囲で、伸縮処理に後半のデータが使われます。 伸縮とも、バッファの先頭からnext個を出力します。 伸張の場合は、さらに伸張する部分(noc個)を出力します。このデータは、nextの位置を中心に、前後のnoc個を合成したものになります。 短縮の場合も、比率から計算した短縮個数(noc個)について、nextの位置を中心に、前後のnoc個を合成して出力します。 次回の入力は、next進んだ位置から行います。短縮の場合は、さらにnoc個をスキップすることでデータ量が小さくなります。
合成は、nextの位置を中心を中心に、直線的な傾斜を乗じて加算した値を使います。 左図は合成のイメージですが、次回、nocの位置から始めれば、短縮になります。伸張の場合は、一旦nextを出力した上で、合成したnoc分を出力するので、伸張になります。 SlowVoiceの結果から、合成時の傾斜は、伸張時は、0-1-0、短縮時は1-0-1にしています。 |