csw2wav.php

discuss pc<>acorn file transfer issues and the use of other utils
Post Reply
User avatar
Diminished
Posts: 1235
Joined: Fri Dec 08, 2017 9:47 pm
Contact:

csw2wav.php

Post by Diminished »

Here's some dire PHP trash which will convert a CSW into a WAV file. I needed something to perform this operation on a Mac. No proper testing has been done. WAVs load in Audacity, look okay, sound okay. Meh.

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;
  }

?>
Last edited by Diminished on Wed Sep 28, 2022 10:15 pm, edited 1 time in total.
User avatar
geraldholdsworth
Posts: 1401
Joined: Tue Nov 04, 2014 9:42 pm
Location: Inverness, Scotland
Contact:

Re: csw2wav.php

Post by geraldholdsworth »

Wow - fabulous.
Gerald Holdsworth, CTS-D
Extron Authorised Programmer
https://www.geraldholdsworth.co.uk
https://www.reptonresourcepage.co.uk
Twitter @radiogezza
User avatar
Diminished
Posts: 1235
Joined: Fri Dec 08, 2017 9:47 pm
Contact:

Re: csw2wav.php

Post by Diminished »

I just updated the above to v2, as I found a dumb mistake.
Post Reply

Return to “software & utilities for the pc, mac or unix”