shithub: sox

Download patch

ref: 14bec174f551f6441157ffda61263d9fb89b64a7
parent: e40568187bda53358aa1ff5b879e01918828c45b
author: rrt <rrt>
date: Tue Feb 13 11:29:25 EST 2007

Add support for ADPCM-encoded PRC files. To do this, move the ADPCM
code from vox.c into adpcms.c, and make the functions public, adding
st_adpcm_flush and st_adpcm_reset, because unlike vox, PRC files use
frames, so need to reset the encoder while reading and writing.

Basically, you shouldn't write this format unless you have to, as
Psion Record seems to insist on frames of <=800 samples, which means
that the encoder is reset every tenth of a second. If you try this
with a pure tone, you'll find out why it's horrible. I've kept this
restriction for compatibility, though in theory it could be lifted
(with one frame per SoX buffer, i.e. by default once per 8192 bytes,
or roughly once a second, the results are fine).

Hence, the main use for this is for reading ADPCM-encoded PCM files
(the decoder copes with arbitrary frame lengths).

I owe a big debt of gratitude to David Smith (drsmith@iee.org) for his
sndcmp and rec2wav utilities, whose framing code I used, and whose
description of the file format I relied on (the code I used originates
from the Mathematisch Centrum, and was released under a free-use and
hence LGPL-compatible license).

--- a/src/adpcms.c
+++ b/src/adpcms.c
@@ -16,11 +16,9 @@
 
 /* ADPCM CODECs: IMA, OKI.   (c) 2007 robs@users.sourceforge.net */
 
-#include "adpcms.h"
 #include "st_i.h"
+#include "adpcms.h"
 
