shithub: sox

Download patch

ref: 7de73b76a65b8f4121cea3c1f573e9f0c16f5aa8
parent: b1143c9c6c75d81eced47e0d60fb410e93c1cd26
author: rrt <rrt>
date: Thu Jan 11 15:18:05 EST 2007

Rename skel.c to skelform.c for clarity, and update it (in particular,
add a seek function).

Add ability to write file formats in Lua: pseudo-file luaform, and
format option --lua-script, to supply the script with which to
implement the format.

--- a/README
+++ b/README
@@ -126,7 +126,7 @@
 
 SoX includes skeleton format files to assist you in supporting new
 formats, sound effect loops, and special-purpose programs. The full
-skeleton format, skel.c, helps you write a driver for a new format
+skeleton format, skelform.c, helps you write a driver for a new format
 which has data structures. skeleff.c is a starting point for writing a
 sound effect loop. sox.c is a good starting point for new programs.
 (Someone finally did this and told me what was wrong...)
--- a/libst.3
+++ b/libst.3
@@ -43,13 +43,7 @@
 .P
 The \fBst_close\fR function dissociates the named \fIft_t\fR from its underlying file or set of functions.  If the format handler was being used for output, any buffered data is written first.
 .P
-Sound Tools includes skeleton C
-files to assist you in writing new formats and effects.  
-The full skeleton driver, skel.c, helps you write drivers 
-for a new format which has data structures.  
-The simple skeleton drivers
-help you write a new driver for raw (headerless) formats, or
-for formats which just have a simple header followed by raw data.
+Sound Tools includes skeleton C files to assist you in writing new formats (skelform.c) and effects (skeleff.c).
 .SH RETURN VALUE
 Upon successful completion \fBst_open_input\fR and \fBst_open_output\fR return a ft_t (which is a pointer).  Otherwise, NULL is returned.  TODO: Need a what to return reason for failures.  Currently, relies on st_warn to print information.
 .P
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -23,10 +23,10 @@
 effects = avg.c band.c bandpass.c biquad.c biquad.h breject.c btrworth.c \
 	  btrworth.h chorus.c compand.c dcshift.c deemphas.c earwax.c \
 	  echo.c echos.c equalizer.c fade.c FFT.c FFT.h filter.c flanger.c \
-	  highp.c highpass.c lowp.c lowpass.c luaeff.c lintlib.c mask.c \
-	  mcompand.c noiseprof.c noisered.c noisered.h pad.c pan.c \
-	  phaser.c pitch.c polyphas.c rabbit.c rate.c repeat.c resample.c \
-	  reverb.c reverse.c silence.c speed.c stat.c \
+	  highp.c highpass.c lowp.c lowpass.c luaeff.c luaform.c \
+	  lintlib.c mask.c mcompand.c noiseprof.c noisered.c noisered.h \
+	  pad.c pan.c phaser.c pitch.c polyphas.c rabbit.c rate.c repeat.c \
+	  resample.c reverb.c reverse.c silence.c speed.c stat.c stlua.c \
 	  stretch.c swap.c synth.c tone.c trim.c vibro.c vol.c
 
 libst_la_SOURCES = $(formats) $(effects) alsa.c oss.c sunaudio.c handlers.c misc.c \
