22
18

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

PHP で、WAV 音声ファイルを操作してみる

Posted at

はじめに

音声ファイルを PHP で操作してみました。拡張していけば、VOCALOID っぽい、何かが作れるかもしれません?

作成したコードと実行結果の wav ファイルは、github にアップしました。

wav ファイルは、重音テトさんの音源を利用したいと思います。音源は以下のサイトから入手しました。
http://kasaneteto.jp/index.html

重音テトさんの wav ファイルは、以下のような仕様になっていますので、今回のサンプルでは分かりやすさを優先して、この形式のデータだけを考慮して作ります。

  • リニアPCM
  • モノラルデータ
  • サンプリング周波数、44100Hz
  • 量子化16ビット

wav ファイルを読み込む

wav ファイルを読み込み、前提としている仕様の wav ファイルかどうかどうかを確認します。仕様に合致すれば、wav ファイルの情報を取得してクラス変数に保存します。

wav ファイルの仕様は、http://www.kk.iij4u.or.jp/~kondo/wave/ を参考にさせてもらいました。

wavctrl.php

	class WavCtrl {

		var $_d		= '';	// データ

		var $datasize	= 0;    // データサイズ
		var $fmtid	= 0;    // フォーマットID
		var $chsize	= 0;    // チャンネル数
		var $freq	= 0;    // サンプリング周波数

		function LoadFile($fn) {

			$this->_d = file_get_contents($fn);

			// 先頭4バイトが、RIFF でなければ、WAV ファイルではない。
			if (substr($this->_d, 0, 4) != 'RIFF') {
				return false;
			}

			// chunk 識別コード WAVE が存在するかどうか調査
			if (substr($this->_d, 8, 4) != 'WAVE') {
				return false;
			}

			// chunk 識別コード fmt が存在するかどうか調査
			if (substr($this->_d, 12, 4) != 'fmt ') {
				return false;
			}

			// chunk 識別コード data が存在するかどうか調査
			if (substr($this->_d, 36, 4) != 'data') {
				return false;
			}

			// フォーマットID を取得します
			// リニアPCMだけを対象にするので、それ以外はエラー
			$d = unpack('v', substr($this->_d, 20, 2));
			$this->fmtid = $d[1];

			if ($this->fmtid != 1) {
				return false;
			}

			// チャンネル数を取得
			// モノラルチャンネルだけを対象にします
			$d = unpack('v', substr($this->_d, 22, 2));
			$this->chsize = $d[1];

			if ($this->fmtid != 1) {
				return false;
			}

			// サンプリング周波数を取得
			// 44100hz のみを対象とします
			$d = unpack('V', substr($this->_d, 24, 4));
			$this->freq = $d[1];

			// データサイズを取得
			$d = unpack('V', substr($this->_d, 40, 4));
			$this->datasize = $d[1];

		}

	}

ファイル保存

出来たファイルを保存する機能も追加しておきます

wavctrl.php
		function SaveFile($p1) {

			file_put_contents($p1, $this->_d);

		}

WAV ファイルの連結

WAV ファイルを連結して、1つのファイルにするメソッドを作成します。連結することで、音が繋がって再生される WAV が作成できます。

wavctrl.php

		function WaveConnect(&$p1) {

			// WAVE ファイルのデータ部分だけ結合します
			$this->_d = $this->_d . substr($p1->_d, 44, $p1->datasize);

			// データサイズを更新します
			$this->datasize = strlen($this->_d) - 44;

			// 実際のデータのサイズも更新します
			$d = pack('V', strlen($this->_d) - 8);
			$this->_d[4] = $d[0];
			$this->_d[5] = $d[1];
			$this->_d[6] = $d[2];
			$this->_d[7] = $d[3];

			$d = pack('V', $this->datasize);
			$this->_d[40] = $d[0];
			$this->_d[41] = $d[1];
			$this->_d[42] = $d[2];
			$this->_d[43] = $d[3];

		}

こちらのサンプルをつかって、WAVE ファイルを連結してみます

wavctrl.php

	$w1 = new WavCtrl();
	$w1->LoadFile('_あ.wav');

	$w2 = new WavCtrl();
	$w2->LoadFile('_い.wav');

	$w3 = new WavCtrl();
	$w3->LoadFile('_う.wav');

	$w1->WaveConnect($w2);
	$w1->WaveConnect($w3);

	$w1->SaveFile('step1.wav');

step1.wav というファイルが作成され、再生すると「あ・い・う」という3音が連結されて再生されます。

音程を変える。

音程は、周波数を変更することによって調整します。

