From 8b88a9ac233e62486fc23b2a9f78cf301be40429 Mon Sep 17 00:00:00 2001
From: Jim O'Regan <jaoregan@tcd.ie>
Date: Mon, 24 Aug 2020 13:06:29 +0100
Subject: [PATCH] Add NSP support

---
 src/Makefile.am |   2 +-
 src/formats.c   |   1 +
 src/formats.h   |   1 +
 src/nsp.c       | 178 ++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 181 insertions(+), 1 deletion(-)
 create mode 100644 src/nsp.c

diff --git a/src/Makefile.am b/src/Makefile.am
index f5f456ec..f51c6402 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -117,7 +117,7 @@ libsox_la_SOURCES += raw-fmt.c s1-fmt.c s2-fmt.c s3-fmt.c \
   lu-fmt.c 8svx.c aiff-fmt.c aifc-fmt.c au.c avr.c cdr.c cvsd-fmt.c \
   dvms-fmt.c dat.c hcom.c htk.c maud.c prc.c sf.c smp.c \
   sounder.c soundtool.c sphere.c tx16w.c voc.c vox-fmt.c ima-fmt.c adpcm.c adpcm.h \
-  ima_rw.c ima_rw.h wav.c wve.c xa.c nulfile.c f4-fmt.c f8-fmt.c gsrt.c
+  ima_rw.c ima_rw.h wav.c wve.c xa.c nulfile.c f4-fmt.c f8-fmt.c gsrt.c nsp.c
 
 libsox_la_LIBADD += @LPC10_LIBS@ @LIBLPC10_LIBADD@
 
diff --git a/src/formats.c b/src/formats.c
index 2adc88b3..33036b06 100644
--- a/src/formats.c
+++ b/src/formats.c
@@ -72,6 +72,7 @@ static char const * auto_detect_format(sox_format_t * ft, char const * ext)
   CHECK(aifc  , 0, 4, "FORM" , 8,  4, "AIFC")
   CHECK(8svx  , 0, 4, "FORM" , 8,  4, "8SVX")
   CHECK(maud  , 0, 4, "FORM" , 8,  4, "MAUD")
+  CHECK(nsp   , 0, 4, "FORM" , 4,  4, "DS16")
   CHECK(xa    , 0, 0, ""     , 0,  4, "XA\0\0")
   CHECK(xa    , 0, 0, ""     , 0,  4, "XAI\0")
   CHECK(xa    , 0, 0, ""     , 0,  4, "XAJ\0")
diff --git a/src/formats.h b/src/formats.h
index a42ce270..b931d83f 100644
--- a/src/formats.h
+++ b/src/formats.h
@@ -36,6 +36,7 @@
   FORMAT(la)
   FORMAT(lu)
   FORMAT(maud)
+  FORMAT(nsp)
   FORMAT(nul)
   FORMAT(prc)
   FORMAT(raw)