--- a/src/fade.c
+++ b/src/fade.c
@@ -1,7 +1,6 @@
-/* This code is based in skel.c
+/*
+ * Ari Moisio <armoi@sci.fi> Aug 29 2000, based on skeleton effect
  * Written by Chris Bagwell (cbagwell@sprynet.com) - March 16, 1999
- * Non-skel parts written by
- * Ari Moisio <armoi@sci.fi> Aug 29 2000.
  *
  * Copyright 1999 Chris Bagwell And Sundry Contributors
  * This source code is freely redistributable and may be used for
--- a/src/handlers.c
+++ b/src/handlers.c
@@ -36,6 +36,7 @@
   st_hcom_format_fn,
   st_la_format_fn,
   st_lu_format_fn,
+  st_lua_format_fn,
   st_maud_format_fn,
 #if defined(HAVE_LIBMAD) || defined(HAVE_LIBMP3LAME)
   st_mp3_format_fn,
--- /dev/null
+++ b/src/luaeff.c
@@ -1,0 +1,171 @@
+/*
+ * luaeff - write effects in Lua.
+ *
+ * Copyright 2006-2007 Reuben Thomas
+ *
+ * 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.  */
+
+ 
+/* TODO: If efficiency is a problem, move the call of the Lua script
+   into the flow phase. Instrument the Lua environment so that scripts
+   can still be written naively: reading beyond the end of the input
+   array yields to read more data, and writing output similarly. In
+   order not to need nonetheless to buffer all input and output until
+   finished, need low-water-marks that the script can update to signal
+   that it has finished reading and writing respectively.
+   Alternatively, assume that each location can only be read/written
+   once. */
+
+ 
+#include "st_i.h"
+
+#include <string.h>
+#include <lua.h>
+#include <lauxlib.h>
+
+/* Private data for effect */
+typedef struct luaeff {
+  char *script;                   /* Script filename */
+  lua_State *L;                 /* Lua state */
+  st_size_t nsamp;              /* Number of samples in input */
+  st_sample_t *data;            /* Input data */
+} *luaeff_t;
+
+assert_static(sizeof(struct luaeff) <= ST_MAX_EFFECT_PRIVSIZE, 
+              /* else */ lua_PRIVSIZE_too_big);
+
+/*
+ * Process command-line options
+ */
+static int lua_getopts(eff_t effp, int n, char **argv) 
+{
+  luaeff_t lua = (luaeff_t)effp->priv;
+  int i, ret;
+
+  if (n < 1) {
+    st_fail(effp->h->usage);
+    return ST_EOF;
+  }
+
+  lua->L = st_lua_new();
+  
+  /* Collect options into global arg table */
+  lua_createtable(lua->L, n - 1, 0);
+  for (i = 1; i < n; i++) {
+    lua_pushstring(lua->L, argv[i]);
+    lua_rawseti(lua->L, -2, i);
+  }
+  lua_setglobal(lua->L, "arg");
+
+  lua->script = xstrdup(argv[0]);
+
+  if ((ret = luaL_loadfile(lua->L, lua->script)) != 0) {
+    st_fail("cannot load Lua script %s: error %d", lua->script, ret);
+    return ST_EOF;
+  }
+  return ST_SUCCESS;
+}
+
+
+/*
+ * Gather samples.
+ */
+static int lua_flow(eff_t effp, const st_sample_t *ibuf, st_sample_t *obuf UNUSED, 
+                       st_size_t *isamp, st_size_t *osamp)
+{
+  luaeff_t lua = (luaeff_t)effp->priv;
+
+  lua->data = (st_sample_t *)xrealloc(lua->data, (lua->nsamp + *isamp) * sizeof(st_sample_t));
+  memcpy(lua->data + lua->nsamp, ibuf, *isamp * sizeof(st_sample_t));
+  lua->nsamp += *isamp;
+
+  *osamp = 0;           /* Signal that we didn't produce any output */
+
+  return ST_SUCCESS;
+}
+
+/*
+ * Send samples to script.
+ */
+static int lua_drain(eff_t effp, st_sample_t *obuf, st_size_t *osamp)
+{
+  luaeff_t lua = (luaeff_t)effp->priv;
+  int ret;
+  st_sample_t_array_t inarr, outarr;
+
+  inarr.size = lua->nsamp;
+  inarr.data = lua->data;
+  outarr.size = *osamp;
+  outarr.data = obuf;
+
+  st_lua_newarr(lua->L, inarr);
+  st_lua_newarr(lua->L, outarr);
+  if ((ret = lua_pcall(lua->L, 2, 0, 0)) != 0)
+    st_fail("error in Lua script: %d", ret);
+
+  *osamp = 0;
+  return ST_EOF;
+}
+
+/*
+ * Free sample data.
+ */
+static int lua_stop(eff_t effp)
+{
+  luaeff_t lua = (luaeff_t)effp->priv;
+
+  free(lua->data);
+  lua->data = NULL;
+
+  return ST_SUCCESS;
+}
+
+/*
+ * Clean up state.
+ */
+static int lua_delete(eff_t effp)
+{
+  luaeff_t lua = (luaeff_t)effp->priv;
+
+  lua_close(lua->L);
+
+  return ST_SUCCESS;
+}
+
+
+/*
+ * Effect descriptor.
+ */
+static st_effect_t st_lua_effect = {
+  "lua",
+  "Usage: lua script [options]",
+  ST_EFF_MCHAN,
+  lua_getopts,
+  st_effect_nothing,
+  lua_flow,
+  lua_drain,
+  lua_stop,
+  lua_delete
+};
+
+/*
+ * Function returning effect descriptor. This should be the only
+ * externally visible object.
+ */
+const st_effect_t *st_lua_effect_fn(void)
+{
+  return &st_lua_effect;
+}
--- /dev/null
+++ b/src/luaform.c
@@ -1,0 +1,163 @@
+/*
+ * luafile - file formats in Lua.
+ *
+ * Copyright 2006-2007 Reuben Thomas
+ *
+ * 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.  */
+
+ 
+/* TODO: If efficiency is a problem, move the call of the Lua script
+   into the read/write phase. Instrument the Lua environment so that
+   scripts can still be written naively: reading beyond the end of the
+   input array yields to read more data, and writing output similarly.
+   In order not to need nonetheless to buffer all input and output
+   until finished, need low-water-marks that the script can update to
+   signal that it has finished reading and writing respectively.
+   Alternatively, assume that each location can only be read/written
+   once. */
+
+
+#include "st_i.h"
+
+#include <string.h>
+#include <lua.h>
+#include <lauxlib.h>
+
+/* Private data */
+typedef struct luafile {
+  lua_State *L;                 /* Lua state */
+} *lua_t;
+
+assert_static(sizeof(struct luafile) <= ST_MAX_FILE_PRIVSIZE, 
+              /* else */ skel_PRIVSIZE_too_big);
+
+/*
+ * Set up Lua state and read script.
+ */
+static int lua_start(ft_t ft)
+{
+  lua_t lua = (lua_t)ft->priv;
+  int ret;
+
+  lua->L = st_lua_new();
+
+  if ((ret = luaL_loadfile(lua->L, ft->signal.lua_script)) != 0) {
+    st_fail("cannot load Lua script %s: error %d", ft->signal.lua_script, ret);
+    return ST_EOF;
+  }
+
+  return ST_SUCCESS;
+}
+
+/*
+ * Read up to len samples of type st_sample_t from file into buf[].
+ * Return number of samples read.
+ */
+static st_size_t lua_read(ft_t ft, st_sample_t *buf, st_size_t len)
+{
+  lua_t lua = (lua_t)ft->priv;
+  st_size_t done;
+  int ret;
+  st_sample_t_array_t inarr;
+
+  inarr.size = len;
+  inarr.data = buf;
+
+  lua_pushstring(lua->L, "read");
+  st_lua_newarr(lua->L, inarr);
+  if ((ret = lua_pcall(lua->L, 2, 1, 0)) != 0)
+    st_fail("error in Lua script: %d", ret);
+  done = lua_tointeger(lua->L, -1);
+  lua_pop(lua->L, 1);
+
+  return done;
+}
+
+/*
+ * Write len samples of type st_sample_t from buf[] to file.
+ * Return number of samples written.
+ */
+static st_size_t lua_write(ft_t ft, const st_sample_t *buf, st_size_t len)
+{
+  lua_t lua = (lua_t)ft->priv;
+  int ret;
+  st_sample_t_array_t outarr;
+
+  outarr.size = len;
+  outarr.data = (st_sample_t *)buf;
+
+  lua_pushstring(lua->L, "write");
+  st_lua_newarr(lua->L, outarr);
+  if ((ret = lua_pcall(lua->L, 2, 1, 0)) != 0)
+    st_fail("error in Lua script: %d", ret);
+
+  return len;
+}
+
+/*
+ * Clean up state.
+ */
+static int lua_stop(ft_t ft)
+{
+  lua_t lua = (lua_t)ft->priv;
+
+  lua_close(lua->L);
+
+  return ST_SUCCESS;
+}
+
+static int lua_seek(ft_t ft, st_size_t offset)
+{
+  lua_t lua = (lua_t)ft->priv;
+  int ret;
+
+  lua_pushstring(lua->L, "seek");
+  lua_pushinteger(lua->L, offset);
+  if ((ret = lua_pcall(lua->L, 2, 1, 0)) != 0)
+    st_fail("error in Lua script: %d", ret);
+  /* Seek relative to current position. */
+
+  return ret;
+}
+
+/* Format file suffixes */
+static const char *lua_names[] = {
+  "lua",
+  NULL
+};
+
+/* Format descriptor */
+static st_format_t st_lua_format = {
+  lua_names,
+  NULL,
+  0,
+  lua_start,
+  lua_read,
+  lua_stop,
+  lua_start,
+  lua_write,
+  lua_stop,
+  lua_seek
+};
+
+/*
+ * Function returning effect descriptor. This should be the only
+ * externally visible object.
+ */
+const st_format_t *st_lua_format_fn(void)
+{
+  return &st_lua_format;
+}
--- a/src/skel.c
+++ /dev/null
@@ -1,201 +1,0 @@
-/*
- * Sound Tools skeleton file format driver.
- *
- * Copyright 1999 Chris Bagwell And Sundry Contributors
- *
- * 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.  */
-
-#include "st_i.h"
-
-/* Private data for SKEL file */
-typedef struct skel
-{
-  st_size_t samples_remaining;
-} *skel_t;
-
-/* Note that if any of your methods doesn't need to do anything, you
-   can instead use the relevant st_*_nothing* method */
-
-/*
- * Do anything required before you start reading samples.
- * Read file header.
- *      Find out sampling rate,
- *      size and encoding of samples,
- *      mono/stereo/quad.
- */
-static int skel_startread(ft_t ft)
-{
-  skel_t sk = (skel_t)ft->priv;
-
-  /* If you need to seek around the input file. */
-  if (!ft->seekable) {
-    st_fail_errno(ft,ST_EVALUE,"SKEL input file must be a file, not a pipe");
-    return (ST_EOF);
-  }
-
-  /*
-   * If your format is headerless and has fixed values for
-   * the following items, you can hard code them here (see cdr.c).
-   * If your format contains a header with format information
-   * then you should set it here.
-   */
-  ft->signal.rate =  44100L;
-  ft->signal.size = ST_SIZE_BYTE or WORD ...;
-  ft->signal.encoding = ST_ENCODING_UNSIGNED or SIGN2 ...;
-  ft->signal.channels = 1 or 2 or 4;
-  ft->comment = xmalloc(size_of_comment);
-  strcpy(ft->comment, "any comment in file header.");
-
-  /* If your format doesn't have a header then samples_in_file
-   * can be determined by the file size.
-   */
-  samples_in_file = st_filelength(ft)/ft->signal.size;
-
-  /* If you can detect the length of your file, record it here. */
-  ft->length = samples_in_file;
-  sk->remaining_samples = samples_in_file;
-
-  return (ST_SUCCESS);
-}
-
-/*
- * Read up to len samples from file.
- * Convert to st_sample_t.
- * Place in buf[].
- * Return number of samples read.
- */
-static st_size_t skel_read(ft_t ft, st_sample_t *buf, st_size_t len)
-{
-  skel_t sk = (skel_t)ft->priv;
-  st_size_t done;
-  st_sample_t l;
-
-  /* Always return a full frame of audio data */
-  if (len % ft->signal.size)
-    len -= (len % ft->signal.size);
-
-  for (done = 0; done < len; done++) {
-    if no more samples
-            break
-            get a sample
-            switch (ft->signal.size) {
-            case ST_SIZE_BYTE:
-              switch (ft->signal.encoding) {
-                case ST_ENCODING_UNSIGNED;
-                *buf++ = ST_UNSIGNED_BYTE_TO_SAMPLE(sample);
-                break;
-              }
-              break;
-            }
-  }
-
-  return done;
-}
-
-/*
- * Do anything required when you stop reading samples.
- * Don't close input file!
- */
-static int skel_stopread(ft_t ft)
-{
-  return ST_SUCCESS;
-}
-
-static int skel_startwrite(ft_t ft)
-{
-  skel_t sk = (skel_t)ft->priv;
-
-  /* If you have to seek around the output file. */
-  /* If header contains a length value then seeking will be
-   * required.  Instead of failing, it's sometimes nice to
-   * just set the length to max value and not fail.
-   */
-  if (!ft->seekable) {
-    st_fail_errno(ft, ST_EVALUE, "Output .skel file must be a file, not a pipe");
-    return ST_EOF;
-  }
-
-  if (ft->signal.rate != 44100L)
-    st_fail_errno(ft, ST_EVALUE, "Output .skel file must have a sample rate of 44100");
-
-  if (ft->signal.size == -1) {
-    st_fail_errno(ft, ST_EVALUE, "Did not specify a size for .skel output file");
-    return ST_EOF;
-  }
-
-  error check ft->signal.encoding;
-  error check ft->signal.channels;
-
-  /* Write file header, if any */
-  /* Write comment field, if any */
-
-  return ST_SUCCESS;
-
-}
-
-static st_size_t skel_write(ft_t ft, const st_sample_t *buf, st_size_t len)
-{
-  skel_t sk = (skel_t)ft->priv;
-  st_size_t len = 0;
-
-  switch (ft->signal.size) {
-  case ST_SIZE_BYTE:
-    switch (ft->signal.encoding) {
-    case ST_ENCODING_UNSIGNED:
-      while (len--) {
-        len = st_writeb(ft, ST_SAMPLE_TO_UNSIGNED_BYTE(*buff++, ft->clippedCount));
-        if (len == ST_EOF)
-          break;
-      }
-      break;
-    }
-    break;
-  }
-
-  return len;
-}
-
-static int skel_stopwrite(ft_t ft)
-{
-  /* All samples are already written out. */
-  /* If file header needs fixing up, for example it needs the */
-  /* the number of samples in a field, seek back and write them here. */
-  return ST_SUCCESS;
-}
-
-/* Format file suffixes */
-static const char *skel_names[] = {
-  "skel",
-  NULL
-};
-
-static st_format_t st_skel_format = {
-  skel_names,
-  NULL,
-  ST_FILE_STEREO | ST_FILE_SEEK,
-  skel_startread,
-  skel_read,
-  skel_stopread,
-  skel_startwrite,
-  skel_write,
-  skel_stopwrite,
-  skel_seek
-};
-
-const st_format_t *st_skel_format_fn()
-{
-  return &st_skel_format;
-}
--- /dev/null
+++ b/src/skelform.c
@@ -1,0 +1,213 @@
+/*
+ * Sound Tools skeleton file format driver.
+ *
+ * Copyright 1999 Chris Bagwell And Sundry Contributors
+ *
+ * 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.  */
+
+#include "st_i.h"
+
+/* Private data for SKEL file */
+typedef struct skelform
+{
+  st_size_t samples_remaining;
+} *skelform_t;
+
+assert_static(sizeof(struct skelform) <= ST_MAX_FILE_PRIVSIZE, 
+              /* else */ skel_PRIVSIZE_too_big);
+
+/* Note that if any of your methods doesn't need to do anything, you
+   can instead use the relevant st_*_nothing* method */
+
+/*
+ * Do anything required before you start reading samples.
+ * Read file header.
+ *      Find out sampling rate,
+ *      size and encoding of samples,
+ *      mono/stereo/quad.
+ */
+static int skel_startread(ft_t ft)
+{
+  skelform_t sk = (skelform_t)ft->priv;
+
+  /* If you need to seek around the input file. */
+  if (!ft->seekable) {
+    st_fail_errno(ft,ST_EVALUE,"SKEL input file must be a file, not a pipe");
+    return (ST_EOF);
+  }
+
+  /*
+   * If your format is headerless and has fixed values for
+   * the following items, you can hard code them here (see cdr.c).
+   * If your format contains a header with format information
+   * then you should set it here.
+   */
+  ft->signal.rate =  44100L;
+  ft->signal.size = ST_SIZE_BYTE or WORD ...;
+  ft->signal.encoding = ST_ENCODING_UNSIGNED or SIGN2 ...;
+  ft->signal.channels = 1 or 2 or 4;
+  ft->comment = xmalloc(size_of_comment);
+  strcpy(ft->comment, "any comment in file header.");
+
+  /* If your format doesn't have a header then samples_in_file
+   * can be determined by the file size.
+   */
+  samples_in_file = st_filelength(ft) / ft->signal.size;
+
+  /* If you can detect the length of your file, record it here. */
+  ft->length = samples_in_file;
+  sk->remaining_samples = samples_in_file;
+
+  return (ST_SUCCESS);
+}
+
+/*
+ * Read up to len samples of type st_sample_t from file into buf[].
+ * Return number of samples read.
+ */
+static st_size_t skel_read(ft_t ft, st_sample_t *buf, st_size_t len)
+{
+  skelform_t sk = (skelform_t)ft->priv;
+  st_size_t done;
+  st_sample_t l;
+
+  /* Always return a full frame of audio data */
+  if (len % ft->signal.size)
+    len -= (len % ft->signal.size);
+
+  for (done = 0; done < len; done++) {
+    if no more samples
+            break
+            get a sample
+            switch (ft->signal.size) {
+            case ST_SIZE_BYTE:
+              switch (ft->signal.encoding) {
+                case ST_ENCODING_UNSIGNED;
+                *buf++ = ST_UNSIGNED_BYTE_TO_SAMPLE(sample);
+                break;
+              }
+              break;
+            }
+  }
+
+  return done;
+}
+
+/*
+ * Do anything required when you stop reading samples.
+ * Don't close input file!
+ */
+static int skel_stopread(ft_t ft)
+{
+  return ST_SUCCESS;
+}
+
+static int skel_startwrite(ft_t ft)
+{
+  skelform_t sk = (skelform_t)ft->priv;
+
+  /* If you have to seek around the output file. */
+  /* If header contains a length value then seeking will be
+   * required.  Instead of failing, it's sometimes nice to
+   * just set the length to max value and not fail.
+   */
+  if (!ft->seekable) {
+    st_fail_errno(ft, ST_EVALUE, "Output .skel file must be a file, not a pipe");
+    return ST_EOF;
+  }
+
+  if (ft->signal.rate != 44100L)
+    st_fail_errno(ft, ST_EVALUE, "Output .skel file must have a sample rate of 44100");
+
+  if (ft->signal.size == -1) {
+    st_fail_errno(ft, ST_EVALUE, "Did not specify a size for .skel output file");
+    return ST_EOF;
+  }
+
+  error check ft->signal.encoding;
+  error check ft->signal.channels;
+
+  /* Write file header, if any */
+  /* Write comment field, if any */
+
+  return ST_SUCCESS;
+
+}
+
+/*
+ * Write len samples of type st_sample_t from buf[] to file.
+ * Return number of samples written.
+ */
+static st_size_t skel_write(ft_t ft, const st_sample_t *buf, st_size_t len)
+{
+  skelform_t sk = (skelform_t)ft->priv;
+  st_size_t len = 0;
+
+  switch (ft->signal.size) {
+  case ST_SIZE_BYTE:
+    switch (ft->signal.encoding) {
+    case ST_ENCODING_UNSIGNED:
+      while (len--) {
+        len = st_writeb(ft, ST_SAMPLE_TO_UNSIGNED_BYTE(*buff++, ft->clippedCount));
+        if (len == ST_EOF)
+          break;
+      }
+      break;
+    }
+    break;
+  }
+
+  return len;
+}
+
+static int skel_stopwrite(ft_t ft)
+{
+  /* All samples are already written out. */
+  /* If file header needs fixing up, for example it needs the number
+     of samples in a field, seek back and write them here. */
+  return ST_SUCCESS;
+}
+
+static int skel_seek(ft_t ft, st_size_t offset)
+{
+  /* Seek relative to current position. */
+  return ST_SUCCESS;
+}
+
+/* Format file suffixes */
+static const char *skel_names[] = {
+  "skel",
+  NULL
+};
+
+/* Format descriptor */
+static st_format_t st_skel_format = {
+  skel_names,
+  NULL,
+  ST_FILE_STEREO | ST_FILE_SEEK,
+  skel_startread,
+  skel_read,
+  skel_stopread,
+  skel_startwrite,
+  skel_write,
+  skel_stopwrite,
+  skel_seek
+};
+
+const st_format_t *st_skel_format_fn()
+{
+  return &st_skel_format;
+}
--- a/src/sox.c
+++ b/src/sox.c
@@ -208,33 +208,33 @@
 
 static file_info_t make_file_info(void)
 {
-  file_info_t fo = xcalloc(sizeof(*fo), 1);
+  file_info_t fi = xcalloc(sizeof(*fi), 1);
 
-  fo->signal.size = -1;
-  fo->signal.encoding = ST_ENCODING_UNKNOWN;
-  fo->signal.channels = 0;
-  fo->signal.reverse_bytes = ST_REVERSE_DEFAULT;
-  fo->signal.reverse_nibbles = ST_REVERSE_DEFAULT;
-  fo->signal.reverse_bits = ST_REVERSE_DEFAULT;
-  fo->signal.compression = HUGE_VAL;
-  fo->volume = HUGE_VAL;
-  fo->volume_clips = 0;
+  fi->signal.size = -1;
+  fi->signal.encoding = ST_ENCODING_UNKNOWN;
+  fi->signal.channels = 0;
+  fi->signal.reverse_bytes = ST_REVERSE_DEFAULT;
+  fi->signal.reverse_nibbles = ST_REVERSE_DEFAULT;
+  fi->signal.reverse_bits = ST_REVERSE_DEFAULT;
+  fi->signal.compression = HUGE_VAL;
+  fi->volume = HUGE_VAL;
+  fi->volume_clips = 0;
 
-  return fo;
+  return fi;
 }
 
