サンプルソース読めばモーフィングくらいは誰でも書けるので、
その他大したことの無い発見をつらつら、要するに検証した部分。
1.フォルマント変化
straightSynthComputeでフォルマント変化率を指定できるが、それだと全体のフォルマントが変わってしまう。フレームごとにフォルマントの変化をさせたい時には、非周期性バッファとスペクトルバッファの長さを伸長させるとよい。
double dChangeRate;
long nSpLength,nApLength; //GetFrequencyLengthで得た値を格納
double *pSpBuffer=new double[nSpLength]; //SpectrumBuffer
double *pApBuffer=new double[nApLength];
.
.
double* pTempApBuffer=new double[nApLength];
double* pTempSpBuffer=new double[nSpLength];
memcpy(pTempApBuffer,pApBuffer,sizeof(double)*nApLength);
memcpy(pTempSpBuffer,pSpBuffer,sizeof(double)*nSpLength);
for(long n=0;n<nApLength;n++){
double dPos=(double)n*dChangeRate;
if(dPos<nApLength-1){
pApBuffer[n]=pTempApBuffer[(long)dPos]*((long)dPos+1.0-dPos)
+ pTempApBuffer[(long)dPos+1]*(dPos-(long)dPos);
}else if(dPos==nApLength-1){
pApBuffer[n]=pTempApBuffer[nApLength-1];
}else{
pApBuffer[n]=0.0;
}
}
for(long n=0;n<nSpLength;n++){
double dPos=(double)n*dChangeRate;
if(dPos<nSpLength-1){
pSpBuffer[n]=pow(pTempSpBuffer[(long)dPos],((long)dPos+1.0-dPos))
* pow(pTempSpBuffer[(long)dPos+1],(dPos-(long)dPos));
}else if(dPos==nSpLength-1){
pSpBuffer[n]=pTempSpBuffer[nSpLength-1];
}else{
pSpBuffer[n]=0.0;
}
}
delete[] pTempApBuffer;
delete[] pTempSpBuffer;
配列位置の指定にdouble型使うなよ俺、ってちょっと思った。
2.ブレスノイズの増幅
これは比較的簡単で、増幅したいフレームの非周期性指標をスペクトルに対して大きくしてやればいい。
3.ノーマライズ処理
straightWriteSynthAudioFileでは単純に内部保持されているdouble型配列を16bit整数型の配列に直してWaveヘッダとかもろもろをつけているだけの模様。振幅の絶対値は1.0未満でなくてはならず、超えるとオーバーフローして負の最大値とかその近辺に行ってしまうため、激しいノイズが発生する。
straightSynthGetOutputWave関数を使用して得られる配列を、配列の最大値で除算してしまってもちゃんとノーマライズされる。Waveヘッダとか書くの面倒ならこれでもいいみたい。
4.音定差による音量の変化
STRAIGHT技術では、空気の運動エネルギーっぽい何かを使っている節がある。高い音はエネルギーが高く、低い音は低く、また大きな音は高く小さな音は低い。エネルギーを考えると、音程を低くすれば音量が大きくなり、逆もまたしかり。高音部で音の芯が抜けてしまうのは、元々のデータと同じエネルギーで高音域を出そうとしても、低い音程の低いエネルギーではしっかり響かない、なんてことがありそう。
というわけでちょりっと調べたところ、周波数が4倍になると音量が約半分、周波数が1/4倍になると音量が約2倍になる。
多分、上の仮説であってると思う。のでより低い音程を扱う時はクリップしないようにノーマライズするなり何らかの処理が必要です。