diff --git a/src/nsp.c b/src/nsp.c
new file mode 100644
index 00000000..e9f128c5
--- /dev/null
+++ b/src/nsp.c
@@ -0,0 +1,178 @@
+/* libSoX CSL NSP format.
+ * http://web.archive.org/web/20160525045942/http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/CSL/CSL.html
+ *
+ * Copyright 2017 Jim O'Regan
+ *
+ * based on aiff.c
+ * Copyright 1991-2007 Guido van Rossum And Sundry Contributors
+ *
+ * This source code is freely redistributable and may be used for
+ * any purpose.  This copyright notice must be maintained.
+ * Guido van Rossum And Sundry Contributors are not responsible for
+ * the consequences of using this software.
+ */
+
+#include "sox_i.h"
+
+#include <time.h>      /* for time stamping comments */
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <limits.h>
+
+int lsx_nspstartread(sox_format_t * ft);
+
+int lsx_nspstartread(sox_format_t * ft)
+{
+  char buf[5];
+  uint32_t hchunksize;
+  uint32_t chunksize;
+  unsigned short channels = 0;
+  sox_encoding_t enc = SOX_ENCODING_SIGN2;
+  unsigned short bits = 16;
+  double rate = 0.0;
+  uint64_t seekto = 0;
+  int i;
+  size_t ssndsize = 0;
+
+  char date[20];
+  char *comment;
+  uint16_t maxabschan[8];
+  uint32_t datalength;
+  uint32_t samplerate;
+  int numchannels;
+
+  uint8_t trash8;
+
+  /* FORM chunk */
+  if (lsx_reads(ft, buf, (size_t)8) == SOX_EOF || strncmp(buf, "FORMDS16", (size_t)8) != 0) {
+    lsx_fail_errno(ft,SOX_EHDR,"NSP header does not begin with magic word `FORMDS16'");
+    return(SOX_EOF);
+  }
+  lsx_readdw(ft, &hchunksize);
+
+  while (1) {
+    if (lsx_reads(ft, buf, (size_t)4) == SOX_EOF) {
+      if (ssndsize > 0)
+        break;
+      else {
+        lsx_fail_errno(ft,SOX_EHDR,"Missing SDA_ chunk in NSP file");
+        return(SOX_EOF);
+      }
+    }
+    if (strncmp(buf, "HEDR", (size_t)4) == 0) {
+      /* HEDR chunk */
+      lsx_readdw(ft, &chunksize);
+      lsx_reads(ft, date, (size_t)20);
+      lsx_readdw(ft, &samplerate);
+      rate = (double)samplerate;
+      lsx_readdw(ft, &datalength);
+      lsx_readw(ft, &maxabschan[0]);
+      lsx_readw(ft, &maxabschan[1]);
+
+      /* Most likely there will only be 1 channel, but there can be 2 here */
+      if (maxabschan[0] == 0xffff && maxabschan[1] == 0xffff) {
+        lsx_fail_errno(ft,SOX_EHDR,"Channels A and B undefined");
+      } else if (maxabschan[0] == 0xffff || maxabschan[1] == 0xffff) {
+        ft->signal.channels = 1;
+      } else {
+        ft->signal.channels = 2;
+      }
+    } else if (strncmp(buf, "HDR8", (size_t)4) == 0) {
+      /* HDR8 chunk */
+      lsx_readdw(ft, &chunksize);
+      lsx_reads(ft, date, (size_t)20);
+      lsx_readdw(ft, &samplerate);
+      rate = (double)samplerate;
+      lsx_readdw(ft, &datalength);
+      lsx_readw(ft, &maxabschan[0]);
+      lsx_readw(ft, &maxabschan[1]);
+      lsx_readw(ft, &maxabschan[2]);
+      lsx_readw(ft, &maxabschan[3]);
+      lsx_readw(ft, &maxabschan[4]);
+      lsx_readw(ft, &maxabschan[5]);
+      lsx_readw(ft, &maxabschan[6]);
+      lsx_readw(ft, &maxabschan[7]);
+
+      /* Can be up to 8 channels */
+      numchannels = 0;
+      for (i = 0; i < 7; i++) {
+        if (maxabschan[i] != 0xffff) {
+          numchannels++;
+        }
+      }
+      if (numchannels == 0) {
+        lsx_fail_errno(ft,SOX_EHDR,"No channels defined");
+      }
+      ft->signal.channels = numchannels;
+    } else if (strncmp(buf, "NOTE", (size_t)4) == 0) {
+      unsigned char nullc = 0;
+      /* NOTE chunk */
+      lsx_readdw(ft, &chunksize);
+      comment = lsx_malloc(chunksize * sizeof(char*));
+      lsx_reads(ft, comment, (size_t)chunksize);
+      if(strlen(comment) != 0)
+        lsx_debug("NSP comment: %s %d", comment);
+      free(comment);
+      lsx_readb(ft, &nullc);
+    } else if (strncmp(buf, "SDA_", (size_t)4) == 0) {
+      lsx_readdw(ft, &chunksize);
+      ssndsize = chunksize;
+      /* if can't seek, just do sound now */
+      if (!ft->seekable)
+        break;
+      /* else, seek to end of sound and hunt for more */
+      seekto = lsx_tell(ft);
+      lsx_seeki(ft, (off_t)chunksize, SEEK_CUR);
+    } else {
+      if (lsx_eof(ft))
+        break;
+      buf[4] = 0;
+      lsx_debug("NSPstartread: ignoring `%s' chunk", buf);
+      lsx_readdw(ft, &chunksize);
+      if (lsx_eof(ft))
+        break;
+      /* Skip the chunk using lsx_readb() so we may read
+         from a pipe */
+      while (chunksize-- > 0) {
+        if (lsx_readb(ft, &trash8) == SOX_EOF)
+          break;
+      }
+    }
+    if (lsx_eof(ft))
+      break;
+  }
+
+  if (ft->seekable) {
+    if (seekto > 0)
+      lsx_seeki(ft, seekto, SEEK_SET);
+    else {
+      lsx_fail_errno(ft,SOX_EOF,"NSP: no sound data on input file");
+      return(SOX_EOF);
+    }
+  }
+
+  return lsx_check_read_params(
+      ft, channels, rate, enc, bits, (uint64_t)ssndsize/2, sox_false);
+}
+
+static int lsx_nspstopread(sox_format_t * ft)
+{
+    ft->sox_errno = SOX_SUCCESS;
+
+    return SOX_SUCCESS;
+}
+
+LSX_FORMAT_HANDLER(nsp)
+{
+  static char const * const names[] = {"nsp", NULL };
+  static sox_format_handler_t const handler = {SOX_LIB_VERSION_CODE,
+    "Computerized Speech Lab NSP file",
+    names, SOX_FILE_LIT_END,
+    lsx_nspstartread, lsx_rawread, NULL,
+    NULL, NULL, NULL,
+    NULL, NULL, NULL, 0
+  };
+  return &handler;
+}
