shithub: sox

Download patch

ref: 2776fb46d9d949230e22ce5099da1db8e0e206c4
parent: fe1c1e06d0c343967b578b81f1bc1fe9f80946ba
author: robs <robs>
date: Sat Sep 27 07:33:00 EDT 2008

embryonic bend effect

--- a/ChangeLog
+++ b/ChangeLog
@@ -52,6 +52,7 @@
   o New `riaa' effect: RIAA vinyl playback EQ.  (robs)
   o New `loudness' effect: gain control with ISO 226 loudness
     compensation.  (robs)
+  o New `bend' effect; pitch bending; W.I.P., subject to change.  (robs)
   o New -b option for the norm effect; can be used to fix stereo
     imbalance.  (robs)
   o New --effects-file option to read effects and arguments from
--- a/sox.1
+++ b/sox.1
@@ -1119,6 +1119,14 @@
 .SP
 See also \fBequalizer\fR for a peaking equalisation effect.
 .TP
+\fBbend\fR [\fB\-f \fIframe-rate\fR(25)] [\fB\-o \fIoversample\fR(16)] { \fIdelay\fB,\fIcents\fB,\fIduration\fR }
+Pitch bending. Work in progress\*msubject to change.
+.SP
+For example:
+.EX
+  play -n synth 2.5 sin 667 bend .35,180,.25 .15,740,.53 0,-520,.3
+.EE
+.TP
 \fBchorus \fIgain-in gain-out\fR <\fIdelay decay speed depth \fB\-s\fR\^|\^\fB\-t\fR>
 Add a chorus effect to the audio.  This can make a single vocal sound
 like a chorus, but can also be applied to instrumentation.
@@ -1586,7 +1594,7 @@
 and one audio output port can be used.  If found, the environment varible
 LADSPA_PATH will be used as search path for plugins.
 .TP
-\fBloudness [\fIgain\fR [\fIreference\fR]]
+\fBloudness\fR [\fIgain\fR [\fIreference\fR]]
 Loudness control\*msimilar to the
 .B gain
 effect, but provides equalisation for the human auditory system.  See
@@ -1598,6 +1606,9 @@
 .I reference
 level may be given if the original audio has been equalised for some
 other optimal level.
+A default gain of \-10dB is used if a
+.I gain
+value is not given.
 .SP
 See also the
 .B gain
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -28,16 +28,16 @@
 
 # Format with: !xargs echo|tr ' ' '\n'|sort|column|expand|sed 's/^/  /'
 set(effects_srcs
-  biquad          echo            noiseprof       remix           swap
-  biquads         echos           noisered        repeat          synth
-  chorus          fade            normalise       resample        tempo
-  compand         fft4g           output          reverb          tremolo
-  compandt        filter          pad             reverse         trim
-  contrast        flanger         pan             silence         vol
-  dcshift         input           phaser          skeleff
-  delay           loudness        pitch           speed
-  dither          mcompand        polyphas        splice
-  earwax          mixer           rate            stat
+  bend            earwax          mcompand        polyphas        splice
+  biquad          echo            mixer           rate            stat
+  biquads         echos           noiseprof       remix           swap
+  chorus          fade            noisered        repeat          synth
+  compand         fft4g           normalise       resample        tempo
+  compandt                        output          reverb          tremolo
+  contrast        filter          pad             reverse         trim
+  dcshift         flanger         pan             silence         vol
+  delay           input           phaser          skeleff
+  dither          loudness        pitch           speed
 )
 set(formats_srcs
   8svx            dat             ima-fmt         s3-fmt          u4-fmt
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -249,7 +249,7 @@
 ############################
 
 libsfx_la_SOURCES = \
-	band.h biquad.c biquad.h biquads.c chorus.c compand.c compandt.c \
+	band.h bend.c biquad.c biquad.h biquads.c chorus.c compand.c compandt.c \
 	compandt.h contrast.c dcshift.c delay.c dither.c earwax.c echo.c \
 	echos.c effects.c effects.h effects_i.c fade.c fft4g.c fft4g.h \
 	fifo.h filter.c flanger.c input.c ladspa.c loudness.c mcompand.c \
--- /dev/null
+++ b/src/bend.c
@@ -1,0 +1,362 @@
+/* libSoX effect: Pitch Bend   (c) 2008 robs@users.sourceforge.net
+ *
+ * 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.1 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,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+/* Portions based on http://www.dspdimension.com/download smbPitchShift.cpp:
+ *
+ * COPYRIGHT 1999-2006 Stephan M. Bernsee <smb [AT] dspdimension [DOT] com>
+ *
+ *             The Wide Open License (WOL)
+ *
+ * Permission to use, copy, modify, distribute and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice and this license appear in all source copies. 
+ * THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY OF
+ * ANY KIND. See http://www.dspguru.com/wol.htm for more information.
+ */
+
+#include "sox_i.h"
+#include "getopt.h"
+
+static void smbFft(float *fftBuffer, long fftFrameSize, long sign)
+/* 
+ * FFT routine, (C)1996 S.M.Bernsee. Sign = -1 is FFT, 1 is iFFT (inverse)
+ * Fills fftBuffer[0...2*fftFrameSize-1] with the Fourier transform of the
+ * time domain data in fftBuffer[0...2*fftFrameSize-1]. The FFT array takes
+ * and returns the cosine and sine parts in an interleaved manner, ie.
+ * fftBuffer[0] = cosPart[0], fftBuffer[1] = sinPart[0], asf. fftFrameSize
+ * must be a power of 2. It expects a complex input signal (see footnote 2),
+ * ie. when working with 'common' audio signals our input signal has to be
+ * passed as {in[0],0.,in[1],0.,in[2],0.,...} asf. In that case, the transform
+ * of the frequencies of interest is in fftBuffer[0...fftFrameSize].
+ */
+{
+  float wr, wi, arg, *p1, *p2, temp;
+  float tr, ti, ur, ui, *p1r, *p1i, *p2r, *p2i;
+  long i, bitm, j, le, le2, k;
+
+  for (i = 2; i < 2 * fftFrameSize - 2; i += 2) {
+    for (bitm = 2, j = 0; bitm < 2 * fftFrameSize; bitm <<= 1) {
+      if (i & bitm)
+        j++;
+      j <<= 1;
+    }
+    if (i < j) {
+      p1 = fftBuffer + i;
+      p2 = fftBuffer + j;
+      temp = *p1;
+      *(p1++) = *p2;
+      *(p2++) = temp;
+      temp = *p1;
+      *p1 = *p2;
+      *p2 = temp;
+    }
+  }
+  for (k = 0, le = 2; k < (long) (log((double) fftFrameSize) / log(2.) + .5);
+       k++) {
+    le <<= 1;
+    le2 = le >> 1;
+    ur = 1.0;
+    ui = 0.0;
+    arg = M_PI / (le2 >> 1);
+    wr = cos(arg);
+    wi = sign * sin(arg);
+    for (j = 0; j < le2; j += 2) {
+      p1r = fftBuffer + j;
+      p1i = p1r + 1;
+      p2r = p1r + le2;
+      p2i = p2r + 1;
+      for (i = j; i < 2 * fftFrameSize; i += le) {
+        tr = *p2r * ur - *p2i * ui;
+        ti = *p2r * ui + *p2i * ur;
+        *p2r = *p1r - tr;
+        *p2i = *p1i - ti;
+        *p1r += tr;
+        *p1i += ti;
+        p1r += le;
+        p1i += le;
+        p2r += le;
+        p2i += le;
+      }
+      tr = ur * wr - ui * wi;
+      ui = ur * wi + ui * wr;
+      ur = tr;
+    }
+  }
+}
+
+#define MAX_FRAME_LENGTH 8192
+
+typedef struct {
+  unsigned nbends;       /* Number of bends requested */
+  struct {
+    char *str;           /* Command-line argument to parse for this bend */
+    size_t start;        /* Start bending when in_pos equals this */
+    double cents;
+    size_t duration;     /* Number of samples to bend */
+  } *bends;
+
+  unsigned frame_rate;
+  size_t in_pos;         /* Number of samples read from the input stream */
+  unsigned bends_pos;    /* Number of bends completed so far */
+
+  double shift;
+
+  float gInFIFO[MAX_FRAME_LENGTH];
+  float gOutFIFO[MAX_FRAME_LENGTH];
+  float gFFTworksp[2 * MAX_FRAME_LENGTH];
+  float gLastPhase[MAX_FRAME_LENGTH / 2 + 1];
+  float gSumPhase[MAX_FRAME_LENGTH / 2 + 1];
+  float gOutputAccum[2 * MAX_FRAME_LENGTH];
+  float gAnaFreq[MAX_FRAME_LENGTH];
+  float gAnaMagn[MAX_FRAME_LENGTH];
+  float gSynFreq[MAX_FRAME_LENGTH];
+  float gSynMagn[MAX_FRAME_LENGTH];
+  long gRover;
+  int fftFrameSize, ovsamp;
+} priv_t;
+
+static int parse(sox_effect_t * effp, char **argv, sox_rate_t rate)
+{
+  priv_t *p = (priv_t *) effp->priv;
+  size_t i, time = 0, delay;
+  char const *next;
+
+  for (i = 0; i < p->nbends; ++i) {
+    if (argv)   /* 1st parse only */
+      p->bends[i].str = lsx_strdup(argv[i]);
+    next = lsx_parsesamples(rate, p->bends[i].str, &delay, 't');
+    if (next == NULL || *next != ',')
+      break;
+    p->bends[i].start = time += delay;
+    p->bends[i].cents = strtod(next + 1, (char **)&next);
+    if (p->bends[i].cents == 0 || *next != ',')
+      break;
+    next = lsx_parsesamples(rate, next + 1, &p->bends[i].duration, 't');
+    if (next == NULL || *next != '\0')
+      break;
+    time += p->bends[i].duration;
+  }
+  if (i < p->nbends)
+    return lsx_usage(effp);
+  return SOX_SUCCESS;
+}
+
+static int create(sox_effect_t * effp, int argc, char **argv)
+{
+  priv_t *p = (priv_t *) effp->priv;
+  char const * opts = "f:o:";
+  int c;
+
+  p->frame_rate = 25;
+  p->ovsamp = 16;
+  while ((c = getopt(argc, argv, opts)) != -1) switch (c) {
+    GETOPT_NUMERIC('f', frame_rate, 10 , 80)
+    GETOPT_NUMERIC('o', ovsamp,  4 , 32)
+    default: sox_fail("unknown option `-%c'", optopt); return lsx_usage(effp);
+  }
+  argc -= optind, argv += optind;
+
+  p->bends = lsx_calloc(p->nbends = argc, sizeof(*p->bends));
+  return parse(effp, argv, 1e6);     /* No rate yet; parse with dummy */
+}
+
+static int start(sox_effect_t * effp)
+{
+  priv_t *p = (priv_t *) effp->priv;
+  unsigned i;
+
+  int n = effp->in_signal.rate / p->frame_rate + .5;
+  for (p->fftFrameSize = 2; n > 2; p->fftFrameSize <<= 1, n >>= 1);
+  p->shift = 1;
+  parse(effp, 0, effp->in_signal.rate); /* Re-parse now rate is known */
+  p->in_pos = p->bends_pos = 0;
+  for (i = 0; i < p->nbends; ++i)
+    if (p->bends[i].duration)
+      return SOX_SUCCESS;
+  return SOX_EFF_NULL;
+}
+
+static int flow(sox_effect_t * effp, const sox_sample_t * ibuf,
+                sox_sample_t * obuf, size_t * isamp, size_t * osamp)
+{
+  priv_t *p = (priv_t *) effp->priv;
+  size_t i, len = *isamp = *osamp = min(*isamp, *osamp);
+  double magn, phase, tmp, window, real, imag;
+  double freqPerBin, expct;
+  long k, qpd, index, inFifoLatency, stepSize, fftFrameSize2;
+  float pitchShift = p->shift;
+
+  /* set up some handy variables */
+  fftFrameSize2 = p->fftFrameSize / 2;
+  stepSize = p->fftFrameSize / p->ovsamp;
+  freqPerBin = effp->in_signal.rate / p->fftFrameSize;
+  expct = 2. * M_PI * (double) stepSize / (double) p->fftFrameSize;
+  inFifoLatency = p->fftFrameSize - stepSize;
+  if (!p->gRover)
+    p->gRover = inFifoLatency;
+
+  /* main processing loop */
+  for (i = 0; i < len; i++) {
+    ++p->in_pos;
+
+    /* As long as we have not yet collected enough data just read in */
+    p->gInFIFO[p->gRover] = SOX_SAMPLE_TO_FLOAT_32BIT(ibuf[i], effp->clips);
+    obuf[i] = SOX_FLOAT_32BIT_TO_SAMPLE(
+        p->gOutFIFO[p->gRover - inFifoLatency], effp->clips);
+    p->gRover++;
+
+    /* now we have enough data for processing */
+    if (p->gRover >= p->fftFrameSize) {
+      if (p->bends_pos != p->nbends && p->in_pos >=
+          p->bends[p->bends_pos].start + p->bends[p->bends_pos].duration) {
+        pitchShift = p->shift *= pow(2., p->bends[p->bends_pos].cents / 1200);
+        ++p->bends_pos;
+      }
+      if (p->bends_pos != p->nbends && p->in_pos >= p->bends[p->bends_pos].start) {
+        double progress = (double)(p->in_pos - p->bends[p->bends_pos].start) /
+             p->bends[p->bends_pos].duration;
+        progress = 1 - cos(M_PI * progress);
+        progress *= p->bends[p->bends_pos].cents * (.5 / 1200);
+        pitchShift = p->shift * pow(2., progress);
+      }
+
+      p->gRover = inFifoLatency;
+
+      /* do windowing and re,im interleave */
+      for (k = 0; k < p->fftFrameSize; k++) {
+        window = -.5 * cos(2 * M_PI * k / (double) p->fftFrameSize) + .5;
+        p->gFFTworksp[2 * k] = p->gInFIFO[k] * window;
+        p->gFFTworksp[2 * k + 1] = 0.;
+      }
+
+      /* ***************** ANALYSIS ******************* */
+      smbFft(p->gFFTworksp, p->fftFrameSize, -1);
+
+      /* this is the analysis step */
+      for (k = 0; k <= fftFrameSize2; k++) {
+        /* de-interlace FFT buffer */
+        real = p->gFFTworksp[2 * k];
+        imag = p->gFFTworksp[2 * k + 1];
+
+        /* compute magnitude and phase */
+        magn = 2. * sqrt(real * real + imag * imag);
+        phase = atan2(imag, real);
+
+        /* compute phase difference */
+        tmp = phase - p->gLastPhase[k];
+        p->gLastPhase[k] = phase;
+
+        tmp -= (double) k *expct; /* subtract expected phase difference */
+
+        /* map delta phase into +/- Pi interval */
+        qpd = tmp / M_PI;
+        if (qpd >= 0)
+          qpd += qpd & 1;
+        else qpd -= qpd & 1;
+        tmp -= M_PI * (double) qpd;
+
+        /* get deviation from bin frequency from the +/- Pi interval */
+        tmp = p->ovsamp * tmp / (2. * M_PI);
+
+        /* compute the k-th partials' true frequency */
+        tmp = (double) k *freqPerBin + tmp * freqPerBin;
+
+        /* store magnitude and true frequency in analysis arrays */
+        p->gAnaMagn[k] = magn;
+        p->gAnaFreq[k] = tmp;
+
+      }
+
+      /* this does the actual pitch shifting */
+      memset(p->gSynMagn, 0, p->fftFrameSize * sizeof(float));
+      memset(p->gSynFreq, 0, p->fftFrameSize * sizeof(float));
+      for (k = 0; k <= fftFrameSize2; k++) {
+        index = k * pitchShift;
+        if (index <= fftFrameSize2) {
+          p->gSynMagn[index] += p->gAnaMagn[k];
+          p->gSynFreq[index] = p->gAnaFreq[k] * pitchShift;
+        }
+      }
+
+      for (k = 0; k <= fftFrameSize2; k++) { /* SYNTHESIS */
+        /* get magnitude and true frequency from synthesis arrays */
+        magn = p->gSynMagn[k], tmp = p->gSynFreq[k];
+        tmp -= (double) k *freqPerBin; /* subtract bin mid frequency */
+        tmp /= freqPerBin; /* get bin deviation from freq deviation */
+        tmp = 2. * M_PI * tmp / p->ovsamp; /* take p->ovsamp into account */
+        tmp += (double) k *expct; /* add the overlap phase advance back in */
+        p->gSumPhase[k] += tmp; /* accumulate delta phase to get bin phase */
+        phase = p->gSumPhase[k];
+        /* get real and imag part and re-interleave */
+        p->gFFTworksp[2 * k] = magn * cos(phase);
+        p->gFFTworksp[2 * k + 1] = magn * sin(phase);
+      }
+
+      for (k = p->fftFrameSize + 2; k < 2 * p->fftFrameSize; k++)
+        p->gFFTworksp[k] = 0.; /* zero negative frequencies */
+
+      smbFft(p->gFFTworksp, p->fftFrameSize, 1); /* do inverse transform */
+
+      /* do windowing and add to output accumulator */
+      for (k = 0; k < p->fftFrameSize; k++) {
+        window =
+            -.5 * cos(2. * M_PI * (double) k / (double) p->fftFrameSize) + .5;
+        p->gOutputAccum[k] +=
+            2. * window * p->gFFTworksp[2 * k] / (fftFrameSize2 * p->ovsamp);
+      }
+      for (k = 0; k < stepSize; k++)
+        p->gOutFIFO[k] = p->gOutputAccum[k];
+
+      memmove(p->gOutputAccum, /* shift accumulator */
+          p->gOutputAccum + stepSize, p->fftFrameSize * sizeof(float));
+
+      for (k = 0; k < inFifoLatency; k++) /* move input FIFO */
+        p->gInFIFO[k] = p->gInFIFO[k + stepSize];
+    }
+  }
+  return SOX_SUCCESS;
+}
+
+static int stop(sox_effect_t * effp)
+{
+  priv_t *p = (priv_t *) effp->priv;
+
+  if (p->bends_pos != p->nbends)
+    sox_warn("Input audio too short; bends not applied: %u",
+        p->nbends - p->bends_pos);
+  return SOX_SUCCESS;
+}
+
+static int kill(sox_effect_t * effp)
+{
+  priv_t *p = (priv_t *) effp->priv;
+  unsigned i;
+
+  for (i = 0; i < p->nbends; ++i)
+    free(p->bends[i].str);
+  free(p->bends);
+  return SOX_SUCCESS;
+}
+
+sox_effect_handler_t const *sox_bend_effect_fn(void)
+{
+  static sox_effect_handler_t handler = {
+    "bend", "{delay,cents,duration}", SOX_EFF_GETOPT,
+    create, start, flow, 0, stop, kill, sizeof(priv_t)
+  };
+  return &handler;
+}
--- a/src/effects.h
+++ b/src/effects.h
@@ -20,6 +20,7 @@
   EFFECT(bandpass)
   EFFECT(bandreject)
   EFFECT(bass)