元になる、テトさんの音声を、ラ(440hz)の音で作成されていると仮定します。何故仮定するかというと、テトさんの音源の音階が何なのか書いてないのと、私が絶対音感を持たないためです。

たとえば、これを倍(880hz)にすると、1オクターブ高いラになります。

また、周波数を変更すると音の長さが変わります。音を低くすると、元の音より長く再生されるようになり、音を高くすると、元の音より短く再生されるようになります。

wavctrl.php

		// $p1 変換後の周波数を指定します。
		function WaveShift($p1) {

			$f1 = $p1 / 440;
			$f2 = 0;

			$dst = substr($this->_d, 0, 44);

			while(1) {

				$d = substr($this->_d, 44 + floor($f2) * 2, 2);

				$dst .= $d[0];
				$dst .= $d[1];

				if ($f2 > $this->datasize) {
					break;
				}

				$f2 += $f1;

			}

			// データ更新
			$this->_d = $dst;
			$this->datasize = strlen($this->_d) - 44;

			// 実際のデータのサイズも更新します
			$d = pack('V', strlen($this->_d) -  8);

			$this->_d[4] = $d[0];
			$this->_d[5] = $d[1];
			$this->_d[6] = $d[2];
			$this->_d[7] = $d[3];

			$d = pack('V', $this->datasize);

			$this->_d[40] = $d[0];
			$this->_d[41] = $d[1];
			$this->_d[42] = $d[2];
			$this->_d[43] = $d[3];

			// 変数解放
			unset($dst);

		}

音階をつけたい場合は、その音階に応じた周波数を指定すればよいわけです。

その周波数は、こちらを参考にさせていただきました

http://www.yk.rim.or.jp/~kamide/music/notes.html

ド、レ、ミとしゃべってもらうには、以下のように、変換します

wavctrl.php

	$w1 = new WavCtrl();
	$w1->LoadFile('_あ.wav');

	$w2 = new WavCtrl();
	$w2->LoadFile('_い.wav');

	$w3 = new WavCtrl();
	$w3->LoadFile('_う.wav');

	$w1->WaveShift(261);
	$w2->WaveShift(329);
	$w3->WaveShift(391);

	$w1->WaveConnect($w2);
	$w1->WaveConnect($w3);

	$w1->SaveFile('step2.wav');

wav ファイルを合成(ミックス)します

音を合成すると、複数の音を混ぜて聞くことができます

wavctrl.php

		function WavCtrl(&$p1) {

			$f2 = 0;

			while(1) {

				$src1 = 0;
				$src2 = 0;

				if ($f2 < $this->datasize) {
					$d = unpack('s', substr($this->_d, 44 + $f2, 2));
					$src1 = $d[1];
				}

				if ($f2 < $p1->datasize) {
					$d = unpack('s', substr($p1->_d,   44 + $f2, 2));
					$src2 = $d[1];
				}

				// ミックスします
				$src3 = floor($src1 + $src2);

				// 音割れ防止
				if ($src3 < -32768) {
					$src3 = -32768;
				}

				// 音割れ防止
				if ($src3 > 32767) {
					$src3 = 32767;
				}

				$d = pack('v', $src3);

				$this->_d[44 + $f2    ] = $d[0];
				$this->_d[44 + $f2 + 1] = $d[1];

				if (($f2 > $this->datasize) and ($f2 > $p1->datasize)) {
					break;
				}

				$f2 += 2;

			}

		}

音階を変更した音声を合成してみます。

wavctrl.php

	$w1 = new WavCtrl();
	$w1->LoadFile('_あ.wav');

	$w2 = new WavCtrl();
	$w2->LoadFile('_あ.wav');

	$w3 = new WavCtrl();
	$w3->LoadFile('_あ.wav');

	$w1->WaveShift(261);
	$w2->WaveShift(329);
	$w3->WaveShift(391);

	$w1->WaveMix($w2);
	$w1->WaveMix($w3);

	$w1->SaveFile('step3.wav');

step3.wav を再生すると、「あ」という発音で和音が聞こえると思います。音の長さが変わっているので、綺麗に途切れませんが、そのあたりを調整すれば、もっと、綺麗に合成できると思います

最後に

基本的な操作になるので、あまり、複雑なことはできませんが、音長変更や、音量変更、無音作成・・・などの、演奏をするのに必要な処理を加えれば、何か、面白いものが作れるかもしれません

何かの参考になればと思います

あと、重音テトさんの音源が、どの音階で作られているのか教えてもらえるとうれしいです。

22
18
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
22
18

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?