[xiph-commits] r17644 - websites/celt-codec.org/squishyball

xiphmont at svn.xiph.org xiphmont at svn.xiph.org
Wed Nov 24 12:46:15 PST 2010


Author: xiphmont
Date: 2010-11-24 12:46:15 -0800 (Wed, 24 Nov 2010)
New Revision: 17644

Added:
   websites/celt-codec.org/squishyball/mincurses.c
   websites/celt-codec.org/squishyball/mincurses.h
   websites/celt-codec.org/squishyball/tty.c
   websites/celt-codec.org/squishyball/tty.h
Modified:
   websites/celt-codec.org/squishyball/Makefile.am
   websites/celt-codec.org/squishyball/configure.ac
   websites/celt-codec.org/squishyball/main.c
   websites/celt-codec.org/squishyball/squishyball.1
Log:
Get more code into SVN.  No, it doesn't do much useful yet.



Modified: websites/celt-codec.org/squishyball/Makefile.am
===================================================================
--- websites/celt-codec.org/squishyball/Makefile.am	2010-11-24 18:09:51 UTC (rev 17643)
+++ websites/celt-codec.org/squishyball/Makefile.am	2010-11-24 20:46:15 UTC (rev 17644)
@@ -8,8 +8,7 @@
 mandir = @MANDIR@
 man_MANS = squishyball.1
 
-squishyball_SOURCES = main.c
-squishyball_LDADD = -lm
+squishyball_SOURCES = main.c tty.c tty.h mincurses.c mincurses.h
 
 debug:
 	$(MAKE) all CFLAGS="@DEBUG@"

Modified: websites/celt-codec.org/squishyball/configure.ac
===================================================================
--- websites/celt-codec.org/squishyball/configure.ac	2010-11-24 18:09:51 UTC (rev 17643)
+++ websites/celt-codec.org/squishyball/configure.ac	2010-11-24 20:46:15 UTC (rev 17644)
@@ -16,6 +16,10 @@
 PKG_CHECK_MODULES([vorbisfile], [vorbisfile])
 PKG_CHECK_MODULES([FLAC], [flac >= 0.8.0])
 PKG_CHECK_MODULES([ao], [ao > 1.0.0])
+AC_CHECK_LIB([ncurses], [initscr],,[AC_MSG_ERROR([ncurses required!])])
+AC_CHECK_LIB([ncurses], [_nc_tinfo_fkeysf], USE_FKEYSF=1, USE_FKEYSF=0)
+AC_CHECK_LIB([m], [cos])
+AC_CHECK_LIB([pthread], [pthread_create])
 
 # Checks for header files.
 AC_HEADER_STDC
@@ -69,9 +73,11 @@
                 ;;
         esac
 fi
-CFLAGS="$CFLAGS $cflags_save -DVERSION='\"$VERSION\"' $vorbisfile_CFLAGS $ao_CFLAGS $FLAC_CFLAGS"
-DEBUG="$DEBUG $cflags_save -DVERSION='\\\"$VERSION\\\"' $vorbisfile_CFLAGS $ao_CFLAGS $FLAC_CFLAGS"
-PROFILE="$PROFILE $cflags_save -DVERSION='\\\"$VERSION\\\"' $vorbisfile_CFLAGS $ao_CFLAGS $FLAC_CFLAGS"
+
+COMMON_FLAGS="$cflags_save $vorbisfile_CFLAGS $ao_CFLAGS $FLAC_CFLAGS -DUSE_FKEYSF=$USE_FKEYSF"
+CFLAGS="$CFLAGS -DVERSION='\"$VERSION\"' $COMMON_FLAGS"
+DEBUG="$DEBUG -DVERSION='\\\"$VERSION\\\"' $COMMON_FLAGS"
+PROFILE="$PROFILE -DVERSION='\\\"$VERSION\\\"' $COMMON_FLAGS"
 LIBS="$LIBS $vorbisfile_LIBS $ao_LIBS $FLAC_LIBS"
 AC_SUBST(DEBUG)
 AC_SUBST(PROFILE)

Modified: websites/celt-codec.org/squishyball/main.c
===================================================================
--- websites/celt-codec.org/squishyball/main.c	2010-11-24 18:09:51 UTC (rev 17643)
+++ websites/celt-codec.org/squishyball/main.c	2010-11-24 20:46:15 UTC (rev 17644)
@@ -1,4 +1,7 @@
 #define _GNU_SOURCE
+#define _LARGEFILE_SOURCE
+#define _LARGEFILE64_SOURCE
+#define _FILE_OFFSET_BITS 64
 #include <stdlib.h>
 #include <string.h>
 #include <stdio.h>
@@ -8,7 +11,16 @@
 #include <ao/ao.h>
 #include <FLAC/stream_decoder.h>
 #include <getopt.h>
+#include <poll.h>
+#include <pthread.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <time.h>
+#include <ncurses.h>
+#include "mincurses.h"
+#include "tty.h"
 
+#define MAXFILES 10
 static int verbose=0;
 
 static inline int host_is_big_endian() {
@@ -29,7 +41,7 @@
   int bits; /* negative indicates IEEE754 float */
   int ch;
   char *matrix;
-  void *data;
+  unsigned char *data;
   off_t size;
   int dither;
 };
@@ -274,7 +286,7 @@
 
   }else{
     fprintf(stderr,
-            "%s: Wav file is unsupported subformat (must be 8,16, or 24 bit PCM\n"
+            "%s: Wav file is unsupported subformat (must be 8,16, or 24-bit PCM\n"
             "or floating point PCM\n",path);
     goto err;
   }
@@ -282,7 +294,7 @@
   /* read the samples into memory */
   switch(pcm->bits){
   case 8:
-    /* load as 8 bit, expand it out to 16. */
+    /* load as 8-bit, expand it out to 16. */
     pcm->data = calloc(1,pcm->size*2);
     break;
   case 24:
@@ -321,7 +333,7 @@
       pcm->size=j;
     }
 
-    /* 8 bit must be expanded to 16 */
+    /* 8-bit must be expanded to 16 */
     if(samplesize==8){
       off_t j;
       unsigned char *d = pcm->data;
@@ -466,7 +478,7 @@
   case 3:
     pcm->matrix = strdup("L,R,C");
     break;
-  case 4:
+  default:
     pcm->matrix = strdup("L,R,BL,BR");
     break;
   }
@@ -509,7 +521,7 @@
       !(pcm->bits==24 || pcm->bits == 16 || pcm->bits == 8)){
     fprintf(stderr,
             "%s: Unsupported type of AIFF/AIFC file\n"
-            " Must be 8, 16 or 24 bit integer PCM.\n",path);
+            " Must be 8-, 16- or 24-bit integer PCM.\n",path);
     goto err;
   }
 
@@ -518,7 +530,7 @@
   /* read the samples into memory */
   switch(pcm->bits){
   case 8:
-    /* load as 8 bit, expand it out to 16. */
+    /* load as 8-bit, expand it out to 16. */
     pcm->data = calloc(1,pcm->size*2);
     break;
   case 24:
@@ -550,7 +562,7 @@
       pcm->size=j;
     }
 
-    /* 8 bit must be expanded to 16 */
+    /* 8-bit must be expanded to 16 */
     switch(pcm->bits){
     case 8:
       for(j=pcm->size-1;j>=0;j--){
@@ -595,6 +607,7 @@
   pcm->bits=16;
   pcm->ch=1;
   pcm->rate=48000;
+  pcm->matrix=strdup("M");
 
   if(fseek(in,0,SEEK_END)==-1){
     fprintf(stderr,"%s: Failed to seek\n",path);
@@ -723,7 +736,7 @@
         }
       break;
     default:
-      fprintf(stderr,"\r%s: Only 16 and 24 bit FLACs are supported for decode right now.\n",pcm->path);
+      fprintf(stderr,"\r%s: Only 16- and 24-bit FLACs are supported for decode right now.\n",pcm->path);
       return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
     }
   }
@@ -1155,7 +1168,7 @@
   off_t i;
   unsigned char *ret=realloc(pcm->data,pcm->size*3/2);
   if(!ret){
-    fprintf(stderr,"Unable to allocate memory while promoting file to 24 bit\n");
+    fprintf(stderr,"Unable to allocate memory while promoting file to 24-bit\n");
     exit(5);
   }
   pcm->data=ret;
@@ -1201,7 +1214,7 @@
   }
 }
 
