PHPを使用してWAVファイルを操作する

それはすべて、ダウンロードしたオーディオファイルに関する情報をサイトに表示する方法を考えたことから始まりました。 まず、最も単純な形式であるwavを扱うことにしました。 判明したように、それについて複雑なことは何もなく、それについて書くことは一般に意味をなさないでしょう。幸いなことに、インターネット上で「内側から」wavファイルがどのように構築されるかに関する情報はいっぱいです。



そして、Ostapは苦しみました。そして、ファイルに関する情報を表示するだけでなく、そのようなファイルをその場で生成できるのも楽しいだろうという明るい考えが浮かびました。 誰もがネット上のあらゆる種類の「オンラインピアノ」や物を見たと思いますよね?



だから、私は午後2時になんとかして-カットの下で。



それでは、まずは、WAVファイルの構造に戻りましょう。 簡単にするために、圧縮なしの最も単純な単一チャネルwavファイルを使用します。



wavファイルは、いくつかのセクション(チャンク、チャンク)で構成されています。 すべてのセクションの詳細は、たとえば参照によって読むことができますが、主に3つのセクションに焦点を当てます。





各セクションには、ID、セクションサイズ、および実際にはこのセクションに固有のデータがあります。



RIFFセクションは、「RIFF <file size-8> WAVE」という不名誉に簡単です。



<file size-8>この値は「次に含まれるバイト数」を特徴付けるため。 したがって、「どのくらい」の値で4バイト、最初にあった「RIFF」でさらに4バイト。



形式セクションには、平均的な人が興味を持っているファイルに関する主な情報が保存されます:サンプルレート(サンプリングレート、たとえば44100 Hz)、チャンネル数(1 =モノ、2 =ステレオなど)。



実際、データセクションには、再生する必要があるオーディオデータがあります。 実際、それらは一度の波の振幅です。



上記とフォーマット自体の仕様に基づいて、必要な各セクションを記述する最も単純なクラスと、wavファイルを読み取って必要なオブジェクトを作成する最も単純なパーサーを書くことを妨げるものは何もありません。



ヘッダー(Header.php)
class Header { ... /** * @var string */ protected $id; /** * @var int */ protected $size; /** * @var string */ protected $format; ...
      
      







セクションのフォーマット(FormatSection.php)
 class FormatSection { ... /** * @var string */ protected $id; /** * @var int */ protected $size; /** * @var int */ protected $audioFormat; /** * @var int */ protected $numberOfChannels; /** * @var int */ protected $sampleRate; /** * @var int */ protected $byteRate; /** * @var int */ protected $blockAlign; /** * @var int */ protected $bitsPerSample; ...
      
      







データセクション(DataSection.php)
 class DataSection { ... /** * @var string */ protected $id; /** * @var int */ protected $size; /** * @var int[] */ protected $raw; ...
      
      







上記のコードではすべてのロジックが削除されており、データ自体の構造にのみ関心があります。



実際に、それらを読み取るために、バイナリデータの読み取りをより便利にするために、freadの小さなラッパーヘルパーを作成します。



Helper.php
 class Helper { ... public static function readString($handle, $length) { return self::readUnpacked($handle, 'a*', $length); } public static function readLong($handle) { return self::readUnpacked($handle, 'V', 4); } public static function readWord($handle) { return self::readUnpacked($handle, 'v', 2); } protected function readUnpacked($handle, $type, $length) { $data = unpack($type, fread($handle, $length)); return array_pop($data); } ... }
      
      







小さい場合は、wavファイルの内容を取得して読み取ります。



wavファイルからデータを読み取る
 class Parser { ... public static function fromFile($filename) { ... $handle = fopen($filename, 'rb'); try { $header = Header::createFromArray(self::parseHeader($handle)); $formatSection = FormatSection::createFromArray(self::parseFormatSection($handle)); $dataSection = DataSection::createFromArray(self::parseDataSection($handle)); } finally { fclose($handle); } return new AudioFile($header, $formatSection, $dataSection); } protected static function parseHeader($handle) { return [ 'id' => Helper::readString($handle, 4), 'size' => Helper::readLong($handle), 'format' => Helper::readString($handle, 4), ]; } protected static function parseFormatSection($handle) { return [ 'id' => Helper::readString($handle, 4), 'size' => Helper::readLong($handle), 'audioFormat' => Helper::readWord($handle), 'numberOfChannels' => Helper::readWord($handle), 'sampleRate' => Helper::readLong($handle), 'byteRate' => Helper::readLong($handle), 'blockAlign' => Helper::readWord($handle), 'bitsPerSample' => Helper::readWord($handle), ]; } protected static function parseDataSection($handle) { $data = [ 'id' => Helper::readString($handle, 4), 'size' => Helper::readLong($handle), ]; if ($data['size'] > 0) { $data['raw'] = fread($handle, $data['size']); } return $data; }
      
      







したがって、データが受信されると、精神の中で何かを単純に実行することにより、適切な場所でデータを推測できます。



