Arduino Pro_Miniで初歩的な音声変換を試してみたというお話。
1.きっかけ
一口にボイスチェンジャーといっても世の中にはいろいろあって、ガチャガチャ声に変換する安いおもちゃや、滑らかな声を作り出す音楽用の専用機、さらに最近はAI技術を使って他人の声に変換するソフトも登場している。筆者はなぜか音声変換に興味をもち、この一か月ほど先達が作成したアプリをいくつか試してみた。特に参考になったのは”maxcreater”さんの記事1)で、高品質の音声をどうやってつくりだすかを順を追って丁寧に説明している。初心者にもわかりやすい。とはいうものの、こうした高度な音声変換アルゴリズムの研究は識者に任せることにして、ここではリアルタイム音声変換のおもちゃを作ってみることにした。当初、BBDやCCD素子を使ったハードウェア音声変換も考えてみたがこれらの機能は今や低価格のマイコンで十分代替できることに気付き、なじみのあるArduinoを使ってみた。
2.Arduino Pro_Mini (Arduino Uno互換)のアナログ入出力
音声出力可能なDAC端子を備えたArduinoといえばDUEが定番だが、とにかく安い機種を(中華も含め)ということで選んだのがPro_Mini。Unoと同様に、アナログ入力は分解能10ビットのADCだが、アナログ出力はPWM(パルス幅変調、8ビット)である。analogRead()の実行時間は120マイクロ秒ていど、analogWrite()は最速でも1ミリ秒ほどかかるため、このままでは音声信号を扱えない。しかしこのようにアナログ入出力が遅い原因はすでに有志によって調べられていて、アナログ入出力の実行速度を1桁以上高速化できる事が知られている2), 3, 4)。こうしたありがたい記事を参考に何ら外部回路を必要とせずPro_Mini単体のみで音声入出力を実現できることがわかった。
確認のため、音声信号をいったんメモリに記録し遅延再生するプログラムを動かしてみた。
[code]
uint8_t sound[2][1000];
int mem = 1000;
void setup(){
// analogRead()の高速化
ADCSRA = ADCSRA & 0xf8; // 分周比を決めるビット(ADPS2:0)を000へ
ADCSRA = ADCSRA | 0x04; // 分周比を決めるビットに分周比16(100)をセット ← デフォルトの分周比は128(111)
// analogWrite()の高速化(PWM出力)
TCCR1B &= B11111000;
TCCR1B |= B00000001; //rを1に設定 fc/(2*255*1)Hzになる ← デフォルトのrは64
pinMode(9, OUTPUT); // アナログ出力ピン
}
void loop(){
for(int i=0; i<2; i++) {
for(int j=0; j<mem; j++) { // sampling and out
sound[(i+1)%2][j] = int(analogRead(A0)/4);
analogWrite(9, sound[i%2][int(j/1)]);
} // for j
} // for i
}
[/code]
sound[1000]というメモリブロックを2つ用意して一方を書き込んでいる時に他方を再生することを繰り返すだけの単純なプログラムで、A0ピンを入力、D9ピンを出力にしている。PWM出力は8ビットなのでanalogRead(A0)を4で割って8ビットの範囲に変換している。処理時間の関係で入力信号は少し遅れて再生される。遅延時間は約20ミリ秒となった。
ソースの音声ファイルは、”こえやさん” 5) から拝借した。
(クリックで再生します。ブラウザの「←」ボタンで戻ってください)
再生音は以下のthrough.wavである。ソース音声と比べても特に気になる劣化はないようだ。ただし8ビット出力なのでダイナミックレンジが狭いため録音レベルには注意が必要だろう。
3.音声変換の方法と結果
テープレコーダーを頭に浮かべると、いわゆる早回しをすれば高音の音声が得られるが同時に再生時間が短縮され話し言葉のテンポ(ピッチ)が上がってしまう。なので原音と同じ時間に引き延ばすために早回し音の一部を小刻みに継ぎ足しながら再生する必要がある。逆に低音に変換するには遅回し音声の一部を小刻みにカットして原音のテンポと同じ再生時間に修正する必要がでてくる。これをArduinoでやってみよう。
まず、音程を2倍に上げるとしよう。メモリブロックを2(A, B)つ用意し、録音済みのメモリブロックAから1つ置きに間引き再生して2倍の音程を得る。次に、メモリブロックブロックBから同じようにに1つ置きに間引き再生するが、どちらも再生時間は1/2になるので再生時間に1メモリブロック分の空きが生じる。これを埋めるため、メモリブロックAの後ろ半分を再生、続けてメモリブロックBの前半を再生することで空きを埋めることにした。この手順を下図に示す。
Fig.1
これを単純にコード化したのがつぎのfunc_high()である。これをvoid loop()内に置いて実行すればよい。
[code]
void func_high()
{
for(int i=0; i<2; i++) {
for(int j=0; j<mem/2; j++) { // sampling and out
sound[(i+1)%2][j] = int(analogRead(A0)/4);
analogWrite(9, sound[i%2][int(j*2)]);
} // for j_1st
for(int j=0; j<mem/4; j++) { // sampling and out
sound[(i+1)%2][j+mem/2] = int(analogRead(A0)/4);
analogWrite(9, sound[i%2][int(j*2+mem/2)]);
} // for j_2nd
for(int j=0; j<mem/4; j++) { // sampling and out
sound[(i+1)%2][j+mem*3/4] = int(analogRead(A0)/4);
analogWrite(9, sound[(i+1)%2][int(j*2)]);
} // for j_3rd
} // for i
}
[/code]
変換後の音声はこんな感じ。(→ high_wav)
お世辞にも滑らかな音声とはいえない代物だが話しの内容は何とか聞き取れるレベルにある。ボツボツ入るノイズにやうがいをしているようなコロコロ声の原因は各音声ブロックのつなぎ目の処理がなされていないことに依る。つなぎ目処理とは音声波形の大きさと位相を隣り合う音声ブロック間で整合させる操作を指しているが、処理能力の低いマイコンでこれをおこなうのはなかなか難しそうだ。
今度は音程を1/2に下げてみる。
[code]
void func_low()
{
for(int i=0; i<2; i++) { // Phase A, B
for(int j=0; j<mem; j++) { // sampling and out
sound[(i+1)%2][(j)] = int(analogRead(0)/4);
analogWrite(9, sound[i%2][int(j/2)]);
} //for j
} // for i
}
[/code]
変換した音声 → low_.wavファイル
高音化のケースと同じようにボツボツとノイズが入っているが、内容は聞き取れるレベルと思う。
4.まとめと課題
とりあえずArduino_ ProMiniを使っておもちゃのボイスチェンジャーが出来上がったが、当然のことながら音質が悪い。もっと処理能力の高いCPUを使えばソフト上で対処可能だろうが”安物で遊ぶ”という趣旨に反する。実際におもちゃを作っていて感じたのはスピーカーの選択によって音質の感じ方に結構な違いがあるということだ。直径4cm程度の小型ダイナミックスピーカーと古いテレビから部品取りした直径13cmのスピーカーを聞き比べると明らかに大型スピーカーのほうが音声変換後の言葉の内容を聞き取りやすい。なので、再生信号に適切なフィルターをかけることで聞き触りの良い音に換え、音質が改善されたかのような錯覚を生み出すことも可能かと思われる。
<References>
1) Pythonで再生速度変更(人の声は意外に難しかった)|MAX CARTERの道具箱 (https://toolbox.aaa-plaza.net/archives/3669)
2) Arduino PWM周波数の高速化|日々ボケボケ研究所(htpps://www.hibihogehoge.com/2014/05/arduino-pwm.html))
3) Arduinoで遊ぶページ:analogWrite()|garretlab(https://garretlab.web.fc2.com/arduino/inside/hardware/arduino/avr/cores/arduino/wiring_analog.c/analogWrite.html)
4) Arduino UNOのanalogReadの高速化|ゴーイングマイウェイの戯言(https://blog.goo.ne.jp/izumame/e/1ee4b875ca237e294f1f28de0ef62153)
5) こえのフリー素材サイト | こえやさん (https://www.koeyasan.com/)