-const char *chlist[]={"M","L","R","C","LFE","SL","SR","BC","BL","BR","CL","CR","X",NULL};
+const char *chlist[]={"X","M","L","R","C","LFE","SL","SR","BC","BL","BR","CL","CR",NULL};
 void tokenize_channels(char *matrix,int *out,int n){
   int i=0;
   char *t=strtok(matrix,",");
@@ -1320,98 +1333,98 @@
 struct option long_options[] = {
   {"ab",no_argument,0,'a'},
   {"abx",no_argument,0,'b'},
-  {"card",required_argument,0,'c'},
-  {"force-dither",no_argument,0,'d'},
+  {"beep-flip",no_argument,0,'B'},
+  {"casual",no_argument,0,'c'},
+  {"device",required_argument,0,'d'},
+  {"force-dither",no_argument,0,'D'},
   {"end-time",no_argument,0,'e'},
   {"help",no_argument,0,'h'},
-  {"comparisons",required_argument,0,'n'},
-  {"restart-mode",no_argument,0,'r'},
+  {"mark-flip",no_argument,0,'M'},
+  {"trials",required_argument,0,'n'},
+  {"restart-after",no_argument,0,'r'},
+  {"restart-every",no_argument,0,'R'},
   {"start-time",required_argument,0,'s'},
+  {"seamless-flip",no_argument,0,'S'},
   {"force-truncate",no_argument,0,'t'},
   {"verbose",no_argument,0,'v'},
   {"version",no_argument,0,'V'},
   {"xxy",no_argument,0,'x'},
   {0,0,0,0}
 };
-char *short_options="abc:de:hn:rs:tvVx";
+char *short_options="abcd:De:hn:rRs:tvVxBMS";
 
 void usage(FILE *out){
   fprintf(out,
-          "\nXiph.Org Squishyball %s\n"
-          "performs simple A/B, A/B/X and X/X/Y sample comparison\n"
-          "testing on the command line\n\n"
+          "\nXiph Squishyball %s\n"
+          "perform sample comparison testing on the command line\n\n"
           "USAGE:\n"
-          "  squishyball [options] fileA fileB\n\n"
+          "  squishyball [options] fileA [fileB [[-c] fileN...]]\n\n"
           "OPTIONS:\n"
           "  -a --ab                : Perform randomized A/B test\n"
-          "  -b --abx               : Perform A/B/X test (default)\n"
-          "  -c --card <N|device>   : If a number, output to Nth\n"
+          "  -b --abx               : Perform randomized A/B/X test\n"
+          "  -B --beep-flip         : Mark transitions between samples with\n"
+          "                           a short beep\n"
+          "  -c --casual            : casual mode; load up to ten\n"
+          "                           samples for non-randomized\n"
+          "                           comparison without trials (default).\n"
+          "  -d --device <N|dev>    : If a number, output to Nth\n"
           "                           sound device.  If a device name,\n"
           "                           use output driver/device matching\n"
           "                           that device name.\n"
-          "  -d --force-dither      : Always use dither when converting\n"
-          "                           to 16 bit for playback on output\n"
-          "                           devices that do not support 24 bit\n"
+          "  -D --force-dither      : Always use dither when converting\n"
+          "                           to 16-bit for playback on output\n"
+          "                           devices that do not support 24-bit\n"
           "                           playback. Currently only affects\n"
           "                           Vorbis playback; all other files\n"
           "                           a dithered by default during down-\n"
           "                           conversion.\n"
           "  -e --end-time <time>   : Set sample end time for playback\n"
           "  -h --help              : Print this usage information.\n"
-          "  -n --comparisons <n>   : Set desired number of comparisons\n"
+          "  -M --mark-flip         : Mark transitions between samples with\n"
+          "                           a short period of silence (default)\n"
+          "  -n --trials <n>        : Set desired number of trials\n"
           "                           (default: 10)\n"
-          "  -r --restart-mode      : Set 'restart mode', where sample\n"
-          "                           playback restarts from start point\n"
-          "                           after every test selection\n"
+          "  -r --restart-after     : Restart playback from sample start\n"
+          "                           after every trial.\n"
+          "  -R --restart-every     : Restart playback from sample start\n"
+          "                           after every 'flip' as well as after\n"
+          "                           every trial.\n"
           "  -s --start-time <time> : Set start time within sample for\n"
           "                           playback\n"
+          "  -S --seamless-flip     : Do not mark transitions between samples;\n"
+          "                           flip with a seamless crossfade\n"
           "  -t --force-truncate    : Always truncate (never dither) when\n"
-          "                           dwon-converting samples to 16 bit for\n"
+          "                           down-converting samples to 16-bit for\n"
           "                           playback.\n"
           "  -v --verbose           : Produce more progress information.\n"
           "  -V --version           : Print version and exit.\n"
-          "  -x --xxy               : Perform X/X/Y testing; This is\n"
-          "                           similar to A/B/X testing but A and\n"
-          "                           B are unknown. Instead of choosing\n"
-          "                           if X matches A or B, choose the\n"
-          "                           sample that does not match the other\n"
-          "                           two.\n"
+          "  -x --xxy               : Perform randomized X/X/Y test.\n"
           "\n"
           "INTERACTION:\n"
-          "  a b x  : Switch between A and B samples (A/B mode), or A, B\n"
-          "           and X samples (A/B/X mode)\n"
-          "   A B   : Select A or B as preferred sample (A/B mode), or\n"
-          "           sample A or sample B as match to sample X (A/B/X\n"
-          "           testing mode)\n"
-          "  1 2 3  : Switch between first second and third samples\n"
-          "           (X/X/Y testing mode)\n"
-          "  ! @ #  : Indicate the 'odd sample out' as sample 1, 2, or 3\n"
-          "           (X/X/Y testing mode)\n"
-          "  <- ->  : seek back/forward one second\n"
-          "           +shift or five seconds\n"
-          "           +control for 15 seconds\n"
-          "           +meta for one minute\n"
-          " <space> : Pause/resume playback\n"
-          "<bckspce>: Reset playback to start point\n"
-          "    e    : set end playback point to current playback time\n"
-          "           (see also -e above)\n"
-          "    E    : reset end playback time to end of sample\n"
-          "    r    : enable restart mode, where each comparison begins\n"
-          "           from the start time of the sample (see -r above)\n"
-          "    R    : clear restart mode; playback continues without\n"
-          "           interruption after each comparison\n"
-          "    s    : set start playback point to current playback time\n"
-          "           (see also -s above)\n"
-          "    S    : reset start playback time to 0:00:00.00\n"
-          "    ?    : Print this keymap\n"
-          "  Ctrl-C : Abort test early\n"
+          "   a b x   : Switch playback between A, B [and X] samples.\n"
+          "    A B    : Choose A or B sample for A/B[/X] trial result.\n"
+          "  1 2 3... : Switch between first, second, etc samples.\n"
+          "   ! @ #   : Choose sample 1, 2, or 3 for X/X/Y trial result.\n"
+          "<ins> <del>: Undo/redo last trial result selection.\n"
+          "  <enter>  : Choose current sample for this trial\n"
+          "   <- ->   : seek back/forward two seconds, +shift for 10 seconds\n"
+          "  <space>  : Pause/resume playback\n"
+          " <backspc> : Reset playback to start point\n"
+          "     e     : set end playback point to current playback time.\n"
+          "     E     : reset end playback time to end of sample\n"
+          "     F     : Toggle through beep-flip/mark-flip/seamless-flip modes.\n"
+          "     r     : Toggle through restart-after/restart-every/no-restart.\n"
+          "     s     : set start playback point to current playback time.\n"
+          "     S     : reset start playback time to 0:00:00.00\n"
+          "     ?     : Print this keymap\n"
+          "    ^-c    : Quit\n"
           "\n"
           "SUPPORTED FILE TYPES:\n"
-          "  WAV and WAVEX    : 8, 16, 24 bit linear integer PCM (format 1)\n"
+          "  WAV and WAVEX    : 8-, 16-, 24-bit linear integer PCM (format 1)\n"
           "                     32 bit float (format 3)\n"
-          "  AIFF and AIFC    : 8, 16, 24 bit linear integer PCM\n"
-          "  FLAC and OggFLAC : 16 and 24 bit\n"
-          "  SW               : mono signed 16 bit little endian raw\n"
+          "  AIFF and AIFC    : 8-, 16-, 24-bit linear integer PCM\n"
+          "  FLAC and OggFLAC : 16- and 24-bit\n"
+          "  SW               : mono signed 16-bit little endian raw\n"
           "  OggVorbis        : all Vorbis I files\n"
           "\n"
           ,VERSION);
@@ -1461,21 +1474,427 @@
   return 0;
 }
 
+float *fadewindow1;
+float *fadewindow2;
+float *fadewindow3;
+float *beep1;
+float *beep2;
+
+void put_val(unsigned char *d,int bps,float v){
+  int i = rint(v);
+  d[0]=i&0xff;
+  d[1]=(i>>8)&0xff;
+  if(bps==24)
+    d[2]=(i>>16)&0xff;
+}
+
+float get_val(unsigned char *d, int bps){
+  if(bps==16){
+    short i = d[0] | (d[1]<<8);
+    return (float)i;
+  }else{
+    int32_t i = ((d[0]<<8) | (d[1]<<16) | (d[2]<<24))>>8;
+    return (float)i;
+  }
+}
+
+int setup_windows(pcm_t **pcm, int test_files){
+  int i;
+  int fragsamples = pcm[0]->rate/20;  /* 50ms */
+
+  /* precompute the fades/beeps */
+  fadewindow1 = calloc(fragsamples,sizeof(*fadewindow1));
+  fadewindow2 = calloc(fragsamples,sizeof(*fadewindow2));
+  fadewindow3 = calloc(fragsamples,sizeof(*fadewindow3));
+  beep1 = calloc(fragsamples,sizeof(*beep1));
+  beep2 = calloc(fragsamples,sizeof(*beep2));
+
+  if(!fadewindow1 ||
+     !fadewindow2 ||
+     !fadewindow3 ||
+     !beep1 ||
+     !beep2)
+    exit(9);
+
+  /* fadewindow1 is a larger simple crossfade */
+  for(i=0;i<fragsamples;i++){
+    float val = cosf(M_PI*.5f*(i+.5f)/fragsamples);
+    fadewindow1[i] = val*val;
+  }
+
+  /* fadewindow2 goes to silence and back */
+  for(i=0;i<fragsamples/3;i++){
+    float val = cosf(M_PI*1.5f*(i+.5f)/fragsamples);
+    fadewindow2[i] = val*val;
+  }
+  for(;i<fragsamples;i++)
+    fadewindow2[i] = 0.f;
+
+  /* fadewindow3 crossfades with attenuation to give headroom for a beep */
+  for(i=0;i<fragsamples/4;i++){
+    float val = cosf(M_PI*2.f*(i+.5f)/fragsamples);
+    fadewindow3[i] = val*val*.875f+.125f;
+  }
+  for(;i<fragsamples*3/4;i++)
+    fadewindow3[i] = .125f;
+  for(;i<fragsamples;i++){
+    float val = cosf(M_PI*2.f*(i-fragsamples*3/4+.5f)/fragsamples);
+    fadewindow3[i] = val*val*.125f;
+  }
+
+  /* Single beep for flipping */
+  for(i=0;i<fragsamples/4;i++){
+    beep1[i]=0.;
+    beep1[fragsamples-i-1]=0.;
+  }
+  float base = 3.14159f*2.f*500./pcm[0]->rate;
+  for(;i<fragsamples*3/4;i++){
+    float f = i-fragsamples/4+.5;
+    float w = cos(3.14159f*f/fragsamples);
+    float b =
+      sin(f*base)+
+      sin(f*base*3)*.33+
+      sin(f*base*5)*.2+
+      sin(f*base*7)*.14+
+      sin(f*base*9)*.11;
+    w*=w;
+    beep1[i] = w*b*.125;
+  }
+
+  /* Double beep for selection */
+  for(i=0;i<fragsamples/4;i++){
+    beep2[i]=0.;
+    beep2[fragsamples-i-1]=0.;
+  }
+  for(;i<fragsamples/2;i++){
+    float f = i-fragsamples/4+.5;
+    float w = cos(3.14159f*2.*f/fragsamples);
+    float b =
+      sin(f*base)+
+      sin(f*base*3)*.33+
+      sin(f*base*5)*.2+
+      sin(f*base*7)*.14+
+      sin(f*base*9)*.11;
+    w*=w;
+    beep2[i] = w*b*.125;
+  }
+  base = 3.14159f*2.f*1000./pcm[0]->rate;
+  for(;i<fragsamples*3/4;i++){
+    float f = i-fragsamples/2+.5;
+    float w = cos(3.14159f*2.*f/fragsamples);
+    float b =
+      sin(f*base)+
+      sin(f*base*3)*.33+
+      sin(f*base*5)*.2+
+      sin(f*base*7)*.14+
+      sin(f*base*9)*.11;
+    w*=w;
+    beep2[i] = w*b*.125;
+  }
+
+  /* make sure that the samples are at least fragsamples*2 in length! If they're not, extend... */
+  if(pcm[0]->size<fragsamples*2){
+    int fadesize = pcm[0]->size/4;
+    int bps = (pcm[0]->bits+7)/8;
+    int ch = pcm[0]->ch;
+    int bpf = bps*ch;
+
+    for(i=0;i<test_files;i++){
+      int j,k;
+      unsigned char *newd=calloc(fadesize,bpf);
+      if(!newd){
+        fprintf(stderr,"Unable to allocate memory to extend sample to minimum length.\n");
+        exit(5);
+      }
+      memcpy(newd,pcm[i]->data,fragsamples*2*bpf);
+      free(pcm[i]->data);
+      pcm[i]->data=newd;
+
+      newd+=pcm[i]->size-fadesize;
+      for(j=0;j<fadesize;j++){
+        float v = cosf(M_PI*.5f*(i+.5f)/fadesize);
+        for(k=0;k<ch;k++){
+          put_val(newd,bps,v * get_val(newd,bps));
+          newd+=bps;
+        }
+      }
+      pcm[i]->size=fragsamples*2;
+    }
+  }
+  return fragsamples;
+}
+
+typedef struct {
+  pthread_mutex_t mutex;
+  pthread_cond_t main_cond;
+  pthread_cond_t play_cond;
+  pthread_cond_t key_cond;
+  int exiting;
+
+  ao_device *adev;
+  unsigned char *fragment;
+  int fragment_size;
+  int key_waiting;
+} threadstate_t;
+
+/* playback is a degenerate thread that simply allows audio output
+   without blocking */
+void *playback_thread(void *arg){
+  threadstate_t *s = (threadstate_t *)arg;
+  ao_device *adev = s->adev;
+
+  pthread_mutex_lock(&s->mutex);
+  while(1){
+    if(s->exiting){
+      pthread_mutex_unlock(&s->mutex);
+      break;
+    }
+    if(s->fragment_size){
+      int ret;
+      unsigned char *data=s->fragment;
+      int n=s->fragment_size;
+      pthread_mutex_unlock(&s->mutex);
+      ret=ao_play(adev, (void *)data, n);
+      pthread_mutex_lock(&s->mutex);
+      if(ret==0)s->exiting=1;
+      s->fragment_size=0;
+      s->fragment=0;
+      pthread_cond_signal(&s->main_cond);
+      if(s->exiting){
+        pthread_mutex_unlock(&s->mutex);
+        break;
+      }
+    }
+
+    pthread_cond_wait(&s->play_cond,&s->mutex);
+  }
+  ao_close(adev);
+  ao_shutdown();
+  return NULL;
+}
+
+/* keyboard is a degenerate thread that wakes the main thread when
+   keyboard input [may] be available */
+void *fd_thread(void *arg){
+  threadstate_t *s = (threadstate_t *)arg;
+
+  pthread_mutex_lock(&s->mutex);
+  while(1){
+    int ret;
+    struct pollfd fds={STDIN_FILENO,POLLIN,0};
+    if(s->exiting){
+      pthread_mutex_unlock(&s->mutex);
+      break;
+    }
+    pthread_mutex_unlock(&s->mutex);
+    ret=poll(&fds, 1, -1);
+    pthread_mutex_lock(&s->mutex);
+    if(fds.revents&(POLLERR|POLLHUP|POLLNVAL))s->exiting=1;
+    s->key_waiting=1;
+    pthread_cond_signal(&s->main_cond);
+    if(s->exiting){
+      pthread_mutex_unlock(&s->mutex);
+      break;
+    }
+    pthread_cond_wait(&s->key_cond,&s->mutex);
+  }
+  return NULL;
+}
+
+/* fragment is filled such that a crossloop never begins after
+   pcm->size-fragsize, and it always begins from the start of the
+   window, even if that means starting a crossloop late because the
+   endpos moved. */
+void fill_fragment1(unsigned char *out, pcm_t *pcm, off_t start, off_t *pos, off_t end, off_t *loop,int fragsamples){
+  int bps = (pcm->bits+7)/8;
+  int cpf = pcm->ch;
+  int bpf = bps*cpf;
+  int fragsize = fragsamples*bpf;
+
+  /* guard limits here */
+  if(end<fragsize)end=fragsize;
+  if(end>pcm->size)end=pcm->size;
+  if(start<0)start=0;
+  if(start>pcm->size-fragsize)start=pcm->size-fragsize;
+
+  /* we fill a fragment from the data buffer of the passed in pcm_t.
+     It's possible we'll need to crossloop from the end of the sample,
+     and the start/end markers may have moved so that the cursor is
+     outside the strict sample bounds. */
+
+  /* if *loop>0, we're in the process of crosslapping at pos ><
+     start+(fragsize-*loop*bpf). Stay the course. */
+  if(*loop){
+    int lp = *loop;
+    int i,j;
+    unsigned char *A = pcm->data+*pos;
+    unsigned char *B = pcm->data+start+(fragsamples-lp)*bpf;
+    for(i=0;i<fragsamples;i++){
+      if(lp){
+        float w = fadewindow1[--lp];
+        for(j=0;j<cpf;j++){
+          float val = get_val(A,bps)*(1.-w) + get_val(B,bps)*w;
+          put_val(out,val,bps);
+          A+=bps;
+          B+=bps;
+          out+=bps;
+        }
+      }else{
+        /* crossloop finished, the rest is B */
+        memcpy(out,B,bpf);
+        B+=bpf;
+        out+=bpf;
+      }
+    }
+    *loop=0;
+    *pos=B-pcm->data;
+  }else{
+    /* no crossloop in progress... should one be? If the cursor is
+       before start, do nothing.  If it's past end-fragsize, begin a
+       crossloop immediately.  If the current fragment will extend
+       beyond end-fragsize, begin the crossloop at end-fragsize */
+    if(*pos>pcm->size-fragsize){
+      /* Error condiiton; should not be possible */
+      fprintf(stderr,"Internal error; %ld>%ld, Monty fucked up.\n",(long)*pos,(long)pcm->size-fragsize);
+      exit(100);
+    }else if(*pos+fragsize>=end-fragsize){
+      int i,j;
+      unsigned char *A = pcm->data+*pos;
+      unsigned char *B = pcm->data+start;
+      int lp = end-*pos;
+      if(lp<fragsamples)lp=fragsamples; /* If we're late, start immediately, but use full window */
+
+      for(i=0;i<fragsamples;i++){
+        if(--lp>fragsamples){
+          /* still before crossloop begins */
+          memcpy(out,A,bpf);
+          A+=bpf;
+          out+=bpf;
+        }else{
+          /* crosslooping */
+          float w = fadewindow1[lp];
+          for(j=0;j<cpf;j++){
+            float val = get_val(A,bps)*(1.-w) + get_val(B,bps)*w;
+            put_val(out,val,bps);
+            A+=bps;
+            B+=bps;
+            out+=bps;
+          }
+        }
+      }
+      *loop=lp;
+      *pos=(lp==0?B-pcm->data:A-pcm->data);
+    }else{
+      /* no crossloop */
+      unsigned char *A = pcm->data+*pos;
+      memcpy(out,A,fragsize);
+      *loop=0;
+      *pos=A-pcm->data+fragsize;
+    }
+  }
+}
+
+/* fragment is filled such that a crossloop is always 'exactly on
+   schedule' even if that means beginning partway through the window. */
+void fill_fragment2(unsigned char *out, pcm_t *pcm, off_t start, off_t *pos, off_t end, off_t *loop,int fragsamples){
+  int bps = (pcm->bits+7)/8;
+  int cpf = pcm->ch;
+  int bpf = bps*cpf;
+  int fragsize=fragsamples*bpf;
+
+  /* guard limits here */
+  if(end<fragsize)end=fragsize;
+  if(end>pcm->size)end=pcm->size;
+  if(start<0)start=0;
+  if(start>pcm->size-fragsize)start=pcm->size-fragsize;
+
+  /* loop is never in progress for a fill_fragment2; called only during a seek crosslap */
+  unsigned char *A = pcm->data+*pos;
+  if(end-*pos>=fragsize*2){
+    /* no crosslap */
+    memcpy(out,A,fragsize);
+    *loop=0;
+    *pos=A-pcm->data+fragsize;
+  }else{
+    /* just before crossloop, in the middle of a crossloop, or just after crossloop */
+    int i,j;
+    off_t lp = end-*pos;
+    unsigned char *B = pcm->data+start;
+    if(lp<fragsamples)B+=(fragsamples-lp)*bpf;
+
+    for(i=0;i<fragsamples;i++){
+      --lp;
+      if(lp>=fragsamples){
+        /* not yet crosslooping */
+        memcpy(out,A,bpf);
+        A+=bpf;
+        out+=bpf;
+      }else if (lp>=0){
+        /* now crosslooping */
+        float w = fadewindow1[lp];
+        for(j=0;j<cpf;j++){
+          float val = get_val(A,bps)*(1.-w) + get_val(B,bps)*w;
+          put_val(out,val,bps);
+          A+=bps;
+          B+=bps;
+          out+=bps;
+        }
+      }else{
+        /* after crosslap */
+        memcpy(out,B,bpf);
+        B+=bpf;
+        out+=bpf;
+       }
+    }
+    *loop=(lp>0?(lp<fragsamples?lp:fragsamples):0);
+    *pos=(lp>0?A-pcm->data:B-pcm->data);
+  }
+}
+
+void randomize_samples(int *r,int test_mode){
+  switch(test_mode){
+  case 1:
+    r[2] = random()&1;
+    /* fall through */
+  case 0:
+    r[0] = random()&1;
+    r[1] = 1-r[0];
+    break;
+  case 2:
+    r[0] = random()&1;
+    r[1] = random()&1;
+    if(r[0] == r[1])
+      r[2]=1-r[0];
+    else
+      r[2] = random()&1;
+    break;
+  }
+}
+
 int main(int argc, char **argv){
+  int fragsamples;
+  int fragsize;
+  unsigned char *fragmentA;
+  unsigned char *fragmentB;
+  pthread_t playback_handle;
+  pthread_t fd_handle;
+  threadstate_t state;
   int c,long_option_index;
-  pcm_t *A=NULL;
-  pcm_t *B=NULL;
-  int test_mode=1;
+  pcm_t *pcm[MAXFILES];
+  int test_mode=3;
+  int test_files;
   char *device=NULL;
   int force_dither=0;
   int force_truncate=0;
   int restart_mode=0;
+  int beep_mode=1;
   int tests=10;
   double start=0;
   double end=-1;
   int outbits=0;
   ao_device *adev=NULL;
-  int decode_to_16=0;
+  int randomize[MAXFILES];
+  int i;
   /* parse options */
 
   while((c=getopt_long(argc,argv,short_options,long_options,&long_option_index))!=EOF){
@@ -1489,13 +1908,25 @@
     case 'b':
       test_mode=1;
       break;
+    case 'c':
+      test_mode=3;
+      break;
     case 'x':
       test_mode=2;
       break;
-    case 'c':
+    case 'M':
+      beep_mode=1;
+      break;
+    case 'B':
+      beep_mode=2;
+      break;
+    case 'S':
+      beep_mode=3;
+      break;
+    case 'd':
       device=strdup(optarg);
       break;
-    case 'd':
+    case 'D':
       force_dither=1;
       force_truncate=0;
       break;
@@ -1519,6 +1950,9 @@
     case 'r':
       restart_mode=1;
       break;
+    case 'R':
+      restart_mode=2;
+      break;
     case 'v':
       verbose=1;
       break;
@@ -1531,76 +1965,65 @@
     }
   }
 
-  if(argc-optind!=2){
-    usage(stderr);
-    exit(1);
+  /* Verify stdin is a tty! */
+
+  test_files=argc-optind;
+  if(test_mode==3){
+    if(test_files<1 || test_files>MAXFILES){
+      usage(stderr);
+      exit(1);
+    }
+  }else{
+    if(test_files!=2){
+      usage(stderr);
+      exit(1);
+    }
   }
 
-  A=load_audio_file(argv[optind]);
-  if(A)
-    B=load_audio_file(argv[optind+1]);
-  if(!B)
-    exit(2);
+  outbits=16;
+  for(i=0;i<test_files;i++){
+    pcm[i]=load_audio_file(argv[optind+i]);
+    if(!pcm[i])exit(2);
 
-  /* An overloaded invariant: Only lossy samples that decode to float
-     (eg, Ogg Vorbis) will have bits==-32 but dither set to 0.  If one
-     input sample is 16 bit and the other is -32/0, we want the floats
-     to render down to 16 bit, not 24 bit */
-  if((A->bits==16 && B->bits==-32 && B->dither==0) ||
-     (B->bits==16 && A->bits==-32 && A->dither==0))
-    decode_to_16=1;
+    if(!pcm[i]->dither && force_dither)pcm[i]->dither=1;
+    if(pcm[i]->bits!=16 && force_truncate)pcm[i]->dither=0;
 
-  if(!A->dither && force_dither)A->dither=1;
-  if(!B->dither && force_dither)B->dither=1;
-  if(A->bits!=16 && force_truncate)A->dither=0;
-  if(B->bits!=16 && force_truncate)B->dither=0;
+    /* Are all samples the same rate?  If not, bail. */
+    if(pcm[0]->rate != pcm[i]->rate){
+      fprintf(stderr,"Input sample rates do not match!\n"
+              "\t%s: %dHz\n"
+              "\t%s: %dHz\n"
+              "Aborting\n",pcm[0]->path,pcm[0]->rate,pcm[i]->path,pcm[i]->rate);
+      exit(3);
+    }
 
-  /* Are the samples the same rate?  If not, bail. */
-  if(A->rate != B->rate){
-    fprintf(stderr,"Input sample rates do not match!\n"
-            "\t%s: %dHz\n"
-            "\t%s: %dHz\n"
-            "Aborting\n",A->path,A->rate,B->path,B->rate);
-    exit(3);
-  }
+    /* Are all samples the same number of channels?  If not, bail. */
+    if(pcm[0]->ch != pcm[i]->ch){
+      fprintf(stderr,"Input channel counts do not match!\n"
+              "\t%s: %dHz\n"
+              "\t%s: %dHz\n"
+              "Aborting\n",pcm[0]->path,pcm[0]->ch,pcm[i]->path,pcm[i]->ch);
+      exit(3);
+    }
 
-  /* Are the samples the same number of channels?  If not, bail. */
-  if(A->ch != B->ch){
-    fprintf(stderr,"Input channel counts do not match!\n"
-            "\t%s: %dHz\n"
-            "\t%s: %dHz\n"
-            "Aborting\n",A->path,A->ch,B->path,B->ch);
-    exit(3);
+    if(abs(pcm[i]->bits)>outbits)outbits=abs(pcm[i]->bits);
   }
 
-  if(decode_to_16){
-    convert_to_16(A);
-    convert_to_16(B);
-  }
-
   /* before proceeding, make sure we can open up playback for the
      requested number of channels and max bit depth; if not, we may
      need to downconvert. */
-  outbits = abs(A->bits);
-  if(abs(B->bits)>outbits)outbits=abs(B->bits);
   if(outbits==32)outbits=24;
-
-  /* See if we can get playback with desired bit-depth/channels */
   ao_initialize();
-  if((adev=setup_playback(A->rate,A->ch,outbits,A->matrix,device))==NULL){
-    /* If opening playback failed for 24 bit, try for 16 */
+  if((adev=setup_playback(pcm[0]->rate,pcm[0]->ch,outbits,pcm[0]->matrix,device))==NULL){
+    /* If opening playback failed for 24-bit, try for 16 */
     if(outbits>16){
-      if((adev=setup_playback(A->rate,A->ch,16,A->matrix,device))==NULL){
+      if((adev=setup_playback(pcm[0]->rate,pcm[0]->ch,16,pcm[0]->matrix,device))==NULL){
         fprintf(stderr,"Unable to open audio device for playback.\n");
         exit(4);
       }else{
         if(verbose)
-          fprintf(stderr,"24 bit playback unavailable; down-converting to 16 bit\n");
-
-        /* demote to 16 bit playback right now */
-        convert_to_16(A);
-        convert_to_16(B);
-
+          fprintf(stderr,"24-bit playback unavailable; down-converting to 16-bit\n");
+        outbits=16;
       }
     }else{
       fprintf(stderr,"Unable to open audio device for playback.\n");
@@ -1608,60 +2031,371 @@
     }
   }
 
-  /* Do we need to reconcile sample depths? */
-  if(A->bits!=B->bits || A->bits<0 || B->bits<0){
-    convert_to_24(A);
-    convert_to_24(B);
+  /* reconcile sample depths */
+  for(i=0;i<test_files;i++){
+    if(outbits==16){
+      convert_to_16(pcm[i]);
+    }else{
+      convert_to_24(pcm[i]);
+    }
   }
 
-  /* Sanity check channel matrices. */
-  if(A->ch>2){
-    if(!A->matrix || !B->matrix){
-      if(A->matrix){
+  /* permute/reconcile the matrices before playback begins */
+  /* Invariant: all loaded files have a channel map */
+  for(i=1;i<test_files;i++)
+    if(strcmp(pcm[0]->matrix,pcm[i]->matrix))
+      reconcile_channel_maps(pcm[0],pcm[i]);
+
+  /* Are the samples the same length?  If not, warn and choose the shortest. */
+  {
+    off_t n=pcm[0]->size;
+    int flag=0;
+    for(i=1;i<test_files;i++){
+      if(pcm[i]->size!=n)flag=1;
+      if(pcm[i]->size<n)n=pcm[i]->size;
+    }
+
+    if(flag){
+      if(verbose)
+        fprintf(stderr,"Input sample lengths do not match!\n");
+
+      for(i=0;i<test_files;i++){
         if(verbose)
-          fprintf(stderr,"%s: No explicit output channel ordering determined.  Using output\n"
-                  "\tmatrix from %s.\n",B->path,A->path);
-        B->matrix = strdup(A->matrix);
-      }else if (B->matrix){
-        if(verbose)
-          fprintf(stderr,"%s: No explicit output channel ordering determined.  Using output\n"
-                  "\tmatrix from %s.\n",A->path,B->path);
-        A->matrix = strdup(B->matrix);
-      }else{
-        if(verbose)
-          fprintf(stderr,"Neither sample specifies an output channel ordering;\n"
-                  "\ttossing samples at sound card, hoping something reasonable happens.\n");
+        fprintf(stderr,"\t%s: %s\n",pcm[i]->path,
+                make_time_string((double)pcm[i]->size/pcm[i]->ch/((pcm[i]->bits+7)/8)/pcm[i]->rate));
+        pcm[i]->size=n;
       }
-    }else{
-      /* permute/reconcile the matrices before playback begins */
-      if(strcmp(A->matrix,B->matrix))
-        reconcile_channel_maps(A,B);
+      if(verbose)
+        fprintf(stderr,"Using the shortest sample for playback length...\n");
     }
   }
 
-  /* Are the samples the same length?  If not, warn and choose the shorter. */
-  if(A->size!=B->size){
-    if(verbose){
-      fprintf(stderr,"Input sample lengths do not match!\n");
-      fprintf(stderr,"\t%s: %s\n",A->path,make_time_string((double)A->size/A->ch/((A->bits+7)/8)/A->rate));
-      fprintf(stderr,"\t%s: %s\n",B->path,make_time_string((double)B->size/B->ch/((B->bits+7)/8)/B->rate));
-      fprintf(stderr,"Using the shorter sample for playback length...\n");
-    }
-    if(A->size>B->size)
-      A->size=B->size;
-    else
-      B->size=A->size;
+  /* set up various transition windows/beeps */
+  fragsamples=setup_windows(pcm,test_files);
+
+  /* set up terminal */
+  min_init_panel(5);
+
+  /* set up shared state */
+  memset(&state,0,sizeof(state));
+  pthread_mutex_init(&state.mutex,NULL);
+  pthread_cond_init(&state.main_cond,NULL);
+  pthread_cond_init(&state.play_cond,NULL);
+  pthread_cond_init(&state.key_cond,NULL);
+  state.adev=adev;
+
+  /* fire off helper threads */
+  if(pthread_create(&playback_handle,NULL,playback_thread,&state)){
+    fprintf(stderr,"Failed to create playback thread.\n");
+    exit(7);
   }
+  if(pthread_create(&fd_handle,NULL,fd_thread,&state)){
+    fprintf(stderr,"Failed to create playback thread.\n");
+    exit(7);
+  }
 
+  /* casual mode is not randomized */
+  for(i=0;i<MAXFILES;i++)
+    randomize[i]=i;
+  /* randomize samples for first trial */
+  srandom(time(NULL)+getpid());
+  randomize_samples(randomize,test_mode);
+
   /* playback loop */
+  pthread_mutex_lock(&state.mutex);
+  {
+    int current_sample=randomize[0];
+    int current_choice=0;
+    int flip_to=0;
+    int do_flip=0;
+    int do_select=0;
+    int do_pause=0;
+    int do_seek=0;
+    off_t loop_pos=0;
+    off_t seek_to=0;
+    int bps=(pcm[0]->bits+7)/8;
+    int ch=pcm[0]->ch;
+    int bpf=ch*bps;
+    int rate=pcm[0]->rate;
+    int size=pcm[0]->size;
+    off_t start_pos=rint(start*rate);
+    off_t end_pos=(end>0?rint(end*rate):size);
+    off_t current_pos;
+    int paused=0;
 
+    fragsize=fragsamples*bpf;
+    fragmentA=calloc(fragsize,1);
+    fragmentB=calloc(fragsize,1);
+    if(!fragmentA || !fragmentB){
+      fprintf(stderr,"Failed to allocate internal fragment memory\n");
+      exit(5);
+    }
+    if(start_pos<0)start_pos=0;
+    if(start_pos>size-fragsize)start_pos=size-fragsize;
+    if(end_pos<fragsize)end_pos=fragsize;
+    if(end_pos>size)end_pos=size;
+    current_pos=start_pos;
 
+    while(1){
 
+      int c;
+      if(state.exiting) break;
 
+      if(state.key_waiting && !do_flip && !do_pause && !do_select){
+        /* service keyboard */
+        pthread_mutex_unlock(&state.mutex);
+        c=min_getch(1);
+        switch(c){
+        case ERR:
+          break;
+        case 3:
+          /* control-c */
 
+          pthread_mutex_lock(&state.mutex);
+          state.exiting=1;
+          pthread_mutex_unlock(&state.mutex);
+          break;
 
+        case '0': case '9': case '8': case '7': case '6':
+        case '5': case '4': case '3': case '2': case '1':
+          flip_to=c-'1';
+          do_flip=1;
+          break;
+        case 'a':
+          flip_to=0;
+          do_flip=1;
+          break;
+        case 'b':
+          flip_to=1;
+          do_flip=1;
+          break;
+        case 'x':
+          flip_to=2;
+          do_flip=1;
+          break;
+        case 'A':
+          flip_to=0;
+          do_select=1;
+          break;
+        case 'B':
+          flip_to=1;
+          do_select=1;
+          break;
+        case 'X':
+          flip_to=2;
+          do_select=1;
+          break;
+        case '!':
+          flip_to=0;
+          do_select=1;
+          break;
+        case '@':
+          flip_to=1;
+          do_select=1;
+          break;
+        case '#':
+          flip_to=2;
+          do_select=1;
+          break;
+        case ' ':
+          do_pause=1;
+          break;
+        case KEY_LEFT:
+          seek_to-=pcm[0]->rate*bpf*2;
+          do_seek=1;
+          break;
+        case KEY_RIGHT:
+          seek_to+=pcm[0]->rate*bpf*2;
+          do_seek=1;
+          break;
+        case KEY_SLEFT:
+          seek_to-=pcm[0]->rate*bpf*10;
+          do_seek=1;
+          break;
+        case KEY_SRIGHT:
+          seek_to+=pcm[0]->rate*bpf*10;
+          do_seek=1;
+          break;
+        case KEY_BACKSPACE:
+          seek_to=start_pos;
+          do_seek=1;
+          break;
+        }
+
+        if(do_flip && flip_to==current_choice) do_flip=0;
+
+        switch(test_mode){
+        case 0:
+          if(flip_to>1){
+            do_flip=0;
+            do_select=0;
+          }
+          break;
+        case 1:
+          if(do_select && flip_to>1){
+            do_select=0;
+          }
+        case 2:
+          if(flip_to>2){
+            do_flip=0;
+            do_select=0;
+          }
+          break;
+        case 3:
+          if(flip_to>=test_files)
+            do_flip=0;
+          if(do_select)
+            do_select=0;
+          break;
+        }
+
+        while(current_pos + seek_to>end_pos)seek_to-=(end_pos-start_pos);
+        while(current_pos + seek_to<start_pos)seek_to+=(end_pos-start_pos);
+
+        pthread_mutex_lock(&state.mutex);
+        state.key_waiting=0;
+        pthread_cond_signal(&state.key_cond);
+      }
+
+
+      /* update terminal */
+
+
+
+      if(state.fragment_size==0 && !state.exiting){
+        /* fill audio output */
+        off_t pos=current_pos;
+        pthread_mutex_unlock(&state.mutex);
+
+        if(do_flip){
+          current_choice=flip_to;
+          if(restart_mode==2){
+            seek_to += start_pos-current_pos;
+            do_seek=1;
+          }
+        }
+
+        if(do_select){
+          /* randomize now so we can fill in fragmentB */
+          randomize_samples(randomize,test_mode);
+          if(restart_mode){
+            seek_to += start_pos-current_pos;
+            do_seek=1;
+          }
+        }
+
+        if(paused){
+          memset(fragmentA,0,fragsize);
+          if(do_seek){
+            current_pos+=seek_to;
+            seek_to=0;
+            do_seek=0;
+            loop_pos=0;
+          }
+        }else{
+          fill_fragment1(fragmentA, pcm[current_sample], start_pos, &current_pos, end_pos, &loop_pos, fragsamples);
+          if(do_flip || do_seek || do_select){
+            current_sample=randomize[current_choice];
+            if(do_seek){
+              current_pos=pos+seek_to;
+              fill_fragment2(fragmentB, pcm[current_sample], start_pos, &current_pos, end_pos, &loop_pos, fragsamples);
+              seek_to=0;
+            }else{
+              fill_fragment1(fragmentB, pcm[current_sample], start_pos, &pos, end_pos, &loop_pos, fragsamples);
+            }
+          }
+        }
+
+        if(do_flip || do_select || do_seek){
+          int j;
+          float *wA=fadewindow1, *wB, *beep=0;
+          if(do_select){
+            wA=fadewindow3;
+            beep=beep2;
+          }
+          if(do_flip){
+            /* A and B are crossfaded according to beep mode */
+            beep=0;
+            switch(beep_mode){
+            case 1: /* mark, fadewindow 2 */
+              wA=fadewindow2;
+              break;
+            case 2:
+              wA=fadewindow3;
+              beep=beep1;
+              break;
+            case 3:
+              wA=fadewindow1;
+              break;
+            }
+          }
+          wB=wA+fragsamples-1;
+          for(i=0;i<fragsamples;i++){
+            for(j=0;j<ch;j++){
+              put_val(fragmentA,bps,get_val(fragmentA,bps)**(wA+i) + get_val(fragmentB,bps)**(wB-i)+
+                      (beep?beep[i]:0));
+              fragmentA+=bps;
+              fragmentB+=bps;
+            }
+          }
+          do_flip=0;
+          do_select=0;
+          do_seek=0;
+        }else if(do_pause){
+          int j;
+          if(paused){
+            float *wA=fadewindow1+fragsamples-1;
+            for(i=0;i<fragsamples;i++){
+              for(j=0;j<ch;j++){
+                put_val(fragmentA,bps,get_val(fragmentA,bps)**(wA-i));
+                fragmentA+=bps;
+              }
+            }
+
+          }else{
+            float *wA=fadewindow1;
+            for(i=0;i<fragsamples;i++){
+              for(j=0;j<ch;j++){
+                put_val(fragmentA,bps,get_val(fragmentA,bps)**(wA+i));
+                fragmentA+=bps;
+              }
+            }
+          }
+          paused = !paused;
+          do_pause=0;
+          memset(fragmentB,0,fragsize);
+        }
+
+        pthread_mutex_lock(&state.mutex);
+        state.fragment=fragmentA;
+        state.fragment_size=fragsize;
+        pthread_cond_signal(&state.play_cond);
+      }
+
+      /* wait */
+      if(!state.key_waiting && state.fragment_size>0)
+        pthread_cond_wait(&state.main_cond,&state.mutex);
+    }
+  }
+
   /* done */
-  ao_close(adev);
-  ao_shutdown();
+  /* join */
+  close(STDIN_FILENO);
+  pthread_cond_signal(&state.play_cond);
+  pthread_mutex_unlock(&state.mutex);
+  pthread_join(playback_handle,NULL);
+  pthread_join(fd_handle,NULL);
+
+  /* tear down terminal */
+  min_remove_panel();
+
+
+  for(i=0;i<test_files;i++)
+    free_pcm(pcm[i]);
   return 0;
 }
+
+
+  /* 0         1         2         3        4         5         6         7         8*/
+  /* 2/16/44.1 | trial ??/?? A | <<00:00:00 | 00:00:00 | 00:00:00>> | sre */
+

Added: websites/celt-codec.org/squishyball/mincurses.c
===================================================================
--- websites/celt-codec.org/squishyball/mincurses.c	                        (rev 0)
+++ websites/celt-codec.org/squishyball/mincurses.c	2010-11-24 20:46:15 UTC (rev 17644)
@@ -0,0 +1,514 @@
+/*
+ *
+ *  mincurses
+ *
+ *      Copyright (C) 2010 Xiph.Org
+ *      Portions Copyright (C) 1998-2009 Free Software Foundation, Inc.
+ *
+ *  mincurses is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2, or (at your option)
+ *  any later version.
+ *
+ *  mincurses 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with rtrecord; see the file COPYING.  If not, write to the
+ *  Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ *
+ */
+
+/* Encapsulate the curses/terminfo calls for presenting a little panel
+   display at the bottom of a terminal window. */
+
+#define _GNU_SOURCE
+#define _LARGEFILE_SOURCE
+#define _LARGEFILE64_SOURCE
+#define _FILE_OFFSET_BITS 64
+
+#ifndef _REENTRANT
+# define _REENTRANT
+#endif
+
+#include <ncurses.h>
+#include <curses.h>
+#include <term.h>
+#include <term_entry.h>
+#include <stdio.h>
+#include <string.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <math.h>
+#include <errno.h>
+#include <poll.h>
+
+struct tinfo_fkeys {
+  unsigned offset;
+  chtype code;
+};
+
+#if USE_FKEYSF
+#define _nc_tinfo_fkeys _nc_tinfo_fkeysf()
+#else
+extern const struct tinfo_fkeys _nc_tinfo_fkeys[];
+#endif
+
+#include "mincurses.h"
+
+/* In ncurses, all the keybindings are tied to the SP. Calls into the
+   keybinding code all require the SP be set. Low level SP
+   management/creation is not exposed, and the high level entry points
+   that initialize the SP also drool on the rest of the terminal.  For
+   this reason, we duplicate some code. */
+
+typedef struct tries TRIES;
+#define FIFO_SIZE    200
+
+struct tries {
+  TRIES    *child;
+  TRIES    *sibling;
+  unsigned char    ch;
+  unsigned short   value;
+};
+
+TRIES *keytree=NULL;
+int fifo[FIFO_SIZE];
+int key_init=0;
+
+/*
+ * Returns the keycode associated with the given string.  If none is found,
+ * return OK.  If the string is only a prefix to other strings, return ERR.
+ * Otherwise, return the keycode's value (neither OK/ERR).
+ */
+
+static int find_definition(TRIES * tree, const char *str){
+  TRIES *ptr;
+  int result = OK;
+
+  if (str != 0 && *str != '\0') {
+    for (ptr = tree; ptr != 0; ptr = ptr->sibling) {
+      if ((unsigned char)(*str) == ptr->ch) {
+        if (str[1] == '\0' && ptr->child != 0) {
+          result = ERR;
+        } else if ((result = find_definition(ptr->child, str + 1)) == OK) {
+          result = ptr->value;
+        } else if (str[1] == '\0') {
+          result = ERR;
+        }
+      }
+      if (result != OK)
+        break;
+    }
+  }
+  return (result);
+}
+
+extern int _nc_add_to_try(TRIES ** tree, const char *str, unsigned code);
+
+static void minc_init_keytry(){
+  size_t n;
+  TERMTYPE *tp=&(cur_term->type);
+
+  for (n = 0; _nc_tinfo_fkeys[n].code; n++) {
+    if (_nc_tinfo_fkeys[n].offset < STRCOUNT) {
+      _nc_add_to_try(&keytree,tp->Strings[_nc_tinfo_fkeys[n].offset],
+                     _nc_tinfo_fkeys[n].code);
+    }
+  }
+#if NCURSES_XNAMES
+  for (n = STRCOUNT; n < NUM_STRINGS(tp); ++n) {
+    const char *name = ExtStrname(tp, n, strnames);
+    char *value = tp->Strings[n];
+    if (name && *name == 'k' && value && find_definition(keytree,value) == OK) {
+      _nc_add_to_try(&keytree, value, n - STRCOUNT + KEY_MAX);
+    }
+  }
+#endif
+  key_init=1;
+}
+
+static int head = -1;
+static int tail = 0;
+static int peek = 0;
+
+#define h_inc() { head == FIFO_SIZE-1 ? head = 0 : head++; if (head == tail) head = -1, tail = 0;}
+#define t_inc() { tail == FIFO_SIZE-1 ? tail = 0 : tail++; if (tail == head) tail = -1;}
+#define t_dec() { tail == 0 ? tail = FIFO_SIZE-1 : tail--; if (head == tail) fifo_clear();}
+#define p_inc() { peek == FIFO_SIZE-1 ? peek = 0 : peek++;}
+
+#define cooked_key_in_fifo()    ((head != -1) && (peek != head))
+#define raw_key_in_fifo()       ((head != -1) && (peek != tail))
+
+static inline int fifo_peek(){
+   int ch = fifo[peek];
+   p_inc();
+   return ch;
+}
+
+static inline int fifo_pull(){
+  int ch;
+  ch = fifo[head];
+
+  if (peek == head) {
+    h_inc();
+    peek = head;
+  } else
+    h_inc();
+
+  return ch;
+}
+
+static inline int fifo_push(int nonblock){
+  int n;
+  int ch = 0;
+  unsigned char c2 = 0;
+
+  if (tail == -1)
+    return ERR;
+
+  if(nonblock){
+    int ret;
+    struct pollfd fds={STDIN_FILENO,POLLIN,0};
+    ret=poll(&fds, 1, 0);
+    if(!fds.revents&(POLLIN)){
+      return ERR;
+    }
+  }
+  n = read(STDIN_FILENO, &c2, 1);
+  ch = c2;
+  if (n<=0) ch = ERR;
+  fifo[tail] = ch;
+  if (head == -1)
+    head = peek = tail;
+  t_inc();
+  return ch;
+}
+
+static inline void fifo_clear(){
+  memset(fifo, 0, sizeof(fifo));
+  head = -1;
+  tail = peek = 0;
+}
+
+int min_getch(int nonblock){
+  TRIES *ptr;
+  int ch = 0;
+
+  if(!key_init) minc_init_keytry();
+  ptr=keytree;
+
+  while (1) {
+    if (cooked_key_in_fifo() && fifo[head] >= KEY_MIN) {
+      break;
+    } else if (!raw_key_in_fifo()) {
+      ch = fifo_push(nonblock);
+      if (ch == ERR) {
+        peek = head;
+        return ERR;
+      }
+    }
+
+    ch = fifo_peek();
+    if (ch >= KEY_MIN) {
+      peek = head;
+      t_dec();
+      return ch;
+    }
+
+    while ((ptr != NULL) && (ptr->ch != (unsigned char) ch))
+      ptr = ptr->sibling;
+
+    if (ptr == NULL) break;
+    if (ptr->value != 0) {
+      if (peek == tail)
+        fifo_clear();
+      else
+        head = peek;
+      return (ptr->value);
+    }
+    ptr = ptr->child;
+  }
+  ch = fifo_pull();
+  peek = head;
+  return ch;
+}
+
+TTY orig;
+TTY term;
+
+static int outfd;
+static int initted=0;
+static int cursor_line_offset=0;
+static int panel_lines=0;
+
+int min_putchar(int c){
+  while(1){
+    unsigned char ch = c;
+    int ret;
+    errno=0;
+    ret=write(outfd,&ch,1);
+    if(ret<=0){
+      if(ret==0 || errno==EINTR)continue;
+      return EOF;
+    }else{
+      return c;
+      break;
+    }
+  }
+}
+
+int min_putp(const char *str){
+  return tputs(str, 1, min_putchar);
+}
+
+int min_write(const char *str,int len){
+  while(len){
+    int ret;
+    errno=0;
+    ret=write(outfd,str,len);
+    if(ret<=0){
+      if(ret==0 || errno==EINTR)continue;
+      return EOF;
+    }else{
+      str+=ret;
+      len-=ret;
+    }
+  }
+  return 0;
+}
+
+int min_putstr(const char *str){
+  int len=strlen(str);
+  return min_write(str,len);
+}
+
+/* relative-line cursor addressing; no idea where the cursor actually
+   is absolutely */
+void min_mvcur(int x, int y){
+  int yoff = y - cursor_line_offset;
+
+  while(yoff<0){
+    if(cursor_up)min_putp(cursor_up);
+    cursor_line_offset--;
+    yoff++;
+  }
+  while(yoff>0){
+    if(cursor_down)min_putp(cursor_down);
+    cursor_line_offset++;
+    yoff--;
+  }
+
+  if(column_address)min_putp(tparm(column_address,x));
+}
+
+static void insert_lines(int n){
+  int i;
+  for(i=0;i<n-1;i++)
+    min_putstr("\r\n");
+  cursor_line_offset=n-1;
+}
+
+static void setup_term_customize(void){
+  if (cur_term != 0) {
+    term = cur_term->Nttyb;
+#ifdef TERMIOS
+    term.c_lflag &= ~ICANON;
+    term.c_iflag &= ~ICRNL;
+    term.c_lflag &= ~(ECHO | ECHONL);
+    term.c_iflag &= ~(ICRNL | INLCR | IGNCR);
+    term.c_oflag &= ~(ONLCR);
+    term.c_lflag |= ISIG;
+    term.c_cc[VMIN] = 1;
+    term.c_cc[VTIME] = 0;
+#else
+    term.sg_flags |= CBREAK;
+    term.sg_flags &= ~(ECHO | CRMOD);
+#endif
+    SET_TTY(outfd,&term);
+    cur_term->Nttyb = term;
+  }
+}
+
+void resetup_term(void){
+  SET_TTY(outfd,&term);
+  cur_term->Nttyb = term;
+}
+
+extern void _nc_init_acs(void);
+int min_init_panel(int pl){
+  int ret = OK;
+  if(!initted){
+
+    if(isatty(STDOUT_FILENO))
+      outfd=STDOUT_FILENO;
+    else
+      outfd=STDERR_FILENO;
+
+    /* save original terminal setup for purposes of restoring later */
+    GET_TTY(outfd,&orig);
+
+    /* low level terminfo setup with defaults and no error return */
+    setupterm(0,outfd,0);
+
+    /* terminal settings now in a known state; further configure */
+    setup_term_customize();
+
+    /* enable graphics character set */
+    //if(ena_acs)min_putp(ena_acs);
+    _nc_init_acs();
+
+    if(!cursor_up || !cursor_down || !column_address){
+      SET_TTY(outfd,&orig);
+      ret=ERR;
+    }
+
+    /* set up keytables */
+    if(keypad_xmit)min_putp(tparm(keypad_xmit,1));
+    minc_init_keytry();
+
+    panel_lines=pl;
+    insert_lines(panel_lines);
+    initted=1;
+  }
+  return ret;
+}
+
+void min_remove_panel(){
+  if(initted){
+    if(delete_line){
+      min_mvcur(0,0);
+      min_putp(tparm(delete_line,panel_lines));
+    }
+    min_showcur();
+    SET_TTY(outfd,&orig);
+    initted=0;
+  }
+}
+
+int min_hidecur(){
+  if(cursor_invisible){
+    min_putp(cursor_invisible);
+    return 0;
+  }else
+    return 1;
+}
+
+int min_showcur(){
+  if(cursor_visible){
+    min_putp(cursor_visible);
+    return 0;
+  }else
+    return 1;
+}
+
+int min_clreol(void){
+  if(clr_eol){
+    min_putp(clr_eol);
+    return 0;
+  }else
+    return 1;
+}
+
+int min_clrbol(void){
+  if(clr_bol){
+    min_putp(clr_bol);
+    return 0;
+  }else
+    return 1;
+}
+
+int min_gfxmode(void){
+  if(enter_alt_charset_mode){
+    min_putp(enter_alt_charset_mode);
+    return 0;
+  }else
+    return 1;
+}
+
+int min_textmode(void){
+  if(exit_alt_charset_mode){
+    min_putp(exit_alt_charset_mode);
+    return 0;
+  }else
+    return 1;
+}
+
+static int unset_attributes(){
+  if(exit_attribute_mode){
+    min_putp(exit_attribute_mode);
+    return 0;
+  }else
+    return 1;
+}
+
+void min_fill(char *buf,char c,int cols){
+  int i;
+  for(i=0;i<cols;i++)
+    buf[i]=c;
+  buf[i]=0;
+}
+
+void min_printover(char *buf,int pos, char *s){
+  int len = strlen(buf);
+  int len2 = strlen(s);
+  int i;
+  for(i=0; i+pos<len && i<len2; i++)
+    buf[i+pos]=s[i];
+}
+
+int min_fg(int c){
+  if(set_a_foreground){
+    min_putp(tparm(set_a_foreground,c));
+    return 0;
+  }else
+    return 1;
+}
+
+int min_bg(int c){
+  if(set_a_background){
+    min_putp(tparm(set_a_background,c));
+    return 0;
+  }else
+    return 1;
+}
+
+void min_color(int f,int b){
+  if(f==-1 || b==-1) unset_attributes();
+  if(f!=-1) min_fg(f);
+  if(b!=-1) min_bg(b);
+}
+
+void min_hline(char *s,int textcolor){
+  int pos=0,last=0;
+
+  while(s[pos]){
+    /* draw line */
+    while(s[pos] && s[pos]=='_')pos++;
+    if(pos>last){
+      if(!min_gfxmode()){
+	for(;last<pos;last++)
+	  min_putchar(ACS_HLINE);
+	min_textmode();
+      }else{
+	min_write(s+last,pos-last);
+      }
+    }
+
+    /* draw text */
+    while(s[pos] && s[pos]!='_')pos++;
+    if(pos>last){
+      if(textcolor>=0)
+	min_fg(textcolor);
+      min_write(s+last,pos-last);
+      if(textcolor>=0)
+	min_color(-1,-1);
+      last = pos;
+    }
+  }
+}
+

Added: websites/celt-codec.org/squishyball/mincurses.h
===================================================================
--- websites/celt-codec.org/squishyball/mincurses.h	                        (rev 0)
+++ websites/celt-codec.org/squishyball/mincurses.h	2010-11-24 20:46:15 UTC (rev 17644)
@@ -0,0 +1,46 @@
+/*
+ *
+ *  mincurses
+ *
+ *      Copyright (C) 2010 Xiph.Org
+ *      Portions Copyright (C) 1998-2009 Free Software Foundation, Inc.
+ *
+ *  mincurses is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2, or (at your option)
+ *  any later version.
+ *
+ *  mincurses 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with rtrecord; see the file COPYING.  If not, write to the
+ *  Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ *
+ */
+
+#ifndef _MINCURSES_H_
+#define _MINCURSES_H_
+
+extern int min_getch(int nonblock);
+extern int min_putchar(int c);
+extern int min_putp(const char *str);
+extern int min_write(const char *str,int len);
+extern int min_putstr(const char *str);
+extern void min_mvcur(int x, int y);
+extern int min_init_panel(int pl);
+extern void min_remove_panel(void);
+extern int min_hidecur(void);
+extern int min_showcur(void);
+extern int min_clreol(void);
+extern int min_clrbol(void);
+extern int min_gfxmode(void);
+extern int min_textmode(void);
+extern int min_fg(int c);
+extern int min_bg(int c);
+extern void min_color(int f,int b);
+
+#endif

Modified: websites/celt-codec.org/squishyball/squishyball.1
===================================================================
--- websites/celt-codec.org/squishyball/squishyball.1	2010-11-24 18:09:51 UTC (rev 17643)
+++ websites/celt-codec.org/squishyball/squishyball.1	2010-11-24 20:46:15 UTC (rev 17644)
@@ -4,35 +4,70 @@
 .TH squishyball 1 "2010 November 18" "Xiph.Org Foundation" "Xiph Evaluation Tools"
 
 .SH NAME
-squishyball \- perform A/B, A/B/X and X/X/Y testing on the command line
+squishyball \- perform sample comparison testing on the command line
 
 .SH SYNOPSIS
 .B squishyball 
-[\fIoptions\fR]
-.I fileA fileB
+[\fIoptions\fR] fileA [fileB [[\fB-c\fR] \fIfileN...\fR]]
 
 .SH DESCRIPTION
 .B squishyball
-is a simple command-line application used to perform double-blind A/B, 
-A/B/X or X/X/Y testing on the command line.  The user specifies two input files 
-to be compared on the command line and uses the keyboard to flip 
-between the randomized samples on-the-fly during playback 
-to perform comparisons.  After a predetermined number of trials, 
+is a simple command-line application used to perform double-blind A/B,
+A/B/X or X/X/Y testing on the command line.  The user specifies two
+input files to be compared and uses the keyboard during playback to
+flip between the randomized samples to perform on-the-fly comparisons.
+After a predetermined number of trials,
 .B squishyball 
 prints the trial results to the terminal and exits.
 
-.SH OPTIONS
+.B squishyball
+can also be used to perform casual, non-randomized comparisons of
+groups of up to ten samples; this is the default mode of operation.
+
+.SH TEST TYPES
 .IP "\fB-a --ab"
-Perform randomized A/B test.
+Perform randomized A/B test on two input samples.
+
+A/B testing randomizes the order of two input samples and presents
+them, unnamed, as sample 'A' and sample 'B'.  In each trial the user
+selects A or B as the preferred sample.  The samples are then
+re-randomized for the next trial.  This test is useful for
+establishing relative or preferred quality between two samples.
 .IP "\fB-b --abx"
-Perform randomized A/B/X test (default).
-.IP "\fB-c --card \fIN\fR|\fIdevice"
+Perform randomized A/B/X test on two input samples.
+
+A/B/X testing randomizes the order of two input samples and presents
+them, unnamed, as sample 'A' and sample 'B'.  A third sample 'X' is
+also chosen randomly from either either 'A' or 'B'.  In each trial, the
+user selects A or B as the sample believed to be the same as X. All
+samples are then re-randomized for the next trial. This test is useful
+for determining if any differences are audible between two samples and
+to what confidence level.
+.IP "\fB-c --casual"
+Perform casual comparison of up to ten samples (default).
+
+Casual comparison mode does not randomize the input samples or perform
+multiple trials.  It simply provides a convenient way to rapidly flip back and
+forth within a group of up to ten samples.
+.IP "\fB-x --xxy"
+Perform randomized X/X/Y test on two input samples.
+
+X/X/Y testing is a form of A/B/X testing in which the position of the 'X'
+sample is not known ahead of time to be in the third position. In each trial,
+the user selects which of sample 1, 2 or 3 is believed to be the
+sample that is different from the other two. This test is useful for
+determining if any differences are audible between two samples and to
+what confidence level.
+.SH OTHER OPTIONS
+.IP "\fB-B --beep-flip"
+Mark transitions between samples with a short beep.
+.IP "\fB-d --device \fIN\fR|\fIdevice"
 If a number, output to Nth available sound device.  If a device name,
 use output device matching that device name.  The backend audio driver is
 selected automatically based on the device name provided.
-.IP "\fB-d --force-dither"
-Always use dither when down-converting to 16 bit sample for playback 
-on audio devices that do not support 24 bit playback. By default, 
+.IP "\fB-D --force-dither"
+Always use dither when down-converting to 16-bit samples for playback 
+on audio devices that do not support 24-bit playback. By default, 
 uncompressed samples are always dithered, but lossy formats (such 
 as Vorbis) are simply rounded.  See the section \fBCONVERSION AND DITHER
 \fRbelow for more details.
@@ -40,16 +75,21 @@
 Set sample end time for playback.
 .IP "\fB-h --help"
 Print usage summary to stdout and exit.
-.IP "\fB-n --comparisons \fIn"
+.IP "\fB-M --mark-flip"
+Mark transitions between samples with a short period of silence (default).
+.IP "\fB-n --trials \fIn"
 Set desired number of comparison trials (default: 10).
 .IP "\fB-r --restart-after"
 Set 'restart-after mode', where sample playback restarts from start point
-after every test selection.
+after every trial.
 .IP "\fB-R --restart-every"
 Set 'restart-every mode', where sample playback restarts from start point
-after 'flip' as well as every test selection.
+after 'flip' as well as after every trial.
 .IP "\fB-s --start-time \fR[[\fIhh\fB:\fR]\fImm\fB:\fR]\fIss\fR[\fB.\fIff\fR]"
 Set start time within sample for playback
+.IP "\fB-S --seamless-flip"
+Do not mark transitions between samples;
+flip with a seamless crossfade.
 .IP "\fB-t --force-truncate"
 Always round/truncate (never dither) when down-converting samples to 16-bit
 for playback on audio devices that do not support 24-bit output.  See the
@@ -58,8 +98,6 @@
 Produce more and more detailed progress information and warnings.
 .IP "\fB-V --version"
 Print version and exit.
-.IP "\fB-x --xxy"
-Perform randomized X/X/Y test.
 
 .SH KEYBOARD INTERACTION
 .IP "\fBa\fR, \fBb\fR, \fBx"  
@@ -67,13 +105,12 @@
 .IP "\fBA\fR, \fBB"
 Select A or B as preferred sample (A/B mode), or sample A or sample B as
 match to sample X (A/B/X testing mode)
-.IP "\fB1\fR, \fB2\fR, \fB3"
-Switch between first second and third samples (X/X/Y testing mode)
-.IP "\fB!\fR, \fB@\R, \fB#"
+.IP "\fB1\fR, \fB2\fR, \fB3\fR..."
+Switch between first, second, third [etc] samples (X/X/Y testing mode, casual comparison mode)
+.IP "\fB!\fR, \fB@\fR, \fB#"
 Indicate the 'odd sample out' as sample 1, 2, or 3 (X/X/Y testing mode)
 .IP "\fB<-\fR, \fB->"
-Seek back/forward one second, \fB+shift \fRfor five seconds,
-\fB+control \fRfor 15 seconds, \fB+meta \fRfor one minute.
+Seek back/forward two seconds, \fB+shift \fRfor ten seconds.
 .IP "\fB<space>"
 Pause/resume playback.
 .IP "\fB<backspace>"
@@ -82,10 +119,12 @@
 Set end playback point to current playback time (see also -e above).
 .IP "\fBE"
 Reset end playback time to end of sample.
+.IP "\fBF"
+Toggle through beep-flip/mark-flip/seamless-flip modes (see \fB-B\fR, \fB-M\fR, and \fB-S \fRabove).
 .IP "\fBr"
-Toggle through restart-after/restart-every mode (see -r and -R above).
+Toggle through restart-after/restart-every/no-restart modes (see \fB-r \fRand \fB-R \fRabove).
 .IP "\fBs"
-Set start playback point to current playback time (see also -s above).
+Set start playback point to current playback time (see also \fB-s \fRabove).
 .IP "\fBS"
 Reset start playback time to beginning of sample.
 .IP "\fB?"
@@ -96,66 +135,37 @@
 .SH SUPPORTED FILE TYPES
 
 .IP \fBWAV/WAVEX
-8, 16, 24 bit linear integer PCM (format 1), 32 bit float (format 3)
+8-, 16-, 24-bit linear integer PCM (format 1), 32-bit float (format 3)
 .IP \fBAIFF/AIFF-C
-8, 16, 24 bit linear integer PCM
+8-, 16-, 24-bit linear integer PCM
 .IP \fBFLAC/OggFLAC
-16 and 24 bit
+16- and 24-bit
 .IP \fBSW 
-Mono signed 16 bit little endian raw with a .sw extension
+Mono signed 16-bit little endian raw with a .sw extension
 .IP \fBOggVorbis
 all Vorbis I files
 
 .SH CONVERSION AND DITHER
 \fBsquishyball \fRloads all linear PCM file types at native bit depth. 
 Uncompressed floating point files (eg, 32 bit floating point WAV) are
-converted to 24-bit integer PCM.  Lossy-encoded files (eg, Ogg Vorbis)
-are also decoded to 24 bit by default, unless being tested against a
-16 bit file, in which case decode is to 16 bit.
+converted to 24-bit integer PCM.  Ogg Vorbis files are also decoded to
+24-bit.
 
 Files are 'reconciled' to identical channel ordering, length and
-bit-depth before playback begins so that CPU and memory resources used
+bit-depth before playback begins so that CPU and memory resources usage
 during playback should be identical for both samples.  When 24-bit
-playback is available and one sample is 24-bit, the other sample is
-promoted to 24 bits. If 24-bit playback is unavailable, 24-bit samples
+playback is available and at least one sample is 24-bit, all samples
+are promoted to 24 bits. If 24-bit playback is unavailable, 24-bit samples
 are demoted to 16 bits.
 
-Floating point samples (32-bit) are not dithered when converting to 24
-bit, as 24 bit PCM has an equivalent bit depth.  24 bit
-and floating point (32 bit) samples are dithered using a TPDF when
-it's necessary to down-convert to 16 bit.  Lossy-encoded samples (eg
-OggVorbis) are an exception; they are not dithered by default during
-down-conversion to 16 bit. This behavior can be overridden by \fB-d\fR, 
+Floating point samples (32-bit) are not dithered when converting to 24-bit. 
+24-bit and floating point (32 bit) samples are dithered using a TPDF 
+when down-conversion to 16-bit is necessary.  Lossy-encoded samples (eg
+Ogg Vorbis files) are an exception; they are not dithered by default during
+down-conversion. This behavior can be overridden by \fB-D\fR, 
 which forces dithering for lossy files as well.  Down-conversion
 dithering can be disabled for all input types with \fB-t\fR.
 
-.SH A/B TESTING
-
-A/B testing randomizes the order of two input samples and presents
-them, unnamed, as sample 'A' and sample 'B'.  In each trial the user
-selects A or B as the preferred sample.  The samples are then
-re-randomized for the next trial.  This test is useful for
-establishing relative or preferred quality between two samples.
-
-.SH A/B/X TESTING
-
-A/B/X testing randomizes the order of two input samples and presents
-them, unnamed, as sample 'A' and sample 'B'.  A third sample 'X' is
-also chosen randomly from either either 'A' or 'B'.  In each trial, the
-user selects A or B as the sample believed to be the same as X. All
-samples are then re-randomized for the next trial. This test is useful
-for determining if any differences are audible between two samples and
-to what confidence level.
-
-.SH X/X/Y TESTING
-
-X/X/Y testing is a form of A/B/X testing in which the 'X' sample is
-not known ahead of time to be in the third position. In each trial,
-the user selects which of sample 1, 2 or 3 is believed to be the
-sample that is different from the other two. This test is useful for
-determining if any differences are audible between two samples and to
-what confidence level.
-
 .SH AUTHORS
 Monty <monty at xiph.org>
 

Added: websites/celt-codec.org/squishyball/tty.c
===================================================================
--- websites/celt-codec.org/squishyball/tty.c	                        (rev 0)
+++ websites/celt-codec.org/squishyball/tty.c	2010-11-24 20:46:15 UTC (rev 17644)
@@ -0,0 +1,201 @@
+/*
+ *
+ *  squishyball
+ *
+ *      Copyright (C) 2010 Xiph.Org
+ *
+ *  squishyball is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2, or (at your option)
+ *  any later version.
+ *
+ *  squishyball 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with rtrecord; see the file COPYING.  If not, write to the
+ *  Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ *
+ */
+
+/* Encapsulate the curses/terminfo calls for presenting a little panel
+   display at the bottom of a terminal window. */
+
+#define _GNU_SOURCE
+#define _LARGEFILE_SOURCE
+#define _LARGEFILE64_SOURCE
+#define _FILE_OFFSET_BITS 64
+
+#ifndef _REENTRANT
+# define _REENTRANT
+#endif
+
+#include "mincurses.h"
+#include "tty.h"
+
+
+
+#if 0
+void terminal_paint_ui(){
+  int cols = columns;
+
+  char topline[cols+1];
+  char botline[cols+1];
+  char dmabuf[cols+1];
+  char diskbuf[cols+1];
+  char hwbuf[cols+1];
+  char linebuf[cols+1];
+
+  int barchoice=-1;
+  int barcols = cols - 25, barpad=0;
+  int barlength=0;
+  int i=0;
+
+  topline[0]='\0';
+  botline[0]='\0';
+  dmabuf[0]='\0';
+  diskbuf[0]='\0';
+  hwbuf[0]='\0';
+  linebuf[0]='\0';
+
+  /* construct the top line */
+  //snprintf(dmabuf,cols+1," DMA buffer: %3d%% ",(int)rint(dma_percent));
+  //snprintf(diskbuf,cols+1," Disk buffer: %3d%% ",(int)rint(disk_percent));
+  //snprintf(hwbuf,cols+1," %s ",device);
+  //fill(topline,'_',cols);
+  //print_into(topline,3,dmabuf);
+  //print_into(topline,5+strlen(dmabuf),diskbuf);
+  //print_into(topline,columns - strlen(hwbuf)-3,hwbuf);
+
+  /* print the top line */
+  //cursor_to(0,0);
+  //print_hline(topline,-1);
+
+  /* blank next line (useful if coming back after SIGSTOP) */
+  //cursor_to(0,1);
+  //clear_line();
+
+  /* bargraphs */
+
+  for(i=0;i<channels;i++){
+    cursor_to(barpad,2+i);
+    clear_to_cursor();
+    snprintf(linebuf,cols+1,"%2d: [",i);
+    putp(linebuf);
+
+    if(barchoice>=0){
+      /* determine bar lengths */
+      int rms_pos = barpos(barchoice,rms_dB[i]);
+      int peak_pos = barpos(barchoice,peak_dB[i]);
+
+      if(peak_pos>barlength)peak_pos=barlength;
+      if(rms_pos>peak_pos)rms_pos=peak_pos;
+
+      /* color the bargraph */
+      if(rms_pos){
+	color(COLOR_BLACK,COLOR_WHITE);
+	fwrite(bargraphs[barchoice],1,rms_pos,stdout);
+      }
+      if(peak_pos-rms_pos>0){
+	color(COLOR_BLACK,COLOR_CYAN);
+	fwrite(bargraphs[barchoice]+rms_pos,1,peak_pos-rms_pos,stdout);
+      }
+      if(barlength-peak_pos>0){
+	color(COLOR_CYAN,COLOR_BLACK);
+	fwrite(bargraphs[barchoice]+peak_pos,1,barlength-peak_pos,stdout);
+      }
+
+      /* the RMS indicator is always normal colors */
+      unset_attributes();
+    }
+
+    if(weight){
+      snprintf(linebuf,cols+1,"] %+6.1fdBA,",(double)rms_dB[i]);
+    }else{
+      snprintf(linebuf,cols+1,"] %+6.1fdB, ",(double)rms_dB[i]);
+    }
+    putp(linebuf);
+
+    /* the peak indicator may read a number or CLIP */
+    if(peak_clip[i]){
+      color(COLOR_RED,-1);
+      putp("**CLIP**");
+      color(-1,-1);
+    }else{
+      snprintf(linebuf,cols+1,"%+6.1fdB",(double)peak_hold_dB[i]);
+      putp(linebuf);
+    }
+    if(barpad + barcols < columns) clear_line();
+  }
+
+  /* blank next line (useful if coming back after SIGSTOP) */
+  cursor_to(0,channels+2);
+  clear_line();
+
+  /* construct the bottom line */
+  fill(botline,'_',cols);
+  {
+    int x=3;
+    if(paused){
+      print_into(botline,x," PAUSED ");
+      x+=10;
+    }
+    if(hold){
+      print_into(botline,x," HOLD ");
+      x+=8;
+    }
+    if(dma_once_overrun){
+      print_into(botline,x," DMA OVERRUN ");
+      x+=16;
+    }
+    if(disk_once_overrun){
+      print_into(botline,x," DISK OVERRUN ");
+      x+=17;
+    }
+  }
+
+  /* print the bottom line */
+  cursor_to(0,channels+3);
+  print_hline(botline,COLOR_RED);
+  fflush(stdout);
+}
+
+void terminal_main_loop(int quiet){
+  int i;
+  int exiting=0;
+
+  while(!exiting){
+    char buf;
+    if(!quiet){
+      resetup_term();
+      terminal_paint_ui();
+    }
+    read(STDIN_FILENO,&buf,1);
+
+    if(!quiet){
+      switch(buf){
+      case 'p':
+	paused = !paused;
+	break;
+      case 'h':
+	hold = !hold;
+	break;
+      case ' ':
+	dma_once_overrun=0;
+	disk_once_overrun=0;
+	for(i=0;i<channels;i++){
+	  peak_clip[i]=0;
+	  peak_hold_dB[i]=peak_dB[i];
+	}
+	break;
+      case 'q':case 'Q':
+	exiting=1;
+	break;
+      }
+    }
+  }
+}
+#endif

Added: websites/celt-codec.org/squishyball/tty.h
===================================================================
--- websites/celt-codec.org/squishyball/tty.h	                        (rev 0)
+++ websites/celt-codec.org/squishyball/tty.h	2010-11-24 20:46:15 UTC (rev 17644)
@@ -0,0 +1,29 @@
+/*
+ *
+ *  squishyball
+ *
+ *      Copyright (C) 2010 Xiph.Org
+ *
+ *  squishyball is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2, or (at your option)
+ *  any later version.
+ *
+ *  squishyball 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with rtrecord; see the file COPYING.  If not, write to the
+ *  Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ *
+ */
+
+#ifndef _SB_TTY_H_
+#define _SB_TTY_H_
+
+
+
+#endif



More information about the commits mailing list