 echo $audio->getSampleRate();
      
      





wavファイルを作成する



だから、私は、音楽学校を一度卒業した人として、音符に基づくメロディーの生成に興味がありました。 音楽のリテラシーと物理学の知識をコードにシフトするだけです。



このビジネスで最も簡単なステップは、メモをコードに変換することでした。 実際、音は主に音の周波数によって特徴付けられます。 たとえば、音符「la」は440 Hzの周波数(楽器をチューニングするための標準的な音叉周波数)です。



実際、各ノートをその周波数にのみ一致させることができます。 オクターブ内のノート(音)の総数は7、ハーフトーン-12です。また、ハーフトーンにはいくつかのスペルがあります。 たとえば、「F-flat」は「E」と同じです。 または、「Gシャープ」は「Aフラット」と同じです。



したがって、この知識をコードに変換します。



すべてのノートの周波数定数
 class Note { const C = 261.63; const C_SHARP = 277.18; const D = 293.66; const D_FLAT = self::C_SHARP; const D_SHARP = 311.13; const E = 329.63; const E_FLAT = self::D_SHARP; const E_SHARP = self::F; const F = 346.23; const F_FLAT = self::E; const F_SHARP = 369.99; const G = 392.00; const G_FLAT = self::F_SHARP; const G_SHARP = 415.30; const A = 440.00; const A_FLAT = self::G_SHARP; const A_SHARP = 466.16; const H = 493.88; const H_FLAT = self::A_SHARP; public static function get($note) { switch ($note) { case 'C': return self::C; case 'C#': return self::C_SHARP; case 'D': return self::D; case 'D#': return self::D_SHARP; case 'E': return self::E; case 'E#': return self::E_SHARP; case 'F': return self::F; case 'F#': return self::F_SHARP; case 'G': return self::G; case 'G#': return self::G_SHARP; case 'A': return self::A; case 'A#': return self::A_SHARP; case 'B': return self::H_FLAT; case 'H': return self::H; } } }
      
      







一般的に、音楽はかなり正確な科学です。 私たちの場合、これは主に、さまざまな楽器のあらゆる音が物理学者や数学者によって長い間説明されてきたことを意味し、実際、たとえばシンセサイザーを製作することができます。 音波の合成に関する詳細は、例えばここに書かれています



まあ、私は怠け者でもあるので、このビジネスをすべて詳しく理解したいという欲求がなかったので、グーグルで激しくグーグルを始めました。 ロシア語でさまざまな楽器の音をエミュレートすることに関する情報は、まったく何も見つかりませんでした(もちろん、私は見た目が悪いのですが、ポイントではありませんでした)。 しかし、結局、JavaScript( GitHub )でオーディオシンセサイザーを見つけることができました。 一般に、JSコードをPHPに変換するだけでした。



結果として、音の音符、オクターブ、および持続時間を設定することにより、サンプル(wavデータの断片)を作成できるSampleBuilderを取得します。



コードの詳細-ネタバレによる。



サンプルビルダー
ピアノ音源
 class Piano extends Generator { ... public function getDampen($sampleRate = null, $frequency = null, $volume = null) { return pow(0.5 * log(($frequency * $volume) / $sampleRate), 2); } ... public function getWave($sampleRate, $frequency, $volume, $i) { $base = $this->getModulations()[0]; return call_user_func_array($base, [ $i, $sampleRate, $frequency, pow(call_user_func_array($base, [$i, $sampleRate, $frequency, 0]), 2) + 0.75 * call_user_func_array($base, [$i, $sampleRate, $frequency, 0.25]) + 0.1 * call_user_func_array($base, [$i, $sampleRate, $frequency, 0.5]) ]); } ... protected function getModulations() { return [ function($i, $sampleRate, $frequency, $x) { return 1 * sin(2 * M_PI * (($i / $sampleRate) * $frequency) + $x); }, ... ]; } }
      
      







サンプルビルダー
 class SampleBuilder { /** * @var Generator */ protected $generator; ... public function note($note, $octave, $duration) { $result = new \SplFixedArray((int) ceil($this->getSampleRate() * $duration * 2)); $octave = min(8, max(1, $octave)); $frequency = Note::get($note) * pow(2, $octave - 4); $attack = $this->generator->getAttack($this->getSampleRate(), $frequency, $this->getVolume()); $dampen = $this->generator->getDampen($this->getSampleRate(), $frequency, $this->getVolume()); $attackLength = (int) ($this->getSampleRate() * $attack); $decayLength = (int) ($this->getSampleRate() * $duration); for ($i = 0; $i < $attackLength; $i++) { $value = $this->getVolume() * ($i / ($this->getSampleRate() * $attack)) * $this->getGenerator()->getWave( $this->getSampleRate(), $frequency, $this->getVolume(), $i ); $result[$i << 1] = Helper::packChar($value); $result[($i << 1) + 1] = Helper::packChar($value >> 8); } for (; $i < $decayLength; $i++) { $value = $this->getVolume() * pow((1 - (($i - ($this->getSampleRate() * $attack)) / ($this->getSampleRate() * ($duration - $attack)))), $dampen) * $this->getGenerator()->getWave( $this->getSampleRate(), $frequency, $this->getVolume(), $i ); $result[$i << 1] = Helper::packChar($value); $result[($i << 1) + 1] = Helper::packChar($value >> 8); } return new Sample($result->getSize(), implode('', $result->toArray())); } }
      
      









さて、有名なL.ベートーベンの「To Eliza」の始まりを失ったコードの小さな例です。



PHPでElizaに
 $sampleBuilder = new \Wav\SampleBuilder(\Wav\Generator\Piano::NAME); $samples = [ $sampleBuilder->note('E', 5, 0.3), $sampleBuilder->note('D#', 5, 0.3), $sampleBuilder->note('E', 5, 0.3), $sampleBuilder->note('D#', 5, 0.3), $sampleBuilder->note('E', 5, 0.3), $sampleBuilder->note('H', 4, 0.3), $sampleBuilder->note('D', 5, 0.3), $sampleBuilder->note('C', 5, 0.3), $sampleBuilder->note('A', 4, 1), ]; $builder = (new Wav\Builder()) ->setAudioFormat(\Wav\WaveFormat::PCM) ->setNumberOfChannels(1) ->setSampleRate(\Wav\Builder::DEFAULT_SAMPLE_RATE) ->setByteRate(\Wav\Builder::DEFAULT_SAMPLE_RATE * 1 * 16 / 8) ->setBlockAlign(1 * 16 / 8) ->setBitsPerSample(16) ->setSamples($samples); $audio = $builder->build(); $audio->returnContent();
      
      







参照資料



コードはgithubで完全にホストされていますhttps : //github.com/nkolosov/wav



誰かが興味を持っている場合は、composerを使用してプロジェクトに接続できます。



composer require nkolosov/wav







今後の計画



まあ、まず、wavファイルの完全なサポート(すべてのセクションの処理)、マルチチャネルファイルのサポート、さまざまなwav形式のサポート(圧縮など)の実装、waveのグラフィカル表示の実装(Habréに関する記事がありました) Pythonそれを行う方法 、私はPHPでそれをしたいです。



生成に関しては、楽器をいくつか追加し、サウンドをより滑らかにしてみてください。そうすれば、音楽全体をコードにコピーしたり、コードを演奏したりできるようになります。



参加したい場合は、GitHubへようこそ。



All Articles