「ボイスチェンジャー」アプリケーションを作成(マイクからの音をピッチシフトしてスピーカーで鳴らす) ~Delphiソースコード集
Delphiでボイスチェンジャーを作成します。
「waveInAddBuffer」でマイクからの音をデータとして取得し、FFT(高速フーリエ変換)で周波数成分に分解を行った後、
ピッチ(音程)をシフトして、IFFT(逆高速フーリエ変換)で音声データに戻し、waveOutWriteで音を鳴らしています。
高い音や低い音に変換してスピーカーから鳴らします。
プロジェクトを作成する
Delphiを起動し、メニューから「ファイル」⇒「新規作成」⇒「Windows VCLアプリケーション -Delphi(W)」 をクリックする。
TComboboxをフォームにドラッグ&ドロップします。

ソースコードの入力
キーボードの「F12」キーを押してソースコードエディタに切り替え、以下のソースコードをコピー&ペーストします。
キーボードの「F12」キーを押してデザインモードに切り替え、
Form1のOnCreateイベントに「FormCreate」を設定し、Form1のOnCloseに「FormClose」を設定します。

unit Unit1; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,Vcl.Controls, Vcl.Forms, MMSystem, System.Math, Vcl.ComCtrls, Vcl.StdCtrls; type TForm1 = class(TForm) ComboBox1: TComboBox; procedure FormCreate(Sender: TObject); procedure FormClose(Sender: TObject; var Action: TCloseAction); private { Private 宣言 } WaveFmt:TWaveFormatEx; BufIn:array of TBytes; public { Public 宣言 } WaveInHandle:HWAVEIN; WaveOutHandle:HWAVEOUT; BufNumId:Integer; WaveHeader:array of TWaveHdr; end; TFFTData=array of Double; var Form1: TForm1; const //サンプリングバッファの数 BufNum:Integer=6; implementation {$R *.dfm} //FFT関数(高速フーリエ変換) procedure FFT(var ReArr,ImArr:TFFTData); var n:Integer; i:integer; ct1,ct2,ct3:integer; TmpRe,TmpIm:Double; nfft:array[0..3] of integer; fcos,fsin:TFFTData; tmp:extended; noblk:integer; cntb:array[0..1] of integer; begin n:=Length(ReArr); ct2:=1; for ct1 := 1 to length(ImArr)-2 do begin TmpRe:=0; TmpIm:=0; if ct1<ct2 then begin TmpRe:=ReArr[ct1-1]; ReArr[ct1-1]:=ReArr[ct2-1]; ReArr[ct2-1]:=TmpRe; TmpIm:=ImArr[ct1-1]; ImArr[ct1-1]:=ImArr[ct2-1]; ImArr[ct2-1]:=TmpIm; end; ct3:=length(ImArr) div 2; while ct3<ct2 do begin ct2:=ct2-ct3; ct3:=ct3 div 2; end; ct2:=ct2+ct3; end; //誤差調整 nfft[0]:=floor(Log2(length(ImArr))/Log2(2)+0.0000000001); SetLength(fcos,n); SetLength(fsin,n); fcos[0]:=1; fsin[0]:=0; for ct1 := 1 to nfft[0] do begin nfft[2]:=floor(Power(2,ct1)); nfft[1]:=n div nfft[2]; nfft[3]:=nfft[2] div 2; for ct2 := 1 to nfft[3] do begin tmp:=-Pi/nfft[3]*ct2; fcos[ct2]:=cos(tmp); fsin[ct2]:=sin(tmp); end; for ct2 := 1 to nfft[1] do begin noblk:=nfft[2]*(ct2-1); for ct3 := 0 to nfft[3]-1 do begin cntb[0]:=noblk+ct3; cntb[1]:=cntb[0]+nfft[3]; TmpRe:=ReArr[cntb[1]]*fcos[ct3]-ImArr[cntb[1]]*fsin[ct3]; TmpIm:=ImArr[cntb[1]]*fcos[ct3]+ReArr[cntb[1]]*fsin[ct3]; ReArr[cntb[1]]:=ReArr[cntb[0]]-TmpRe; ImArr[cntb[1]]:=ImArr[cntb[0]]-TmpIm; ReArr[cntb[0]]:=ReArr[cntb[0]]+TmpRe; ImArr[cntb[0]]:=ImArr[cntb[0]]+TmpIm; end; end; end; end; //逆FFT関数 procedure IFFT(var ReArr, ImArr: TFFTData); var i,n: Integer; begin n:=Length(ReArr); for i := 0 to n-1 do ImArr[i] := -ImArr[i]; FFT(ReArr, ImArr); for i := 0 to n-1 do begin ReArr[i] := ReArr[i] / n; ImArr[i] := -ImArr[i] / n; end; end; //ShiftFactor を 1未満に設定すると低くなり、1以上に定すると高くなる procedure PitchShift(var ReArr, ImArr: TFFTData; ShiftFactor: Double); var i, n, NewIdx, halfN: Integer; ShiftedRe, ShiftedIm:TFFTData; begin n:=Length(ReArr); SetLength(ShiftedRe, n); SetLength(ShiftedIm, n); halfN := n div 2; // 配列の初期化 for i := 0 to n - 1 do begin ShiftedRe[i] := 0; ShiftedIm[i] := 0; end; // 周波数を ShiftFactor 倍にシフト for i:=0 to halfN-1 do begin NewIdx := Round(i*ShiftFactor); if (NewIdx < halfN) then begin //値をシフト ShiftedRe[NewIdx] := ShiftedRe[NewIdx] + ReArr[i]; ShiftedIm[NewIdx] := ShiftedIm[NewIdx] + ImArr[i]; // 鏡像の値もシフト if (NewIdx > 0) then begin ShiftedRe[n-NewIdx] := ShiftedRe[n-NewIdx] + ReArr[n-i]; ShiftedIm[n-NewIdx] := ShiftedIm[n-NewIdx] + ImArr[n-i]; end; end; end; for i:=0 to n-1 do begin ReArr[i] := ShiftedRe[i]; ImArr[i] := ShiftedIm[i]; end; end; //コールバック関数 procedure WaveInCallBackFunc(hW:HWAVEOUT;uMsg:Cardinal; dwInstance,dwParam1,dwParam2:UINT_PTR);stdcall; type PInt16=^Int16; var OldBufId:Integer; i,n:Integer; re,im:TFFTData; pi16:PInt16; begin if uMsg=MM_WIM_DATA then begin OldBufId:=Form1.BufNumId; inc(Form1.BufNumId); if Form1.BufNumId>=BufNum then Form1.BufNumId:=0; waveInAddBuffer( Form1.WaveInHandle, @Form1.WaveHeader[Form1.BufNumId],SizeOf(TWaveHdr) ); n:=Length(Form1.BufIn[OldBufId]) div 2; SetLength(re, n); SetLength(im, n); pi16:=@Form1.BufIn[OldBufId][0]; for i := 0 to n-1 do begin re[i]:=Double(pi16^)/32768; im[i]:=0; inc(pi16); end; //高速フーリエ変換 FFT(re,im); //ピッチをシフトする PitchShift(re,im,0.6+Form1.ComboBox1.ItemIndex/5); //逆高速フーリエ変換 IFFT(re,im); pi16:=@Form1.BufIn[OldBufId][0]; for i := 0 to n-1 do begin pi16^:=Int16(Trunc(re[i]*32767)); inc(pi16); end; //ピッチをシフトした音を鳴らす waveOutWrite( Form1.WaveOutHandle,@Form1.WaveHeader[OldBufId],SizeOf(TWaveHdr)); end; end; procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction); var i:Integer; begin Action:=caFree; waveInStop(WaveInHandle); for i := 0 to BufNum-1 do begin waveInUnprepareHeader(WaveInHandle,@WaveHeader[i],SizeOf(TWaveHdr)); waveOutUnprepareHeader(WaveOutHandle,@WaveHeader[i],SizeOf(TWaveHdr)); end; waveInClose(WaveInHandle); waveOutClose(WaveOutHandle); end; procedure TForm1.FormCreate(Sender: TObject); var i:Integer; begin ComboBox1.Style:=csDropDownList; ComboBox1.Items.Add('低い'); ComboBox1.Items.Add('少し低い'); ComboBox1.Items.Add('普通'); ComboBox1.Items.Add('少し高め'); ComboBox1.Items.Add('高め'); ComboBox1.Items.Add('高い'); ComboBox1.Items.Add('かなり高い'); ComboBox1.ItemIndex:=6; WaveFmt.wFormatTag:=WAVE_FORMAT_PCM; //1チャンネル(モノラル) WaveFmt.nChannels:=1; //サンプリング周波数 11025 又は 22050 又は 44100 WaveFmt.nSamplesPerSec:=11025; //1サンプル当たりのビット数を指定(符号付き16Bit整数 -32768~32767の範囲) WaveFmt.wBitsPerSample:=16; //1ブロックのバイト数 WaveFmt.nBlockAlign:=WaveFmt.nChannels*WaveFmt.wBitsPerSample div 8; //1秒当たりの平均バイト数 WaveFmt.nAvgBytesPerSec:=WaveFmt.nBlockAlign*WaveFmt.nSamplesPerSec; WaveFmt.cbSize:=0;//必ず0 waveInOpen( @WaveInHandle,WAVE_MAPPER,@WaveFmt,NativeUInt(@WaveInCallBackFunc), 0,CALLBACK_FUNCTION+WAVE_ALLOWSYNC); waveOutOpen( @WaveOutHandle,WAVE_MAPPER,@WaveFmt,0,0, CALLBACK_FUNCTION+WAVE_ALLOWSYNC); SetLength(WaveHeader,BufNum); SetLength(BufIn,BufNum); for i := 0 to BufNum-1 do begin //バッファサイズの設定 SetLength(BufIn[i],1024); //バッファの設定 WaveHeader[i].lpData:=PAnsiChar(BufIn[i]); WaveHeader[i].dwBufferLength:=Length(BufIn[i]); WaveHeader[i].dwBytesRecorded:=0; WaveHeader[i].dwUser:=i; WaveHeader[i].dwFlags:=0; WaveHeader[i].dwLoops:=0; WaveHeader[i].lpNext:=nil; WaveHeader[i].reserved:=0; waveInPrepareHeader(WaveInHandle,@WaveHeader[i],SizeOf(TWaveHdr)); waveOutPrepareHeader(WaveOutHandle,@WaveHeader[i],SizeOf(TWaveHdr)); end; BufNumId:=0; waveInAddBuffer(WaveInHandle,@WaveHeader[BufNumId],SizeOf(TWaveHdr)); waveInStart(WaveInHandle); end; end.
実行
実行時には以下の注意点があります。以下の注意点を踏まえたうえで実行してください。
- ※注意1 Windows10 の場合「マイクブースト」の機能を切る必要があります
-
(マイクブースト機能対応デバイスで、この機能を使用している場合)
ツールバーの右側にある「スピーカー」アイコンを右クリック
⇒「サウンドの設定を開く」を左クリック
⇒マイクの「デバイスのプロパティ」を左クリック
⇒「追加のデバイスのプロパティ」を左クリック
⇒「レベル」タブを左クリックして切り替え、
[マイクブースト]を0.0dbに設定して[OK]ボタンを押す - ※注意2 マイクとスピーカーを近づけて使用するとハウリングが発生します
-
マイクとスピーカーの距離が近すぎるとハウリング(キーンと高い音)が発生する場合があります。
外付けマイク又は外付けスピーカーなどで離して使用するとうまくいくかもしれません。
少なくともスピーカーの音が出る方向側にマイクを置かないようにしてください。
内臓又は外付けマイクとスピーカー、又はヘッドフォンなどをパソコンに接続してください。
実行してマイクに向けて話すと高い声でスピーカーから出力されます。
コンボボックスを「低い」に設定してマイクに向けて話すと低い声でスピーカーから出力されます。
ノイズを減らしたい(音質を良くしたい)場合はクロスフェード(前の音声データをフェードアウトしながら新しい音声データをフェードインした合成音声の出力)を行うソースコードを追加する必要があるようです。
