前回の予告通り、スペアナと連動してwaveファイルを再生するようにする。
・ddf.minimの落とし穴
Processingで音声ファイルの再生と言ったら外部ライブラリに頼るのが手っ取り早い。ddf.minimなるライブラリについて調べてみたところ、音声の再生どころかFFTまでできるらしい(前回の労力は無駄だったのか?)。
が、実際に音声を再生してみようとしたところ、"couldn't load the file”などとのたまって再生できない。別のファイルで試したところ再生できるものもある。意味が分からないのでライブラリのソースを覗いてみた。
結局、どうもminimでは8bitか16bitしか対応していないようだということが判明した。まあCD規格で16bit/44.1kHzが一般的だし致し方あるまい。
・ならばjavaのAPIを使ってやろうと思ったが
minimが使えないならjavaのサウンドAPI ( javax.sound.sampled )でも使ってやれと思ったがやっぱりエラーが出る。どうもパソコンのミキサー自体が16bitまでしか対応していないっぽいのである。
ということで、APIでは非対応形式のファイルを直接読み込ませて再生できないことがわかった。
・読み込めないならデータの方を変えればいいじゃない
ここで、折角wavデータを既に読み込んでいるのだから、ファイルを直接読ませられないなら変換すればいいじゃないかという発想に至る。
というわけで前々回のデータ読み込みのコードにいくらか追加する。
sampleNum=chunkSize/(ch*sampleByte);
waveData=new float[ch][sampleNum];
copy=new byte[sampleNum*2*ch]; //変換後のバイト配列
int copyBytePos=0;
if(fmtCode==1){
int maxLv=(1<<sampleByte*8-1)-1;
for(int i=0;i<sampleNum;i++){
for(int c=0;c<ch;c++){
float val=(float)ReadLittleEnd(sampleByte,true)/maxLv;
waveData[c][i]=val;
//以下追加
int copyVal=(int)(val*0x7fff); //...(1)
copy[copyBytePos++]=(byte)(copyVal&0xff);
copy[copyBytePos++]=(byte)(copyVal>>8&0xff); //...(2)
}
}
}else if(fmtCode==3){
for(int i=0;i<sampleNum;i++){
for(int c=0;c<ch;c++){
float val=ReadLittleEnd();
waveData[c][i]=val;
//
int copyVal=(int)(val*0x7fff);
copy[copyBytePos++]=(byte)(copyVal&0xff);
copy[copyBytePos++]=(byte)(copyVal>>8&0xff);
}
}
}
今回は音声データを16bit signed int、リトルエンディアンに変換してみようと思うので、まず-1から1の値を取っている波形データに対し、符号付16bit整数の最大値0x7fffを掛ける...(1)。
次にこれをリトルエンディアンのバイト配列にするので、まずそのまま0xffでマスクして格納し、次に8ビットシフトしてまたマスクして格納する...(2)。
これで16bit signed int、リトルエンディアンの音声データが出来上がったので、実際に読み込ませてみる。今回は再生前に全部ロードしてあるのでClipを使う。
try{
c=AudioSystem.getClip();
AudioFormat auf=new AudioFormat(sampleRate,16,ch,true,false);
//...(3)
c.open(auf,copy,0,copy.length); //...(4)
}catch(LineUnavailableException e){
e.printStackTrace();
}
AudioFormatのコンストラクタにはいくつかある*1が、これはPCMなので、サンプルレート、サンプルサイズ、チャンネル数、符号の有無、ビッグエンディアンかどうかだけ指定する...(3)。そしてそのフォーマットと音声データをClip.openにぶちこめばClipの完成である...(4)。
あとはClip.start()を呼び出せば再生してくれて、Clip.stop()で停止できる。今回のスペアナはクリックすると表示が一時停止するようにしたのでちゃんと音声も止められるようにしておく...(5)。
void mousePressed(){
if(!setUp){
if(running){
running=false;
if(c!=null)c.stop(); //...(5)
noLoop();
}else{
running=true;
if(c!=null)c.start();
prevMillis=millis();
loop();
}
}
}
というわけでスペアナ制作編は多分終わりである。実はControlP5を使ってGUIをつけてみたりしたが、紹介するほどの内容はないよう、ということでこれで終わりにしようと思う。
気が向いたらスペアナの紹介動画でも出そうかなと思う。面倒なので出さない気もする。
以上