「TD-PSOLA」とは時間領域でピッチの調節を行う方法です。
実装に関しての詳しい内容はソースコードのコメントに書いています。
# include <vector>
std::vector<float> PSOLA(const std::vector<float>& data, const float& rate)
{
//相関関数範囲
const unsigned int size = (info.sample * 0.01) * info.channel;
//ずれ最小値
const unsigned int min = size / 2;
//ずれ最大値
const unsigned int max = size * 2;
//変換データ格納用
std::vector<float>convert(data.size() * 2);
//元データの配列インデックス
unsigned int offset0 = 0;
//変換データの配列インデックス
unsigned int offset1 = 0;
while (offset0 + max < data.size())
{
//自己相関関数
float peak = 0.0;
int index = min;
for (unsigned int i = min; i <= max; ++i)
{
float tmp = 0.0f;
for (unsigned int n = 0; n < size; ++n)
{
tmp += data[n] * data[i + n];
}
//ピーク値更新
if (peak < tmp)
{
peak = tmp;
index = i;
}
}
//伸縮率を適応したブロックサイズ
int tmp = index * rate;
//ピーク位置を中心にしたブロックサイズ分の配列を作成
std::vector<float>cut(&data[std::fmax(int(offset0 + index - tmp), 0)], &data[offset0 + index + tmp]);
//クロスフェード処理
for (size_t i = 0; i < cut.size(); ++i)
{
//ハニング窓関数はwikiに載っている公式をそのまま使用
cut[i] *= okmonn::Hanning<float>(i, cut.size());
}
for (size_t i = 0; i < cut.size(); ++i)
{
convert[offset1 + i] += cut[i];
}
//元データの配列インデックス更新
offset0 += index;
//変換データの配列インデックス更新
//ピークから次のピークまでの区間をクロスフェードするので更新は半分だけで大丈夫
offset1 += cut.size() / 2;
}
return convert;
}
//エントリーポイント
int main(void)
{
//テスト用の波形「ラ」の音
std::vector<float>data(44100 * 2);
for(size_t i = 0; i < data.size(); i += 2)
{
data[i] = std::sin(2.0f * std::acos(-1.0f) * 440.0f * i / 44100.0f);
data[i + 1] = data[i];
}
//ピッチを2倍に設定
auto psola = PSOLA(data, 2.0f);
/*
波形のサンプリング周波数を2倍に設定する
前回の記事に書いたリサンプリングの処理を行えばOK
*/
return 0;
}
ピッチだけをn倍にするには、再生時間をn倍にして再生速度を1/n倍にすれば良いみたいです。
自己相関を行う部分で結構な処理時間を要してしまうので注意してください。
参考サイト,本
・タイムストレッチ、ピッチシフトのアルゴリズム
・恋声の技術に迫る
・C言語ではじめる音のプログラミング―サウンドエフェクトの信号処理