134 2.513, 2.667, 2.841, 3.038, 3.262,
135 3.520, 3.819, 4.171, 4.589, 5.093,
136 5.711, 6.487, 7.483, 8.806, 10.634,
137 13.302, 17.510, 24.970, 41.155, 96.088
221 public function __construct($numChannelsOrFileName = null, $sampleRateOrReadData = null, $bitsPerSample = null)
223 $this->_actualSize = 44;
224 $this->_chunkSize = 36;
225 $this->_fmtChunkSize = 16;
226 $this->_fmtExtendedSize = 0;
227 $this->_factChunkSize = 0;
228 $this->_dataSize = 0;
229 $this->_dataSize_fp = 0;
230 $this->_dataSize_valid =
true;
231 $this->_dataOffset = 44;
232 $this->_audioFormat = self::WAVE_FORMAT_PCM;
233 $this->_audioSubFormat = null;
234 $this->_numChannels = 1;
235 $this->_channelMask = self::SPEAKER_DEFAULT;
236 $this->_sampleRate = 8000;
237 $this->_bitsPerSample = 8;
238 $this->_validBitsPerSample = 8;
239 $this->_blockAlign = 1;
240 $this->_numBlocks = 0;
241 $this->_byteRate = 8000;
242 $this->_samples =
'';
246 if (is_string($numChannelsOrFileName)) {
247 $this->
openWav($numChannelsOrFileName, is_bool($sampleRateOrReadData) ? $sampleRateOrReadData :
true);
249 $this->
setNumChannels(is_null($numChannelsOrFileName) ? 1 : $numChannelsOrFileName)
250 ->setSampleRate(is_null($sampleRateOrReadData) ? 8000 : $sampleRateOrReadData)
251 ->setBitsPerSample(is_null($bitsPerSample) ? 8 : $bitsPerSample);
257 if (is_resource($this->_fp)) {
291 if ($bitDepth === null) {
292 $bitDepth = strlen($sampleBinary) * 8;
298 return ord($sampleBinary);
302 $data = unpack(
'v', $sampleBinary);
304 if ($sample >= 0x8000) {
311 $data = unpack(
'C3', $sampleBinary);
313 if ($sample >= 0x800000) {
314 $sample -= 0x1000000;
320 $data = unpack(
'f', $sampleBinary);
347 return pack(
'v', $sample);
352 $sample += 0x1000000;
354 return pack(
'C3', $sample & 0xff, ($sample >> 8) & 0xff, ($sample >> 16) & 0xff);
358 return pack(
'f', $sample);
375 $sampleBytes = $bitDepth / 8;
376 if ($numChannels === null) {
377 $numChannels = strlen($sampleBlock) / $sampleBytes;
381 for (
$i = 0;
$i < $numChannels;
$i++) {
382 $sampleBinary = substr($sampleBlock,
$i * $sampleBytes, $sampleBytes);
383 $samples[
$i + 1] = self::unpackSample($sampleBinary, $bitDepth);
399 foreach ($samples as $sample) {
400 $sampleBlock .= self::packSample($sample, $bitDepth);
427 if ($threshold >= 1) {
428 return $sampleFloat * $threshold;
432 if ($threshold <= -1) {
433 return $sampleFloat / -$threshold;
436 $sign = $sampleFloat < 0 ? -1 : 1;
437 $sampleAbs = abs($sampleFloat);
440 if ($threshold >= 0 && $threshold < 1 && $sampleAbs > $threshold) {
441 $loga = self::$LOOKUP_LOGBASE[(int) ($threshold * 20)];
442 return $sign * ($threshold + (1 - $threshold) * log(1 + $loga * ($sampleAbs - $threshold) / (2 - $threshold)) / log(1 + $loga));
446 $thresholdAbs = abs($threshold);
447 if ($threshold > -1 && $threshold < 0 && $sampleAbs > $thresholdAbs) {
448 return $sign * ($thresholdAbs + (1 - $thresholdAbs) / (2 - $thresholdAbs) * ($sampleAbs - $thresholdAbs));
466 if (is_null($actualSize)) {
469 $this->_actualSize = $actualSize;
482 if (is_null($chunkSize)) {
483 $this->_chunkSize = 4 +
484 8 + $this->_fmtChunkSize +
485 ($this->_factChunkSize > 0 ? 8 + $this->_factChunkSize : 0) +
486 8 + $this->_dataSize +
487 ($this->_dataSize & 1);
489 $this->_chunkSize = $chunkSize;
504 if (is_null($fmtChunkSize)) {
507 $this->_fmtChunkSize = $fmtChunkSize;
523 if (is_null($fmtExtendedSize)) {
524 if ($this->_audioFormat == self::WAVE_FORMAT_EXTENSIBLE) {
525 $this->_fmtExtendedSize = 2 + 22;
526 } elseif ($this->_audioFormat != self::WAVE_FORMAT_PCM) {
527 $this->_fmtExtendedSize = 2 + 0;
529 $this->_fmtExtendedSize = 0;
532 $this->_fmtExtendedSize = $fmtExtendedSize;
547 if (is_null($factChunkSize)) {
548 if ($this->_audioFormat != self::WAVE_FORMAT_PCM) {
549 $this->_factChunkSize = 4;
551 $this->_factChunkSize = 0;
554 $this->_factChunkSize = $factChunkSize;
570 if (is_null($dataSize)) {
571 $this->_dataSize = strlen($this->_samples);
573 $this->_dataSize = $dataSize;
578 $this->_dataSize_valid =
true;
590 if (is_null($dataOffset)) {
591 $this->_dataOffset = 8 +
593 8 + $this->_fmtChunkSize +
594 ($this->_factChunkSize > 0 ? 8 + $this->_factChunkSize : 0) +
597 $this->_dataOffset = $dataOffset;
610 if (is_null($audioFormat)) {
611 if (($this->_bitsPerSample <= 16 || $this->_bitsPerSample == 32)
612 && $this->_validBitsPerSample == $this->_bitsPerSample
613 && $this->_channelMask == self::SPEAKER_DEFAULT
614 && $this->_numChannels <= 2) {
615 if ($this->_bitsPerSample <= 16) {
616 $this->_audioFormat = self::WAVE_FORMAT_PCM;
618 $this->_audioFormat = self::WAVE_FORMAT_IEEE_FLOAT;
621 $this->_audioFormat = self::WAVE_FORMAT_EXTENSIBLE;
624 $this->_audioFormat = $audioFormat;
629 ->setFmtExtendedSize();
641 if (is_null($audioSubFormat)) {
642 if ($this->_bitsPerSample == 32) {
643 $this->_audioSubFormat = self::WAVE_SUBFORMAT_IEEE_FLOAT;
645 $this->_audioSubFormat = self::WAVE_SUBFORMAT_PCM;
648 $this->_audioSubFormat = $audioSubFormat;
661 if ($numChannels < 1 || $numChannels > self::MAX_CHANNEL) {
662 throw new WavFileException(
'Unsupported number of channels. Only up to ' . self::MAX_CHANNEL .
' channels are supported.');
663 } elseif ($this->_samples !==
'') {
664 trigger_error(
'Wav already has sample data. Changing the number of channels does not convert and may corrupt the data.', E_USER_NOTICE);
667 $this->_numChannels = (int) $numChannels;
683 if ($channelMask != 0) {
685 $c = (int) $channelMask;
691 if (
$n != $this->_numChannels || (((
int) $channelMask | self::SPEAKER_ALL) != self::SPEAKER_ALL)) {
692 throw new WavFileException(
'Invalid channel mask. The number of channels does not match the number of locations in the mask.');
696 $this->_channelMask = (int) $channelMask;
710 if ($sampleRate < 1 || $sampleRate > self::MAX_SAMPLERATE) {
712 } elseif ($this->_samples !==
'') {
713 trigger_error(
'Wav already has sample data. Changing the sample rate does not convert the data and may yield undesired results.', E_USER_NOTICE);
716 $this->_sampleRate = (int) $sampleRate;
730 if (!in_array($bitsPerSample, array(8, 16, 24, 32))) {
731 throw new WavFileException(
'Unsupported bits per sample. Only 8, 16, 24 and 32 bits are supported.');
732 } elseif ($this->_samples !==
'') {
733 trigger_error(
'Wav already has sample data. Changing the bits per sample does not convert and may corrupt the data.', E_USER_NOTICE);
736 $this->_bitsPerSample = (int) $bitsPerSample;
752 if (is_null($validBitsPerSample)) {
755 if ($validBitsPerSample < 1 || $validBitsPerSample > $this->_bitsPerSample) {
756 throw new WavFileException(
'ValidBitsPerSample cannot be greater than BitsPerSample.');
758 $this->_validBitsPerSample = (int) $validBitsPerSample;
773 if (is_null($blockAlign)) {
774 $this->_blockAlign = $this->_numChannels * $this->_bitsPerSample / 8;
776 $this->_blockAlign = $blockAlign;
791 if (is_null($numBlocks)) {
792 $this->_numBlocks = (int) ($this->_dataSize / $this->_blockAlign);
794 $this->_numBlocks = $numBlocks;
807 if (is_null($byteRate)) {
808 $this->_byteRate = $this->_sampleRate * $this->_numChannels * $this->_bitsPerSample / 8;
810 $this->_byteRate = $byteRate;
823 if (strlen($samples) % $this->_blockAlign != 0) {
824 throw new WavFileException(
'Incorrect samples size. Has to be a multiple of BlockAlign.');
827 $this->_samples = $samples;
840 if ($this->_bitsPerSample == 8) {
842 } elseif ($this->_bitsPerSample == 32) {
845 return -(1 << ($this->_bitsPerSample - 1));
851 if ($this->_bitsPerSample == 8) {
853 } elseif ($this->_bitsPerSample == 32) {
862 if ($this->_bitsPerSample == 8) {
864 } elseif ($this->_bitsPerSample == 32) {
867 return (1 << ($this->_bitsPerSample - 1)) - 1;
888 $header = pack(
'N', 0x52494646);
890 $header .= pack(
'N', 0x57415645);
893 $header .= pack(
'N', 0x666d7420);
912 $header .= pack(
'N', 0x66616374);
928 if (!$this->_dataSize_valid) {
934 return pack(
'N', 0x64617461) .
949 if (!is_resource($fp)) {
975 } elseif (is_resource($this->_fp)) {
982 if (!is_resource($this->_fp)) {
987 return $this->
readWav($readData);
996 if (is_resource($this->_fp)) {
1014 if (is_resource($this->_fp)) {
1020 $this->_fp = @
fopen(
'php://memory',
'w+b');
1021 if (!is_resource($this->_fp)) {
1022 throw new WavFileException(
'Failed to open memory stream to write wav data. Use openWav() instead.');
1026 fwrite($this->_fp,
$data);
1047 if (!is_resource($this->_fp)) {
1074 if (!is_resource($this->_fp)) {
1079 $stat = fstat($this->_fp);
1080 $actualSize = $stat[
'size'];
1082 $this->_actualSize = $actualSize;
1086 $header = fread($this->_fp, 36);
1093 $RIFF = unpack(
'NChunkID/VChunkSize/NFormat',
$header);
1095 if ($RIFF[
'ChunkID'] != 0x52494646) {
1099 if ($actualSize - 8 < $RIFF[
'ChunkSize']) {
1100 trigger_error(
'"RIFF" chunk size does not match actual file size. Found ' . $RIFF[
'ChunkSize'] .
', expected ' . ($actualSize - 8) .
'.', E_USER_NOTICE);
1101 $RIFF[
'ChunkSize'] = $actualSize - 8;
1105 if ($RIFF[
'Format'] != 0x57415645) {
1106 throw new WavFormatException(
'Not wav format. "RIFF" chunk format is not "WAVE".', 4);
1109 $this->_chunkSize = $RIFF[
'ChunkSize'];
1114 'NSubchunkID/VSubchunkSize/vAudioFormat/vNumChannels/' 1115 .
'VSampleRate/VByteRate/vBlockAlign/vBitsPerSample',
1119 if ($fmt[
'SubchunkID'] != 0x666d7420) {
1123 if ($fmt[
'SubchunkSize'] < 16) {
1127 if ($fmt[
'AudioFormat'] != self::WAVE_FORMAT_PCM
1128 && $fmt[
'AudioFormat'] != self::WAVE_FORMAT_IEEE_FLOAT
1129 && $fmt[
'AudioFormat'] != self::WAVE_FORMAT_EXTENSIBLE) {
1130 throw new WavFormatException(
'Unsupported audio format. Only PCM or IEEE FLOAT (EXTENSIBLE) audio is supported.', 13);
1133 if ($fmt[
'NumChannels'] < 1 || $fmt[
'NumChannels'] > self::MAX_CHANNEL) {
1137 if ($fmt[
'SampleRate'] < 1 || $fmt[
'SampleRate'] > self::MAX_SAMPLERATE) {
1141 if (($fmt[
'AudioFormat'] == self::WAVE_FORMAT_PCM && !in_array($fmt[
'BitsPerSample'], array(8, 16, 24)))
1142 || ($fmt[
'AudioFormat'] == self::WAVE_FORMAT_IEEE_FLOAT && $fmt[
'BitsPerSample'] != 32)
1143 || ($fmt[
'AudioFormat'] == self::WAVE_FORMAT_EXTENSIBLE && !in_array($fmt[
'BitsPerSample'], array(8, 16, 24, 32)))) {
1144 throw new WavFormatException(
'Only 8, 16 and 24-bit PCM and 32-bit IEEE FLOAT (EXTENSIBLE) audio is supported.', 16);
1147 $blockAlign = $fmt[
'NumChannels'] * $fmt[
'BitsPerSample'] / 8;
1148 if ($blockAlign != $fmt[
'BlockAlign']) {
1149 trigger_error(
'Invalid block align in "fmt " subchunk. Found ' . $fmt[
'BlockAlign'] .
', expected ' . $blockAlign .
'.', E_USER_NOTICE);
1150 $fmt[
'BlockAlign'] = $blockAlign;
1154 $byteRate = $fmt[
'SampleRate'] * $blockAlign;
1155 if ($byteRate != $fmt[
'ByteRate']) {
1156 trigger_error(
'Invalid average byte rate in "fmt " subchunk. Found ' . $fmt[
'ByteRate'] .
', expected ' . $byteRate .
'.', E_USER_NOTICE);
1157 $fmt[
'ByteRate'] = $byteRate;
1161 $this->_fmtChunkSize = $fmt[
'SubchunkSize'];
1162 $this->_audioFormat = $fmt[
'AudioFormat'];
1163 $this->_numChannels = $fmt[
'NumChannels'];
1164 $this->_sampleRate = $fmt[
'SampleRate'];
1165 $this->_byteRate = $fmt[
'ByteRate'];
1166 $this->_blockAlign = $fmt[
'BlockAlign'];
1167 $this->_bitsPerSample = $fmt[
'BitsPerSample'];
1172 if ($fmt[
'SubchunkSize'] > 16) {
1174 $extendedFmt = fread($this->_fp, $fmt[
'SubchunkSize'] - 16 + ($fmt[
'SubchunkSize'] & 1));
1175 if (strlen($extendedFmt) < $fmt[
'SubchunkSize'] - 16) {
1182 if ($fmt[
'AudioFormat'] == self::WAVE_FORMAT_EXTENSIBLE) {
1183 if (strlen($extendedFmt) < 24) {
1184 throw new WavFormatException(
'Invalid EXTENSIBLE "fmt " subchunk size. Found ' . $fmt[
'SubchunkSize'] .
', expected at least 40.', 19);
1187 $extensibleFmt = unpack(
'vSize/vValidBitsPerSample/VChannelMask/H32SubFormat', substr($extendedFmt, 0, 24));
1189 if ($extensibleFmt[
'SubFormat'] != self::WAVE_SUBFORMAT_PCM
1190 && $extensibleFmt[
'SubFormat'] != self::WAVE_SUBFORMAT_IEEE_FLOAT) {
1191 throw new WavFormatException(
'Unsupported audio format. Only PCM or IEEE FLOAT (EXTENSIBLE) audio is supported.', 13);
1194 if (($extensibleFmt[
'SubFormat'] == self::WAVE_SUBFORMAT_PCM && !in_array($fmt[
'BitsPerSample'], array(8, 16, 24)))
1195 || ($extensibleFmt[
'SubFormat'] == self::WAVE_SUBFORMAT_IEEE_FLOAT && $fmt[
'BitsPerSample'] != 32)) {
1196 throw new WavFormatException(
'Only 8, 16 and 24-bit PCM and 32-bit IEEE FLOAT (EXTENSIBLE) audio is supported.', 16);
1199 if ($extensibleFmt[
'Size'] != 22) {
1200 trigger_error(
'Invaid extension size in EXTENSIBLE "fmt " subchunk.', E_USER_NOTICE);
1201 $extensibleFmt[
'Size'] = 22;
1205 if ($extensibleFmt[
'ValidBitsPerSample'] != $fmt[
'BitsPerSample']) {
1206 trigger_error(
'Invaid or unsupported valid bits per sample in EXTENSIBLE "fmt " subchunk.', E_USER_NOTICE);
1207 $extensibleFmt[
'ValidBitsPerSample'] = $fmt[
'BitsPerSample'];
1211 if ($extensibleFmt[
'ChannelMask'] != 0) {
1213 $c = (int) $extensibleFmt[
'ChannelMask'];
1219 if (
$n != $fmt[
'NumChannels'] || (((
int) $extensibleFmt[
'ChannelMask'] | self::SPEAKER_ALL) != self::SPEAKER_ALL)) {
1220 trigger_error(
'Invalid channel mask in EXTENSIBLE "fmt " subchunk. The number of channels does not match the number of locations in the mask.', E_USER_NOTICE);
1221 $extensibleFmt[
'ChannelMask'] = 0;
1226 $this->_fmtExtendedSize = strlen($extendedFmt);
1227 $this->_validBitsPerSample = $extensibleFmt[
'ValidBitsPerSample'];
1228 $this->_channelMask = $extensibleFmt[
'ChannelMask'];
1229 $this->_audioSubFormat = $extensibleFmt[
'SubFormat'];
1231 $this->_fmtExtendedSize = strlen($extendedFmt);
1232 $this->_validBitsPerSample = $fmt[
'BitsPerSample'];
1233 $this->_channelMask = 0;
1234 $this->_audioSubFormat = null;
1239 $factSubchunk = array();
1240 $dataSubchunk = array();
1242 while (!feof($this->_fp)) {
1243 $subchunkHeader = fread($this->_fp, 8);
1244 if (strlen($subchunkHeader) < 8) {
1248 $subchunk = unpack(
'NSubchunkID/VSubchunkSize', $subchunkHeader);
1250 if ($subchunk[
'SubchunkID'] == 0x66616374) {
1252 $subchunkData = fread($this->_fp, $subchunk[
'SubchunkSize'] + ($subchunk[
'SubchunkSize'] & 1));
1253 if (strlen($subchunkData) < 4) {
1257 $factParams = unpack(
'VSampleLength', substr($subchunkData, 0, 4));
1258 $factSubchunk = array_merge($subchunk, $factParams);
1259 } elseif ($subchunk[
'SubchunkID'] == 0x64617461) {
1260 $dataSubchunk = $subchunk;
1263 } elseif ($subchunk[
'SubchunkID'] == 0x7761766C) {
1264 throw new WavFormatException(
'Wave List Chunk ("wavl" subchunk) is not supported.', 106);
1268 if ($subchunk[
'SubchunkSize'] < 0
1269 || fseek($this->_fp, $subchunk[
'SubchunkSize'] + ($subchunk[
'SubchunkSize'] & 1), SEEK_CUR) !== 0) {
1270 throw new WavFormatException(
'Invalid subchunk (0x' . dechex($subchunk[
'SubchunkID']) .
') encountered.', 103);
1275 if (empty($dataSubchunk)) {
1281 $dataOffset = ftell($this->_fp);
1282 if ($dataSubchunk[
'SubchunkSize'] < 0 || $actualSize - $dataOffset < $dataSubchunk[
'SubchunkSize']) {
1283 trigger_error(
'Invalid "data" subchunk size.', E_USER_NOTICE);
1284 $dataSubchunk[
'SubchunkSize'] = $actualSize - $dataOffset;
1288 $this->_dataOffset = $dataOffset;
1289 $this->_dataSize = $dataSubchunk[
'SubchunkSize'];
1290 $this->_dataSize_fp = $dataSubchunk[
'SubchunkSize'];
1291 $this->_dataSize_valid =
false;
1292 $this->_samples =
'';
1296 $numBlocks = (int) ($dataSubchunk[
'SubchunkSize'] / $fmt[
'BlockAlign']);
1298 if (empty($factSubchunk)) {
1299 $factSubchunk = array(
'SubchunkSize' => 0,
'SampleLength' => $numBlocks);
1302 if ($factSubchunk[
'SampleLength'] != $numBlocks) {
1303 trigger_error(
'Invalid sample length in "fact" subchunk.', E_USER_NOTICE);
1304 $factSubchunk[
'SampleLength'] = $numBlocks;
1308 $this->_factChunkSize = $factSubchunk[
'SubchunkSize'];
1309 $this->_numBlocks = $factSubchunk[
'SampleLength'];
1325 if (!is_resource($this->_fp)) {
1329 if ($dataOffset < 0 || $dataOffset % $this->
getBlockAlign() > 0) {
1330 throw new WavFileException(
'Invalid data offset. Has to be a multiple of BlockAlign.');
1333 if (is_null($dataSize)) {
1334 $dataSize = $this->_dataSize_fp - ($this->_dataSize_fp % $this->
getBlockAlign());
1335 } elseif ($dataSize < 0 || $dataSize % $this->
getBlockAlign() > 0) {
1336 throw new WavFileException(
'Invalid data size to read. Has to be a multiple of BlockAlign.');
1341 if ($dataOffset > 0 && fseek($this->_fp, $dataOffset, SEEK_CUR) !== 0) {
1346 $this->_samples .= fread($this->_fp, $dataSize);
1366 if (!$this->_dataSize_valid) {
1371 if ($offset + $this->_blockAlign > $this->_dataSize || $offset < 0) {
1377 return substr($this->_samples, $offset, $this->_blockAlign);
1392 if (!isset($sampleBlock[$blockAlign - 1]) || isset($sampleBlock[$blockAlign])) {
1393 throw new WavFileException(
'Incorrect sample block size. Got ' . strlen($sampleBlock) .
', expected ' . $blockAlign .
'.');
1396 if (!$this->_dataSize_valid) {
1400 $numBlocks = (int) ($this->_dataSize / $blockAlign);
1401 $offset = $blockNum * $blockAlign;
1402 if ($blockNum > $numBlocks || $blockNum < 0) {
1408 if ($blockNum == $numBlocks) {
1410 $this->_samples .= $sampleBlock;
1411 $this->_dataSize += $blockAlign;
1412 $this->_chunkSize += $blockAlign;
1413 $this->_actualSize += $blockAlign;
1414 $this->_numBlocks++;
1417 for (
$i = 0;
$i < $blockAlign; ++
$i) {
1418 $this->_samples[$offset +
$i] = $sampleBlock[
$i];
1436 if ($channelNum < 1 || $channelNum > $this->_numChannels) {
1440 if (!$this->_dataSize_valid) {
1444 $sampleBytes = $this->_bitsPerSample / 8;
1445 $offset = $blockNum * $this->_blockAlign + ($channelNum - 1) * $sampleBytes;
1446 if ($offset + $sampleBytes > $this->_dataSize || $offset < 0) {
1451 $sampleBinary = substr($this->_samples, $offset, $sampleBytes);
1454 switch ($this->_bitsPerSample) {
1457 return (
float) ((ord($sampleBinary) - 0x80) / 0x80);
1461 $data = unpack(
'v', $sampleBinary);
1463 if ($sample >= 0x8000) {
1466 return (
float) ($sample / 0x8000);
1470 $data = unpack(
'C3', $sampleBinary);
1472 if ($sample >= 0x800000) {
1473 $sample -= 0x1000000;
1475 return (
float) ($sample / 0x800000);
1479 $data = unpack(
'f', $sampleBinary);
1480 return (
float)
$data[1];
1500 if ($channelNum < 1 || $channelNum > $this->_numChannels) {
1504 if (!$this->_dataSize_valid) {
1510 $sampleBytes = $bitsPerSample / 8;
1511 $offset = $blockNum * $this->_blockAlign + ($channelNum - 1) * $sampleBytes;
1512 if (($offset + $sampleBytes > $dataSize && $offset != $dataSize) || $offset < 0) {
1513 throw new WavFileException(
'Sample block or channel number is out of range.');
1518 if ($bitsPerSample == 32) {
1519 $sample = $sampleFloat < -1.0 ? -1.0 : ($sampleFloat > 1.0 ? 1.0 : $sampleFloat);
1521 $p = 1 << ($bitsPerSample - 1);
1524 $sample = $sampleFloat < 0 ? (int) ($sampleFloat * $p - 0.5) : (int) ($sampleFloat * $p + 0.5);
1527 if ($sample < -$p) {
1529 } elseif ($sample > $p - 1) {
1535 switch ($bitsPerSample) {
1538 $sampleBinary = chr($sample + 0x80);
1546 $sampleBinary = pack(
'v', $sample);
1552 $sample += 0x1000000;
1554 $sampleBinary = pack(
'C3', $sample & 0xff, ($sample >> 8) & 0xff, ($sample >> 16) & 0xff);
1559 $sampleBinary = pack(
'f', $sample);
1563 $sampleBinary = null;
1569 if ($offset == $dataSize) {
1571 $this->_samples .= $sampleBinary;
1572 $this->_dataSize += $sampleBytes;
1573 $this->_chunkSize += $sampleBytes;
1574 $this->_actualSize += $sampleBytes;
1575 $this->_numBlocks = (int) ($this->_dataSize / $this->_blockAlign);
1578 for (
$i = 0;
$i < $sampleBytes; ++
$i) {
1579 $this->_samples{$offset +
$i} = $sampleBinary{$i};
1615 public function filter($filters, $blockOffset = 0, $numBlocks = null)
1620 if (is_null($numBlocks)) {
1621 $numBlocks = $totalBlocks - $blockOffset;
1624 if (!is_array($filters) || empty($filters) || $blockOffset < 0 || $blockOffset > $totalBlocks || $numBlocks <= 0) {
1630 $filter_mix =
false;
1631 if (array_key_exists(self::FILTER_MIX, $filters)) {
1632 if (!is_array($filters[self::FILTER_MIX])) {
1634 $filters[self::FILTER_MIX] = array(
'wav' => $filters[self::FILTER_MIX]);
1637 $mix_wav = @$filters[self::FILTER_MIX][
'wav'];
1638 if (!($mix_wav instanceof
WavFile)) {
1640 } elseif ($mix_wav->getSampleRate() != $this->
getSampleRate()) {
1641 throw new WavFileException(
"Sample rate of WavFile to mix does not match.");
1642 } elseif ($mix_wav->getNumChannels() != $this->
getNumChannels()) {
1643 throw new WavFileException(
"Number of channels of WavFile to mix does not match.");
1646 $mix_loop = @$filters[self::FILTER_MIX][
'loop'];
1647 if (is_null($mix_loop)) {
1651 $mix_blockOffset = @$filters[self::FILTER_MIX][
'blockOffset'];
1652 if (is_null($mix_blockOffset)) {
1653 $mix_blockOffset = 0;
1656 $mix_totalBlocks = $mix_wav->getNumBlocks();
1657 $mix_numBlocks = @$filters[self::FILTER_MIX][
'numBlocks'];
1658 if (is_null($mix_numBlocks)) {
1659 $mix_numBlocks = $mix_loop ? $mix_totalBlocks : $mix_totalBlocks - $mix_blockOffset;
1661 $mix_maxBlock = min($mix_blockOffset + $mix_numBlocks, $mix_totalBlocks);
1666 $filter_normalize =
false;
1667 if (array_key_exists(self::FILTER_NORMALIZE, $filters)) {
1668 $normalize_threshold = @$filters[self::FILTER_NORMALIZE];
1670 if (!is_null($normalize_threshold) && abs($normalize_threshold) != 1) {
1671 $filter_normalize =
true;
1675 $filter_degrade =
false;
1676 if (array_key_exists(self::FILTER_DEGRADE, $filters)) {
1677 $degrade_quality = @$filters[self::FILTER_DEGRADE];
1678 if (is_null($degrade_quality)) {
1679 $degrade_quality = 1;
1682 if ($degrade_quality >= 0 && $degrade_quality < 1) {
1683 $filter_degrade =
true;
1689 for ($block = 0; $block < $numBlocks; ++$block) {
1691 for ($channel = 1; $channel <= $numChannels; ++$channel) {
1693 $currentBlock = $blockOffset + $block;
1700 $mixBlock = ($mix_blockOffset + ($block % $mix_numBlocks)) % $mix_totalBlocks;
1702 $mixBlock = $mix_blockOffset + $block;
1705 if ($mixBlock < $mix_maxBlock) {
1706 $sampleFloat += $mix_wav->getSampleValue($mixBlock, $channel);
1711 if ($filter_normalize) {
1712 $sampleFloat = $this->
normalizeSample($sampleFloat, $normalize_threshold);
1716 if ($filter_degrade) {
1717 $sampleFloat += rand(1000000 * ($degrade_quality - 1), 1000000 * (1 - $degrade_quality)) / 1000000;
1744 throw new WavFileException(
"Number of channels for wav files do not match.");
1747 $this->_samples .= $wav->_samples;
1763 return $this->
filter(array(
1776 $numSamples = (int) ($this->
getSampleRate() * abs($duration));
1780 if ($duration >= 0) {
1781 $this->_samples .=
$data;
1798 return $this->
filter(self::FILTER_DEGRADE, array(
1817 for (
$s = 0;
$s < $numSamples; ++
$s) {
1818 if ($bitDepth == 32) {
1819 $val = rand(-$percent * 10000, $percent * 10000) / 1000000;
1821 $val = rand($minAmp, $maxAmp);
1822 $val = (int) ($val * $percent / 100);
1825 $this->_samples .= str_repeat(self::packSample($val, $bitDepth), $numChannels);
1847 array(self::FILTER_MIX => $this),
1853 ->setBitsPerSample($bitsPerSample);
1854 $this->_samples = $tempWav->_samples;
1869 $s =
"File Size: %u\n" 1870 .
"Chunk Size: %u\n" 1871 .
"fmt Subchunk Size: %u\n" 1872 .
"Extended fmt Size: %u\n" 1873 .
"fact Subchunk Size: %u\n" 1874 .
"Data Offset: %u\n" 1876 .
"Audio Format: %s\n" 1877 .
"Audio SubFormat: %s\n" 1879 .
"Channel Mask: 0x%s\n" 1880 .
"Sample Rate: %u\n" 1881 .
"Bits Per Sample: %u\n" 1882 .
"Valid Bits Per Sample: %u\n" 1883 .
"Sample Block Size: %u\n" 1884 .
"Number of Sample Blocks: %u\n" 1885 .
"Byte Rate: %uBps\n";
1896 $this->
getAudioFormat() == self::WAVE_FORMAT_PCM ?
'PCM' : ($this->
getAudioFormat() == self::WAVE_FORMAT_IEEE_FLOAT ?
'IEEE FLOAT' :
'EXTENSIBLE'),
1908 if (php_sapi_name() ==
'cli') {
setSampleValue($sampleFloat, $blockNum, $channelNum)
Sets a float sample value for a specific sample block number and channel.
const SPEAKER_FRONT_RIGHT_OF_CENTER
const SPEAKER_TOP_BACK_RIGHT
closeWav()
Close a with openWav() previously opened wav file or free the buffer of setWavData().
const SPEAKER_TOP_BACK_LEFT
static packSampleBlock($samples, $bitDepth)
Packs an array of numeric channel samples to a binary sample block.
displayInfo()
Output information about the wav object.
const WAVE_FORMAT_EXTENSIBLE
WavFileException indicates an illegal state or argument in this class.
setByteRate($byteRate=null)
const SPEAKER_TOP_FRONT_LEFT
save($filename)
Save the wav data to a file.
const SPEAKER_TOP_FRONT_RIGHT
static packSample($sample, $bitDepth)
Packs a single numeric sample to binary.
const SPEAKER_FRONT_CENTER
readWav($readData=true)
Read wav file from a stream.
setFmtChunkSize($fmtChunkSize=null)
setAudioFormat($audioFormat=null)
setFmtExtendedSize($fmtExtendedSize=null)
setActualSize($actualSize=null)
setWavData(&$data, $free=true)
Set the wav file data and properties from a wav file in a string.
setChunkSize($chunkSize=null)
const SPEAKER_DEFAULT
Channel Locations for ChannelMask.
setDataOffset($dataOffset=null)
getSampleValue($blockNum, $channelNum)
Get a float sample value for a specific sample block and channel number.
appendWav(WavFile $wav)
Append a wav file to the current wav.
setSampleBlock($sampleBlock, $blockNum)
Set a single sample block.
readWavData($dataOffset=0, $dataSize=null)
Read the wav data from the file into the buffer.
generateNoise($duration=1.0, $percent=100)
Generate noise at the end of the wav for the specified duration and volume.
openWav($filename, $readData=true)
Reads a wav header and data from a file.
const SPEAKER_FRONT_LEFT_OF_CENTER
setValidBitsPerSample($validBitsPerSample=null)
const SPEAKER_TOP_BACK_CENTER
setChannelMask($channelMask=self::SPEAKER_DEFAULT)
filter($filters, $blockOffset=0, $numBlocks=null)
Run samples through audio processing filters.
__toString()
Output the wav file headers and data.
makeHeader()
Construct a wav header from this object.
static unpackSampleBlock($sampleBlock, $bitDepth, $numChannels=null)
Unpacks a binary sample block to numeric values.
getDataSubchunk()
Construct wav DATA chunk.
setNumChannels($numChannels)
const SPEAKER_LOW_FREQUENCY
__construct($numChannelsOrFileName=null, $sampleRateOrReadData=null, $bitsPerSample=null)
WavFile Constructor.
setBitsPerSample($bitsPerSample)
setDataSize($dataSize=null)
static normalizeSample($sampleFloat, $threshold)
Normalizes a float audio sample.
const WAVE_FORMAT_IEEE_FLOAT
convertBitsPerSample($bitsPerSample)
Convert sample data to different bits per sample.
setFactChunkSize($factChunkSize=null)
setAudioSubFormat($audioSubFormat=null)
const SPEAKER_BACK_CENTER
setNumBlocks($numBlocks=null)
const SPEAKER_FRONT_RIGHT
setSampleRate($sampleRate)
insertSilence($duration=1.0)
Add silence to the wav file.
readWavHeader()
Parse a wav header.
const WAVE_SUBFORMAT_IEEE_FLOAT
const SPEAKER_TOP_FRONT_CENTER
setBlockAlign($blockAlign=null)
static unpackSample($sampleBinary, $bitDepth=null)
Unpacks a single binary sample to numeric value.
mergeWav(WavFile $wav, $normalizeThreshold=null)
Mix 2 wav files together.
degrade($quality=1.0)
Degrade the quality of the wav file by introducing random noise.
getSampleBlock($blockNum)
Return a single sample block from the file.