You might need to increase PHP's memory limit.
Code: Select all
<?php
// trash CSW -> WAV generator
// v2
// 28 September 2022
// 'Diminished'
// PHP >=7, reduce stupidity
declare (strict_types=1);
define ("MAX_OP_FILE_LEN", 1000000000); // 1 GB
define ("MAX_PULSE_DURATION_SMPS", 60000000);
define ("MAX_NON_SILENT_PULSE_DURATION_SMPS", 100);
define ("WAV_HEADER_LEN", 44);
$argv = $_SERVER['argv'];
$argc = $_SERVER['argc'];
if ($argc != 3) {
print "Usage:\n php -f $argv[0] <CSW file> <output WAV file>\n\n";
print "Output file will be 16-bit mono.\n";
exit(1);
}
$csw_fn = $argv[1];
$op_fn = $argv[2];
if (!file_exists($csw_fn)) {
print "E: Not found: $csw_fn\n";
exit(2);
}
print "M: Loading $csw_fn ... ";
$csw = file_get_contents($csw_fn);
$csw_len = strlen($csw);
print "$csw_len bytes.\n";
$data = array();
$polarity = FALSE;
$rate = 0;
$e = parse_csw ($csw, $data, $polarity, $rate); // data, polarity, rate out
if (0 != $e) { exit($e); }
$e = save ($data, $op_fn, $polarity, $rate, true, 0.5);
exit($e);
function parse_csw (string $csw, array &$data, bool &$polarity, int &$rate) : int {
$rate = 0;
$h_len = 0;
$zip = false;
$polarity = false;
$e = parse_header ($csw, $rate, $h_len, $zip, $polarity); // rate, h_len, zip, polarity out
if ($e != 0) { return $e; }
print "\nM: Body starts at 0x".sprintf("%x", $h_len)."\n";
$e = parse_body (substr($csw, $h_len), $zip, $data); // data out
if ($e != 0) { return $e; }
print "M: Counted ".count($data)." pulses.\n";
return $e;
}
function save_header ($fp, int $file_len, int $rate) : int {
print "file_len = ".$file_len."\n";
$a = "RIFF".le4($file_len - 8)."WAVEfmt ".le4(16).le2(1).le2(1).le4($rate);
$j = (int) (16 * 1) / 8;
$i = ($rate * $j);
$a .= le4($i).le2($j).le2(16)."data".le4($file_len - WAV_HEADER_LEN);
if (FALSE === fwrite($fp, $a)) {
print "E: Updating output file header failed.\n";
return 16;
}
return 0;
}
function save (array $data,
string $opfn,
bool $polarity,
int $rate,
bool $use_sine,
float $amplitude) : int {
$op = "";
$c=0;
if (FALSE === ($fp = fopen($opfn, "wb"))) {
print "E: Could not open output file $opfn\n";
return 10;
}
// temporary fake header
if (FALSE === fwrite ($fp, str_repeat("\0", WAV_HEADER_LEN))) {
print "E: Error writing to output file $opfn\n";
return 11;
}
// looking up existing calculated sine values seems to improve performance
// by maybe 10-20%, so
$sin_lookup = array();
for ($i=0; $i < count($data); $i++) {
$p = $data[$i];
// write one pulse
if ($p >= MAX_PULSE_DURATION_SMPS) {
print "E: Maximum pulse duration exceeded ($p >= ".MAX_PULSE_DURATION_SMPS."), pulse #$i\n";
return 13;
}
$v = 1.0;
$us = $use_sine;
$flip = $polarity?1.0:-1.0;
if ($p >= MAX_NON_SILENT_PULSE_DURATION_SMPS) {
// silence, don't render
$v = 0.0;
$us = false;
} else {
if (isset($sin_lookup[$p])) {
$have_lookup = 1;
} else {
$have_lookup = 0;
$sin_lookup[$p] = array();
}
}
for ($j=0; $j<$p; $j++) {
if ( $us ) {
if ( ! $have_lookup ) {
$v = sin(($j/(float)$p)*M_PI);
$sin_lookup[$p][$j] = $v;
} else {
// re-use lookup table value
$v = $sin_lookup[$p][$j];
}
}
$s = le2((int)round($flip * 32767.0 * $amplitude * $v));
$op .= $s;
$c+=2;
}
if ($c >= MAX_OP_FILE_LEN) {
print "E: Maximum output file len exceeded\n";
return 12;
}
if ((strlen($op) > 1000000) || ($i == (count($data) - 1))) {
// flush buffer
if (FALSE === fwrite($fp, $op)) {
print "E: Error writing to output file $opfn\n";
return 11;
}
$op="";
}
// reverse polarity for next pulse
$polarity = ! $polarity;
}
$flen = 0;
if (FALSE === ($flen = ftell($fp))) {
print "E: ftell on output file $opfn failed\n";
return 15;
}
if (FALSE === fseek($fp, 0)) {
print "E: fseek back to header failed writing output file $opfn\n";
return 14;
}
$e = save_header($fp, $flen, $rate);
fclose($fp);
print "M: Wrote $c bytes.\n";
//print_r($sin_lookup);
return $e;
}
function parse_header (string $csw,
int &$rate,
int &$h_len,
bool &$zipped,
bool &$polarity) : int {
$magic_s = substr($csw, 0, 25);
$rate_s = substr($csw, 25, 4);
$num_pulses_s = substr($csw, 29, 4);
$cmp_type_s = substr($csw, 33, 1);
$flags_s = substr($csw, 34, 1);
$hdr_extlen_s = substr($csw, 35, 1);
$appdesc_s = substr($csw, 36, 16);
$rate=0;
$num_pulses=0;
if (0 != ($e = parse_le4 ($rate_s, $rate))) { return $e; }
if (0 != ($e = parse_le4 ($num_pulses_s, $num_pulses))) { return $e; }
$cmp_type = ord($cmp_type_s);
$flags = ord($flags_s);
if (($cmp_type != 1) && ($cmp_type != 2)) {
print "E: Bad compression type: 0x".sprintf("%02x", $cmp_type)."\n";
return 5;
}
if ($flags & 0xfe) {
print "E: Bad flags: 0x".sprintf("%02x", $flags)."\n";
return 6;
}
$polarity = (1 == ($flags & 1));
$hdr_extlen = ord($hdr_extlen_s);
if ($magic_s !== "Compressed Square Wave\x1a\x02\x00") {
print "E: Bad juju\n";
return 7;
}
print "App: \"$appdesc_s\"\n";
print "Rate: $rate\n";
print "Pulses: $num_pulses\n";
print "Polarity: ".($polarity?"starts high":"starts low")."\n";
$zipped = ($cmp_type == 2);
print "Zipped: ". ($zipped ? "yes" : "no")."\n";
print "Hdr. ext. len.: $hdr_extlen\n";
$h_len = 52 + $hdr_extlen;
return 0;
}
function parse_body (string $body,
bool $zip,
array &$values) : int {
$e = 0;
if ($zip) {
$e = body_unzip($body); // body modified
if (0 != $e) { return $e; }
}
$len = strlen($body);
for ($i=0; $i < $len; $i++) {
$b = ord($body[$i]);
if ($b == 0) {
if (($i+4) >= $len) {
print "E: long pulse overflows buffer\n";
return 9;
}
$e = parse_le4 (substr($body, $i+1, 4), $b);
print "M: Long pulse, body offset $i (0x".sprintf("%x",$i)."): $b\n";
if (0 != $e) { return $e; }
$i+=4;
}
$values[] = $b;
}
return $e;
}
function body_unzip (string &$b) : int {
$in = $b;
$b = zlib_decode($in);
if (false === $b) {
print "E: zlib decode failed.\n";
return 8;
}
print "M: Unzipped body from ".strlen($in)." to ".strlen($b)." bytes.\n";
return 0;
}
function parse_le4 (string $s, int &$i) : int {
$len = strlen($s);
if ($len != 4) {
print "E: parse_le4: string has len $len, should be 4\n";
return 4;
}
$b = array();
for ($j=0; $j < 4; $j++) { $b[$j] = ord($s[$j]); }
$i=0;
$i |= $b[0];
$i |= ($b[1] << 8) & 0xff00;
$i |= ($b[2] << 16) & 0xff0000;
$i |= ($b[3] << 24) & 0xff000000;
return 0;
}
function le4 (int $i) : string {
$a="";
$a[0] = chr($i&0xff);
$a[1] = chr(($i>>8)&0xff);
$a[2] = chr(($i>>16)&0xff);
$a[3] = chr(($i>>24)&0xff);
return $a;
}
function le2 (int $i) : string {
$a="";
$a[0] = chr($i&0xff);
$a[1] = chr(($i>>8)&0xff);
return $a;
}
?>