-#define range_limit(x,min,max)((x)<(min)?(min):(x)>(max)?(max):(x))
-
 static int const ima_steps[89] = { /* ~16-bit precision */
   7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 19, 21, 23, 25, 28, 31, 34, 37, 41, 45,
   50, 55, 60, 66, 73, 80, 88, 97, 107, 118, 130, 143, 157, 173, 190, 209, 230,
@@ -38,7 +36,7 @@
 
 static int const step_changes[8] = {-1, -1, -1, -1, 2, 4, 6, 8};
 
-void adpcm_init(adpcm_t state, int type)
+static void adpcm_init(adpcm_t state, int type)
 {
   state->last_output = 0;
   state->step_index = 0;
@@ -47,13 +45,13 @@
   state->mask = type? ~15 : ~0;
 }
 
-int adpcm_decode(int code, adpcm_t state)
+static int adpcm_decode(int code, adpcm_t state)
 {
   int s = ((code & 7) << 1) | 1;
   s = ((state->steps[state->step_index] * s) >> 3) & state->mask;
   if (code & 8)
     s = -s;
-  s = state->last_output + s;
+  s += state->last_output;
   s = range_limit(s, -0x8000, 0x7fff);
   state->step_index += step_changes[code & 0x07];
   state->step_index = range_limit(state->step_index, 0, state->max_step_index);
@@ -60,7 +58,7 @@
   return state->last_output = s;
 }
 
-int adpcm_encode(int sample, adpcm_t state)
+static int adpcm_encode(int sample, adpcm_t state)
 {
   int delta = sample - state->last_output;
   int sign = 0;
@@ -73,4 +71,218 @@
   code = sign | min(code, 7);
   adpcm_decode(code, state); /* Update encoder state */
   return code;
+}
+
+
+/*
+ * Format methods
+ *
+ * Almost like the raw format functions, but cannot be used directly
+ * since they require an additional state parameter.
+ */
+
+/******************************************************************************
+ * Function   : st_adpcm_reset
+ * Description: Resets the ADPCM codec state.
+ * Parameters : state - ADPCM state structure
+ *              type - ST_ENCODING_OKI_ADPCM or ST_ENCODING_IMA_ADPCM
+ * Returns    :
+ * Exceptions :
+ * Notes      : 1. This function is used for framed ADPCM formats to reset
+ *                 the decoder between frames.
+ ******************************************************************************/
+
+void st_adpcm_reset(adpcm_io_t state, st_encoding_t type)
+{
+  state->file.count = 0;
+  state->file.pos = 0;
+  state->file.eof = 0;
+  state->store.byte = 0;
+  state->store.flag = 0;
+
+  adpcm_init(&state->encoder, (type == ST_ENCODING_OKI_ADPCM) ? 1 : 0);
+}
+
+/******************************************************************************
+ * Function   : adpcm_start
+ * Description: Initialises the file parameters and ADPCM codec state.
+ * Parameters : ft  - file info structure
+ *              state - ADPCM state structure
+ *              type - ST_ENCODING_OKI_ADPCM or ST_ENCODING_IMA_ADPCM
+ * Returns    : int - ST_SUCCESS
+ *                    ST_EOF
+ * Exceptions :
+ * Notes      : 1. This function can be used as a startread or
+ *                 startwrite method.
+ *              2. VOX file format is 4-bit OKI ADPCM that decodes to 
+ *                 to 12 bit signed linear PCM.
+ *              3. Dialogic only supports 6kHz, 8kHz and 11 kHz sampling
+ *                 rates but the codecs allows any user specified rate.
+ ******************************************************************************/
+
+static int adpcm_start(ft_t ft, adpcm_io_t state, st_encoding_t type)
+{
+  /* setup file info */
+  state->file.buf = (char *) xmalloc(ST_BUFSIZ);
+  state->file.size = ST_BUFSIZ;
+  ft->signal.channels = 1;
+
+  st_adpcm_reset(state, type);
+  
+  return st_rawstart(ft, st_true, st_false, type, ST_SIZE_16BIT, ST_OPTION_DEFAULT);
+}
+
+int st_adpcm_oki_start(ft_t ft, adpcm_io_t state)
+{
+  return adpcm_start(ft, state, ST_ENCODING_OKI_ADPCM);
+}
+
+int st_adpcm_ima_start(ft_t ft, adpcm_io_t state)
+{
+  return adpcm_start(ft, state, ST_ENCODING_IMA_ADPCM);
+}
+
+/******************************************************************************
+ * Function   : st_adpcm_read 
+ * Description: Fills an internal buffer from the VOX file, converts the 
+ *              OKI ADPCM 4-bit samples to 12-bit signed PCM and then scales 
+ *              the samples to full range 16 bit PCM.
+ * Parameters : ft     - file info structure
+ *              state  - ADPCM state structure
+ *              buffer - output buffer
+ *              length - size of output buffer
+ * Returns    : int    - number of samples returned in buffer
+ * Exceptions :
+ * Notes      : 
+ ******************************************************************************/
+
+st_size_t st_adpcm_read(ft_t ft, adpcm_io_t state, st_sample_t * buffer, st_size_t len)
+{
+  st_size_t n;
+  uint8_t byte;
+
+  for (n = 0; n < (len&~1) && st_readb(ft, &byte) == ST_SUCCESS; n += 2) {
+    short word = adpcm_decode(byte >> 4, &state->encoder);
+    *buffer++ = ST_SIGNED_WORD_TO_SAMPLE(word, ft->clips);
+
+    word = adpcm_decode(byte, &state->encoder);
+    *buffer++ = ST_SIGNED_WORD_TO_SAMPLE(word, ft->clips);
+  }
+  return n;
+}
+
+/******************************************************************************
+ * Function   : stopread 
+ * Description: Frees the internal buffer allocated in voxstart/imastart.
+ * Parameters : ft   - file info structure
+ *              state  - ADPCM state structure
+ * Returns    : int  - ST_SUCCESS
+ * Exceptions :
+ * Notes      : 
+ ******************************************************************************/
+
+int st_adpcm_stopread(ft_t ft UNUSED, adpcm_io_t state)
+{
+  free(state->file.buf);
+
+  return (ST_SUCCESS);
+}
+
+
+/******************************************************************************
+ * Function   : write
+ * Description: Converts the supplied buffer to 12 bit linear PCM and encodes
+ *              to OKI ADPCM 4-bit samples (packed a two nibbles per byte).
+ * Parameters : ft     - file info structure
+ *              state  - ADPCM state structure
+ *              buffer - output buffer
+ *              length - size of output buffer
+ * Returns    : int    - ST_SUCCESS
+ *                       ST_EOF
+ * Exceptions :
+ * Notes      : 
+ ******************************************************************************/
+
+st_size_t st_adpcm_write(ft_t ft, adpcm_io_t state, const st_sample_t * buffer, st_size_t length)
+{
+  st_size_t count = 0;
+  uint8_t byte = state->store.byte;
+  uint8_t flag = state->store.flag;
+  short word;
+
+  while (count < length) {
+    word = ST_SAMPLE_TO_SIGNED_WORD(*buffer++, ft->clips);
+
+    byte <<= 4;
+    byte |= adpcm_encode(word, &state->encoder) & 0x0F;
+
+    flag = !flag;
+
+    if (flag == 0) {
+      state->file.buf[state->file.count++] = byte;
+
+      if (state->file.count >= state->file.size) {
+        st_writebuf(ft, state->file.buf, 1, state->file.count);
+
+        state->file.count = 0;
+      }
+    }
+
+    count++;
+  }
+
+  /* keep last byte across calls */
+
+  state->store.byte = byte;
+  state->store.flag = flag;
+
+  return (count);
+}
+
+/******************************************************************************
+ * Function   : st_adpcm_flush
+ * Description: Flushes any leftover samples.
+ * Parameters : ft   - file info structure
+ *              state  - ADPCM state structure
+ * Returns    :
+ * Exceptions :
+ * Notes      : 1. Called directly for writing framed formats
+ ******************************************************************************/
+
+void st_adpcm_flush(ft_t ft, adpcm_io_t state)
+{
+  uint8_t byte = state->store.byte;
+  uint8_t flag = state->store.flag;
+
+  /* flush remaining samples */
+
+  if (flag != 0) {
+    byte <<= 4;
+    byte |= adpcm_encode(0, &state->encoder) & 0x0F;
+
+    state->file.buf[state->file.count++] = byte;
+  }
+
+  if (state->file.count > 0)
+    st_writebuf(ft, state->file.buf, 1, state->file.count);
+}
+
+/******************************************************************************
+ * Function   : st_adpcm_stopwrite
+ * Description: Flushes any leftover samples and frees the internal buffer 
+ *              allocated in voxstart/imastart.
+ * Parameters : ft   - file info structure
+ *              state  - ADPCM state structure
+ * Returns    : int  - ST_SUCCESS
+ * Exceptions :
+ * Notes      : 
+ ******************************************************************************/
+
+int st_adpcm_stopwrite(ft_t ft, adpcm_io_t state)
+{
+  st_adpcm_flush(ft, state);
+
+  free(state->file.buf);
+
+  return (ST_SUCCESS);
 }
--- a/src/adpcms.h
+++ b/src/adpcms.h
@@ -1,3 +1,21 @@
+/*
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library.  If not, write to the Free Software Foundation,
+ * Fifth Floor, 51 Franklin Street, Boston, MA 02111-1301, USA.
+ */
+
+/* ADPCM CODECs: IMA, OKI.   (c) 2007 robs@users.sourceforge.net */
+
 typedef struct adpcm_struct
 {
   int last_output;
@@ -7,6 +25,21 @@
   int mask;
 } * adpcm_t;
 
-int adpcm_decode(int code, adpcm_t state);   /* 4-bit -> 16-bit */
-int adpcm_encode(int sample, adpcm_t state); /* 16-bit -> 4-bit */
-void adpcm_init(adpcm_t state, int type /* 0:IMA, 1:OKI */);
+typedef struct adpcm_io {
+  struct adpcm_struct encoder;
+  struct {
+    uint8_t byte;               /* write store */
+    uint8_t flag;
+  } store;
+  st_fileinfo_t file;
+} *adpcm_io_t;
+
+/* Format methods */
+void st_adpcm_reset(adpcm_io_t state, st_encoding_t type);
+int st_adpcm_oki_start(ft_t ft, adpcm_io_t state);
+int st_adpcm_ima_start(ft_t ft, adpcm_io_t state);
+st_size_t st_adpcm_read(ft_t ft, adpcm_io_t state, st_sample_t *buffer, st_size_t len);
+int st_adpcm_stopread(ft_t ft, adpcm_io_t state);
+st_size_t st_adpcm_write(ft_t ft, adpcm_io_t state, const st_sample_t *buffer, st_size_t length);
+void st_adpcm_flush(ft_t ft, adpcm_io_t state);
+int st_adpcm_stopwrite(ft_t ft, adpcm_io_t state);
--- a/src/prc.c
+++ b/src/prc.c
@@ -1,12 +1,12 @@
 /*
- * Psion Record format (format of files used for Revo,Revo+,Mako in 
- * System/Alarms to provide alarm sounds. Note that the file normally
- * has no extension, so I've made it .prc for now (Psion ReCord), until
- * somebody can come up with a better one.
+ * Psion Record format (format of sound files used for EPOC machines).
+ * The file normally has no extension, so SoX uses .prc (Psion ReCord).
  * Based (heavily) on the wve.c format file. 
  * Hacked by Bert van Leeuwen (bert@e.co.za)
- * Header check truncated to first 16 bytes (i.e. EPOC file header)
- * and other improvements by Reuben Thomas <rrt@sc3d.org>
+ * 
+ * Header check improved, ADPCM encoding added, and other improvements
+ * by Reuben Thomas <rrt@sc3d.org>, using file format info at
+ * http://software.frodo.looijaard.name/psiconv/formats/
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -21,29 +21,90 @@
  * You should have received a copy of the GNU Lesser General Public
  * License along with this library. If not, write to the Free Software
  * Foundation, Fifth Floor, 51 Franklin Street, Boston, MA 02111-1301,
- * USA.  */
+ * USA.
+ *
+ * Includes code for ADPCM framing based on code carrying the
+ * following copyright:
+ *
+ *******************************************************************
+ Copyright 1992 by Stichting Mathematisch Centrum, Amsterdam, The
+ Netherlands.
+ 
+                        All Rights Reserved
 
+ Permission to use, copy, modify, and distribute this software and its
+ documentation for any purpose and without fee is hereby granted,
+ provided that the above copyright notice appear in all copies and that
+ both that copyright notice and this permission notice appear in 
+ supporting documentation, and that the names of Stichting Mathematisch
+ Centrum or CWI not be used in advertising or publicity pertaining to
+ distribution of the software without specific, written prior permission.
+
+ STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO
+ THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE
+ FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+ OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ ******************************************************************/
+
+ 
 #include "st_i.h"
-#include "g72x.h"
+
+#include "adpcms.h"
+
+#include <assert.h>
 #include <string.h>
 #include <errno.h>
+#include <limits.h>
 
-typedef struct prcpriv
-    {
-    uint32_t length;
-    short padding;
-    short repeats;
-      /*For seeking */
-        st_size_t dataStart;
-    } *prc_t;
+typedef struct prcpriv {
+  uint32_t nsamp, nbytes;
+  short padding;
+  short repeats;
+  st_size_t data_start;         /* for seeking */
+  struct adpcm_io adpcm;
+  unsigned frame_samp;     /* samples left to read in current frame */
+} *prc_t;
 
-/* 16 bytes header = 3 UIDs plus checksum, standard Symbian/EPOC file
-   header */
+/* File header. The first 4 words are fixed; the rest of the header
+   could theoretically be different, and this is the first place to
+   check with apparently invalid files.
+
+   N.B. All offsets are from start of file. */
 static const char prc_header[]={
-  '\x37','\x00','\x00','\x10','\x6d','\x00','\x00','\x10',
-  '\x7e','\x00','\x00','\x10','\xcf','\xac','\x08','\x55'
+  /* Header section */
+  '\x37','\x00','\x00','\x10', /* 0x00: File type (UID 1) */
+  '\x6d','\x00','\x00','\x10', /* 0x04: File kind (UID 2) */
+  '\x7e','\x00','\x00','\x10', /* 0x08: Application ID (UID 3) */
+  '\xcf','\xac','\x08','\x55', /* 0x0c: Checksum of UIDs 1-3 */
+  '\x14','\x00','\x00','\x00', /* 0x10: File offset of Section Table Section */
+  /* Section Table Section: a BListL, i.e. a list of longs preceded by
+     length byte.
+     The longs are in (ID, offset) pairs, each pair identifying a
+     section. */
+  '\x04',                      /* 0x14: List has 4 bytes, i.e. 2 pairs */
+  '\x52','\x00','\x00','\x10', /* 0x15: ID: Record Section */
+  '\x34','\x00','\x00','\x00', /* 0x19: Offset to Record Section */
+  '\x89','\x00','\x00','\x10', /* 0x1d: ID: Application ID Section */
+  '\x25','\x00','\x00','\x00', /* 0x21: Offset to Application ID Section */
+  '\x7e','\x00','\x00','\x10', /* 0x25: Application ID Section:
+                                  Record.app identifier */
+  /* Next comes the string, which can be either case. */
 };
 
+/* Format of the Record Section (offset 0x34):
+
+00 L Uncompressed data length
+04 ID a1 01 00 10 for ADPCM, 00 00 00 00 for A-law
+08 W number of times sound will be repeated (0 = played once)
+0a B Volume setting (01-05)
+0b B Always 00 (?)
+0c L Time between repeats in usec
+10 LListB (i.e. long giving number of bytes followed by bytes) Sound Data
+*/
+
 int prc_checkheader(ft_t ft, char *head)
 {
   st_readbuf(ft, head, 1, sizeof(prc_header));
@@ -52,72 +113,200 @@
 
 static void prcwriteheader(ft_t ft);
 
-static int st_prcseek(ft_t ft, st_size_t offset)
+static int seek(ft_t ft, st_size_t offset)
 {
-    prc_t prc = (prc_t ) ft->priv;
-    st_size_t new_offset, channel_block, alignment;
+  prc_t prc = (prc_t)ft->priv;
+  st_size_t new_offset, channel_block, alignment;
 
-    new_offset = offset * ft->signal.size;
-    /* Make sure request aligns to a channel block (i.e. left+right) */
-    channel_block = ft->signal.channels * ft->signal.size;
-    alignment = new_offset % channel_block;
-    /* Most common mistaken is to compute something like
-     * "skip everthing upto and including this sample" so
-     * advance to next sample block in this case.
-     */
-    if (alignment != 0)
-        new_offset += (channel_block - alignment);
-    new_offset += prc->dataStart;
+  new_offset = offset * ft->signal.size;
+  /* Make sure request aligns to a channel block (i.e. left+right) */
+  channel_block = ft->signal.channels * ft->signal.size;
+  alignment = new_offset % channel_block;
+  /* Most common mistake is to compute something like
+   * "skip everthing up to and including this sample" so
+   * advance to next sample block in this case.
+   */
+  if (alignment != 0)
+    new_offset += (channel_block - alignment);
+  new_offset += prc->data_start;
 
-    return st_seeki(ft, new_offset, SEEK_SET);
+  return st_seeki(ft, new_offset, SEEK_SET);
 }
 
-static int st_prcstartread(ft_t ft)
+static int startread(ft_t ft)
 {
-        prc_t p = (prc_t ) ft->priv;
-        char head[sizeof(prc_header)];
-        int rc;
+  prc_t p = (prc_t)ft->priv;
+  char head[sizeof(prc_header)];
+  uint8_t byte;
+  uint16_t reps;
+  uint32_t len, listlen, encoding, repgap;
+  unsigned char volume;
+  char appname[0x40]; /* Maximum possible length of name */
 
-        uint16_t len;
+  /* Check the header */
+  if (prc_checkheader(ft, head))
+    st_debug("Found Psion Record header");
+  else {
+      st_fail_errno(ft,ST_EHDR,"Not a Psion Record file");
+      return (ST_EOF);
+  }
 
-        /* Needed for rawread() */
-        rc = st_rawstartread(ft);
-        if (rc)
-            return rc;
+  st_readb(ft, &byte);
+  if ((byte & 0x3) != 0x2) {
+    st_fail_errno(ft, ST_EHDR, "Invalid length byte for application name string %d", (int)(byte));
+    return ST_EOF;
+  }
 
-        /* Check the header */
-        if (prc_checkheader(ft, head))
-                st_debug("Found Psion Record header");
-        else
-        {
-                st_fail_errno(ft,ST_EHDR,"Psion header doesn't start with the correct bytes\nTry the '.al' (A-law raw) file type with '-t al -r 8000 filename'");
-                return (ST_EOF);
-        }
+  byte >>= 2;
+  assert(byte < 64);
+  st_reads(ft, appname, byte);
+  if (strncasecmp(appname, "record.app", byte) != 0) {
+    st_fail_errno(ft, ST_EHDR, "Invalid application name string %.63s", appname);
+    return ST_EOF;
+  }
+        
+  st_readdw(ft, &len);
+  p->nsamp = len;
+  st_debug("Number of samples: %d", len);
 
-        st_readw(ft, &(len));
-        p->length=len;
-        st_debug("Found length=%d",len);
+  st_readdw(ft, &encoding);
+  st_debug("Encoding of samples: %x", encoding);
+  if (encoding == 0)
+    ft->signal.encoding = ST_ENCODING_ALAW;
+  else if (encoding == 0x100001a1)
+    ft->signal.encoding = ST_ENCODING_IMA_ADPCM;
+  else {
+    st_fail_errno(ft, ST_EHDR, "Unrecognised encoding");
+    return ST_EOF;
+  }
 
-        /* dummy read rest */
-        st_readbuf(ft, head,1,14+2+2);
+  st_readw(ft, &reps);    /* Number of repeats */
+  st_debug("Repeats: %d", reps);
+        
+  st_readb(ft, &volume);
+  st_debug("Volume: %d", (unsigned)volume);
+  if (volume < 1 || volume > 5)
+    st_warn("Volume %d outside range 1..5", volume);
 
-        ft->signal.encoding = ST_ENCODING_ALAW;
-        ft->signal.size = ST_SIZE_BYTE;
+  st_readb(ft, &byte);   /* Unused and seems always zero */
 
-        if (ft->signal.rate != 0)
-            st_report("PRC must use 8000 sample rate.  Overriding");
-        ft->signal.rate = 8000;
+  st_readdw(ft, &repgap); /* Time between repeats in usec */
+  st_debug("Time between repeats (usec): %ld", repgap);
 
-        if (ft->signal.channels != ST_ENCODING_UNKNOWN && ft->signal.channels != 0)
-            st_report("PRC must only supports 1 channel.  Overriding");
-        ft->signal.channels = 1;
+  st_readdw(ft, &listlen); /* Length of samples list */
+  st_debug("Number of bytes in samples list: %ld", listlen);
 
-        p->dataStart = st_tell(ft);
-        ft->length = p->length/ft->signal.size;
+  if (ft->signal.rate != 0 && ft->signal.rate != 8000)
+    st_report("PRC only supports 8 kHz; overriding.");
+  ft->signal.rate = 8000;
 
-        return (ST_SUCCESS);
+  if (ft->signal.channels != 1 && ft->signal.channels != 0)
+    st_report("PRC only supports 1 channel; overriding.");
+  ft->signal.channels = 1;
+
+  p->data_start = st_tell(ft);
+  ft->length = p->nsamp / ft->signal.channels;
+
+  if (ft->signal.encoding == ST_ENCODING_ALAW) {
+    ft->signal.size = ST_SIZE_BYTE;
+    if (st_rawstartread(ft))
+      return ST_EOF;
+  } else if (ft->signal.encoding == ST_ENCODING_IMA_ADPCM) {
+    p->frame_samp = 0;
+    if (st_adpcm_ima_start(ft, &p->adpcm))
+      return ST_EOF;
+  }
+
+  return (ST_SUCCESS);
 }
 
+/* Read a variable-length encoded count */
+/* Ignore return code of st_readb, as it doesn't really matter if EOF
+   is delayed until the caller. */
+static unsigned read_cardinal(ft_t ft)
+{
+  unsigned a;
+  uint8_t byte;
+
+  if (st_readb(ft, &byte) == ST_EOF)
+    return (unsigned)ST_EOF;
+  st_debug_more("Cardinal byte 1: %x", byte);
+  a = byte;
+  if (!(a & 1))
+    a >>= 1;
+  else {
+    if (st_readb(ft, &byte) == ST_EOF)
+      return (unsigned)ST_EOF;
+    st_debug_more("Cardinal byte 2: %x", byte);
+    a |= byte << 8;
+    if (!(a & 2))
+      a >>= 2;
+    else if (!(a & 4)) {
+      if (st_readb(ft, &byte) == ST_EOF)
+        return (unsigned)ST_EOF;
+      st_debug_more("Cardinal byte 3: %x", byte);
+      a |= byte << 16;
+      if (st_readb(ft, &byte) == ST_EOF)
+        return (unsigned)ST_EOF;
+      st_debug_more("Cardinal byte 4: %x", byte);
+      a |= byte << 24;
+      a >>= 3;
+    }
+  }
+
+  return a;
+}
+
+static st_size_t read(ft_t ft, st_sample_t *buf, st_size_t samp)
+{
+  prc_t p = (prc_t)ft->priv;
+
+  st_debug_more("length now = %d", p->nsamp);
+
+  if (ft->signal.encoding == ST_ENCODING_IMA_ADPCM) {
+    st_size_t nsamp, read;
+
+    if (p->frame_samp == 0) {
+      unsigned framelen = read_cardinal(ft);
+      uint32_t trash;
+
+      if (framelen == (unsigned)ST_EOF)
+        return 0;
+
+      st_debug_more("frame length %d", framelen);
+      p->frame_samp = framelen;
+
+      /* Discard length of compressed data */
+      st_debug_more("compressed length %d", read_cardinal(ft));
+      /* Discard length of BListL */
+      st_readdw(ft, &trash);
+      st_debug_more("list length %d", trash);
+
+      /* Reset CODEC for start of frame */
+      st_adpcm_reset(&p->adpcm, ft->signal.encoding);
+    }
+    nsamp = min(p->frame_samp, samp);
+    p->nsamp += nsamp;
+    read = st_adpcm_read(ft, &p->adpcm, buf, nsamp);
+    p->frame_samp -= read;
+    st_debug_more("samples left in this frame: %d", p->frame_samp);
+    return read;
+  } else {
+    p->nsamp += samp;
+    return st_rawread(ft, buf, samp);
+  }
+}
+
+static int stopread(ft_t ft)
+{
+  prc_t p = (prc_t)ft->priv;
+
+  if (ft->signal.encoding == ST_ENCODING_IMA_ADPCM)
+    return st_adpcm_stopread(ft, &p->adpcm);
+  else
+    return st_rawstopread(ft);
+}
+
 /* When writing, the header is supposed to contain the number of
    data bytes written, unless it is written to a pipe.
    Since we don't know how many bytes will follow until we're done,
@@ -127,74 +316,146 @@
    if it is not, the unspecified size remains in the header
    (this is illegal). */
 
-static int st_prcstartwrite(ft_t ft)
+static int startwrite(ft_t ft)
 {
-        prc_t p = (prc_t ) ft->priv;
-        int rc;
+  prc_t p = (prc_t)ft->priv;
 
-        /* Needed for rawwrite() */
-        rc = st_rawstartwrite(ft);
-        if (rc)
-            return ST_EOF;
+  if (ft->signal.encoding != ST_ENCODING_ALAW &&
+      ft->signal.encoding != ST_ENCODING_IMA_ADPCM) {
+    st_report("PRC only supports A-law and ADPCM encoding; choosing A-law");
+    ft->signal.encoding = ST_ENCODING_ALAW;
+  }
+        
+  if (ft->signal.encoding == ST_ENCODING_ALAW) {
+    if (st_rawstartwrite(ft))
+      return ST_EOF;
+  } else if (ft->signal.encoding == ST_ENCODING_IMA_ADPCM) {
+    if (st_adpcm_ima_start(ft, &p->adpcm))
+      return ST_EOF;
+  }
+        
+  p->nsamp = 0;
+  p->nbytes = 0;
+  if (p->repeats == 0)
+    p->repeats = 1;
 
-        p->length = 0;
-        if (p->repeats == 0)
-            p->repeats = 1;
+  if (ft->signal.rate != 0 && ft->signal.rate != 8000)
+    st_report("PRC only supports 8 kHz sample rate; overriding.");
+  ft->signal.rate = 8000;
 
-        if (ft->signal.rate != 0)
-            st_report("PRC must use 8000 sample rate.  Overriding");
+  if (ft->signal.channels != 1 && ft->signal.channels != 0)
+    st_report("PRC only supports 1 channel; overriding.");
+  ft->signal.channels = 1;
 
-        if (ft->signal.channels != ST_ENCODING_UNKNOWN && ft->signal.channels != 0)
-            st_report("PRC must only supports 1 channel.  Overriding");
+  ft->signal.size = ST_SIZE_BYTE;
 
-        ft->signal.encoding = ST_ENCODING_ALAW;
-        ft->signal.size = ST_SIZE_BYTE;
-        ft->signal.rate = 8000;
+  prcwriteheader(ft);
 
-        prcwriteheader(ft);
-        return ST_SUCCESS;
+  p->data_start = st_tell(ft);
+
+  return ST_SUCCESS;
 }
 
-static st_size_t st_prcwrite(ft_t ft, const st_sample_t *buf, st_size_t samp)
+static void write_cardinal(ft_t ft, unsigned a)
 {
-        prc_t p = (prc_t ) ft->priv;
-        p->length += samp * ft->signal.size;
-        st_debug("length now = %d", p->length);
-        return st_rawwrite(ft, buf, samp);
+  uint8_t byte;
+
+  if (a < 0x80) {
+    byte = a << 1;
+    st_debug_more("Cardinal byte 1: %x", byte);
+    st_writeb(ft, byte);
+  } else if (a < 0x8000) {
+    byte = (a << 2) | 1;
+    st_debug_more("Cardinal byte 1: %x", byte);
+    st_writeb(ft, byte);
+    byte = a >> 6;
+    st_debug_more("Cardinal byte 2: %x", byte);
+    st_writeb(ft, byte);
+  } else {
+    byte = (a << 3) | 3;
+    st_debug_more("Cardinal byte 1: %x", byte);
+    st_writeb(ft, byte);
+    byte = a >> 5;
+    st_debug_more("Cardinal byte 2: %x", byte);
+    st_writeb(ft, byte);
+    byte = a >> 13;
+    st_debug_more("Cardinal byte 3: %x", byte);
+    st_writeb(ft, byte);
+    byte = a >> 21;
+    st_debug_more("Cardinal byte 4: %x", byte);
+    st_writeb(ft, byte);
+  }
 }
 
-static int st_prcstopwrite(ft_t ft)
+static st_size_t write(ft_t ft, const st_sample_t *buf, st_size_t samp)
 {
-        /* Call before seeking to flush buffer */
-        st_rawstopwrite(ft);
+  prc_t p = (prc_t)ft->priv;
+  /* Psion Record seems not to be able to handle frames > 800 samples */
+  samp = min(samp, 800);
+  p->nsamp += samp;
+  st_debug_more("length now = %d", p->nsamp);
+  if (ft->signal.encoding == ST_ENCODING_IMA_ADPCM) {
+    st_size_t written;
 
-        if (!ft->seekable)
-        {
-            st_warn("Header will be have invalid file length since file is not seekable");
-            return ST_SUCCESS;
-        }
+    write_cardinal(ft, samp);
+    /* Write compressed length */
+    write_cardinal(ft, (samp / 2) + (samp % 2) + 4);
+    /* Write length again (seems to be a BListL) */
+    st_debug_more("list length %d", samp);
+    st_writedw(ft, samp);
+    st_adpcm_reset(&p->adpcm, ft->signal.encoding);
+    written = st_adpcm_write(ft, &p->adpcm, buf, samp);
+    st_adpcm_flush(ft, &p->adpcm);
+    return written;
+  } else
+    return st_rawwrite(ft, buf, samp);
+}
 
-        if (st_seeki(ft, 0, 0) != 0)
-        {
-                st_fail_errno(ft,errno,"Can't rewind output file to rewrite Psion header.");
-                return(ST_EOF);
-        }
-        prcwriteheader(ft);
-        return ST_SUCCESS;
+static int stopwrite(ft_t ft)
+{
+  prc_t p = (prc_t)ft->priv;
+
+  /* Call before seeking to flush buffer (ADPCM has already been flushed) */
+  if (ft->signal.encoding != ST_ENCODING_IMA_ADPCM)
+    st_rawstopwrite(ft);
+
+  p->nbytes = st_tell(ft) - p->data_start;
+
+  if (!ft->seekable) {
+      st_warn("Header will have invalid file length since file is not seekable");
+      return ST_SUCCESS;
+  }
+
+  if (st_seeki(ft, 0, 0) != 0) {
+      st_fail_errno(ft,errno,"Can't rewind output file to rewrite Psion header.");
+      return(ST_EOF);
+  }
+  prcwriteheader(ft);
+  return ST_SUCCESS;
 }
 
 static void prcwriteheader(ft_t ft)
 {
-  char nullbuf[15];
-  prc_t p = (prc_t ) ft->priv;
+  prc_t p = (prc_t)ft->priv;
 
-  st_debug("Final length=%d",p->length);
-  memset(nullbuf,0,14);
   st_writebuf(ft, prc_header, 1, sizeof(prc_header));
-  st_writew(ft, p->length);
-  st_writebuf(ft, nullbuf,1,14);
-  st_writew(ft, p->length);
-  st_writebuf(ft, nullbuf,1,2);
+  st_writes(ft, "\x2arecord.app");
+
+  st_debug("Number of samples: %d",p->nsamp);
+  st_writedw(ft, p->nsamp);
+
+  if (ft->signal.encoding == ST_ENCODING_ALAW)
+    st_writedw(ft, 0);
+  else
+    st_writedw(ft, 0x100001a1); /* ADPCM */
+  
+  st_writew(ft, 0);             /* Number of repeats */
+  st_writeb(ft, 3);             /* Volume: use default value of Record.app */
+  st_writeb(ft, 0);             /* Unused and seems always zero */
+  st_writedw(ft, 0);            /* Time between repeats in usec */
+
+  st_debug("Number of bytes: %d", p->nbytes);
+  st_writedw(ft, p->nbytes);    /* Number of bytes of data */
 }
 
 /* Psion .prc */
@@ -206,17 +467,17 @@
 static st_format_t st_prc_format = {
   prcnames,
   NULL,
-  ST_FILE_SEEK | ST_FILE_BIG_END,
-  st_prcstartread,
-  st_rawread,
-  st_rawstopread,
-  st_prcstartwrite,
-  st_prcwrite,
-  st_prcstopwrite,
-  st_prcseek
+  ST_FILE_SEEK | ST_FILE_LIT_END,
+  startread,
+  read,
+  stopread,
+  startwrite,
+  write,
+  stopwrite,
+  seek
 };
 
 const st_format_t *st_prc_format_fn(void)
 {
-    return &st_prc_format;
+  return &st_prc_format;
 }
--- a/src/sox.c
+++ b/src/sox.c
@@ -1326,7 +1326,7 @@
                        &efftab[neffects - 1].obuf[total],
                        efftab[neffects - 1].olen - total);
 
-        if (len != efftab[neffects - 1].olen - total || ofile->desc->eof) {
+        if (len == 0 || ofile->desc->eof) {
           st_warn("Error writing: %s", ofile->desc->st_errstr);
           return ST_EOF;
         }
--- a/src/vox.c
+++ b/src/vox.c
@@ -1,231 +1,49 @@
+/*
+ * SOX file format handler for Dialogic/Oki ADPCM VOX files.
+ *
+ * Copyright 1991-2004 Tony Seebregts And Sundry Contributors
+ *
+ * This source code is freely redistributable and may be used for any
+ * purpose.  This copyright notice must be maintained.
+ *
+ * Tony Seebregts And Sundry Contributors are not responsible for the
+ * consequences of using this software.
+ */
 
-/************************************************************************
- *                                   SOX                                *
- *                                                                      *
- *                       AUDIO FILE PROCESSING UTILITY                  *
- *                                                                      *
- * Project : SOX                                                        *
- * File    : vox.c                                                      *
- *                                                                      *
- * Version History : V12.17.4 - Tony Seebregts                          *
- *                              5 May 2004                              *
- *                                                                      *
- * Description : SOX file format handler for Dialogic/Oki ADPCM VOX     *
- *               files.                                                 *
- *                                                                      *
- * Notes : Coded from SOX skeleton code supplied with SOX source.       *
- *                                                                      *
- ************************************************************************/
-
-/************************************************************************
- * July 5, 1991                                                         *
- *                                                                      *
- * Copyright 1991 Lance Norskog And Sundry Contributors                 *
- *                                                                      *
- * This source code is freely redistributable and may be used for any   *
- * purpose.  This copyright notice must be maintained.                  *
- *                                                                      *
- * Lance Norskog And Sundry Contributors are not responsible for the    *
- * consequences of using this software.                                 *
- *                                                                      *
- ************************************************************************/
-
-#include "adpcms.h"
 #include "st_i.h"
+#include "adpcms.h"
 
-typedef struct voxstuff
-{
-  struct adpcm_struct encoder;
+/* .vox doesn't need any private state over and above adpcm_io_t, so
+   just have simple wrappers that pass it on directly. */
 
-  struct {
-    uint8_t byte;               /* write store */
-    uint8_t flag;
-  } store;
-  st_fileinfo_t file;
-} *vox_t;
-
-
-/******************************************************************************
- * Function   : voxstart
- * Description: Initialises the file parameters and ADPCM codec state.
- * Parameters : ft  - file info structure
- * Returns    : int - ST_SUCCESS
- *                    ST_EOF
- * Exceptions :
- * Notes      : 1. VOX file format is 4-bit OKI ADPCM that decodes to 
- *                 to 12 bit signed linear PCM.
- *              2. Dialogic only supports 6kHz, 8kHz and 11 kHz sampling
- *                 rates but the codecs allows any user specified rate. 
- ******************************************************************************/
-
-static int voxstart(ft_t ft)
+static int vox_start(ft_t ft)
 {
-  vox_t state = (vox_t) ft->priv;
-
-  /* ... setup file info */
-  state->file.buf = (char *) xmalloc(ST_BUFSIZ);
-  state->file.size = ST_BUFSIZ;
-  state->file.count = 0;
-  state->file.pos = 0;
-  state->file.eof = 0;
-  state->store.byte = 0;
-  state->store.flag = 0;
-
-  adpcm_init(&state->encoder, 1);
-  ft->signal.channels = 1;
-  return st_rawstart(ft, st_true, st_false, ST_ENCODING_OKI_ADPCM, ST_SIZE_16BIT, ST_OPTION_DEFAULT);
+  return st_adpcm_oki_start(ft, (adpcm_io_t)ft->priv);
 }
 
-
-static int imastart(ft_t ft)
+static int ima_start(ft_t ft)
 {
-  vox_t state = (vox_t) ft->priv;
-
-  /* ... setup file info */
-  state->file.buf = (char *) xmalloc(ST_BUFSIZ);
-  state->file.size = ST_BUFSIZ;
-  state->file.count = 0;
-  state->file.pos = 0;
-  state->file.eof = 0;
-  state->store.byte = 0;
-  state->store.flag = 0;
-
-  adpcm_init(&state->encoder, 0);
-  ft->signal.channels = 1;
-  return st_rawstart(ft, st_true, st_false, ST_ENCODING_IMA_ADPCM, ST_SIZE_16BIT, ST_OPTION_DEFAULT);
+  return st_adpcm_ima_start(ft, (adpcm_io_t)ft->priv);
 }
 
-
-/******************************************************************************
- * Function   : read 
- * Description: Fills an internal buffer from the VOX file, converts the 
- *              OKI ADPCM 4-bit samples to 12-bit signed PCM and then scales 
- *              the samples to full range 16 bit PCM.
- * Parameters : ft     - file info structure
- *              buffer - output buffer
- *              length - size of output buffer
- * Returns    : int    - number of samples returned in buffer
- * Exceptions :
- * Notes      : 
- ******************************************************************************/
-
-static st_size_t read(ft_t ft, st_sample_t * buffer, st_size_t len)
+static st_size_t read(ft_t ft, st_sample_t *buffer, st_size_t len)
 {
-  vox_t state = (vox_t) ft->priv;
-  st_size_t n;
-  uint8_t byte;
-
-  for (n = 0; n < (len&~1) && st_readb(ft, &byte) == ST_SUCCESS; n += 2) {
-    short word = adpcm_decode(byte >> 4, &state->encoder);
-    *buffer++ = ST_SIGNED_WORD_TO_SAMPLE(word, ft->clips);
-
-    word = adpcm_decode(byte, &state->encoder);
-    *buffer++ = ST_SIGNED_WORD_TO_SAMPLE(word, ft->clips);
-  }
-  return n;
+  return st_adpcm_read(ft, (adpcm_io_t)ft->priv, buffer, len);
 }
 
-/******************************************************************************
- * Function   : stopread 
- * Description: Frees the internal buffer allocated in voxstart/imastart.
- * Parameters : ft   - file info structure
- * Returns    : int  - ST_SUCCESS
- * Exceptions :
- * Notes      : 
- ******************************************************************************/
-
 static int stopread(ft_t ft)
 {
-  vox_t state = (vox_t) ft->priv;
-
-  free(state->file.buf);
-
-  return (ST_SUCCESS);
+  return st_adpcm_stopread(ft, (adpcm_io_t)ft->priv);
 }
 
-
-/******************************************************************************
- * Function   : write
- * Description: Converts the supplied buffer to 12 bit linear PCM and encodes
- *              to OKI ADPCM 4-bit samples (packed a two nibbles per byte).
- * Parameters : ft     - file info structure
- *              buffer - output buffer
- *              length - size of output buffer
- * Returns    : int    - ST_SUCCESS
- *                       ST_EOF
- * Exceptions :
- * Notes      : 
- ******************************************************************************/
-
-static st_size_t write(ft_t ft, const st_sample_t * buffer, st_size_t length)
+static st_size_t write(ft_t ft, const st_sample_t *buffer, st_size_t length)
 {
-  vox_t state = (vox_t) ft->priv;
-  st_size_t count = 0;
-  uint8_t byte = state->store.byte;
-  uint8_t flag = state->store.flag;
-  short word;
-
-  while (count < length) {
-    word = ST_SAMPLE_TO_SIGNED_WORD(*buffer++, ft->clips);
-
-    byte <<= 4;
-    byte |= adpcm_encode(word, &state->encoder) & 0x0F;
-
-    flag++;
-    flag %= 2;
-
-    if (flag == 0) {
-      state->file.buf[state->file.count++] = byte;
-
-      if (state->file.count >= state->file.size) {
-        st_writebuf(ft, state->file.buf, 1, state->file.count);
-
-        state->file.count = 0;
-      }
-    }
-
-    count++;
-  }
-
-  /* ... keep last byte across calls */
-
-  state->store.byte = byte;
-  state->store.flag = flag;
-
-  return (count);
+  return st_adpcm_write(ft, (adpcm_io_t)ft->priv, buffer, length);
 }
 
-/******************************************************************************
- * Function   : stopwrite
- * Description: Flushes any leftover samples and frees the internal buffer 
- *              allocated in voxstart/imastart.
- * Parameters : ft   - file info structure
- * Returns    : int  - ST_SUCCESS
- * Exceptions :
- * Notes      : 
- ******************************************************************************/
-
 static int stopwrite(ft_t ft)
 {
-  vox_t state = (vox_t) ft->priv;
-  uint8_t byte = state->store.byte;
-  uint8_t flag = state->store.flag;
-
-  /* ... flush remaining samples */
-
-  if (flag != 0) {
-    byte <<= 4;
-    byte |= adpcm_encode(0, &state->encoder) & 0x0F;
-
-    state->file.buf[state->file.count++] = byte;
-  }
-
-  if (state->file.count > 0)
-    st_writebuf(ft, state->file.buf, 1, state->file.count);
-
-  free(state->file.buf);
-
-  return (ST_SUCCESS);
+  return st_adpcm_stopwrite(ft, (adpcm_io_t)ft->priv);
 }
 
 const st_format_t *st_vox_format_fn(void)
@@ -233,8 +51,12 @@
   static char const * names[] = {"vox", NULL};
   static st_format_t driver = {
     names, NULL, 0,
-    voxstart, read, stopread,
-    voxstart, write, stopwrite,
+    vox_start,
+    read,
+    stopread,
+    vox_start,
+    write,
+    stopwrite,
     st_format_nothing_seek
   };
   return &driver;
@@ -245,8 +67,12 @@
   static char const * names[] = {"ima", NULL};
   static st_format_t driver = {
     names, NULL, 0,
-    imastart, read, stopread,
-    imastart, write, stopwrite,
+    ima_start,
+    read,
+    stopread,
+    ima_start,
+    write,
+    stopwrite,
     st_format_nothing_seek
   };
   return &driver;