-static void set_device(file_info_t fo)
+static void set_device(file_info_t fi)
 {
 #if defined(HAVE_ALSA)
-  fo->filetype = "alsa";
-  fo->filename = xstrdup("default");
+  fi->filetype = "alsa";
+  fi->filename = xstrdup("default");
 #elif defined(HAVE_OSS)
-  fo->filetype = "ossdsp";
-  fo->filename = xstrdup("/dev/dsp");
+  fi->filetype = "ossdsp";
+  fi->filename = xstrdup("/dev/dsp");
 #elif defined (HAVE_SUN_AUDIO)
   char *device = getenv("AUDIODEV");
-  fo->filetype = "sunau";
-  fo->filename = xstrdup(device ? device : "/dev/audio");
+  fi->filetype = "sunau";
+  fi->filename = xstrdup(device ? device : "/dev/audio");
 #endif
 }
 
@@ -259,8 +259,8 @@
   /* Loop over arguments and filenames, stop when an effect name is 
    * found. */
   while (optind < argc && !is_effect_name(argv[optind])) {
-    file_info_t fo = make_file_info();
-    struct file_info fo_none;
+    file_info_t fi = make_file_info();
+    struct file_info fi_none;
 
     if (file_count >= MAX_FILES) {
       st_fail("Too many filenames; maximum is %d input files and 1 output file", MAX_INPUT_FILES);
@@ -267,23 +267,23 @@
       exit(1);
     }
 
-    fo_none = *fo;
+    fi_none = *fi;
     
-    if (doopts(fo, argc, argv)) { /* is null file? */
-      if (fo->filetype != NULL && strcmp(fo->filetype, "null") != 0)
-        st_warn("Ignoring \"-t %s\".", fo->filetype);
-      fo->filetype = "null";
-      fo->filename = xstrdup(fo->filetype);
+    if (doopts(fi, argc, argv)) { /* is null file? */
+      if (fi->filetype != NULL && strcmp(fi->filetype, "null") != 0)
+        st_warn("Ignoring \"-t %s\".", fi->filetype);
+      fi->filetype = "null";
+      fi->filename = xstrdup(fi->filetype);
     } else {
       if (optind >= argc || is_effect_name(argv[optind])) {
-        if (memcmp(fo, &fo_none, sizeof(fo_none)) != 0) /* fopts but no file */
+        if (memcmp(fi, &fi_none, sizeof(fi_none)) != 0) /* fopts but no file */
           usage("missing filename"); /* No return */
-        free(fo); /* No file opts and no filename, so that's okay */
+        free(fi); /* No file opts and no filename, so that's okay */
         continue;
       }
-      fo->filename = xstrdup(argv[optind++]);
+      fi->filename = xstrdup(argv[optind++]);
     }
-    file_opts[file_count++] = fo;
+    file_opts[file_count++] = fi;
   }
 
   if (rec) {
@@ -425,6 +425,7 @@
     {"endian"          , required_argument, NULL, 0},
     {"interactive"     ,       no_argument, NULL, 0},
     {"help-effect"     , required_argument, NULL, 0},
+    {"lua-script"      , required_argument, NULL, 0},
     {"octave"          ,       no_argument, NULL, 0},
     {"version"         ,       no_argument, NULL, 0},
 
@@ -444,7 +445,7 @@
     {NULL, 0, NULL, 0}
   };
 
-static bool doopts(file_info_t fo, int argc, char **argv)
+static bool doopts(file_info_t fi, int argc, char **argv)
 {
   bool isnull = false;
   int option_index, c;
@@ -457,20 +458,20 @@
     case 0:       /* Long options with no short equivalent. */
       switch (option_index) {
       case 0:
-        fo->comment = read_comment_file(optarg);
+        fi->comment = read_comment_file(optarg);
         break;
 
       case 1:
-        fo->comment = xstrdup(optarg);
+        fi->comment = xstrdup(optarg);
         break;
 
       case 2:
         if (!strcmp(optarg, "little"))
-          fo->signal.reverse_bytes = ST_IS_BIGENDIAN;
+          fi->signal.reverse_bytes = ST_IS_BIGENDIAN;
         else if (!strcmp(optarg, "big"))
-          fo->signal.reverse_bytes = ST_IS_LITTLEENDIAN;
+          fi->signal.reverse_bytes = ST_IS_LITTLEENDIAN;
         else if (!strcmp(optarg, "swap"))
-          fo->signal.reverse_bytes = true;
+          fi->signal.reverse_bytes = true;
         break;
 
       case 3:
@@ -482,10 +483,14 @@
         break;
 
       case 5:
+        fi->signal.lua_script = xstrdup(optarg);
+        break;
+        
+      case 6:
         globalinfo.octave_plot_effect = true;
         break;
 
-      case 8:
+      case 7:
         printf("%s: v%s\n", myname, st_version());
         exit(0);
         break;
@@ -513,9 +518,9 @@
       break;
 
     case 't':
-      fo->filetype = optarg;
-      if (fo->filetype[0] == '.')
-        fo->filetype++;
+      fi->filetype = optarg;
+      if (fi->filetype[0] == '.')
+        fi->filetype++;
       break;
 
     case 'r':
@@ -523,16 +528,16 @@
         st_fail("Rate value '%s' is not a positive integer", optarg);
         exit(1);
       }
-      fo->signal.rate = i;
+      fi->signal.rate = i;
       break;
 
     case 'v':
-      if (sscanf(optarg, "%lf %c", &fo->volume, &dummy) != 1) {
+      if (sscanf(optarg, "%lf %c", &fi->volume, &dummy) != 1) {
         st_fail("Volume value '%s' is not a number", optarg);
         exit(1);
       }
       uservolume = 1;
-      if (fo->volume < 0.0)
+      if (fi->volume < 0.0)
         st_report("Volume adjustment is negative; "
                   "this will result in a phase change");
       break;
@@ -542,59 +547,59 @@
         st_fail("Channels value '%s' is not a positive integer", optarg);
         exit(1);
       }
-      fo->signal.channels = i;
+      fi->signal.channels = i;
       break;
 
     case 'C':
-      if (sscanf(optarg, "%lf %c", &fo->signal.compression, &dummy) != 1) {
+      if (sscanf(optarg, "%lf %c", &fi->signal.compression, &dummy) != 1) {
         st_fail("Compression value '%s' is not a number", optarg);
         exit(1);
       }
       break;
 
-    case '1': case 'b': fo->signal.size = ST_SIZE_BYTE;   break;
-    case '2': case 'w': fo->signal.size = ST_SIZE_WORD;   break;
-    case '3':           fo->signal.size = ST_SIZE_24BIT;  break;
-    case '4': case 'l': fo->signal.size = ST_SIZE_DWORD;  break;
-    case '8': case 'd': fo->signal.size = ST_SIZE_DDWORD; break;
+    case '1': case 'b': fi->signal.size = ST_SIZE_BYTE;   break;
+    case '2': case 'w': fi->signal.size = ST_SIZE_WORD;   break;
+    case '3':           fi->signal.size = ST_SIZE_24BIT;  break;
+    case '4': case 'l': fi->signal.size = ST_SIZE_DWORD;  break;
+    case '8': case 'd': fi->signal.size = ST_SIZE_DDWORD; break;
 
-    case 's': fo->signal.encoding = ST_ENCODING_SIGN2;     break;
-    case 'u': fo->signal.encoding = ST_ENCODING_UNSIGNED;  break;
-    case 'f': fo->signal.encoding = ST_ENCODING_FLOAT;     break;
-    case 'a': fo->signal.encoding = ST_ENCODING_ADPCM;     break;
-    case 'D': fo->signal.encoding = ST_ENCODING_MS_ADPCM;  break; /* WIP */
-    case 'i': fo->signal.encoding = ST_ENCODING_IMA_ADPCM; break;
-    case 'o': fo->signal.encoding = ST_ENCODING_OKI_ADPCM; break; /* WIP */
-    case 'g': fo->signal.encoding = ST_ENCODING_GSM;       break;
+    case 's': fi->signal.encoding = ST_ENCODING_SIGN2;     break;
+    case 'u': fi->signal.encoding = ST_ENCODING_UNSIGNED;  break;
+    case 'f': fi->signal.encoding = ST_ENCODING_FLOAT;     break;
+    case 'a': fi->signal.encoding = ST_ENCODING_ADPCM;     break;
+    case 'D': fi->signal.encoding = ST_ENCODING_MS_ADPCM;  break; /* WIP */
+    case 'i': fi->signal.encoding = ST_ENCODING_IMA_ADPCM; break;
+    case 'o': fi->signal.encoding = ST_ENCODING_OKI_ADPCM; break; /* WIP */
+    case 'g': fi->signal.encoding = ST_ENCODING_GSM;       break;
 
-    case 'U': fo->signal.encoding = ST_ENCODING_ULAW;
-      if (fo->signal.size == -1)
-        fo->signal.size = ST_SIZE_BYTE;
+    case 'U': fi->signal.encoding = ST_ENCODING_ULAW;
+      if (fi->signal.size == -1)
+        fi->signal.size = ST_SIZE_BYTE;
       break;
 
-    case 'A': fo->signal.encoding = ST_ENCODING_ALAW;
-      if (fo->signal.size == -1)
-        fo->signal.size = ST_SIZE_BYTE;
+    case 'A': fi->signal.encoding = ST_ENCODING_ALAW;
+      if (fi->signal.size == -1)
+        fi->signal.size = ST_SIZE_BYTE;
       break;
 
     case 'L':
-      fo->signal.reverse_bytes = ST_IS_BIGENDIAN;
+      fi->signal.reverse_bytes = ST_IS_BIGENDIAN;
       break;
 
     case 'B':
-      fo->signal.reverse_bytes = ST_IS_LITTLEENDIAN;
+      fi->signal.reverse_bytes = ST_IS_LITTLEENDIAN;
       break;
 
     case 'x':
-      fo->signal.reverse_bytes = ST_REVERSE_YES;
+      fi->signal.reverse_bytes = ST_REVERSE_YES;
       break;
 
     case 'X':
-      fo->signal.reverse_bits = ST_REVERSE_YES;
+      fi->signal.reverse_bits = ST_REVERSE_YES;
       break;
 
     case 'N':
-      fo->signal.reverse_nibbles = ST_REVERSE_YES;
+      fi->signal.reverse_nibbles = ST_REVERSE_YES;
       break;
 
     case 'V':
@@ -1599,12 +1604,12 @@
 }
 
 /* Adjust volume based on value specified by the -v option for this file. */
-static void volumechange(st_sample_t * buf, st_ssize_t len, file_info_t fo)
+static void volumechange(st_sample_t * buf, st_ssize_t len, file_info_t fi)
 {
-  if (fo->volume != HUGE_VAL && fo->volume != 1)
+  if (fi->volume != HUGE_VAL && fi->volume != 1)
     while (len--) {
-      double d = fo->volume * *buf;
-      *buf++ = ST_ROUND_CLIP_COUNT(d, fo->volume_clips);
+      double d = fi->volume * *buf;
+      *buf++ = ST_ROUND_CLIP_COUNT(d, fi->volume_clips);
     }
 }
 
@@ -1657,8 +1662,8 @@
          "-c channels     number of channels in audio data\n"
          "-C compression  compression factor for variably compressing output formats\n"
          "--comment text  Specify comment text for the output file\n"
-         "--comment-file filename\n"
-         "                Specify file containing comment text for the output file\n"
+         "--comment-file filename  file containing comment text for the output file\n"
+         "--lua-script filename  file containing script for Lua pseudo-file\n"
          "-r rate         sample rate of audio\n"
          "-t filetype     file type of audio\n"
          "-x/-N/-X        invert auto-detected endianness/nibble-order/bit-order of data\n"
@@ -1667,7 +1672,6 @@
          "  -a/-i/-g/-f   ADPCM/IMA_ADPCM/GSM/floating point\n"
          "-1/-2/-3/-4/-8  sample size in bytes\n"
          "-b/-w/-l/-d     aliases for -1/-2/-4/-8 (byte, word, long, double-long)\n"
-         "\n"
          "-v volume       input file volume adjustment factor (real number)\n"
          "-R              use default random numbers (same on each run of SoX)\n"
          "\n");
--- a/src/st.h
+++ b/src/st.h
@@ -204,7 +204,7 @@
 
 /* Signal parameters */
 
-typedef struct  st_signalinfo
+typedef struct st_signalinfo
 {
     st_rate_t rate;       /* sampling rate */
     signed char size;     /* word length of data */
@@ -214,6 +214,7 @@
     st_reverse_t reverse_nibbles;
     st_reverse_t reverse_bits;
     double compression;   /* compression factor (where applicable) */
+    char *lua_script;      /* Lua script to use for Lua pseudo-file */
 } st_signalinfo_t;
 
 /* Loop parameters */
--- a/src/st_i.h
+++ b/src/st_i.h
@@ -16,6 +16,8 @@
 #include "stconfig.h"
 #include "st.h"
 
+#include <lua.h>
+
 #include "xmalloc.h"
 
 #ifdef HAVE_BYTESWAP_H
@@ -202,6 +204,7 @@
 extern const st_format_t *st_al_format_fn(void);
 extern const st_format_t *st_la_format_fn(void);
 extern const st_format_t *st_lu_format_fn(void);
+extern const st_format_t *st_lua_format_fn(void);
 extern const st_format_t *st_s3_format_fn(void);
 extern const st_format_t *st_sb_format_fn(void);
 extern const st_format_t *st_sl_format_fn(void);
@@ -255,6 +258,14 @@
 #define st_rawstartread(ft) st_rawstart(ft, false, false, ST_ENCODING_UNKNOWN, -1, ST_REVERSE_DEFAULT)
 #define st_rawstartwrite st_rawstartread
 #define st_rawstopread st_format_nothing
+
+/* Lua functions and types in stlua.c */
+typedef struct {
+  st_size_t size;
+  st_sample_t *data;
+} st_sample_t_array_t;
+int st_lua_newarr(lua_State *L, st_sample_t_array_t arr);
+void *st_lua_new(void);
 
 
 /*=============================================================================
--- /dev/null
+++ b/src/stlua.c
@@ -1,0 +1,129 @@
+/*
+ * luast - miscellaneous Lua support functions.
+ *
+ * Copyright 2006-2007 Reuben Thomas
+ *
+ * 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.  */
+
+#include "st_i.h"
+
+#include <assert.h>
+#include <lua.h>
+#include <lualib.h>
+#include <lauxlib.h>
+
+/* st_sample_t arrays */
+
+static const char *handle = "st_sample_t array";
+
+int st_lua_newarr(lua_State *L, st_sample_t_array_t arr)
+{
+  lua_newuserdata(L, sizeof(st_sample_t_array_t));
+  *(st_sample_t_array_t *)lua_touserdata(L, -1) = arr;
+  luaL_getmetatable(L, handle);
+  lua_setmetatable(L, -2);
+  return 1;
+}
+
+static int arr_index(lua_State *L)
+  /* array, key -> value */
+{
+  st_sample_t_array_t *p = luaL_checkudata(L, 1, handle);
+  lua_Integer k = luaL_checkinteger(L, 2);
+
+  if ((st_size_t)k >= p->size)
+    lua_pushnil(L);
+  else
+    lua_pushinteger(L, (lua_Integer)p->data[k]);
+    
+  return 1;
+}
+
+static int arr_newindex(lua_State *L)
+  /* array, key, value -> */
+{
+  st_sample_t_array_t *p = luaL_checkudata(L, 1, handle);
+  lua_Integer k = luaL_checkinteger(L, 2);
+  lua_Integer v = luaL_checkinteger(L, 3);
+
+  /* FIXME: Have some indication for out of range */
+  if ((st_size_t)k < p->size)
+    p->data[k] = v;
+    
+  return 0;
+}
+
+static int arr_len(lua_State *L)
+  /* array -> #array */
+{
+  st_sample_t_array_t *p;
+  p = luaL_checkudata(L, 1, handle);
+  lua_pushinteger(L, (lua_Integer)p->size);
+  return 1;
+}
+
+static int arr_tostring(lua_State *L)
+{
+  char buf[256];
+  void *udata = luaL_checkudata(L, 1, handle);
+  if(udata) {
+    sprintf(buf, "%s (%p)", handle, udata);
+    lua_pushstring(L, buf);
+  }
+  else {
+    sprintf(buf, "must be userdata of type '%s'", handle);
+    luaL_argerror(L, 1, buf);
+  }
+  return 1;
+}
+
+/* Metatable */
+static const luaL_reg meta[] = {
+  {"__index", arr_index},
+  {"__newindex", arr_newindex},
+  {"__len", arr_len},
+  {"__tostring", arr_tostring},
+  {NULL, NULL}
+};
+
+/* Allocator function for use by Lua */
+static void *lua_alloc(void *ud UNUSED, void *ptr, size_t osize UNUSED, size_t nsize)
+{
+  if (nsize == 0) {
+    free(ptr);
+    return NULL;
+  } else
+    return xrealloc(ptr, nsize);
+}
+
+void *st_lua_new(void)
+{
+  lua_State *L;
+
+  /* Since the allocator quits if it fails, this should always
+     succeed if it returns. */
+  assert((L = lua_newstate(lua_alloc, NULL)));
+
+  /* TODO: If concerned about security, lock down here: in particular,
+     don't open the io library. */
+  luaL_openlibs(L);
+
+  luaopen_int(L);
+
+  /* Create st_sample_t array userdata type */
+  createmeta(L, handle);
+  luaL_register(L, NULL, meta);
+}