PHP で PCM wav ファイル作成
適当な音声信号を PHP で生成した時に気軽に音を確かめるには wav 形式に落とせば良い。と思って作ったコードです。
↑こちらを参考にして PHP でも wav ファイルの作成を。
function makeWaveData
<?php function makeWaveData($data, $nChannel, $sampleBits, $sampleRate) { $blockSize = $nChannel*($sampleBits/8); $bytePerSecs = $blockSize*$sampleRate; $formatId = 1; // linear PCM $fmtChunk = 'WAVEfmt '; $fmtChunk .= pack("V", 16); // fmt chunk length $fmtChunk .= pack("v", $formatId); $fmtChunk .= pack("vVV", $nChannel, $sampleRate, $bytePerSecs); $fmtChunk .= pack("vv", $blockSize, $sampleBits); // chunk $dataChunk = 'data'.pack('V', strlen($data)).$data; $riffLength = strlen($fmtChunk)+strlen($dataChunk); return 'RIFF'.pack("V", $riffLength).$fmtChunk.$dataChunk; }
実験 (8bit monoral, 電話品質)
<?php $sampleRate = 8000; // Phone quality $nChannel = 1; // 1:monoral, 2:stereo $toneA = 440; // Hz $sampleBits = 8; // 8 or 16 $period = 3; // seconds; $data = ''; $theta = 0; $theta_delta = $toneA * 2 * M_PI / $sampleRate; for ($i = 0 ; $i < $sampleRate * $period ; $i++) { // unsigned 8-bit array $v = 0x80 + 0x40 * sin($theta); $data .= pack('C', $v); $theta += $theta_delta; } echo makeWaveData($data, $nChannel, $sampleBits, $sampleRate);
実験 (16bit monoral, CD音質)
<?php // $sampleRate = 8000; // Phone quality $sampleRate = 44100; // CD quality $nChannel = 1; // 1:monoral, 2:stereo $toneA = 440; // $sampleBits = 8; // 8 or 16 $sampleBits = 16; // 8 or 16 $period = 3; // seconds; $data = ''; $theta = 0; $theta_delta = $toneA * 2 * M_PI / $sampleRate; for ($i = 0 ; $i < $sampleRate * $period ; $i++) { // unsigned 8-bit array // $v = 0x80 + 0x40 * sin($theta); // $data .= pack('C', $v); // signed 16-bit array (little endian) $v = 0 + 0x4000 * sin($theta); $data .= pack('v', $v); // acrovatic using for 'v' $theta += $theta_delta; } echo makeWaveData($data, $nChannel, $sampleBits, $sampleRate);
実験 (16bit stereo, CD音質)
- short の扱いに
ちょっと自信ない。... 正しかったようです。
<?php $sampleRate = 44100; // CD quality // $nChannel = 1; // 1:monoral, 2:stereo $nChannel = 2; // 1:monoral, 2:stereo $toneA = 440; $sampleBits = 16; // 8 or 16 $period = 3; // seconds; $data = ''; $theta = 0; $theta_delta = $toneA * 2 * M_PI / $sampleRate; for ($i = 0 ; $i < $sampleRate * $period ; $i++) { // signed 16-bit array (little endian) $v = 0 + 0x4000 * sin($theta); // $data .= pack('v', $v); // acrovatic using for 'v' $data .= pack('vv', $v, 0 ); // L:$v, R:0 $theta += $theta_delta; } for ($i = 0 ; $i < $sampleRate * $period ; $i++) { // signed 16-bit array (little endian) $v = 0 + 0x4000 * sin($theta); $data .= pack('vv', 0, $v); // L:0, R:$v $theta += $theta_delta; } echo makeWaveData($data, $nChannel, $sampleBits, $sampleRate);
生成物
- 出来たファイル)
- http://diary.awm.jp/~yoya/data/2013/04/30/soundwave.wav (8bit monoral)
- http://diary.awm.jp/~yoya/data/2013/04/30/soundwave2.wav (16bit stereo)
追記 (2013/04/30)
8-bit samples are stored as unsigned bytes, ranging from 0 to 255. 16-bit samples are stored as 2's-complement signed integers, ranging from -32768 to 32767.
- 8-bit サンプリングは unsigned bytes で 0〜255
- 16-bit サンプリングは signed integers の 2 の補数表現で -32768 〜 32767.
正直なところ自信なかったけど正しかった。 :-)
$v = 0 + 0x4000 * sin($theta); $data .= pack('v', $v); // acrovatic using for 'v'
の何がアクロバティックかというと PHP の pack は signed short を byte order 指定で使えないので、unsigned short を意味する 'v' を無理筋で使ってます。
概念的には、
$v = 0 + 0x4000 * sin($theta); $v = $v + 0x10000; // 2の補数表現でバイナリ的にひっくり返す $data .= pack('v', $v); // acrovatic using for 'v'
が正しいのですが、結果的には同じですので。0x10000 足してません。