+  EFFECT(bend)
   EFFECT(chorus)
   EFFECT(compand)
   EFFECT(contrast)
--- a/src/effects_i.c
+++ b/src/effects_i.c
@@ -211,71 +211,129 @@
  * # of samples.
  * Returns NULL on error, pointer to next char to parse otherwise.
  */
-char const * lsx_parsesamples(sox_rate_t rate, const char *str, size_t *samples, int def)
+char const * lsx_parsesamples(sox_rate_t rate, const char *str0, size_t *samples, int def)
 {
-    int found_samples = 0, found_time = 0;
-    int time = 0;
-    long long_samples;
-    float frac = 0;
-    char const * end;
-    char const * pos;
-    sox_bool found_colon, found_dot;
+  int i, found_samples = 0, found_time = 0;
+  char const * end;
+  char const * pos;
+  sox_bool found_colon, found_dot;
+  char * str = (char *)str0;
 
-    for (end = str; *end && strchr("0123456789:.ts", *end); ++end);
-    if (end == str)
+  for (;*str == ' '; ++str);
+  for (end = str; *end && strchr("0123456789:.ets", *end); ++end);
+  if (end == str)
+    return NULL;
+
+  pos = strchr(str, ':');
+  found_colon = pos && pos < end;
+
+  pos = strchr(str, '.');
+  found_dot = pos && pos < end;
+
+  if (found_colon || found_dot || *(end-1) == 't')
+    found_time = 1;
+  else if (*(end-1) == 's')
+    found_samples = 1;
+
+  if (found_time || (def == 't' && !found_samples)) {
+    for (*samples = 0, i = 0; *str != '.' && i < 3; ++i) {
+      char * last_str = str;
+      long part = strtol(str, &str, 10);
+      if (!i && str == last_str)
+        return NULL;
+      *samples += rate * part;
+      if (i < 2) {
+        if (*str != ':')
+          break;
+        ++str;
+        *samples *= 60;
+      }
+    }
+    if (*str == '.') {
+      char * last_str = str;
+      double part = strtod(str, &str);
+      if (str == last_str)
+        return NULL;
+      *samples += rate * part + .5;
+    }
+    return *str == 't'? str + 1 : str;
+  }
+  {
+    char * last_str = str;
+    double part = strtod(str, &str);
+    if (str == last_str)
       return NULL;
+    *samples = part + .5;
+    return *str == 's'? str + 1 : str;
+  }
+}
 
-    pos = strchr(str, ':');
-    found_colon = pos && pos < end;
+#if 0
 
-    pos = strchr(str, '.');
-    found_dot = pos && pos < end;
+#define TEST(st, samp, len) \
+  str = st; \
+  next = lsx_parsesamples(10000, str, &samples, 't'); \
+  assert(samples == samp && next == str + len);
 
-    if (found_colon || found_dot || *(end-1) == 't')
-        found_time = 1;
-    else if (*(end-1) == 's')
-        found_samples = 1;
+int main(int argc, char * * argv)
+{
+  char const * str, * next;
+  size_t samples;
 
-    if (found_time || (def == 't' && !found_samples))
-    {
-        *samples = 0;
+  TEST("0"  , 0, 1)
+  TEST("1" , 10000, 1)
 
-        while(1)
-        {
-            if (str[0] != '.' && sscanf(str, "%d", &time) != 1)
-                return NULL;
-            *samples += time;
+  TEST("0s" , 0, 2)
+  TEST("0s,", 0, 2)
+  TEST("0s/", 0, 2)
+  TEST("0s@", 0, 2)
 
-            while (*str != ':' && *str != '.' && *str != 0)
-                str++;
+  TEST("0t" , 0, 2)
+  TEST("0t,", 0, 2)
+  TEST("0t/", 0, 2)
+  TEST("0t@", 0, 2)
 
-            if (*str == '.' || *str == 0)
-                break;
+  TEST("1s" , 1, 2)
+  TEST("1s,", 1, 2)
+  TEST("1s/", 1, 2)
+  TEST("1s@", 1, 2)
+  TEST(" 01s" , 1, 4)
+  TEST("1e6s" , 1000000, 4)
 
-            /* Skip past ':' */
-            str++;
-            *samples *= 60;
-        }
+  TEST("1t" , 10000, 2)
+  TEST("1t,", 10000, 2)
+  TEST("1t/", 10000, 2)
+  TEST("1t@", 10000, 2)
+  TEST("1.1t" , 11000, 4)
+  TEST("1.1t,", 11000, 4)
+  TEST("1.1t/", 11000, 4)
+  TEST("1.1t@", 11000, 4)
+  TEST("1e6t" , 10000, 1)
 
-        if (*str == '.')
-        {
-            if (sscanf(str, "%f", &frac) != 1)
-                return NULL;
-        }
+  TEST(".0", 0, 2)
+  TEST("0.0", 0, 3)
+  TEST("0:0.0", 0, 5)
+  TEST("0:0:0.0", 0, 7)
 
-        *samples *= rate;
-        *samples += (rate * frac) + 0.5;
-        return end;
-    }
-    if (found_samples || (def == 's' && !found_time))
-    {
-        if (sscanf(str, "%ld", &long_samples) != 1)
-            return NULL;
-        *samples = long_samples;
-        return end;
-    }
-    return NULL;
+  TEST(".1", 1000, 2)
+  TEST(".10", 1000, 3)
+  TEST("0.1", 1000, 3)
+  TEST("1.1", 11000, 3)
+  TEST("1:1.1", 611000, 5)
+  TEST("1:1:1.1", 36611000, 7)
+  TEST("1:1", 610000, 3)
+  TEST("1:01", 610000, 4)
+  TEST("1:1:1", 36610000, 5)
+  TEST("1:", 600000, 2)
+  TEST("1::", 36000000, 3)
+
+  TEST("0.444444", 4444, 8)
+  TEST("0.555555", 5556, 8)
+
+  assert(!lsx_parsesamples(10000, "x", &samples, 't'));
+  return 0;
 }
+#endif 
 
 /* a note is given as an int,
  * 0   => 440 Hz = A