[xiph-commits] r18812 - in trunk: . Xiph-episode-II Xiph-episode-II/bounce Xiph-episode-II/cairo

xiphmont at svn.xiph.org xiphmont at svn.xiph.org
Sun Feb 24 14:04:10 PST 2013


Author: xiphmont
Date: 2013-02-24 14:04:10 -0800 (Sun, 24 Feb 2013)
New Revision: 18812

Added:
   trunk/Xiph-episode-II/
   trunk/Xiph-episode-II/bounce/
   trunk/Xiph-episode-II/bounce/Makefile
   trunk/Xiph-episode-II/bounce/gtk-bounce-audio.c
   trunk/Xiph-episode-II/bounce/gtk-bounce-convolve.c
   trunk/Xiph-episode-II/bounce/gtk-bounce-convolve.h
   trunk/Xiph-episode-II/bounce/gtk-bounce-dither.c
   trunk/Xiph-episode-II/bounce/gtk-bounce-dither.h
   trunk/Xiph-episode-II/bounce/gtk-bounce-filter.c
   trunk/Xiph-episode-II/bounce/gtk-bounce-filter.h
   trunk/Xiph-episode-II/bounce/gtk-bounce-panel.c
   trunk/Xiph-episode-II/bounce/gtk-bounce-wavheader.c
   trunk/Xiph-episode-II/bounce/gtk-bounce-widget.c
   trunk/Xiph-episode-II/bounce/gtk-bounce-widget.h
   trunk/Xiph-episode-II/bounce/gtk-bounce.c
   trunk/Xiph-episode-II/bounce/gtk-bounce.c.old
   trunk/Xiph-episode-II/bounce/gtk-bounce.h
   trunk/Xiph-episode-II/bounce/twopole.c
   trunk/Xiph-episode-II/bounce/twopole.h
   trunk/Xiph-episode-II/cairo/
   trunk/Xiph-episode-II/cairo/README
   trunk/Xiph-episode-II/cairo/ch3_convenient.c
   trunk/Xiph-episode-II/cairo/ch3_minutely.c
   trunk/Xiph-episode-II/cairo/ch3_samples.c
   trunk/Xiph-episode-II/cairo/ch3_stairsteps.c
   trunk/Xiph-episode-II/cairo/ch3_zerohold.c
   trunk/Xiph-episode-II/cairo/ch4-samplequant.c
   trunk/Xiph-episode-II/cairo/ch5-quantize.c
   trunk/Xiph-episode-II/cairo/ch6-overlay.c
   trunk/Xiph-episode-II/cairo/ch6-right.c
   trunk/Xiph-episode-II/cairo/ch6-squareband.c
   trunk/Xiph-episode-II/cairo/ch6-squarefreq.c
   trunk/Xiph-episode-II/cairo/ch6-squaretime.c
   trunk/Xiph-episode-II/cairo/ch6-wrong.c
Log:
Initial import of source exactly as used in video

Added: trunk/Xiph-episode-II/bounce/Makefile
===================================================================
--- trunk/Xiph-episode-II/bounce/Makefile	                        (rev 0)
+++ trunk/Xiph-episode-II/bounce/Makefile	2013-02-24 22:04:10 UTC (rev 18812)
@@ -0,0 +1,41 @@
+CC=gcc
+LD=gcc
+INSTALL=install
+PREFIX=/usr/local
+BINDIR=$(PREFIX)/bin
+MANDIR=$(PREFIX)/man
+
+SRC = gtk-bounce-panel.c gtk-bounce-audio.c gtk-bounce-widget.c twopole.c \
+      gtk-bounce-dither.c gtk-bounce-filter.c gtk-bounce-wavheader.c
+OBJ = gtk-bounce-panel.o gtk-bounce-audio.o gtk-bounce-widget.o twopole.o \
+      gtk-bounce-dither.o gtk-bounce-filter.o gtk-bounce-wavheader.o
+
+GCF = `pkg-config --cflags gtk+-2.0` -DETCDIR=$(ETCDIR)
+
+CFLAGS := ${CFLAGS} $(GCF) $(ADD_DEF)
+LIBS   := -lfftw3 -lfftw3f -lasound -lm -lpthread
+
+all:
+	$(MAKE) target CFLAGS="${CFLAGS} -g -O2 -ffast-math -fomit-frame-pointer"
+
+debug:
+	$(MAKE) target CFLAGS="${CFLAGS} -g -Wall -W -Wno-unused-parameter -D__NO_MATH_INLINES"
+
+profile:
+	$(MAKE) target CFLAGS="${CFLAGS} -pg -g -O2 -ffast-math"
+
+clean:
+	rm -f $(OBJ) *.d *.d.* gmon.out gtk-bounce
+
+%.d: %.c
+	$(CC) -M $(CFLAGS) $< > $@.$$$$; sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; rm -f $@.$$$$
+
+ifeq ($(MAKECMDGOALS),target)
+include $(SRC:.c=.d)
+endif
+
+gtk-bounce:  $(OBJ)
+	$(LD) $(OBJ) -o gtk-bounce $(LIBS) $(CFLAGS) `pkg-config --libs gtk+-2.0`
+
+target:  gtk-bounce
+

Added: trunk/Xiph-episode-II/bounce/gtk-bounce-audio.c
===================================================================
--- trunk/Xiph-episode-II/bounce/gtk-bounce-audio.c	                        (rev 0)
+++ trunk/Xiph-episode-II/bounce/gtk-bounce-audio.c	2013-02-24 22:04:10 UTC (rev 18812)
@@ -0,0 +1,1149 @@
+#include "gtk-bounce.h"
+#include "gtk-bounce-filter.h"
+#include "gtk-bounce-dither.h"
+#include <time.h>
+#include <alsa/asoundlib.h>
+
+extern int header_out(FILE *out, int rate, int bits, int channels);
+
+static snd_pcm_uframes_t pframes = 128;
+static snd_pcm_uframes_t pframes_mark = 128*8;
+static snd_pcm_uframes_t bframes = 4096;
+
+/* only apply to ch0 and ch1 */
+
+static shapestate shapestate_ch0;
+static shapestate shapestate_ch1;
+
+static notchfilter *notch[2]={0,0};
+static convofilter *lowpass;
+static convostate *lowpass_state[2]={0,0};
+static int active_1kHz_notch;
+static int active_lp_filter;
+static int state_1kHz_modulation = 0;
+static float amplitude_1kHz_modmix = 0;
+
+static int param_setup_i(snd_pcm_t *devhandle, int dir, int rate,
+                         int format, int channels){
+  int ret;
+  snd_pcm_hw_params_t *hw;
+  snd_pcm_sw_params_t *sw;
+  char *prompt = (!dir ? "capture" : "playback");
+
+  /* allocate the parameter structures */
+  snd_pcm_hw_params_alloca (&hw);
+  snd_pcm_sw_params_alloca (&sw);
+
+  /* fetch all possible hardware parameters */
+  if ((ret = snd_pcm_hw_params_any (devhandle, hw)) < 0) {
+    fprintf (stderr, "%s cannot fetch hardware parameters (%s)\n",
+             prompt, snd_strerror (ret));
+    return ret;
+  }
+
+  /* use file-like syscall interface as opposed to mmap */
+  if ((ret = snd_pcm_hw_params_set_access (devhandle, hw, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
+    fprintf (stderr, "%s cannot set access type (%s)\n",
+             prompt, snd_strerror (ret));
+    return ret;
+  }
+
+  /* set the sample bitformat */
+  if ((ret = snd_pcm_hw_params_set_format (devhandle, hw, format)) < 0) {
+    fprintf (stderr, "%s cannot set sample format (%s)\n",
+             prompt, snd_strerror (ret));
+    return ret;
+  }
+
+  /* set the sample rate */
+  if ((ret = snd_pcm_hw_params_set_rate (devhandle, hw, rate, 0)) < 0) {
+    fprintf (stderr, "%s cannot set sample rate (%s)\n",
+             prompt, snd_strerror (ret));
+    return ret;
+  }
+
+  /* set the number of channels */
+  if ((ret = snd_pcm_hw_params_set_channels (devhandle, hw, channels)) < 0) {
+    fprintf (stderr, "%s cannot set channel count (%s)\n",
+             prompt, snd_strerror (ret));
+    return ret;
+  }
+
+  /* set the time per hardware sample transfer */
+  if ((ret = snd_pcm_hw_params_set_period_size_near(devhandle,
+                                                      hw, &pframes, 0)) < 0) {
+
+    fprintf (stderr, "%s cannot set period size (%s)\n",
+             prompt, snd_strerror (ret));
+    return ret;
+  }
+
+  /* set the length of the hardware sample buffer */
+  if ((ret = snd_pcm_hw_params_set_buffer_size_near(devhandle,
+                                                    hw, &bframes)) < 0) {
+
+    fprintf (stderr, "%s cannot set buffer size (%s)\n",
+             prompt, snd_strerror (ret));
+    return ret;
+  }
+
+  if ((ret = snd_pcm_hw_params (devhandle, hw)) < 0) {
+    fprintf (stderr, "%s cannot set hard parameters (%s)\n",
+             prompt, snd_strerror (ret));
+    return ret;
+  }
+
+  if((ret = snd_pcm_hw_params_get_period_size(hw, &pframes, 0)) < 0){
+    fprintf (stderr, "%s cannot query period size (%s)\n",
+             prompt, snd_strerror (ret));
+    return ret;
+  }
+
+  if((ret = snd_pcm_hw_params_get_buffer_size(hw, &bframes)) < 0){
+    fprintf (stderr, "%s cannot query buffer size (%s)\n",
+             prompt, snd_strerror (ret));
+    return ret;
+  }
+
+  /* fetch software parameter settings */
+  if ((ret = snd_pcm_sw_params_current (devhandle, sw)) < 0) {
+    fprintf (stderr, "%s cannot initialize hardware parameter structure (%s)\n",
+             prompt, snd_strerror (ret));
+    return ret;
+  }
+
+  /* start transfers at lowest possible buffer watermark */
+  if(dir){
+    if ((ret = snd_pcm_sw_params_set_start_threshold (devhandle, sw, pframes_mark)) < 0) {
+      fprintf (stderr, "%s cannot set start threshold (%s)\n",
+               prompt, snd_strerror (ret));
+      return ret;
+    }
+  }else{
+    if ((ret = snd_pcm_sw_params_set_start_threshold (devhandle, sw, pframes)) < 0) {
+      fprintf (stderr, "%s cannot set start threshold (%s)\n",
+               prompt, snd_strerror (ret));
+      return ret;
+    }
+  }
+  if ((ret = snd_pcm_sw_params_set_avail_min (devhandle, sw, pframes)) < 0) {
+    fprintf (stderr, "%s cannot set minimum activity threshold (%s)\n",
+             prompt,snd_strerror (ret));
+    return ret;
+  }
+
+  if ((ret = snd_pcm_sw_params (devhandle, sw)) < 0) {
+    fprintf (stderr, "%s cannot set soft parameters (%s)\n",
+             prompt, snd_strerror (ret));
+    return ret;
+  }
+
+  /* go time */
+  if ((ret = snd_pcm_prepare (devhandle)) < 0) {
+    fprintf (stderr, "%s cannot prepare audio interface for use (%s)\n",
+             prompt,snd_strerror (ret));
+    return ret;
+  }
+
+  return 0;
+}
+
+static int param_setup(snd_pcm_t *devhandle, int dir, int rate,
+                       int bits, int *channels){
+
+  /* The eMagic 2|6 only does 24 bit playback if also using 6
+     channels.  The Focusrite Scarlett 2i2 can only do 32 bit
+     output */
+  int format_list[3] = {SND_PCM_FORMAT_S16_LE,
+                        SND_PCM_FORMAT_S24_3LE,
+                        SND_PCM_FORMAT_S32_LE};
+  int channel_list[2] = {2,6};
+
+  int fi = (bits==16?0:1);
+  int ci;
+
+  for(;fi<3;fi++){
+    for(ci=0;ci<2;ci++){
+      if(!param_setup_i(devhandle,dir,rate,format_list[fi],channel_list[ci])){
+        *channels = channel_list[ci];
+        return format_list[fi];
+      }
+    }
+  }
+  return -1;
+}
+
+static void alsa_to_dev_null(const char *file,
+                               int line,
+                               const char *function,
+                               int err,
+                               const char *fmt, ...){
+}
+
+/* capture open blocks as we can't do anything much without a capture
+   clock */
+static snd_pcm_t *open_capture(char *dev, int rate, int bits, int *channels, int *format){
+  snd_pcm_t *handle;
+  int ret,fail=0;
+  struct timespec t = {0,250000000};
+  while((ret=snd_pcm_open(&handle, dev, SND_PCM_STREAM_CAPTURE, 0)) < 0) {
+    if(ret!=-ENOENT){
+      /* A device that's in the middle of initializing may throw a
+         random error */
+      fail++;
+      if(fail==3)return NULL;
+    }
+    nanosleep(&t,NULL);
+  }
+
+  *format = param_setup(handle,0,rate,bits,channels);
+  if(*format<0){
+    snd_pcm_close(handle);
+    return NULL;
+  }
+
+  memset(&shapestate_ch0,0,sizeof(shapestate_ch0));
+  memset(&shapestate_ch1,0,sizeof(shapestate_ch1));
+
+  return handle;
+}
+
+/* don't block; try it and if we fail, fail quickly */
+static snd_pcm_t *open_playback(char *dev, int rate, int bits,
+                                int *channels, int *format){
+  snd_pcm_t *handle;
+  int ret;
+
+  if ((ret = snd_pcm_open (&handle, dev, SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
+    return NULL;
+  }
+
+  *format = param_setup(handle,1,rate,bits,channels);
+  if(*format<0){
+    snd_pcm_close(handle);
+    return NULL;
+  }
+
+  return handle;
+}
+
+static float *square_coeffs;
+static float *square_phases;
+static int square_coeffs_n;
+static double square_coeffs_w;
+
+/* brute-force the expansion of a synthetic aliased 'squarewave';
+   choose a fundamental that evenly divides the rate, doesn't land a
+   harmonic at nyquist, and has an integer period, so that each folded
+   component lands directly on another. */
+/* we don't adjust for phase through the complete aliased expansion
+   during later generation because this isn't a real squarewave.  The
+   additional mirrored components exist only to fill in the illusory
+   appearance of it being a squarewave when drawn with a zero-hold at
+   phase=0 */
+
+void generate_squarewave_expansion(int rate){
+  int period = rint(rate/4000.)*4;
+  //double freq = rate/period;
+  double w = 2./period*M_PI;
+  int coeffs = period/4;
+  int i,j;
+  float *work = fftwf_malloc((period+2)*sizeof(*work));
+  fftwf_plan plan = fftwf_plan_dft_r2c_1d(period,work,
+                                          (fftwf_complex *)work,
+                                          FFTW_ESTIMATE);
+
+  for(i=0;i<period/2;i++)
+    work[i]=1.;
+  for(;i<period;i++)
+    work[i]=-1.;
+  fftwf_execute(plan);
+
+  square_coeffs_n = coeffs;
+  square_coeffs_w = w;
+  if(square_coeffs)free(square_coeffs);
+  if(square_phases)free(square_phases);
+  square_coeffs = calloc(coeffs,sizeof(*square_coeffs));
+  square_phases = calloc(coeffs,sizeof(*square_phases));
+
+  for(i=1,j=0;j<square_coeffs_n;i+=2,j++){
+    square_coeffs[j] = hypotf(work[i<<1],work[(i<<1)+1]) / period;
+    square_phases[j] = atan2f(work[(i<<1)+1],work[i<<1]);
+  }
+
+}
+
+void *io_thread(void *dummy){
+  snd_pcm_t *in_devhandle=NULL;
+  snd_pcm_t *out_devhandle=NULL;
+  FILE *outpipe0=NULL;
+  FILE *outpipe1=NULL;
+  pole2 filter_1kHz_amplitude;
+  pole2 filter_5kHz_amplitude;
+  pole2 filter_1kHz_mix;
+  pole2 filter_output_noise;
+  pole2 filter_output_sweep;
+  pole2 filter_modulation1;
+  pole2 filter_modulation2;
+
+  int channels = 2; //request_ch;
+  int in_channels = 2;
+  int out_channels = 2;
+  int rate = request_rate;
+  int bits = request_bits;
+  int informat=-1;
+  int outformat=-1;
+
+  int i,j;
+
+  unsigned char *iobuffer=NULL;
+  float *crossbuffer;
+  float *fbuffer[32];
+
+  filter_make_critical(.0001,1,&filter_1kHz_amplitude);
+  filter_make_critical(.0001,1,&filter_5kHz_amplitude);
+  filter_make_critical(.0001,1,&filter_1kHz_mix);
+  filter_make_critical(.0001,1,&filter_output_noise);
+  filter_make_critical(.0001,1,&filter_output_sweep);
+  filter_make_critical(.00005,2,&filter_modulation1);
+  filter_make_critical(.00005,2,&filter_modulation2);
+
+  /* libasound prints debugging output directly to stderr; tell it to
+     shut up */
+  snd_lib_error_set_handler(alsa_to_dev_null);
+
+  while(!exiting){
+
+    if(request_bits != bits || request_rate!=rate){
+      /* the panel has requested a differnet bitdepth than we're running.
+         Close any already-open devices, reopen below */
+      bits=request_bits;
+      //channels=request_ch;
+      rate=request_rate;
+
+      if(notch[0])filter_notch_destroy(notch[0]);
+      notch[0]=NULL;
+      if(notch[1])filter_notch_destroy(notch[1]);
+      notch[1]=NULL;
+      if(lowpass)convofilter_destroy(lowpass);
+      lowpass=NULL;
+
+      if(lowpass_state[0])convostate_destroy(lowpass_state[0]);
+      lowpass_state[0]=NULL;
+      if(lowpass_state[1])convostate_destroy(lowpass_state[1]);
+      lowpass_state[1]=NULL;
+
+      if(in_devhandle){
+        snd_pcm_close(in_devhandle);
+        in_devhandle=NULL;
+        write(eventpipe[1],"\002",1);
+      }
+      if(out_devhandle){
+        snd_pcm_close(out_devhandle);
+        out_devhandle=NULL;
+        write(eventpipe[1],"\002",1);
+      }
+      if(outpipe0) fclose(outpipe0);
+      if(outpipe1) fclose(outpipe1);
+
+      if(outpipe0 || outpipe1){
+        /* solve a race with a sleep; we want the readers to realize
+           this closed */
+        struct timespec t = {0,250000000};
+        nanosleep(&t,NULL);
+        outpipe0=NULL;
+        outpipe1=NULL;
+
+        if(in_devhandle)
+          snd_pcm_drop(in_devhandle);
+        if(out_devhandle)
+          snd_pcm_drop(out_devhandle);
+        if(in_devhandle)
+          snd_pcm_prepare(in_devhandle);
+        if(out_devhandle)
+          snd_pcm_prepare(out_devhandle);
+
+      }
+    }
+
+    if(!outpipe0){
+      int fd = open("pipe0", O_WRONLY|O_NONBLOCK);
+      if(fd>=0){
+        outpipe0 = fdopen(fd,"wb");
+        if(outpipe0==NULL){
+          close(fd);
+          write(eventpipe[1],"\000",1);
+        }
+        header_out(outpipe0,rate,bits,channels);
+        fprintf(stderr,"Opened outpipe0\n");
+      }
+    }
+
+    if(!outpipe1){
+      int fd = open("pipe1", O_WRONLY|O_NONBLOCK);
+      if(fd>=0){
+        outpipe1 = fdopen(fd,"wb");
+        if(outpipe1==NULL){
+          close(fd);
+          write(eventpipe[1],"\000",1);
+        }
+        header_out(outpipe1,rate,bits,channels);
+        fprintf(stderr,"Opened outpipe1\n");
+      }
+    }
+
+    if(!in_devhandle || !out_devhandle){
+
+
+      /* assume that hw:1 is always our capture handle, and if we don't
+         have capture, we can't do anything */
+      if(!in_devhandle){
+        in_channels = channels;
+        in_devhandle = open_capture("hw:1",rate,bits,&in_channels,&informat);
+
+        if(in_devhandle){
+          /* configure/reconfigure buffering for i/o */
+          int maxchannels=8;
+          if(iobuffer){
+            iobuffer=realloc(iobuffer,pframes*maxchannels*4);
+            for(i=0;i<maxchannels;i++)
+              fbuffer[i]=realloc(fbuffer[i],pframes*sizeof(**fbuffer));
+            crossbuffer=realloc(crossbuffer,pframes*sizeof(**fbuffer));
+          }else{
+            iobuffer=malloc(pframes*maxchannels*4);
+            for(i=0;i<maxchannels;i++)
+              fbuffer[i]=malloc(pframes*sizeof(**fbuffer));
+            crossbuffer=malloc(pframes*sizeof(**fbuffer));
+          }
+
+          /* squarewave */
+          generate_squarewave_expansion(rate);
+
+          /* set-up our on-demand filtering that has
+             expensive-to-construct persistent state */
+
+          notch[0]=filter_notch_new(1000.,request_rate);
+          lowpass=filter_lowpass_new(20500,request_rate,512);
+
+          lowpass_state[0] = convostate_new(lowpass);
+          if(channels>1){
+            notch[1]=filter_notch_new(1000.,request_rate);
+            lowpass_state[1] = convostate_new(lowpass);
+          }
+        }
+      }
+
+      if(!out_devhandle){
+        out_channels = channels;
+        out_devhandle = open_playback("hw:1",rate,bits,
+                                      &out_channels,&outformat);
+      }
+
+      if(in_devhandle && out_devhandle){
+        char *name;
+        snd_card_get_name(1,&name);
+        unsigned char len = strlen(name)+1;
+        write(eventpipe[1],"\001",1);
+        write(eventpipe[1],&len,1);
+        write(eventpipe[1],name,(int)len);
+      }
+    }
+
+    /* capture a block of input */
+    if(in_devhandle){
+      int frames = snd_pcm_readi (in_devhandle, iobuffer, pframes);
+      if(frames<0){
+        switch(frames){
+        case -EAGAIN:
+          break;
+        case -EPIPE: case -ESTRPIPE:
+          // underrun; set starve and reset soft device
+          snd_pcm_drop(in_devhandle);
+          if(out_devhandle)
+            snd_pcm_drop(out_devhandle);
+          snd_pcm_prepare(in_devhandle);
+          if(out_devhandle)
+            snd_pcm_prepare(out_devhandle);
+          frames=0;
+          fprintf(stderr,"\n CAPTURE XRUN @ (%ld)\n ", (long)time(NULL));
+          break;
+        case -EBADFD:
+        default:
+          /* shut down device */
+          snd_pcm_close(in_devhandle);
+          in_devhandle=NULL;
+          write(eventpipe[1],"\002",1);
+          frames=0;
+
+          if(outpipe0) fclose(outpipe0);
+          if(outpipe1) fclose(outpipe1);
+
+          if(outpipe0 || outpipe1){
+            /* solve a race with a sleep; we want the readers to realize
+               this closed */
+            struct timespec t = {0,250000000};
+            nanosleep(&t,NULL);
+            outpipe0=NULL;
+            outpipe1=NULL;
+          }
+
+          break;
+        }
+      }else{
+
+        /* convert our frames to 32-bit float intermediary */
+        unsigned char *bufp = iobuffer;
+        switch(informat){
+        case SND_PCM_FORMAT_S32_LE:
+          for(i=0;i<frames;i++){
+            for(j=0;j<in_channels;j++){
+              int32_t in = bufp[0]|(bufp[1]<<8)|(bufp[2]<<16)|(bufp[3]<<24);
+              fbuffer[j][i]= in * (1./128./256./256./256.);
+              bufp+=4;
+            }
+            for(;j<channels;j++) fbuffer[j][i]=0;
+          }
+          break;
+        case SND_PCM_FORMAT_S32_BE:
+          for(i=0;i<frames;i++){
+            for(j=0;j<channels;j++){
+              int32_t in = bufp[3]|(bufp[2]<<8)|(bufp[1]<<16)|(bufp[0]<<24);
+              fbuffer[j][i]= in * (1./128./256./256./256.);
+              bufp+=4;
+            }
+            for(;j<channels;j++) fbuffer[j][i]=0;
+          }
+          break;
+        case SND_PCM_FORMAT_S24_LE:
+          for(i=0;i<frames;i++){
+            for(j=0;j<channels;j++){
+              int32_t in = (bufp[1]<<8)|(bufp[2]<<16)|(bufp[3]<<24);
+              fbuffer[j][i]= in * (1./128./256./256./256.);
+              bufp+=4;
+            }
+            for(;j<channels;j++) fbuffer[j][i]=0;
+          }
+          break;
+        case SND_PCM_FORMAT_S24_BE:
+          for(i=0;i<frames;i++){
+            for(j=0;j<channels;j++){
+              int32_t in = (bufp[2]<<8)|(bufp[1]<<16)|(bufp[0]<<24);
+              fbuffer[j][i]= in * (1./128./256./256./256.);
+              bufp+=4;
+            }
+            for(;j<channels;j++) fbuffer[j][i]=0;
+          }
+          break;
+        case SND_PCM_FORMAT_S24_3LE:
+          for(i=0;i<frames;i++){
+            for(j=0;j<channels;j++){
+              int32_t in = (bufp[0]<<8)|(bufp[1]<<16)|(bufp[2]<<24);
+              fbuffer[j][i]= in * (1./128./256./256./256.);
+              bufp+=3;
+            }
+            for(;j<channels;j++) fbuffer[j][i]=0;
+          }
+          break;
+        case SND_PCM_FORMAT_S24_3BE:
+          for(i=0;i<frames;i++){
+            for(j=0;j<channels;j++){
+              int32_t in = (bufp[2]<<8)|(bufp[1]<<16)|(bufp[0]<<24);
+              fbuffer[j][i]= in * (1./128./256./256./256.);
+              bufp+=3;
+            }
+            for(;j<channels;j++) fbuffer[j][i]=0;
+          }
+          break;
+        case SND_PCM_FORMAT_S16_LE:
+          for(i=0;i<frames;i++){
+            for(j=0;j<channels;j++){
+              int32_t in = (bufp[0]<<16)|(bufp[1]<<24);
+              fbuffer[j][i]= in * (1./128./256./256./256.);
+              bufp+=2;
+            }
+            for(;j<channels;j++) fbuffer[j][i]=0;
+          }
+          break;
+        case SND_PCM_FORMAT_S16_BE:
+          for(i=0;i<frames;i++){
+            for(j=0;j<channels;j++){
+              int32_t in = (bufp[1]<<16)|(bufp[0]<<24);
+              fbuffer[j][i]= in * (1./128./256./256./256.);
+              bufp+=2;
+            }
+            for(;j<channels;j++) fbuffer[j][i]=0;
+          }
+          break;
+        }
+
+        //if(!(request_output_silence ||
+        //   request_output_tone ||
+        //   request_output_noise ||
+        //   request_output_sweep ||
+        //   request_output_logsweep)){
+        ///* not in panel 10; mirror input 0 into input 1 */
+        //memcpy(fbuffer[1],fbuffer[0],sizeof(**fbuffer)*frames);
+        //}
+
+        /* save the ch0 input in two-input mode */
+        if(request_output_duallisten && channels>1){
+          float *temp = crossbuffer;
+          crossbuffer=fbuffer[0];
+          fbuffer[0]=temp;
+        }
+
+        if(request_output_silence){
+          for(i=0;i<frames;i++)
+            fbuffer[0][i]=0;
+        }
+
+        /* input lowpass */
+        if((request_lp_filter || active_lp_filter) && !request_1kHz_square){
+          active_lp_filter =
+            run_convolution_filter(lowpass,lowpass_state[0],
+                                   fbuffer[0],
+                                   frames,0,request_lp_filter,1.);
+
+          if(channels>1)
+            active_lp_filter |=
+              run_convolution_filter(lowpass,lowpass_state[1],
+                                     fbuffer[1],
+                                     frames,0,request_lp_filter,1.);
+        }
+
+        /* panel 10 white noise burst */
+        static int count_output_noise;
+        static float mix_output_noise;
+        if(request_output_noise || mix_output_noise>0.){
+          float target = request_output_noise ? 1. : 0.;
+          if(request_output_noise && count_output_noise==0)
+            count_output_noise = 10 * rate;
+
+          if(fabsf(mix_output_noise - target) > .0000001){
+            float m;
+            for(i=0;i<frames;i++){
+              float v = .99*(drand48()-drand48());
+              m = filter_filter(target,&filter_output_noise);
+              fbuffer[0][i] =  v*m;
+            }
+            mix_output_noise = m;
+          }else{
+            mix_output_noise = target;
+            if(request_output_noise){
+              for(i=0;i<frames;i++){
+                float v = .99*(drand48()-drand48());
+                fbuffer[0][i] =  v;
+              }
+            }else
+              count_output_noise=0;
+          }
+          count_output_noise-=frames;
+          if(count_output_noise<=0){
+            count_output_noise=0;
+            request_output_noise=0;
+            write(eventpipe[1],"\006",1);
+          }
+        }
+
+        /* panel 10 linear/log sweep  */
+        static float mix_output_sweep=0.;
+        if(request_output_sweep ||
+           request_output_logsweep ||
+           mix_output_sweep){
+          float target =
+            (request_output_sweep||request_output_logsweep) ? 1. : 0.;
+
+          double totalsamples = rate * 20.;
+          float m;
+
+          static int logp;
+          static double theta = 0.;
+          static double samples = 0.;
+          static double d;
+          double l0 = log(5./rate*2*M_PI);
+          double l1 = log(M_PI);
+          double dd = logp ? (l1-l0)/totalsamples : M_PI/totalsamples;
+
+          if(request_output_logsweep)logp=1;
+          if(request_output_sweep)logp=0;
+          if(samples==0.){
+            //filter_set(&filter_output_sweep,1.);
+            d = logp ? l0 : 0;
+          }
+
+          for(i=0;i<frames;i++){
+            double instf = logp ? exp(d) : d;
+            float v = .99*sin(theta);
+            m = filter_filter(target,&filter_output_sweep);
+
+            d+=dd;
+            theta+=instf;
+            samples++;
+
+            if(theta>2*M_PI)theta-=2.*M_PI;
+            if(totalsamples-samples<2000){
+              target=0;
+              request_output_sweep=0;
+              request_output_logsweep=0;
+            }
+            fbuffer[0][i] =  v*m;
+          }
+
+          mix_output_sweep=m;
+          if(!request_output_sweep &&
+             !request_output_logsweep &&
+             m<.00000001){
+            mix_output_sweep=0;
+            samples=0;
+            theta=0;
+            write(eventpipe[1],"\006",1);
+          }
+        }
+
+        /* synthetic ~ 1kHz 'squarewave' */
+        /* generate it via previously computed Fourier expansion */
+        if(request_1kHz_square){
+          static double phase=0.;
+          double offset = request_1kHz_square_offset/65535.*square_coeffs_w;
+          double spread = offset + request_1kHz_square_spread/65535.*square_coeffs_w;
+
+          for(i=0;i<frames;i++){
+            float acc=0.;
+            int k;
+            for(j=0,k=1;j<square_coeffs_n;j++,k+=2)
+              acc += square_coeffs[j] *
+                cos( square_phases[j] + k*(phase+offset));
+            fbuffer[0][i] = acc;
+
+            if(channels>1 && spread!=offset){
+              acc=0;
+              for(j=0,k=1;j<square_coeffs_n;j++,k+=2)
+                acc += square_coeffs[j] *
+                  cos( square_phases[j] + k*(phase+spread));
+              fbuffer[1][i] = acc;
+            }else
+              fbuffer[1][i] = fbuffer[0][i];
+
+            phase+=square_coeffs_w;
+            if(phase>=2*M_PI)phase-=2*M_PI;
+          }
+        }
+
+        /* synthetic ~ 5kHz generation */
+        static double amplitude_5kHz_sine=0;
+        if(request_5kHz_sine || amplitude_5kHz_sine>0.){
+          static double phase=0.;
+          double w = 1./rate*5000.*2*M_PI;
+
+          /* avoid clipping when dithering at 8 bits */
+          float amp_target = request_5kHz_sine ?
+            (1.-1.5f/pow(2,7)) *
+            (pow(2,request_1kHz_amplitude/65536.f-1.f)/32768.):
+            0.;
+
+          if(fabsf(amplitude_5kHz_sine - amp_target) > .0000001){
+
+            for(i=0;i<frames;i++){
+
+              amplitude_5kHz_sine =
+                filter_filter(amp_target, &filter_5kHz_amplitude);
+
+              float v = sinf(phase)*amplitude_5kHz_sine;
+              fbuffer[0][i] = v;
+              phase+=w;
+              if(phase>=2*M_PI)phase-=2*M_PI;
+            }
+          }else{
+            amplitude_5kHz_sine = amp_target;
+            if(request_5kHz_sine){
+              for(i=0;i<frames;i++){
+                float v = sinf(phase)*amplitude_5kHz_sine;
+                fbuffer[0][i] =  v;
+                phase+=w;
+                if(phase>=2*M_PI)phase-=2*M_PI;
+              }
+            }
+          }
+        }
+
+        /* synthetic ~ 1kHz generation; we choose a value that's an
+           integer factor of the sample rate */
+        static double amplitude_1kHz_sine=0;
+        static double mix_1kHz_sine=0;
+        if(request_1kHz_sine || mix_1kHz_sine>0.){
+          static double phase=0.;
+          int quadrant=rate/2;
+          int period = ceil(rate/1000);
+          double w = 1./period*2*M_PI;
+
+          /* avoid clipping when dithering at 8 bits */
+          float amp_target = (1.-1.5f/pow(2,7)) *
+            (pow(2,request_1kHz_amplitude/65536.f-1.f)/32768.);
+          float mix_target = request_1kHz_sine ? 1. : 0.;
+          float modmix_target = request_1kHz_modulate ? 1. : 0.;
+
+          if(amplitude_1kHz_modmix > 0. ||
+             modmix_target > 0. ||
+             fabsf(amplitude_1kHz_sine - amp_target) > .0000001 ||
+             fabsf(mix_1kHz_sine - mix_target) > .0000001){
+
+            for(i=0;i<frames;i++){
+
+              float first =
+                filter_filter(modmix_target, &filter_modulation1);
+              amplitude_1kHz_modmix =
+                filter_filter(first, &filter_modulation2);
+
+              float mod = sinf(.5*M_PI*state_1kHz_modulation/quadrant)*
+                amplitude_1kHz_modmix;
+
+              state_1kHz_modulation++;
+              if(state_1kHz_modulation>=2*quadrant)
+                state_1kHz_modulation=0;
+              mod *= mod;
+              mod = fromdB(mod*-110);
+
+              amplitude_1kHz_sine =
+                filter_filter(amp_target, &filter_1kHz_amplitude)*mod;
+
+              mix_1kHz_sine =
+                filter_filter(mix_target, &filter_1kHz_mix);
+
+              float v = sinf(phase)*amplitude_1kHz_sine;
+              fbuffer[0][i] *= (1.-mix_1kHz_sine);
+              fbuffer[0][i] +=  v*mix_1kHz_sine;
+              if(channels>1 && request_1kHz_sine2){
+                fbuffer[1][i] *= (1.-mix_1kHz_sine);
+                fbuffer[1][i] +=  v*mix_1kHz_sine;
+              }
+              phase+=w;
+              if(phase>=2*M_PI)phase-=2*M_PI;
+            }
+          }else{
+            amplitude_1kHz_modmix = 0;
+            amplitude_1kHz_sine = amp_target;
+            mix_1kHz_sine = mix_target;
+            state_1kHz_modulation = 0;
+
+            if(request_1kHz_sine){
+              for(i=0;i<frames;i++){
+                float v = sinf(phase)*amplitude_1kHz_sine;
+                fbuffer[0][i] =  v;
+                if(channels>1 && request_1kHz_sine2)
+                  fbuffer[1][i] =  v;
+                phase+=w;
+                if(phase>=2*M_PI)phase-=2*M_PI;
+              }
+            }
+          }
+        }
+
+        /* conditionally subquantize and dither output */
+        subquant_dither_to_X(&shapestate_ch0,fbuffer[0],frames,
+                             request_ch1_quant);
+        if(channels>1)
+          subquant_dither_to_X(&shapestate_ch1,fbuffer[1],frames,
+                               request_ch2_quant);
+
+        /* notch filter has to come after dither as it's sort of the
+           point...  The notch filter would normally break the dither
+           (it would be ~equivalent to adding an out-of-phase since
+           wave that is not itself dithered, and thus upon final quant
+           it adds distortion), but because we add gain with the
+           notch, it's OK */
+        if(request_1kHz_notch ||
+           active_1kHz_notch ||
+           prime_1kHz_notch){
+          float notch_gain_target = fromdB( 6*request_ch2_quant-30);
+
+          active_1kHz_notch =
+            run_notch_filter(notch[0],
+                             fbuffer[0],
+                             frames,
+                             prime_1kHz_notch,
+                             request_1kHz_notch,
+                             notch_gain_target);
+          if(channels>1)
+            active_1kHz_notch |=
+              run_notch_filter(notch[1],
+                               fbuffer[1],
+                               frames,
+                               prime_1kHz_notch,
+                               request_1kHz_notch,
+                               notch_gain_target);
+        }
+
+        /* in dual listen mode, pipe sees original ch0 and ch1 */
+        if(request_output_duallisten && channels>1){
+          float *temp = crossbuffer;
+          crossbuffer=fbuffer[0];
+          fbuffer[0]=temp;
+        }
+
+        /* float buffer -> out pipe */
+        {
+          unsigned char *buf = iobuffer;
+          switch(bits){
+          case 16:
+            for(i=0;i<frames;i++){
+              for(j=0;j<channels;j++){
+                int v = rint(fbuffer[j][i]*32768.f);
+                if(v>32767)v=32767;
+                if(v<-32768)v=-32768.;
+                *buf++=v&0xff;
+                *buf++=(v>>8)&0xff;
+              }
+            }
+            break;
+          case 24:
+            for(i=0;i<frames;i++){
+              for(j=0;j<channels;j++){
+                int v = rint(fbuffer[j][i]*8388608.f);
+                if(v>8388607)v=8388607;
+                if(v<-8388608)v=-8388608;
+                *buf++=v&0xff;
+                *buf++=(v>>8)&0xff;
+                *buf++=(v>>16)&0xff;
+              }
+            }
+            break;
+          }
+
+          if(outpipe0 || outpipe1){
+            if(outpipe0){
+              fwrite(iobuffer,1,buf-iobuffer,outpipe0);
+              fflush(outpipe0);
+            }
+            if(outpipe1){
+              fwrite(iobuffer,1,buf-iobuffer,outpipe1);
+              fflush(outpipe1);
+            }
+          }
+        }
+
+        /* output filtering for 'perfect squarewave' */
+        if((request_lp_filter || active_lp_filter) && request_1kHz_square){
+          active_lp_filter =
+            run_convolution_filter(lowpass,lowpass_state[0],
+                                   fbuffer[0],
+                                   frames,0,request_lp_filter,1.);
+
+          if(channels>1)
+            active_lp_filter |=
+              run_convolution_filter(lowpass,lowpass_state[1],
+                                     fbuffer[1],
+                                     frames,0,request_lp_filter,1.);
+
+        }
+
+        /* in dual listen mode, audio device sees silence on ch1,
+           generated output on ch0 */
+        if(request_output_duallisten && channels>1){
+          float *temp=crossbuffer;
+          crossbuffer=fbuffer[0];
+          fbuffer[0]=temp;
+        }
+
+        /* panel 10 modes all require silence on ch1 */
+        if(request_output_silence ||
+           request_output_tone ||
+           request_output_noise ||
+           request_output_sweep ||
+           request_output_logsweep){
+          for(i=0;i<frames;i++)
+            fbuffer[1][i]=0;
+        }
+
+        if(out_devhandle){
+        /* float buffer -> PCM stream */
+
+          /* the output pipe and the hardware device format may not
+             match */
+          unsigned char *buf = iobuffer;
+          switch(outformat){
+          case SND_PCM_FORMAT_S16_LE:
+            for(i=0;i<frames;i++){
+              for(j=0;j<channels;j++){
+                int v = rint(fbuffer[j][i]*32768.f);
+                if(v>32767)v=32767;
+                if(v<-32768)v=-32768.;
+                *buf++=v&0xff;
+                *buf++=(v>>8)&0xff;
+              }
+              for(;j<out_channels;j++){
+                *buf++=0;
+                *buf++=0;
+              }
+            }
+            break;
+          case SND_PCM_FORMAT_S24_3LE:
+            for(i=0;i<frames;i++){
+              for(j=0;j<channels;j++){
+                int v = rint(fbuffer[j][i]*8388608.f);
+                if(v>8388607)v=8388607;
+                if(v<-8388608)v=-8388608;
+                *buf++=v&0xff;
+                *buf++=(v>>8)&0xff;
+                *buf++=(v>>16)&0xff;
+              }
+              for(;j<out_channels;j++){
+                *buf++=0;
+                *buf++=0;
+                *buf++=0;
+              }
+            }
+            break;
+          case SND_PCM_FORMAT_S24_LE:
+            for(i=0;i<frames;i++){
+              for(j=0;j<channels;j++){
+                int v = rint(fbuffer[j][i]*8388608.f);
+                if(v>8388607)v=8388607;
+                if(v<-8388608)v=-8388608;
+                *buf++=0;
+                *buf++=v&0xff;
+                *buf++=(v>>8)&0xff;
+                *buf++=(v>>16)&0xff;
+              }
+              for(;j<out_channels;j++){
+                *buf++=0;
+                *buf++=0;
+                *buf++=0;
+                *buf++=0;
+              }
+            }
+            break;
+          case SND_PCM_FORMAT_S32_LE:
+            for(i=0;i<frames;i++){
+              for(j=0;j<channels;j++){
+                double vv = rint(fbuffer[j][i]*2147483648.f);
+
+                if(vv>2147483647)vv=2147483647;
+                if(vv<-2147483648)vv=-2147483648;
+                int v = vv;
+                *buf++=v&0xff;
+                *buf++=(v>>8)&0xff;
+                *buf++=(v>>16)&0xff;
+                *buf++=(v>>24)&0xff;
+              }
+              for(;j<out_channels;j++){
+                *buf++=0;
+                *buf++=0;
+                *buf++=0;
+                *buf++=0;
+              }
+            }
+            break;
+          case SND_PCM_FORMAT_S16_BE:
+            for(i=0;i<frames;i++){
+              for(j=0;j<channels;j++){
+                int v = rint(fbuffer[j][i]*32768.f);
+                if(v>32767)v=32767;
+                if(v<-32768)v=-32768.;
+                *buf++=(v>>8)&0xff;
+                *buf++=v&0xff;
+              }
+              for(;j<out_channels;j++){
+                *buf++=0;
+                *buf++=0;
+              }
+            }
+            break;
+          case SND_PCM_FORMAT_S24_3BE:
+            for(i=0;i<frames;i++){
+              for(j=0;j<channels;j++){
+                int v = rint(fbuffer[j][i]*8388608.f);
+                if(v>8388607)v=8388607;
+                if(v<-8388608)v=-8388608;
+                *buf++=(v>>16)&0xff;
+                *buf++=(v>>8)&0xff;
+                *buf++=v&0xff;
+              }
+              for(;j<out_channels;j++){
+                *buf++=0;
+                *buf++=0;
+                *buf++=0;
+              }
+            }
+            break;
+          case SND_PCM_FORMAT_S24_BE:
+            for(i=0;i<frames;i++){
+              for(j=0;j<channels;j++){
+                int v = rint(fbuffer[j][i]*8388608.f);
+                if(v>8388607)v=8388607;
+                if(v<-8388608)v=-8388608;
+                *buf++=(v>>16)&0xff;
+                *buf++=(v>>8)&0xff;
+                *buf++=v&0xff;
+                *buf++=0;
+              }
+              for(;j<out_channels;j++){
+                *buf++=0;
+                *buf++=0;
+                *buf++=0;
+                *buf++=0;
+              }
+            }
+            break;
+          case SND_PCM_FORMAT_S32_BE:
+            for(i=0;i<frames;i++){
+              for(j=0;j<channels;j++){
+                double vv = rint(fbuffer[j][i]*2147483648.f);
+                if(vv>2147483647)vv=2147483647;
+                if(vv<-2147483648)vv=-2147483648;
+                int v=vv;
+                *buf++=(v>>24)&0xff;
+                *buf++=(v>>16)&0xff;
+                *buf++=(v>>8)&0xff;
+                *buf++=v&0xff;
+              }
+              for(;j<out_channels;j++){
+                *buf++=0;
+                *buf++=0;
+                *buf++=0;
+                *buf++=0;
+              }
+            }
+            break;
+          }
+
+          int togo = frames;
+          unsigned char *outbuf=iobuffer;
+          while(togo){
+            int wret = snd_pcm_writei (out_devhandle,outbuf,togo);
+            if(wret<0){
+              switch(wret){
+              case -EAGAIN:
+                wret = 0;
+                break;
+              case -EPIPE: case -ESTRPIPE:
+                // underrun; set starve and reset soft device
+                snd_pcm_drop(in_devhandle);
+                snd_pcm_drop(out_devhandle);
+                snd_pcm_prepare(in_devhandle);
+                snd_pcm_prepare(out_devhandle);
+                wret=0;
+                togo=0;
+                fprintf(stderr,"\n PLAYBACK XRUN @ (%ld)\n ",
+                        (long)time(NULL));
+              break;
+              case -EBADFD:
+              default:
+                /* shut down device */
+                snd_pcm_close(out_devhandle);
+                out_devhandle=NULL;
+                wret=0;
+                togo=0;
+                break;
+              }
+            }else{
+              outbuf += wret*channels*((bits+7)>>3);
+              togo -= wret;
+            }
+          }
+        }
+      }
+    }else
+      sleep(1);
+  }
+  return NULL;
+}

Added: trunk/Xiph-episode-II/bounce/gtk-bounce-convolve.c
===================================================================
--- trunk/Xiph-episode-II/bounce/gtk-bounce-convolve.c	                        (rev 0)
+++ trunk/Xiph-episode-II/bounce/gtk-bounce-convolve.c	2013-02-24 22:04:10 UTC (rev 18812)
@@ -0,0 +1,332 @@
+#include <math.h>
+#include <stdlib.h>
+#include <string.h>
+#include "gtk-bounce-convolve.h"
+
+static inline float todB(float x){
+  return log((double)(x)*(x)+1e-50)*4.34294480;
+}
+
+#define A0 .35875f
+#define A1 .48829f
+#define A2 .14128f
+#define A3 .01168f
+
+static float beta(int n, float alpha){
+  return cosh(acosh(pow(10,alpha))/(n-1));
+}
+
+static double T(double n, double x){
+  if(fabs(x)<=1){
+    return cos(n*acos(x));
+  }else{
+    return cosh(n*acosh(x));
+  }
+}
+
+/* 'n' is equivalent to complex blocksize; the returned array is a
+   real-only array of frequency-domain amplitudes of size n/2+1 */
+convofilter *filter_bandstop_new(float w0,
+                                 float w1,
+                                 int rate,
+                                 int n){
+  int i;
+  convofilter *ret = calloc(1,sizeof(*ret));
+  float *f = malloc((n/2+1)*sizeof(*f));
+
+  double *freqbuffer = fftw_malloc((n/2+1)*sizeof(*freqbuffer));
+  double *winbuffer = fftw_malloc((n/4+1)*sizeof(*winbuffer));
+
+  fftw_plan freqplan_i =
+    fftw_plan_r2r_1d(n/4+1,freqbuffer,freqbuffer,
+                     FFTW_REDFT00,FFTW_ESTIMATE);
+  fftw_plan winplan_i =
+    fftw_plan_r2r_1d(n/4+1,winbuffer,winbuffer,
+                     FFTW_REDFT00,FFTW_ESTIMATE);
+  fftw_plan freqplan_f =
+    fftw_plan_r2r_1d(n/2+1,freqbuffer,freqbuffer,
+                     FFTW_REDFT00,FFTW_ESTIMATE);
+
+  /* construct box response */
+  int a = rint(w0/rate*n*.5);
+  int b = rint(w1/rate*n*.5);
+  for(i=0;i<a;i++)
+    freqbuffer[i]=1.;
+  for(;i<=b;i++)
+    freqbuffer[i]=0.;
+  for(;i<n/4+1;i++)
+    freqbuffer[i]=1.;
+
+  /* to time domain for windowing/padding */
+  fftw_execute(freqplan_i);
+
+  /* ~160dB deep Dolph-Tschebyshev window; exceeds precision of fftwf */
+  /* be aware that the 'Net is rife with incorrect definitions of this
+      window (and many that don't even make sense).  This version is
+      adapted from Dolph's 1946 paper. */
+  int N=n/2;
+  float alpha = 8.;
+  double B = beta(N,alpha);
+
+  for(i=0;i<n/4+1;i++)
+    winbuffer[i] = T(N,B*cos( M_PI*i/N ));
+
+  fftw_execute(winplan_i);
+
+  double D = 2./(winbuffer[0]*n*n);
+
+  for(i=0;i<n/4+1;i++)
+    freqbuffer[i]*=winbuffer[i];
+
+  /* pad */
+  for(;i<n/2+1;i++)
+    freqbuffer[i]=0;
+
+  /* back to frequency */
+  fftw_execute(freqplan_f);
+
+  for(i=0;i<n/2+1;i++)
+    f[i]=freqbuffer[i]*D;
+
+#if 0
+  {
+    FILE *file=fopen("notch.m","w");
+    for(i=0;i<n/2+1;i++)
+      fprintf(file,"%d %lf\n",(int)(rate/2.*i/(n/2)),todB(f[i]));
+    fclose(file);
+  }
+#endif
+
+  fftw_destroy_plan(winplan_i);
+  fftw_destroy_plan(freqplan_i);
+  fftw_destroy_plan(freqplan_f);
+  fftw_free(winbuffer);
+  fftw_free(freqbuffer);
+
+  ret->blocksize=n;
+  ret->filter=f;
+
+  return ret;
+}
+
+convofilter *filter_lowpass_new(float w,
+                                int rate,
+                                int n){
+  int i;
+  convofilter *ret=calloc(1,sizeof(*ret));
+
+  float *f = malloc((n/2+1)*sizeof(*f));
+  double *freqbuffer = fftw_malloc((n/2+1)*sizeof(*freqbuffer));
+
+  fftw_plan freqplan_i =
+    fftw_plan_r2r_1d(n/4+1,freqbuffer,freqbuffer,
+                      FFTW_REDFT00,FFTW_ESTIMATE);
+  fftw_plan freqplan_f =
+    fftw_plan_r2r_1d(n/2+1,freqbuffer,freqbuffer,
+                      FFTW_REDFT00,FFTW_ESTIMATE);
+
+  /* construct box response */
+  int a = rint(w/rate*n*.5);
+  for(i=0;i<a;i++)
+    freqbuffer[i]=1.;
+  for(;i<n/4+1;i++)
+    freqbuffer[i]=0.;
+
+  /* to time domain for windowing/padding */
+  fftw_execute(freqplan_i);
+
+  /* Blackmann-Harris */
+  float scale = 4*M_PI/n;
+  for(i=0;i<n/4+1;i++){
+    float w = A0 + A1*cos(scale*i) + A2*cos(scale*i*2) + A3*cos(scale*i*3);
+    freqbuffer[i] *= w;
+  }
+
+  /* pad */
+  for(;i<n/2+1;i++)
+    freqbuffer[i]=0;
+
+  /* back to frequency */
+  fftw_execute(freqplan_f);
+
+  for(i=0;i<n/2+1;i++)
+    f[i]=2.*freqbuffer[i]/(n*n); /* normalize */
+
+#if 0
+  {
+    FILE *file=fopen("low.m","w");
+    for(i=0;i<n/2+1;i++)
+      fprintf(file,"%d %f\n",(int)(rate/2.*i/(n/2)),todB(f[i]));
+    fclose(file);
+  }
+#endif
+
+  fftw_destroy_plan(freqplan_i);
+  fftw_destroy_plan(freqplan_f);
+  fftw_free(freqbuffer);
+
+  ret->blocksize=n;
+  ret->filter=f;
+  return ret;
+}
+
+convostate *convostate_new(convofilter *f){
+  convostate *ret = calloc(1,sizeof(*ret));
+  int i;
+
+  for(i=0;i<3;i++){
+    ret->fbuffer[i]=fftwf_malloc((f->blocksize+2)*sizeof(*ret->fbuffer));
+    ret->forward[i]=fftwf_plan_dft_r2c_1d(f->blocksize,ret->fbuffer[i],
+                                          (fftwf_complex *)ret->fbuffer[i],
+                                          FFTW_ESTIMATE);
+    ret->inverse[i]=fftwf_plan_dft_c2r_1d(f->blocksize,
+                                          (fftwf_complex *)ret->fbuffer[i],
+                                          ret->fbuffer[i],
+                                          FFTW_ESTIMATE);
+  }
+  filter_make_critical(.0001,1,&ret->mix_filter);
+  ret->mix_now=0;
+  return ret;
+}
+
+void convofilter_destroy(convofilter *f){
+  if(f){
+    if(f->filter)free(f->filter);
+    free(f);
+  }
+}
+
+void convostate_destroy(convostate *f){
+  if(f){
+    int i;
+    for(i=0;i<3;i++){
+      if(f->fbuffer[i])fftwf_free(f->fbuffer[i]);
+      if(f->forward[i])fftwf_destroy_plan(f->forward[i]);
+      if(f->inverse[i])fftwf_destroy_plan(f->inverse[i]);
+    }
+    free(f);
+  }
+}
+
+/* modifies buf in-place */
+int run_convolution_filter(convofilter *f,
+                           convostate *s,
+                           float *buf,
+                           int n,
+                           int run,
+                           int active,
+                           float gain){
+  int i;
+  int bs = f->blocksize;
+  int bs2 = f->blocksize/2;
+  int bs4 = f->blocksize/4;
+
+  while(n){
+    float *head = s->fbuffer[s->head];
+    float *head4 = head+bs4;
+    float *head2 = head+bs2;
+    int copysamples = n;
+    if(copysamples+s->headfill>bs2) copysamples = bs2-s->headfill;
+
+    /* accumulate new samples into the head */
+    memcpy(head4+s->headfill,buf,sizeof(*head)*copysamples);
+
+    /* overlap-add samples out of the lap buffer if it's deep enough */
+    if(s->storefill==2){
+      float *tail = s->fbuffer[(s->head+1)%3]+bs2+s->headfill;
+      float *mid  = s->fbuffer[(s->head+2)%3]+s->headfill;
+      float mix_target = active ? 1. : 0.;
+
+      if(fabs(s->mix_now-mix_target)>.0000001){
+        float mix;
+        for(i=0;i<copysamples;i++){
+          float val = tail[i]+mid[i];
+          mix = filter_filter(mix_target, &s->mix_filter);
+
+          buf[i] *= (1.-mix);
+          buf[i] += val*mix*gain;
+        }
+        s->mix_now = mix;
+      }else{
+        if(!active && !run){
+          /* reset state and return; leave rest of buf untouched */
+          for(i=0;i<3;i++)
+            memset(s->fbuffer[i],0,(f->blocksize+2)*sizeof(**s->fbuffer));
+          s->head=0;
+          s->headfill=0;
+          s->storefill=0;
+          return 0;
+        }else{
+          s->mix_now = mix_target;
+          if(mix_target>.0000001){
+            for(i=0;i<copysamples;i++){
+              float val = tail[i]+mid[i];
+              buf[i] = val*gain;
+            }
+          }
+        }
+      }
+    } //else nothing; leave buf untouched
+
+    s->headfill+=copysamples;
+    buf+=copysamples;
+    n-=copysamples;
+
+    /* full accumulation? */
+    if(s->headfill==bs2){
+
+      /* pad fftw3f buffer */
+      memset(head,0,bs4*sizeof(*head2));
+      memset(head+bs2+bs4,0,bs4*sizeof(*head2));
+
+      /* transform */
+      fftwf_execute(s->forward[s->head]);
+
+      /* filter */
+      float *mag = f->filter;
+      for(i=0;i<bs+2;i+=2){
+        head[i] *= mag[i>>1];
+        head[i+1] *= mag[i>>1];
+      }
+
+      /* inverse transform */
+      fftwf_execute(s->inverse[s->head]);
+
+      /* cycle */
+      s->head = (s->head+1)%3;
+      s->headfill=0;
+      s->storefill++;
+      if(s->storefill>2)s->storefill=2;
+    }
+  }
+  return 1;
+}
+
+#if 0
+int main(int argc, char **argv){
+  int i;
+  float data[131072];
+
+  for(i=0;i<131072;i++){
+    data[i]=copysignf(1,sin(1000*2.*M_PI/44100*i));
+
+  }
+  convofilter *notch=filter_bandstop_new(850,1150,44100,4096);
+  convofilter *lowpass=filter_lowpass_new(20500,44100,512);
+
+  convostate *nt = convostate_new(notch);
+  convostate *lt = convostate_new(lowpass);
+
+  for(i=0;i<131072;i+=64){
+    run_convolution_filter(lowpass,lt,data+i,64,1,256.);
+  }
+
+  FILE *f=fopen("test.m","w");
+  for(i=0;i<131072;i++)
+    fprintf(f,"%d %f\n",i,data[i]);
+  fclose(f);
+
+  return 0;
+}
+
+#endif

Added: trunk/Xiph-episode-II/bounce/gtk-bounce-convolve.h
===================================================================
--- trunk/Xiph-episode-II/bounce/gtk-bounce-convolve.h	                        (rev 0)
+++ trunk/Xiph-episode-II/bounce/gtk-bounce-convolve.h	2013-02-24 22:04:10 UTC (rev 18812)
@@ -0,0 +1,49 @@
+
+#ifndef _BOUNCE_CONVO_H_
+#define _BOUNCE_CONVO_H_
+
+#include "twopole.h"
+#include <fftw3.h>
+
+typedef struct {
+  int blocksize;
+  float *filter;
+} convofilter;
+
+typedef struct {
+  fftwf_plan forward[3];
+  fftwf_plan inverse[3];
+  float *fbuffer[3];
+  int head;
+
+  int headfill;
+  int storefill;
+
+  pole2 mix_filter;
+  float mix_now;
+} convostate;
+
+extern convofilter *filter_bandstop_new(float w0,
+                                        float w1,
+                                        int rate,
+                                        int n);
+
+extern convofilter *filter_lowpass_new(float w,
+                                       int rate,
+                                       int n);
+
+extern void convofilter_destroy(convofilter *f);
+
+extern convostate *convostate_new(convofilter *f);
+
+extern void convostate_destroy(convostate *f);
+
+extern int run_convolution_filter(convofilter *f,
+                                  convostate *s,
+                                  float *buf,
+                                  int n,
+                                  int run,
+                                  int active,
+                                  float gain);
+
+#endif

Added: trunk/Xiph-episode-II/bounce/gtk-bounce-dither.c
===================================================================
--- trunk/Xiph-episode-II/bounce/gtk-bounce-dither.c	                        (rev 0)
+++ trunk/Xiph-episode-II/bounce/gtk-bounce-dither.c	2013-02-24 22:04:10 UTC (rev 18812)
@@ -0,0 +1,100 @@
+#include "gtk-bounce.h"
+#include "gtk-bounce-dither.h"
+
+/* flat and shaped dither, adapted from gmaxwell's code in opusdec */
+
+static unsigned int rngseed = 22222;
+static inline unsigned int fast_rand() {
+  /* Greg, this is one of the craziest things I've ever seen.  Bravo. */
+  rngseed = (rngseed * 96314165) + 907633515;
+  return rngseed;
+}
+
+const float fcoef[3][8] ={
+  /* 44.1 kHz noise shaping filter sd=2.51*/
+  {2.2061f, -.4706f, -.2534f, -.6214f, 1.0587f, .0676f, -.6054f, -.2738f},
+  /* 48.0kHz noise shaping filter sd=2.34*/
+  {2.2374f, -.7339f, -.1251f, -.6033f, 0.9030f, .0116f, -.5853f, -.2571f},
+  /* lowpass noise shaping filter sd=0.65*/
+  {1.0000f, 0.0000f, 0.0000f, 0.0000f, 0.0000f,0.0000f, 0.0000f, 0.0000f},
+};
+
+/* This implements a n-bit quantization with full triangular dither
+   and IIR noise shaping. The noise shaping filters were designed by
+   Sebastian Gesemann based on the LAME ATH curves with flattening
+   to limit their peak gain to 20dB.
+
+   The 48kHz version of this filter is just a warped version of the
+   44.1kHz filter and probably could be improved by shifting the
+   HF shelf up in frequency a little bit since 48k has a bit more
+   room and being more conservative against bat-ears is probably
+   more important than more noise suppression.
+   This process can increase the peak level of the signal (in theory
+   by the peak error of 1.5 +20dB though this much is unobservably rare).
+
+   We're not bothering to guard against clipping here because this is
+   in a demo and the condiitons are chosen so the we know we won't run
+   into it.  It also saves a little noise in the non-dithered case as
+   we won't be slightly shifting the quantzation scale */
+
+void subquant_dither_to_X(shapestate *ss, float *data, int n,
+                       float quant_bits){
+  int i;
+  if(quant_bits==0)quant_bits=24;
+  int filter_choice = request_rate==44100?0:(request_rate==48000?1:2);
+  const float *dither_fcoef = fcoef[filter_choice];
+  float quant_gain = pow(2,quant_bits-1);
+  float quant_invgain = 1./quant_gain;
+  float quant_clamp = quant_gain;
+  float dither_amp = request_dither_amplitude/65536.;
+
+  /* don't shape if we don't have a filter */
+  if(request_rate != 48000 && request_rate != 44100){
+    /* pop the request button back out */
+    request_dither_shaped=0;
+    write(eventpipe[1],"\005",1);
+  }
+
+  float *b_buf=ss->b_buf;
+  float *a_buf=ss->a_buf;
+  int mute=ss->mute;
+
+  if(mute>64)
+    memset(a_buf,0,sizeof(float)*4);
+
+  for(i=0;i<n;i++){
+    int silent=data[i]==0;
+    int j;
+    float si,r=0,err=0;
+    float s = data[i] * quant_gain;
+
+    if(request_dither && request_dither_shaped)
+      for(j=0;j<4;j++)
+        err += dither_fcoef[j]*b_buf[j] - dither_fcoef[j+4]*a_buf[j];
+
+    memmove(&a_buf[1],&a_buf[0],sizeof(float)*3);
+    memmove(&b_buf[1],&b_buf[0],sizeof(float)*3);
+    a_buf[0]=err;
+    s = s - err;
+
+    if(request_dither && mute<16)
+      r=((float)fast_rand()*(1/(float)UINT_MAX) -
+         (float)fast_rand()*(1/(float)UINT_MAX)) * dither_amp;
+
+    /* no need to clamp; this is a demo, and we won't clip */
+    si = rintf(s + r);
+
+    /* output width may be higher than the subquant */
+    data[i] = si*quant_invgain;
+
+    if(request_dither_shaped)
+      b_buf[0] = (mute>16)?0:si-s;
+    else
+      b_buf[0] = 0;
+
+    mute++;
+    if(!silent)mute=0;
+  }
+  ss->mute = (mute>960?960:mute);
+}
+

Added: trunk/Xiph-episode-II/bounce/gtk-bounce-dither.h
===================================================================
--- trunk/Xiph-episode-II/bounce/gtk-bounce-dither.h	                        (rev 0)
+++ trunk/Xiph-episode-II/bounce/gtk-bounce-dither.h	2013-02-24 22:04:10 UTC (rev 18812)
@@ -0,0 +1,16 @@
+#ifndef _BOUNCE_DITHER_H_
+#define _BOUNCE_DITHER_H_
+
+#include <math.h>
+#include <stdlib.h>
+
+typedef struct shapestate {
+  float b_buf[8];
+  float a_buf[8];
+  int mute;
+} shapestate;
+
+extern void subquant_dither_to_X(shapestate *ss, float *data, int n,
+                              float quant_bits);
+
+#endif

Added: trunk/Xiph-episode-II/bounce/gtk-bounce-filter.c
===================================================================
--- trunk/Xiph-episode-II/bounce/gtk-bounce-filter.c	                        (rev 0)
+++ trunk/Xiph-episode-II/bounce/gtk-bounce-filter.c	2013-02-24 22:04:10 UTC (rev 18812)
@@ -0,0 +1,497 @@
+#include <math.h>
+#include <stdlib.h>
+#include <string.h>
+#include "gtk-bounce-filter.h"
+
+static inline float todB(float x){
+  return log((double)(x)*(x)+1e-50)*4.34294480;
+}
+
+#define A0 .35875f
+#define A1 .48829f
+#define A2 .14128f
+#define A3 .01168f
+
+static float beta(int n, float alpha){
+  return cosh(acosh(pow(10,alpha))/(n-1));
+}
+
+static double T(double n, double x){
+  if(fabs(x)<=1){
+    return cos(n*acos(x));
+  }else{
+    return cosh(n*acosh(x));
+  }
+}
+
+void filter_notch_reset(notchfilter *f){
+  f->preroll = 0;
+  f->mix_now = 0.;
+  f->z1p = f->delayz1;
+  f->z2p = f->delayz2;
+  memset(f->delayz1,0,sizeof(f->delayz1));
+  memset(f->delayz2,0,sizeof(f->delayz2));
+}
+
+void filter_notch_destroy(notchfilter *f){
+  if(f)free(f);
+}
+
+/* Hardwired / specialized notch filter implementation **************/
+
+/* Intended for very deep notches; we want greater than single
+   precision mantissa depth (~200db).  Although it is possible to make
+   that go in single precision, there's no reason to be clever.  Just
+   run the filter in double precision and be done with it. */
+
+/* Normally, we'd probably want to use a fast convolution FIR filter
+   for this, but the additional latency of a sharp enough FIR filter
+   will impact the responsiveness of the demo.  The IIR notch filter
+   is inferior in several ways, but its reaction outside the stop band
+   will be much faster than a big convolution filter and we really
+   don't care about phase behavior or precision requirements. */
+
+int run_notch_filter(notchfilter *f,
+                     float *buf,
+                     int n,
+                     int run,
+                     int active,
+                     float gain){
+  int i,j;
+  float mix_target = active ? 1. : 0.;
+  float mix = f->mix_now;
+  double *z1 = f->z1p;
+  double *z2 = f->z2p;
+  double *a1 = f->a1;
+  double *a2 = f->a2;
+  double *b1 = f->b1;
+
+  for(i=0;i<n;i++){
+
+    double x = buf[i];
+    for(j=0;j<f->stages;j++){
+      double z0 = x - a1[j]*z1[j] - a2[j]*z2[j];
+      x = z0 + b1[j]*z1[j] + z2[j];
+      z2[j] = z0;
+    }
+
+    double *temp = z1;
+    z1 = z2;
+    z2 = temp;
+
+    if(f->preroll > f->rate){
+      if(fabs(mix-mix_target)>.0000001){
+        mix = filter_filter(mix_target, &f->mix_filter);
+        buf[i] *= (1.-mix);
+        buf[i] += x*mix*gain;
+      }else{
+        if(!active && !run){
+          /* reset state and return; leave rest of buf untouched */
+          filter_notch_reset(f);
+          return 0;
+        }else{
+          f->mix_now = mix_target;
+          if(mix_target>.0000001){
+            buf[i] = x*gain;
+          }
+        }
+      }
+    }else{
+      f->preroll++;
+    }
+  }
+
+  f->mix_now = mix;
+  f->z1p = z1;
+  f->z2p = z2;
+  return 1;
+}
+
+/* Analog filter gurus, please don't laugh too hard */
+/* Hand drawn in the z-plane, hardwired to our specific use. */
+/* For a more general-purpose filter, see the convolution based
+   bandstop with a deep Dolph-Tschebyshev window later below. */
+#define DUMPH 0
+notchfilter *filter_notch_new(float f, int rate){
+  int i,j;
+
+#if DUMPH
+  float Hmag[rate*8];
+  float Hang[rate*8];
+  for(i=0;i<rate*8;i++)Hmag[i]=1.;
+  for(i=0;i<rate*8;i++)Hang[i]=0.;
+#endif
+
+  notchfilter *ret = calloc(1,sizeof(*ret));
+  int stages = ret->stages = 30;
+  float side = (stages-1.)/2;
+  float tw = 30;
+  float fscale = tw/2./side;
+
+  for(j=0;j<stages;j++){
+    float bw = 15 + 6.2*cos(M_PI*(j-side)/side);
+    double r = 1. - M_PI*(bw)/rate;
+
+    float jfz = sin(M_PI*(j-side)/(side+1)/2)*side;
+    float jfp = sin(M_PI*(j-side)/(side)/2)*side;
+    float wz = 2*M_PI*(f+jfz*fscale)/rate;
+    float wp = 2*M_PI*(f+jfp*fscale*1.206)/rate;
+
+    double b1 = ret->b1[j] = -2. * cos(wz);
+    double a1 = ret->a1[j] = r * -2 * cos(wp);
+    double a2 = ret->a2[j] = r*r;
+
+#if DUMPH
+    for(i=0;i<rate*8;i++){
+      float w = 2*M_PI*i/rate/16;
+      double a = 1 + b1*cos(w) + cos(2*w);
+      float b = -b1*sin(w) - sin(2*w);
+      double c = 1. + a1*cos(w) + a2*cos(2*w);
+      double d = -a1*sin(w) - a2*sin(2*w);
+      double re = (a*c + b*d)/(c*c+d*d);
+      double im = (b*c - a*d)/(c*c+d*d);
+      Hmag[i] *= hypot(im,re);
+      Hang[i] += atan2(im,re);
+    }
+#endif
+  }
+
+#if DUMPH
+  {
+    FILE *file=fopen("iirnotchM.m","w");
+    for(i=0;i<rate*8;i++){
+      fprintf(file,"%f %lf\n",(i/16.), todB(Hmag[i])-90.6);
+    }
+    fclose(file);
+  }
+  {
+    FILE *file=fopen("iirnotchA.m","w");
+    for(i=0;i<rate*8;i++){
+      float a = Hang[i]*180/M_PI;
+      fprintf(file,"%f %lf\n",(i/16.), a);
+    }
+    fclose(file);
+  }
+#endif
+
+  ret->rate = rate;
+  filter_make_critical(.0001,1,&ret->mix_filter);
+  filter_notch_reset(ret);
+  return (ret);
+}
+
+/* FIR bandstop filter constructed out of a frequency domain box
+   response convolved with a deep Dolph-Tschebyshev window */
+
+/* 'n' is equivalent to complex blocksize; the returned array is a
+   real-only array of frequency-domain amplitudes of size n/2+1 */
+convofilter *filter_bandstop_new(float w0,
+                                 float w1,
+                                 int rate,
+                                 int n){
+  int i;
+  convofilter *ret = calloc(1,sizeof(*ret));
+  float *f = malloc((n/2+1)*sizeof(*f));
+
+  double *freqbuffer = fftw_malloc((n*2+1)*sizeof(*freqbuffer));
+  double *winbuffer = fftw_malloc((n/4+1)*sizeof(*winbuffer));
+
+  fftw_plan freqplan_i =
+    fftw_plan_r2r_1d(n*2+1,freqbuffer,freqbuffer,
+                     FFTW_REDFT00,FFTW_ESTIMATE);
+  fftw_plan winplan_i =
+    fftw_plan_r2r_1d(n/4+1,winbuffer,winbuffer,
+                     FFTW_REDFT00,FFTW_ESTIMATE);
+  fftw_plan freqplan_f =
+    fftw_plan_r2r_1d(n/2+1,freqbuffer,freqbuffer,
+                     FFTW_REDFT00,FFTW_ESTIMATE);
+
+  /* construct box response */
+  int a = rint(w0/rate*n*4);
+  int b = rint(w1/rate*n*4);
+  for(i=0;i<a;i++)
+    freqbuffer[i]=1.;
+  for(;i<=b;i++)
+    freqbuffer[i]=0.;
+  for(;i<n*2+1;i++)
+    freqbuffer[i]=1.;
+
+  /* to time domain for windowing/padding */
+  fftw_execute(freqplan_i);
+
+  /* ~160dB deep Dolph-Tschebyshev window; exceeds precision of fftwf */
+  /* be aware that the 'Net is rife with incorrect definitions of this
+      window (and many that don't even make sense).  This version is
+      adapted from Dolph's 1946 paper. */
+  int N=n/2;
+  float alpha = 8.;
+  double B = beta(N,alpha);
+
+  for(i=0;i<n/4+1;i++)
+    winbuffer[i] = T(N,B*cos( M_PI*i/N ));
+
+  fftw_execute(winplan_i);
+
+  double D = 2./(winbuffer[0]*n*n*8);
+
+  for(i=0;i<n/4+1;i++)
+    freqbuffer[i]*=winbuffer[i];
+
+  /* pad */
+  for(;i<n/2+1;i++)
+    freqbuffer[i]=0;
+
+  /* back to frequency */
+  fftw_execute(freqplan_f);
+
+  for(i=0;i<n/2+1;i++)
+    f[i]=freqbuffer[i]*D;
+
+#if 0
+  {
+    FILE *file=fopen("notch.m","w");
+    for(i=0;i<n/2+1;i++)
+      fprintf(file,"%d %lf\n",(int)(rate/2.*i/(n/2)),todB(f[i]));
+    fclose(file);
+  }
+#endif
+
+  fftw_destroy_plan(winplan_i);
+  fftw_destroy_plan(freqplan_i);
+  fftw_destroy_plan(freqplan_f);
+  fftw_free(winbuffer);
+  fftw_free(freqbuffer);
+
+  ret->blocksize=n;
+  ret->filter=f;
+
+  return ret;
+}
+
+convofilter *filter_lowpass_new(float w,
+                                int rate,
+                                int n){
+  int i;
+  convofilter *ret=calloc(1,sizeof(*ret));
+
+  float *f = malloc((n/2+1)*sizeof(*f));
+  double *freqbuffer = fftw_malloc((n/2+1)*sizeof(*freqbuffer));
+
+  fftw_plan freqplan_i =
+    fftw_plan_r2r_1d(n/4+1,freqbuffer,freqbuffer,
+                      FFTW_REDFT00,FFTW_ESTIMATE);
+  fftw_plan freqplan_f =
+    fftw_plan_r2r_1d(n/2+1,freqbuffer,freqbuffer,
+                      FFTW_REDFT00,FFTW_ESTIMATE);
+
+  /* construct box response */
+  int a = rint(w/rate*n*.5);
+  for(i=0;i<a;i++)
+    freqbuffer[i]=1.;
+  for(;i<n/4+1;i++)
+    freqbuffer[i]=0.;
+
+  /* to time domain for windowing/padding */
+  fftw_execute(freqplan_i);
+
+  /* Blackmann-Harris */
+  float scale = 4*M_PI/n;
+  for(i=0;i<n/4+1;i++){
+    float w = A0 + A1*cos(scale*i) + A2*cos(scale*i*2) + A3*cos(scale*i*3);
+    freqbuffer[i] *= w;
+  }
+
+  /* pad */
+  for(;i<n/2+1;i++)
+    freqbuffer[i]=0;
+
+  /* back to frequency */
+  fftw_execute(freqplan_f);
+
+  for(i=0;i<n/2+1;i++)
+    f[i]=2.*freqbuffer[i]/(n*n); /* normalize */
+
+#if 0
+  {
+    FILE *file=fopen("low.m","w");
+    for(i=0;i<n/2+1;i++)
+      fprintf(file,"%d %f\n",(int)(rate/2.*i/(n/2)),todB(f[i]));
+    fclose(file);
+  }
+#endif
+
+  fftw_destroy_plan(freqplan_i);
+  fftw_destroy_plan(freqplan_f);
+  fftw_free(freqbuffer);
+
+  ret->blocksize=n;
+  ret->filter=f;
+  return ret;
+}
+
+convostate *convostate_new(convofilter *f){
+  convostate *ret = calloc(1,sizeof(*ret));
+  int i;
+
+  for(i=0;i<3;i++){
+    ret->fbuffer[i]=fftwf_malloc((f->blocksize+2)*sizeof(*ret->fbuffer));
+    ret->forward[i]=fftwf_plan_dft_r2c_1d(f->blocksize,ret->fbuffer[i],
+                                          (fftwf_complex *)ret->fbuffer[i],
+                                          FFTW_ESTIMATE);
+    ret->inverse[i]=fftwf_plan_dft_c2r_1d(f->blocksize,
+                                          (fftwf_complex *)ret->fbuffer[i],
+                                          ret->fbuffer[i],
+                                          FFTW_ESTIMATE);
+  }
+  filter_make_critical(.0001,1,&ret->mix_filter);
+  ret->mix_now=0;
+  return ret;
+}
+
+void convofilter_destroy(convofilter *f){
+  if(f){
+    if(f->filter)free(f->filter);
+    free(f);
+  }
+}
+
+void convostate_destroy(convostate *f){
+  if(f){
+    int i;
+    for(i=0;i<3;i++){
+      if(f->fbuffer[i])fftwf_free(f->fbuffer[i]);
+      if(f->forward[i])fftwf_destroy_plan(f->forward[i]);
+      if(f->inverse[i])fftwf_destroy_plan(f->inverse[i]);
+    }
+    free(f);
+  }
+}
+
+/* modifies buf in-place */
+int run_convolution_filter(convofilter *f,
+                           convostate *s,
+                           float *buf,
+                           int n,
+                           int run,
+                           int active,
+                           float gain){
+  int i;
+  int bs = f->blocksize;
+  int bs2 = f->blocksize/2;
+  int bs4 = f->blocksize/4;
+
+  while(n){
+    float *head = s->fbuffer[s->head];
+    float *head4 = head+bs4;
+    float *head2 = head+bs2;
+    int copysamples = n;
+    if(copysamples+s->headfill>bs2) copysamples = bs2-s->headfill;
+
+    /* accumulate new samples into the head */
+    memcpy(head4+s->headfill,buf,sizeof(*head)*copysamples);
+
+    /* overlap-add samples out of the lap buffer if it's deep enough */
+    if(s->storefill==2){
+      float *tail = s->fbuffer[(s->head+1)%3]+bs2+s->headfill;
+      float *mid  = s->fbuffer[(s->head+2)%3]+s->headfill;
+      float mix_target = active ? 1. : 0.;
+
+      if(fabs(s->mix_now-mix_target)>.0000001){
+        float mix;
+        for(i=0;i<copysamples;i++){
+          float val = tail[i]+mid[i];
+          mix = filter_filter(mix_target, &s->mix_filter);
+
+          buf[i] *= (1.-mix);
+          buf[i] += val*mix*gain;
+        }
+        s->mix_now = mix;
+      }else{
+        if(!active && !run){
+          /* reset state and return; leave rest of buf untouched */
+          for(i=0;i<3;i++)
+            memset(s->fbuffer[i],0,(f->blocksize+2)*sizeof(**s->fbuffer));
+          s->head=0;
+          s->headfill=0;
+          s->storefill=0;
+          return 0;
+        }else{
+          s->mix_now = mix_target;
+          if(mix_target>.0000001){
+            for(i=0;i<copysamples;i++){
+              float val = tail[i]+mid[i];
+              buf[i] = val*gain;
+            }
+          }
+        }
+      }
+    } //else nothing; leave buf untouched
+
+    s->headfill+=copysamples;
+    buf+=copysamples;
+    n-=copysamples;
+
+    /* full accumulation? */
+    if(s->headfill==bs2){
+
+      /* pad fftw3f buffer */
+      memset(head,0,bs4*sizeof(*head2));
+      memset(head+bs2+bs4,0,bs4*sizeof(*head2));
+
+      /* transform */
+      fftwf_execute(s->forward[s->head]);
+
+      /* filter */
+      float *mag = f->filter;
+      for(i=0;i<bs+2;i+=2){
+        head[i] *= mag[i>>1];
+        head[i+1] *= mag[i>>1];
+      }
+
+      /* inverse transform */
+      fftwf_execute(s->inverse[s->head]);
+
+      /* cycle */
+      s->head = (s->head+1)%3;
+      s->headfill=0;
+      s->storefill++;
+      if(s->storefill>2)s->storefill=2;
+    }
+  }
+  return 1;
+}
+
+#if 0
+#define fromdB(x) (exp((x)*.11512925f))
+int main(int argc, char **argv){
+  int i;
+  float data[131072];
+
+  for(i=0;i<131072;i++){
+    data[i]=sin(1000*2.*M_PI/44100*i)*.95;
+    data[i]+=(drand48()-drand48())*fromdB(-93.);
+  }
+  notchfilter *notch=filter_notch_new(1000,44100);
+  //convofilter *notch=filter_bandstop_new(970,1030,44100,32768);
+  //convofilter *lowpass=filter_lowpass_new(20500,44100,512);
+
+  //convostate *nt = convostate_new(notch);
+  //convostate *lt = convostate_new(lowpass);
+
+  for(i=0;i<131072;i+=4096){
+    //run_convolution_filter(notch,nt,data+i,4096,1,1,256.);
+    run_notch_filter(notch,data+i,4096,1,1,6309);
+  }
+
+  FILE *f=fopen("test.m","w");
+  for(i=0;i<131072;i++){
+    fputc(((int)rint(data[i]*32768))&0xff,stdout);
+    fputc((((int)rint(data[i]*32768))>>8)&0xff,stdout);
+    fprintf(f,"%d %f\n",i,data[i]);
+  }
+  fclose(f);
+
+  return 0;
+}
+
+#endif

Added: trunk/Xiph-episode-II/bounce/gtk-bounce-filter.h
===================================================================
--- trunk/Xiph-episode-II/bounce/gtk-bounce-filter.h	                        (rev 0)
+++ trunk/Xiph-episode-II/bounce/gtk-bounce-filter.h	2013-02-24 22:04:10 UTC (rev 18812)
@@ -0,0 +1,75 @@
+
+#ifndef _BOUNCE_CONVO_H_
+#define _BOUNCE_CONVO_H_
+
+#include "twopole.h"
+#include <fftw3.h>
+
+typedef struct {
+  int blocksize;
+  float *filter;
+} convofilter;
+
+typedef struct {
+  fftwf_plan forward[3];
+  fftwf_plan inverse[3];
+  float *fbuffer[3];
+  int head;
+
+  int headfill;
+  int storefill;
+
+  pole2 mix_filter;
+  float mix_now;
+} convostate;
+
+#define MAXSTAGES 41
+
+typedef struct {
+  int stages;
+  int rate;
+  double a1[MAXSTAGES];
+  double a2[MAXSTAGES];
+  double b1[MAXSTAGES];
+  double *z1p;
+  double *z2p;
+  double delayz1[MAXSTAGES];
+  double delayz2[MAXSTAGES];
+  pole2 mix_filter;
+  float mix_now;
+  int preroll;
+} notchfilter;
+
+extern convofilter *filter_bandstop_new(float w0,
+                                        float w1,
+                                        int rate,
+                                        int n);
+
+extern convofilter *filter_lowpass_new(float w,
+                                       int rate,
+                                       int n);
+
+extern void convofilter_destroy(convofilter *f);
+
+extern convostate *convostate_new(convofilter *f);
+
+extern void convostate_destroy(convostate *f);
+
+extern int run_convolution_filter(convofilter *f,
+                                  convostate *s,
+                                  float *buf,
+                                  int n,
+                                  int run,
+                                  int active,
+                                  float gain);
+
+extern notchfilter *filter_notch_new(float f,int rate);
+extern void filter_notch_destroy(notchfilter *f);
+extern int run_notch_filter(notchfilter *f,
+                            float *buf,
+                            int n,
+                            int run,
+                            int active,
+                            float gain);
+
+#endif

Added: trunk/Xiph-episode-II/bounce/gtk-bounce-panel.c
===================================================================
--- trunk/Xiph-episode-II/bounce/gtk-bounce-panel.c	                        (rev 0)
+++ trunk/Xiph-episode-II/bounce/gtk-bounce-panel.c	2013-02-24 22:04:10 UTC (rev 18812)
@@ -0,0 +1,935 @@
+#include "gtk-bounce.h"
+#include "gtk-bounce-widget.h"
+#include <pthread.h>
+
+#define PANEL_WIDTH 1024
+#define PANEL_HEIGHT 90
+
+/* parameters that set the uncondiitonal behavior of the io thread */
+sig_atomic_t exiting=0;
+
+sig_atomic_t request_bits=16;
+//sig_atomic_t request_ch=2;
+sig_atomic_t request_rate=44100;
+
+sig_atomic_t request_ch1_quant=0;
+sig_atomic_t request_ch2_quant=0;
+sig_atomic_t prime_1kHz_notch=0;
+sig_atomic_t request_1kHz_notch=0;
+sig_atomic_t request_1kHz_sine=0;
+sig_atomic_t request_1kHz_sine2=0;
+sig_atomic_t request_5kHz_sine=0;
+sig_atomic_t request_1kHz_amplitude=0;
+sig_atomic_t request_1kHz_modulate=0;
+sig_atomic_t request_dither=0;
+sig_atomic_t request_dither_shaped=0;
+sig_atomic_t request_dither_amplitude=0;
+
+sig_atomic_t request_lp_filter=0;
+sig_atomic_t request_1kHz_square=0;
+sig_atomic_t request_1kHz_square_offset=0;
+sig_atomic_t request_1kHz_square_spread=0;
+
+sig_atomic_t request_output_silence=0;
+sig_atomic_t request_output_noise=0;
+sig_atomic_t request_output_sweep=0;
+sig_atomic_t request_output_logsweep=0;
+sig_atomic_t request_output_tone=0;
+sig_atomic_t request_output_duallisten=0;
+
+/* these are panel settings remembered between panels but not
+   necessarily active on a given panel. They're used to update the io
+   settings when switching between panels */
+
+int current_panel=0;
+
+int panel_bits;
+int panel_ch1_quant;
+int panel_ch2_quant;
+int panel_1kHz_notch;
+int panel_1kHz_sine;
+int panel_5kHz_sine;
+int panel_1kHz_amplitude;
+int panel_dither;
+int panel_dither_shaped;
+int panel_dither_amplitude;
+
+int panel_lp_filter;
+int panel_1kHz_square;
+int panel_1kHz_square_offset;
+int panel_1kHz_square_spread;
+
+
+pthread_t io_thread_id;
+int eventpipe[2];
+
+/* panel 0 */
+rowwidget *panel0_readout_hw;
+rowwidget *panel0_button_16bit;
+rowwidget *panel0_button_24bit;
+rowwidget *panel0_button_44;
+rowwidget *panel0_button_48;
+rowwidget *panel0_button_96;
+rowwidget *panel0_button_192;
+
+/* panel 1 */
+rowwidget *panel1_button_c1_8;
+rowwidget *panel1_button_c1_16;
+rowwidget *panel1_button_c2_8;
+rowwidget *panel1_button_c2_16;
+
+/* panel 2 */
+rowslider *panel2_slider_bits;
+rowwidget *panel2_button_notch;
+
+/* panel 3 */
+rowwidget *panel3_button_1k;
+rowwidget *panel3_button_8bit;
+rowwidget *panel3_button_16bit;
+rowwidget *panel3_button_dither;
+
+/* panel 4 */
+rowslider *panel4_slider_amp;
+rowwidget *panel4_button_dither;
+
+/* panel 5 */
+rowwidget *panel5_button_dither;
+rowwidget *panel5_button_flat;
+rowwidget *panel5_button_shaped;
+rowwidget *panel5_button_notch;
+
+/* panel 6 */
+rowslider *panel6_slider_amp;
+rowwidget *panel6_button_notch;
+rowwidget *panel6_button_modulate;
+rowwidget *panel6_button_flat;
+rowwidget *panel6_button_shaped;
+
+/* panel 7 */
+
+/* panel 8 */
+rowwidget *panel8_button_square;
+rowslider *panel8_slider_offset;
+
+/* panel 9 */
+rowslider *panel9_slider_spread;
+
+/* panel 10 */
+rowwidget *panel10_readout_hw;
+
+rowwidget *panel10_button_silence;
+rowwidget *panel10_button_noise;
+rowwidget *panel10_button_sweep;
+rowwidget *panel10_button_logsweep;
+rowwidget *panel10_button_tone1;
+rowwidget *panel10_button_tone5;
+rowwidget *panel10_button_duallisten;
+
+static int ready=0;
+void panelchange(rowpanel *p){
+  if(!ready)return;
+  current_panel = p->current_panel;
+
+  if(current_panel == 10){
+    /* we use 24 bit for the response testing */
+    request_bits=24;
+  }else{
+    request_bits=panel_bits;
+  }
+
+  switch(current_panel){
+  case 1:
+    panel_ch1_quant=16;
+    panel_ch2_quant=16;
+    /* fall through */
+  case 2:
+  case 3:
+    if(current_panel!=2){
+      if(panel_ch1_quant<16)
+        panel_ch1_quant=8;
+      if(panel_ch2_quant<16)
+        panel_ch2_quant=8;
+    }
+
+    request_ch1_quant=panel_ch1_quant;
+    request_ch2_quant=panel_ch2_quant;
+
+    rowtoggle_set_active(panel1_button_c1_8,panel_ch1_quant==8);
+    rowtoggle_set_active(panel1_button_c2_8,panel_ch2_quant==8);
+    rowtoggle_set_active(panel1_button_c1_16,panel_ch1_quant==16);
+    rowtoggle_set_active(panel1_button_c2_16,panel_ch2_quant==16);
+
+    rowslider_set(panel2_slider_bits,panel_ch2_quant);
+
+    rowtoggle_set_active(panel3_button_8bit,panel_ch2_quant==8);
+    rowtoggle_set_active(panel3_button_16bit,panel_ch2_quant==16);
+
+    break;
+  case 4:
+  case 5:
+  case 6:
+    request_ch1_quant=16;
+    request_ch2_quant=16;
+    break;
+  default:
+    request_ch1_quant=0;
+    request_ch2_quant=0;
+    break;
+  }
+
+  if(current_panel==2 || current_panel==5 || current_panel==6){
+    request_1kHz_notch=panel_1kHz_notch=0;
+    prime_1kHz_notch=1;
+    rowtoggle_set_active(panel2_button_notch,panel_1kHz_notch);
+    rowtoggle_set_active(panel5_button_notch,panel_1kHz_notch);
+    rowtoggle_set_active(panel6_button_notch,panel_1kHz_notch);
+  }else{
+    request_1kHz_notch=0;
+    prime_1kHz_notch=0;
+  }
+
+  switch(current_panel){
+  case 3:
+    request_1kHz_sine = request_1kHz_sine2 = panel_1kHz_sine = 0;
+    rowtoggle_set_active(panel3_button_1k,panel_1kHz_sine);
+    break;
+  case 4:
+  case 5:
+  case 6:
+    request_1kHz_sine = 1;
+    request_1kHz_sine2 = 1;
+    break;
+  default:
+    request_1kHz_sine = 0;
+    request_1kHz_sine2 = 0;
+  }
+
+  if(current_panel==4){
+    request_1kHz_amplitude=panel_1kHz_amplitude;
+  }else{
+    request_1kHz_amplitude=65536*16.;
+  }
+
+  switch(current_panel){
+  case 1:
+  case 2:
+  case 5:
+  case 6:
+    //case 8:
+    //case 9:
+    panel_dither=request_dither=1;
+    rowtoggle_set_active(panel3_button_dither,panel_dither);
+    rowtoggle_set_active(panel4_button_dither,panel_dither);
+    rowtoggle_set_active(panel5_button_dither,panel_dither);
+
+    break;
+  case 3:
+  case 4:
+    request_dither=panel_dither;
+    rowtoggle_set_active(panel3_button_dither,panel_dither);
+    rowtoggle_set_active(panel4_button_dither,panel_dither);
+    rowtoggle_set_active(panel5_button_dither,panel_dither);
+
+    break;
+  default:
+    request_dither=0;
+    break;
+  }
+
+  //if(current_panel==5){
+  request_dither_shaped=panel_dither_shaped=0;
+  request_1kHz_modulate=0;
+  // }else{
+  //if(current_panel != 6)request_dither_shaped=0;
+  //}
+  rowtoggle_set_active(panel5_button_flat,!panel_dither_shaped);
+  rowtoggle_set_active(panel5_button_shaped,panel_dither_shaped);
+  rowtoggle_set_active(panel6_button_flat,!panel_dither_shaped);
+  rowtoggle_set_active(panel6_button_shaped,panel_dither_shaped);
+  rowtoggle_set_active(panel6_button_modulate,request_1kHz_modulate);
+
+  if(current_panel==6){
+    request_dither_amplitude=panel_dither_amplitude;
+  }else{
+    request_dither_amplitude=65536;
+  }
+
+  if(current_panel>=7 && current_panel<=9){
+    request_lp_filter=1;
+  }else{
+    request_lp_filter=0;
+  }
+
+  switch(current_panel){
+  case 8:
+    request_1kHz_square=panel_1kHz_square;
+    request_1kHz_square_offset=panel_1kHz_square_offset;
+    panel_1kHz_square_spread=request_1kHz_square_spread=0;
+    break;
+  case 9:
+    request_1kHz_square=1;
+    panel_1kHz_square_offset=request_1kHz_square_offset=0;
+    request_1kHz_square_spread=panel_1kHz_square_spread;
+    break;
+  default:
+    panel_1kHz_square=request_1kHz_square=0;
+    panel_1kHz_square_offset=request_1kHz_square_offset=0;
+    panel_1kHz_square_spread=request_1kHz_square_spread=0;
+    break;
+  }
+  rowtoggle_set_active(panel8_button_square,panel_1kHz_square);
+  rowslider_set(panel8_slider_offset,panel_1kHz_square_offset);
+  rowslider_set(panel9_slider_spread,panel_1kHz_square_spread);
+
+  if(current_panel==10){
+    request_output_duallisten=0;
+    request_output_silence=1;
+    rowtoggle_set_active(panel10_button_duallisten,0);
+    rowtoggle_set_active(panel10_button_silence,1);
+  }else{
+    request_output_silence=0;
+  }
+}
+
+/* action handlers for panel buttons */
+static int clicked_enter=0;
+void panel0_clicked_bits(rowwidget *t){
+  if(!clicked_enter){
+    clicked_enter=1;
+    if(rowtoggle_get_active(t)){
+
+      if(t==panel0_button_16bit)
+        panel_bits=request_bits=16;
+      else
+        rowtoggle_set_active(panel0_button_16bit,0);
+
+      if(t==panel0_button_24bit)
+        panel_bits=request_bits=24;
+      else
+        rowtoggle_set_active(panel0_button_24bit,0);
+
+    }else
+      rowtoggle_set_active(t,1);
+    clicked_enter=0;
+  }
+}
+
+void panel0_clicked_rate(rowwidget *t){
+  if(!clicked_enter){
+    clicked_enter=1;
+    if(rowtoggle_get_active(t)){
+
+      if(t==panel0_button_44)
+        request_rate=44100;
+      else
+        rowtoggle_set_active(panel0_button_44,0);
+
+      if(t==panel0_button_48)
+        request_rate=48000;
+      else
+        rowtoggle_set_active(panel0_button_48,0);
+
+      if(t==panel0_button_96)
+        request_rate=96000;
+      else
+        rowtoggle_set_active(panel0_button_96,0);
+
+      if(t==panel0_button_192)
+        request_rate=192000;
+      else
+        rowtoggle_set_active(panel0_button_192,0);
+    }else
+      rowtoggle_set_active(t,1);
+    clicked_enter=0;
+  }
+}
+
+void panel1_clicked_c1(rowwidget *t){
+  if(!clicked_enter){
+    clicked_enter=1;
+    if(rowtoggle_get_active(t)){
+
+      if(t==panel1_button_c1_8)
+        panel_ch1_quant=request_ch1_quant=8;
+      else
+        rowtoggle_set_active(panel1_button_c1_8,0);
+
+      if(t==panel1_button_c1_16)
+        panel_ch1_quant=request_ch1_quant=16;
+      else
+        rowtoggle_set_active(panel1_button_c1_16,0);
+    }else
+      rowtoggle_set_active(t,1);
+    clicked_enter=0;
+  }
+}
+
+void panel1_clicked_c2(rowwidget *t){
+  if(!clicked_enter){
+    clicked_enter=1;
+    if(rowtoggle_get_active(t)){
+
+      if(t==panel1_button_c2_8)
+        panel_ch2_quant=request_ch2_quant=8;
+      else
+        rowtoggle_set_active(panel1_button_c2_8,0);
+
+      if(t==panel1_button_c2_16)
+        panel_ch2_quant=request_ch2_quant=16;
+      else
+        rowtoggle_set_active(panel1_button_c2_16,0);
+    }else
+      rowtoggle_set_active(t,1);
+    clicked_enter=0;
+  }
+}
+
+void panel2_clicked_bits(rowslider *t){
+  panel_ch1_quant = request_ch1_quant = t->value;
+  panel_ch2_quant = request_ch2_quant = t->value;
+}
+
+void panel2_clicked_notch(rowwidget *t){
+  panel_1kHz_notch = request_1kHz_notch = rowtoggle_get_active(t);
+}
+
+void panel3_clicked_1kHz(rowwidget *t){
+  panel_1kHz_sine = request_1kHz_sine = request_1kHz_sine2 =
+    rowtoggle_get_active(t);
+}
+
+void panel3_clicked_dither(rowwidget *t){
+  panel_dither = request_dither = rowtoggle_get_active(t);
+}
+
+void panel3_clicked_bits(rowwidget *t){
+  if(!clicked_enter){
+    clicked_enter=1;
+    if(rowtoggle_get_active(t)){
+
+      if(t==panel3_button_8bit){
+        panel_ch1_quant=request_ch1_quant=8;
+        panel_ch2_quant=request_ch2_quant=8;
+      }else
+        rowtoggle_set_active(panel3_button_8bit,0);
+
+      if(t==panel3_button_16bit){
+        panel_ch1_quant=request_ch1_quant=16;
+        panel_ch2_quant=request_ch2_quant=16;
+      }else
+        rowtoggle_set_active(panel3_button_16bit,0);
+    }else
+      rowtoggle_set_active(t,1);
+    clicked_enter=0;
+  }
+}
+
+void panel4_clicked_amp(rowslider *t){
+  panel_1kHz_amplitude = request_1kHz_amplitude = rint(t->value*65536);
+}
+
+void panel4_clicked_dither(rowwidget *t){
+  panel_dither = request_dither = rowtoggle_get_active(t);
+}
+
+void panel5_clicked_dither(rowwidget *t){
+  panel_dither = request_dither = rowtoggle_get_active(t);
+}
+
+void panel5_clicked_notch(rowwidget *t){
+  panel_1kHz_notch = request_1kHz_notch = rowtoggle_get_active(t);
+}
+
+void panel5_clicked_shape(rowwidget *t){
+  if(!clicked_enter){
+    clicked_enter=1;
+    if(rowtoggle_get_active(t)){
+
+      if(t==panel5_button_flat){
+        panel_dither_shaped=request_dither_shaped=0;
+      }else
+        rowtoggle_set_active(panel5_button_flat,0);
+
+      if(t==panel5_button_shaped){
+        panel_dither_shaped=request_dither_shaped=1;
+      }else
+        rowtoggle_set_active(panel5_button_shaped,0);
+    }else
+      rowtoggle_set_active(t,1);
+    clicked_enter=0;
+  }
+}
+
+void panel6_clicked_notch(rowwidget *t){
+  panel_1kHz_notch = request_1kHz_notch = rowtoggle_get_active(t);
+}
+
+void panel6_clicked_modulate(rowwidget *t){
+  request_1kHz_modulate = rowtoggle_get_active(t);
+}
+
+void panel6_clicked_dithamp(rowslider *t){
+  panel_dither_amplitude = request_dither_amplitude = rint(t->value*65536);
+}
+
+void panel6_clicked_shape(rowwidget *t){
+  if(!clicked_enter){
+    clicked_enter=1;
+    if(rowtoggle_get_active(t)){
+
+      if(t==panel6_button_flat){
+        panel_dither_shaped=request_dither_shaped=0;
+      }else
+        rowtoggle_set_active(panel6_button_flat,0);
+
+      if(t==panel6_button_shaped){
+        panel_dither_shaped=request_dither_shaped=1;
+      }else
+        rowtoggle_set_active(panel6_button_shaped,0);
+    }else
+      rowtoggle_set_active(t,1);
+    clicked_enter=0;
+  }
+}
+
+void panel8_clicked_square(rowwidget *t){
+  panel_1kHz_square = request_1kHz_square = rowtoggle_get_active(t);
+}
+
+void panel8_clicked_offset(rowslider *t){
+  panel_1kHz_square_offset = request_1kHz_square_offset = rint(t->value*65536.);
+}
+
+void panel9_clicked_spread(rowslider *t){
+  panel_1kHz_square_spread = request_1kHz_square_spread = rint(t->value*65536.);
+}
+
+void panel10_clicked_output(rowwidget *t){
+  if(!clicked_enter){
+    clicked_enter=1;
+    if(rowtoggle_get_active(t)){
+
+      if(t==panel10_button_silence){
+        request_output_silence=1;
+      }else{
+        rowtoggle_set_active(panel10_button_silence,0);
+        request_output_silence=0;
+      }
+
+      if(t==panel10_button_tone1){
+        request_1kHz_sine=1;
+      }else{
+        rowtoggle_set_active(panel10_button_tone1,0);
+        request_1kHz_sine=0;
+      }
+
+      if(t==panel10_button_tone5){
+        request_5kHz_sine=1;
+      }else{
+        rowtoggle_set_active(panel10_button_tone5,0);
+        request_5kHz_sine=0;
+      }
+
+      if(t!=panel10_button_tone1 &&
+         t!=panel10_button_tone5)
+        request_output_tone=0;
+      else
+        request_output_tone=1;
+
+      if(t==panel10_button_noise){
+        request_output_noise=1;
+      }else{
+        rowtoggle_set_active(panel10_button_noise,0);
+        request_output_noise=0;
+      }
+
+      if(t==panel10_button_sweep){
+        request_output_sweep=1;
+      }else{
+        rowtoggle_set_active(panel10_button_sweep,0);
+        request_output_sweep=0;
+      }
+
+      if(t==panel10_button_logsweep){
+        request_output_logsweep=1;
+      }else{
+        rowtoggle_set_active(panel10_button_logsweep,0);
+        request_output_logsweep=0;
+      }
+    }else
+      rowtoggle_set_active(t,1);
+    clicked_enter=0;
+  }
+}
+
+void panel10_clicked_duallisten(rowwidget *t){
+  request_output_duallisten = rowtoggle_get_active(t);
+}
+
+/* panel communication */
+
+static void blocking_read(int fd,char *buffer, int len){
+  while(len){
+    int bytes = read(fd,buffer,len);
+    if(bytes>0){
+      len-=bytes;
+      buffer+=bytes;
+    }
+  }
+}
+
+static gboolean async_event_handle(GIOChannel *channel,
+                                   GIOCondition condition,
+                                   gpointer data){
+  unsigned char buf[1];
+
+  while(read(eventpipe[0],buf,1)>0){
+    switch((int)buf[0]){
+    case 0: /* exit */
+      gtk_main_quit();
+      break;
+    case 1: /* hw1 opened */
+      {
+        char name[255];
+        char len;
+        blocking_read(eventpipe[0],&len,1);
+        blocking_read(eventpipe[0],name,(int)len);
+
+        rowreadout_light(panel0_readout_hw,1);
+        rowwidget_add_label(panel0_readout_hw,name,1);
+        rowreadout_light(panel10_readout_hw,1);
+        rowwidget_add_label(panel10_readout_hw,name,1);
+      }
+      break;
+    case 2: /* hw1 closed */
+      rowreadout_light(panel0_readout_hw,0);
+      rowwidget_add_label(panel0_readout_hw,NULL,1);
+      rowreadout_light(panel10_readout_hw,0);
+      rowwidget_add_label(panel10_readout_hw,NULL,1);
+      break;
+    case 5: /* update shaped dither setting */
+      panel_dither_shaped = request_dither_shaped;
+      rowtoggle_set_active(panel5_button_shaped,panel_dither_shaped);
+      break;
+    case 6: /* done with an output burst; back to silence */
+      rowtoggle_set_active(panel10_button_silence,1);
+    }
+  }
+  return TRUE;
+}
+
+/* toplevel panel itself */
+
+static void make_panel(void){
+  rowpanel *p=rowpanel_new(PANEL_WIDTH,PANEL_HEIGHT,panelchange);
+  GtkBox *b;
+
+  /* panel 0 */
+  b = rowpanel_new_row(p,0);
+  panel0_readout_hw = rowreadout_new
+    (b, "<span foreground=\"#c0c0c0\">hw:1</span>");
+
+  rowspacer_new(b,10);
+
+  panel0_button_16bit = rowtoggle_new(b,"16 bit",panel0_clicked_bits);
+  panel0_button_24bit = rowtoggle_new(b,"24 bit",panel0_clicked_bits);
+
+  rowspacer_new(b,10);
+
+  panel0_button_44 = rowtoggle_new(b,"44.1kHz",panel0_clicked_rate);
+  panel0_button_48 = rowtoggle_new(b,"48kHz",panel0_clicked_rate);
+  panel0_button_96 = rowtoggle_new(b,"96kHz",panel0_clicked_rate);
+  panel0_button_192 = rowtoggle_new(b,"192kHz",panel0_clicked_rate);
+
+  rowtoggle_set_active(panel0_button_16bit,1);
+  rowtoggle_set_active(panel0_button_44,1);
+
+  /* panel 1 */
+  b = rowpanel_new_row(p,0);
+  rowlabel_new(b, "channel 1:");
+  panel1_button_c1_8 = rowtoggle_new(b,"8 bit",panel1_clicked_c1);
+  panel1_button_c1_16 = rowtoggle_new(b,"16 bit",panel1_clicked_c1);
+
+  rowspacer_new(b,10);
+  rowspacer_new(b,10);
+
+  rowlabel_new(b, "channel 2:");
+  panel1_button_c2_8 = rowtoggle_new(b,"8 bit",panel1_clicked_c2);
+  panel1_button_c2_16 = rowtoggle_new(b,"16 bit",panel1_clicked_c2);
+
+  rowtoggle_set_active(panel1_button_c1_16,1);
+  rowtoggle_set_active(panel1_button_c2_16,1);
+
+  /* panel 2 */
+  b = rowpanel_new_row(p,1);
+  rowspacer_new(b,10);
+  rowslider *t = panel2_slider_bits =
+    rowslider_new(b,"bits",panel2_clicked_bits);
+  rowslider_add_stop(t,"8",8,1);
+  rowslider_add_stop(t,"9",9,1);
+  rowslider_add_stop(t,"10",10,1);
+  rowslider_add_stop(t,"11",11,1);
+  rowslider_add_stop(t,"12",12,1);
+  rowslider_add_stop(t,"13",13,1);
+  rowslider_add_stop(t,"14",14,1);
+  rowslider_add_stop(t,"15",15,1);
+  rowslider_add_stop(t,"16",16,1);
+  rowspacer_new(b,20);
+  panel2_button_notch = rowtoggle_new(b,"notch and",panel2_clicked_notch);
+  rowwidget_add_label(panel2_button_notch,"gain",1);
+  rowspacer_new(b,10);
+
+  /* panel 3 */
+  b = rowpanel_new_row(p,0);
+  panel3_button_1k = rowtoggle_new(b,"generate",panel3_clicked_1kHz);
+  rowwidget_add_label(panel3_button_1k,"sine wave",1);
+  rowspacer_new(b,40);
+  panel3_button_8bit = rowtoggle_new(b,"8 bit",panel3_clicked_bits);
+  panel3_button_16bit = rowtoggle_new(b,"16 bit",panel3_clicked_bits);
+  rowspacer_new(b,40);
+  panel3_button_dither = rowtoggle_new(b,"dither",panel3_clicked_dither);
+
+  rowtoggle_set_active(panel3_button_1k,1);
+  rowtoggle_set_active(panel3_button_8bit,1);
+  rowtoggle_set_active(panel3_button_dither,1);
+
+  /* panel 4 */
+  b = rowpanel_new_row(p,1);
+  rowspacer_new(b,20);
+  t = panel4_slider_amp =
+    rowslider_new(b,"amplitude (bits)",panel4_clicked_amp);
+  rowslider_add_stop(t,"1/4",-1,0);
+  rowslider_add_stop(t,"1/2",0,0);
+  rowslider_add_stop(t,"1",1,0);
+  rowslider_add_stop(t,"2",2,0);
+  rowslider_add_stop(t,"4",4,0);
+  rowslider_add_stop(t,"8",8,0);
+  rowslider_add_stop(t,"16",16,0);
+  rowspacer_new(b,20);
+
+  panel4_button_dither = rowtoggle_new(b,"dither",panel4_clicked_dither);
+  rowspacer_new(b,10);
+  rowslider_set(t,16);
+
+  /* panel 5 */
+  b = rowpanel_new_row(p,0);
+
+  panel5_button_notch = rowtoggle_new(b,"notch and",panel5_clicked_notch);
+  rowwidget_add_label(panel5_button_notch,"gain",1);
+
+  rowspacer_new(b,40);
+
+  panel5_button_flat = rowtoggle_new(b,"flat",panel5_clicked_shape);
+  panel5_button_shaped = rowtoggle_new(b,"shaped",panel5_clicked_shape);
+  rowtoggle_set_active(panel5_button_flat,1);
+
+  rowspacer_new(b,40);
+
+  panel5_button_dither = rowtoggle_new(b,"dither",panel5_clicked_dither);
+
+  /* panel 6 */
+  b = rowpanel_new_row(p,1);
+
+  rowspacer_new(b,0);
+  panel6_button_notch = rowtoggle_new(b,"notch and",panel6_clicked_notch);
+  rowwidget_add_label(panel6_button_notch,"gain",1);
+  panel6_button_modulate = rowtoggle_new(b,"modulate",panel6_clicked_modulate);
+  rowwidget_add_label(panel6_button_modulate,"input",1);
+
+  rowspacer_new(b,10);
+  t = panel6_slider_amp = rowslider_new(b,"dither  ",panel6_clicked_dithamp);
+  rowslider_add_stop(t,"none",0,0);
+  rowslider_add_stop(t,"",.1,0);
+  rowslider_add_stop(t,"",.2,0);
+  rowslider_add_stop(t,"",.3,0);
+  rowslider_add_stop(t,"",.4,0);
+  rowslider_add_stop(t,"",.5,0);
+  rowslider_add_stop(t,"",.6,0);
+  rowslider_add_stop(t,"",.7,0);
+  rowslider_add_stop(t,"",.8,0);
+  rowslider_add_stop(t,"",.9,0);
+  rowslider_add_stop(t,"full",1,0);
+
+  rowspacer_new(b,10);
+
+  panel6_button_flat = rowtoggle_new(b,"flat",panel6_clicked_shape);
+  panel6_button_shaped = rowtoggle_new(b,"shaped",panel6_clicked_shape);
+
+  rowspacer_new(b,0);
+  rowslider_set(t,1.0);
+
+  /* panel 7 */
+  b = rowpanel_new_row(p,0);
+  rowlabel_new(b, "filter: "
+               "<span color=\"#a0c0ff\">"
+               "cutoff=</span>"
+               "20.5kHz "
+               "<span color=\"#a0c0ff\">"
+               "rolloff="
+               "</span>"
+               "-100dB @ 21kHz");
+
+  /* panel 8 */
+  b = rowpanel_new_row(p,1);
+
+  rowspacer_new(b,25);
+
+  panel8_button_square = rowtoggle_new(b,"generate",panel8_clicked_square);
+  rowwidget_add_label(panel8_button_square,"square wave",1);
+
+  rowspacer_new(b,30);
+
+  t = panel8_slider_offset =
+    rowslider_new(b,"sample offset",panel8_clicked_offset);
+  rowslider_add_stop(t,"-1.0",-1,0);
+  rowslider_add_stop(t,"",-.8,0);
+  rowslider_add_stop(t,"",-.6,0);
+  rowslider_add_stop(t,"",-.4,0);
+  rowslider_add_stop(t,"",-.2,0);
+  rowslider_add_stop(t,"0",0,0);
+  rowslider_add_stop(t,"",.2,0);
+  rowslider_add_stop(t,"",.4,0);
+  rowslider_add_stop(t,"",.6,0);
+  rowslider_add_stop(t,"",.8,0);
+  rowslider_add_stop(t,"1.0",1,0);
+  rowspacer_new(b,20);
+  rowslider_set(t,0);
+
+  /* panel 9 */
+  b = rowpanel_new_row(p,1);
+  rowspacer_new(b,60);
+  t = panel9_slider_spread =
+    rowslider_new(b,"sample spread",panel9_clicked_spread);
+  rowslider_add_stop(t,"-1.0",-1,0);
+  rowslider_add_stop(t,"",-.8,0);
+  rowslider_add_stop(t,"",-.6,0);
+  rowslider_add_stop(t,"",-.4,0);
+  rowslider_add_stop(t,"",-.2,0);
+  rowslider_add_stop(t,"0",0,0);
+  rowslider_add_stop(t,"",.2,0);
+  rowslider_add_stop(t,"",.4,0);
+  rowslider_add_stop(t,"",.6,0);
+  rowslider_add_stop(t,"",.8,0);
+  rowslider_add_stop(t,"1.0",1,0);
+  rowspacer_new(b,50);
+  rowslider_set(t,0);
+
+  /* panel 10 */
+  b = rowpanel_new_row(p,0);
+  panel10_readout_hw = rowreadout_new
+    (b, "<span foreground=\"#c0c0c0\">hw:1</span>");
+
+  rowspacer_new(b,20);
+
+  panel10_button_silence =
+    rowtoggle_new(b,"silence",panel10_clicked_output);
+  panel10_button_tone1 =
+    rowtoggle_new(b,"1kHz tone",panel10_clicked_output);
+  panel10_button_tone5 =
+    rowtoggle_new(b,"5kHz tone",panel10_clicked_output);
+  panel10_button_noise =
+    rowtoggle_new(b,"white noise",panel10_clicked_output);
+  panel10_button_sweep =
+    rowtoggle_new(b,"linear sweep",panel10_clicked_output);
+  panel10_button_logsweep =
+    rowtoggle_new(b,"log sweep",panel10_clicked_output);
+
+  rowspacer_new(b,20);
+
+  panel10_button_duallisten =
+    rowtoggle_new(b,"two-input",panel10_clicked_duallisten);
+  rowwidget_add_label(panel10_button_duallisten,"mode",1);
+
+  rowtoggle_set_active(panel10_button_silence,1);
+  if(current_panel!=10)request_output_silence=0;
+  ready=1;
+  panelchange(p); /* force setup to be consistent with current panel */
+}
+
+int main(int argc, char **argv){
+  gtk_init (&argc, &argv);
+
+  gtk_rc_parse_string
+    ("style \"panel\" {"
+     "  fg[NORMAL]=\"#ffffff\""
+     "}"
+     "style \"topframe\" {"
+     "  font_name = \"sans 10 bold\""
+     "  fg[NORMAL]=\"#cccccc\""
+     "}"
+     "style \"rowlabel\" {"
+     "  font_name = \"sans 13 \""
+     "  fg[NORMAL]=\"#cccccc\""
+     "}"
+     "class \"*\" style \"panel\""
+     "widget \"*.topframe.GtkLabel\" style \"topframe\""
+     "widget \"*.topframe*.rowlabel*\" style \"rowlabel\""
+     );
+
+  /* easiest way to inform gtk of changes and not deal with locking
+     issues around the UI */
+  if(pipe(eventpipe)){
+    fprintf(stderr,"Unable to open event pipe:\n"
+            "  %s\n",strerror(errno));
+    return 1;
+  }
+
+  /* Allows event compression on the read side */
+  if(fcntl(eventpipe[0], F_SETFL, O_NONBLOCK)){
+    fprintf(stderr,"Unable to set O_NONBLOCK on event pipe:\n"
+            "  %s\n",strerror(errno));
+    return 1;
+  }
+
+  /* Tell glib to watch the notificaiton pipe in gtk_main() */
+  GIOChannel *channel = g_io_channel_unix_new (eventpipe[0]);
+  g_io_channel_set_encoding (channel, NULL, NULL);
+  g_io_channel_set_buffered (channel, FALSE);
+  g_io_channel_set_close_on_unref (channel, TRUE);
+  g_io_add_watch (channel, G_IO_IN, async_event_handle, NULL);
+  g_io_channel_unref (channel);
+
+  /* go */
+  make_panel();
+
+  struct sched_param sched;
+  int policy,s;
+  sched.sched_priority = 99;
+  pthread_create(&io_thread_id,NULL,&io_thread,NULL);
+
+  if(pthread_setschedparam(io_thread_id, SCHED_FIFO, &sched)){
+    fprintf(stderr,"Unable to set realtime scheduling on io thread\n");
+  }
+
+  if(pthread_getschedparam(io_thread_id, &policy, &sched)){
+    fprintf(stderr,"Unable to check realtime scheduling on io thread\n");
+  }
+
+  printf("   io thread policy=%s, priority=%d\n",
+         (policy == SCHED_FIFO)  ? "SCHED_FIFO" :
+         (policy == SCHED_RR)    ? "SCHED_RR" :
+         (policy == SCHED_OTHER) ? "SCHED_OTHER" :
+         "???",
+         sched.sched_priority);
+
+
+  sched.sched_priority = 90;
+  if(pthread_setschedparam(pthread_self(), SCHED_FIFO, &sched)){
+    fprintf(stderr,"Unable to set realtime scheduling on main thread\n");
+  }
+
+  if(pthread_getschedparam(pthread_self(), &policy, &sched)){
+    fprintf(stderr,"Unable to check realtime scheduling on main thread\n");
+  }
+
+  printf("   main thread policy=%s, priority=%d\n",
+         (policy == SCHED_FIFO)  ? "SCHED_FIFO" :
+         (policy == SCHED_RR)    ? "SCHED_RR" :
+         (policy == SCHED_OTHER) ? "SCHED_OTHER" :
+         "???",
+         sched.sched_priority);
+
+  gtk_main();
+
+  return 0;
+}

Added: trunk/Xiph-episode-II/bounce/gtk-bounce-wavheader.c
===================================================================
--- trunk/Xiph-episode-II/bounce/gtk-bounce-wavheader.c	                        (rev 0)
+++ trunk/Xiph-episode-II/bounce/gtk-bounce-wavheader.c	2013-02-24 22:04:10 UTC (rev 18812)
@@ -0,0 +1,113 @@
+#include <stdio.h>
+#include <string.h>
+
+/* simple WAVE header writer */
+
+#define WAVE_FORMAT_PCM         0x0001
+#define WAVE_FORMAT_EXTENSIBLE  0xfffe
+#define WAV_HEADER_LEN 68
+
+#define WRITE_U32(buf, x) *(buf)     = (unsigned char)(x&0xff);\
+  *((buf)+1) = (unsigned char)((x>>8)&0xff);                            \
+  *((buf)+2) = (unsigned char)((x>>16)&0xff);                           \
+  *((buf)+3) = (unsigned char)((x>>24)&0xff);
+
+#define WRITE_U16(buf, x) *(buf)     = (unsigned char)(x&0xff);\
+  *((buf)+1) = (unsigned char)((x>>8)&0xff);
+
+struct riff_struct {
+  char id[4];   /* RIFF */
+  unsigned int len;
+  char wave_id[4]; /* WAVE */
+};
+
+struct chunk_struct {
+  char id[4];
+  unsigned int len;
+};
+
+struct common_struct
+{
+  unsigned short wFormatTag;
+  unsigned short wChannels;
+  unsigned int dwSamplesPerSec;
+  unsigned int dwAvgBytesPerSec;
+  unsigned short wBlockAlign;
+  unsigned short wBitsPerSample;
+  unsigned short cbSize;
+  unsigned short wValidBitsPerSample;
+  unsigned int   dwChannelMask;
+  unsigned short subFormat;
+};
+
+struct wave_header
+{
+  struct riff_struct   riff;
+  struct chunk_struct  format;
+  struct common_struct common;
+  struct chunk_struct  data;
+};
+
+int header_out(FILE *out, int rate, int bits, int channels){
+  struct wave_header wave;
+  char buf[WAV_HEADER_LEN];
+
+  /* Store information */
+  wave.common.wChannels = channels;
+  wave.common.wBitsPerSample = ((bits+7)>>3)<<3;
+  wave.common.wValidBitsPerSample = bits;
+  wave.common.dwSamplesPerSec = rate;
+
+  memset(buf, 0, WAV_HEADER_LEN);
+
+  /* Fill out our wav-header with some information. */
+  strncpy(wave.riff.id, "RIFF",4);
+  wave.riff.len = 0xffffffff; //size - 8; Use a bogus size for streaming */
+  strncpy(wave.riff.wave_id, "WAVE",4);
+  strncpy(wave.format.id, "fmt ",4);
+  wave.format.len = 40;
+
+  wave.common.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
+  wave.common.dwAvgBytesPerSec =
+    wave.common.wChannels *
+    wave.common.dwSamplesPerSec *
+    (wave.common.wBitsPerSample >> 3);
+
+  wave.common.wBlockAlign =
+    wave.common.wChannels *
+    (wave.common.wBitsPerSample >> 3);
+  wave.common.cbSize = 22;
+  wave.common.subFormat = WAVE_FORMAT_PCM;
+  wave.common.dwChannelMask = 0;
+
+  strncpy(wave.data.id, "data",4);
+
+  wave.data.len = 0xffffffff; // size - WAV_HEADER_LEN; Use a bogus size for streaming */
+
+  strncpy(buf, wave.riff.id, 4);
+  WRITE_U32(buf+4, wave.riff.len);
+  strncpy(buf+8, wave.riff.wave_id, 4);
+  strncpy(buf+12, wave.format.id,4);
+  WRITE_U32(buf+16, wave.format.len);
+  WRITE_U16(buf+20, wave.common.wFormatTag);
+  WRITE_U16(buf+22, wave.common.wChannels);
+  WRITE_U32(buf+24, wave.common.dwSamplesPerSec);
+  WRITE_U32(buf+28, wave.common.dwAvgBytesPerSec);
+  WRITE_U16(buf+32, wave.common.wBlockAlign);
+  WRITE_U16(buf+34, wave.common.wBitsPerSample);
+  WRITE_U16(buf+36, wave.common.cbSize);
+  WRITE_U16(buf+38, wave.common.wValidBitsPerSample);
+  WRITE_U32(buf+40, wave.common.dwChannelMask);
+  WRITE_U16(buf+44, wave.common.subFormat);
+  memcpy(buf+46,"\x00\x00\x00\x00\x10\x00\x80\x00\x00\xAA\x00\x38\x9B\x71",14);
+  strncpy(buf+60, wave.data.id, 4);
+  WRITE_U32(buf+64, wave.data.len);
+
+  if (fwrite(buf, sizeof(char), WAV_HEADER_LEN, out)
+      != WAV_HEADER_LEN) {
+    return 0; /* Could not write wav header */
+  }
+
+  return 1;
+}
+

Added: trunk/Xiph-episode-II/bounce/gtk-bounce-widget.c
===================================================================
--- trunk/Xiph-episode-II/bounce/gtk-bounce-widget.c	                        (rev 0)
+++ trunk/Xiph-episode-II/bounce/gtk-bounce-widget.c	2013-02-24 22:04:10 UTC (rev 18812)
@@ -0,0 +1,983 @@
+#include "gtk-bounce.h"
+#include "gtk-bounce-widget.h"
+
+/* implement a peudowidget (rowwidget) for most entries in the panel */
+
+/* Because we're in a GtkFixed that's being scrolled around, but
+   drawing is being done to the GdkWindow owned by the toplevel, the
+   clip region is set incorrectly to allow us to overdraw things in
+   the toplevel outside our parents' bounds.  Follow the tree up and
+   reclip. */
+static void narrow_clip(GtkWidget *w,GdkRectangle *area){
+  int x1 = w->allocation.x;
+  int y1 = w->allocation.y;
+  int x2 = w->allocation.x+w->allocation.width;
+  int y2 = w->allocation.y+w->allocation.height;
+
+  if(x1>area->x)area->x=x1;
+  if(x2<area->width)area->width=x2;
+  if(y1>area->y)area->y=y1;
+  if(y2<area->height)area->height=y2;
+
+  if(w->parent && w->parent->window == w->window)
+    narrow_clip(w->parent,area);
+}
+
+static void clip_to_ancestors(GtkWidget *w, cairo_t *cr){
+  GdkRectangle a;
+  a.x = w->allocation.x;
+  a.width = a.x + w->allocation.width; /* overload */
+  a.y = w->allocation.y;
+  a.height = a.y + w->allocation.height; /* overload */
+  if(w->parent && w->parent->window == w->window)
+    narrow_clip(w->parent,&a);
+  cairo_rectangle(cr,a.x,a.y,a.width-a.x,a.height-a.y);
+  cairo_clip(cr);
+}
+
+static void expose_a_widget(gpointer a, gpointer b){
+  GtkWidget *widget = (GtkWidget *)a;
+  GdkEventExpose *event = (GdkEventExpose *)b;
+  GTK_WIDGET_CLASS(GTK_WIDGET_GET_CLASS(widget))->
+    expose_event (widget, event);
+}
+
+static gboolean expose_rectarea(GtkWidget *widget,
+                                GdkEventExpose *event,
+                                gpointer userdata){
+  if(!widget->window)return TRUE;
+  cairo_t *cr = gdk_cairo_create(widget->window);
+  rowwidget *t = (rowwidget *)userdata;
+  int w=widget->allocation.width;
+  int h=widget->allocation.height;
+  GtkStateType state = gtk_widget_get_state(widget);
+  int lit=t->lit;
+  /* rounded rectangle path */
+
+  double degrees = M_PI / 180.0;
+  double radius = t->radius;
+  double rounding = t->rounding;
+  double height = h-2;
+  double rxd = radius-cos(rounding*degrees)*radius;
+  double ryd = sin(rounding*degrees)*radius;
+  double R = (height/2-radius+ryd)/sin(rounding*degrees);
+  double RxD = (isinf(R)?0:R-cos(rounding*degrees)*R);
+
+  double x = widget->allocation.x+RxD-rxd+1;
+  double width = w - RxD*2 + rxd*2 - 2;
+  double y = widget->allocation.y+1;
+
+  double Rx1 = widget->allocation.x+R+1;
+  double Rx2 = widget->allocation.x+w-R-1;
+
+  if(state == GTK_STATE_INSENSITIVE){
+    y+=.5;
+    x+=.5;
+    height-=1;
+    width-=1;
+  }
+
+  clip_to_ancestors(widget,cr);
+
+  cairo_new_sub_path (cr);
+  cairo_arc (cr, x+width-radius, y+radius, radius, -90*degrees, -rounding*degrees);
+  if(!isinf(R))
+    cairo_arc (cr, Rx2, y+height/2, R, -rounding*degrees, rounding*degrees);
+  cairo_arc (cr, x+width-radius, y+height-radius, radius, rounding*degrees, 90*degrees);
+
+  cairo_arc (cr, x+radius, y+height-radius, radius, 90*degrees, (180-rounding)*degrees);
+  if(!isinf(R))
+    cairo_arc (cr, Rx1, y+height/2, R, (180-rounding)*degrees, (180+rounding)*degrees);
+  cairo_arc (cr, x+radius, y+radius, radius, (180+rounding)*degrees, 270*degrees);
+  cairo_close_path (cr);
+
+  /* fill background */
+  if(state != GTK_STATE_INSENSITIVE){
+    /* won't get here is not togglebutton */
+    if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)))
+      cairo_set_source_rgba (cr, .1, .3, .6, 1);
+    else
+      cairo_set_source_rgba (cr, .1, .1, .1, 1);
+  }else{
+    if(lit)
+      cairo_set_source_rgba (cr, .18, .5, 1, .3);
+    else
+      cairo_set_source_rgba (cr, 0, 0, 0, .2);
+  }
+  cairo_fill_preserve (cr);
+
+  /* gradient */
+  if(state != GTK_STATE_INSENSITIVE && !t->pressed){
+    cairo_pattern_t *pat = cairo_pattern_create_linear(x, y, x, y+h);
+    cairo_pattern_add_color_stop_rgba(pat, 1.0, 0, 0, 0, .2);
+    cairo_pattern_add_color_stop_rgba(pat, 0.8, 0, 0, 0, 0);
+    cairo_pattern_add_color_stop_rgba(pat, 0.3, 1, 1, 1, 0);
+    cairo_pattern_add_color_stop_rgba(pat, 0, 1, 1, 1, .15);
+    cairo_set_source(cr, pat);
+    cairo_fill_preserve (cr);
+    cairo_pattern_destroy(pat);
+  }
+
+  /* stroke border */
+  switch(state){
+  case GTK_STATE_ACTIVE:
+  case GTK_STATE_NORMAL:
+    cairo_set_source_rgba (cr, .8, .8, .8, 1);
+    cairo_set_line_width(cr,2.0);
+    break;
+  case GTK_STATE_PRELIGHT:
+  case GTK_STATE_SELECTED:
+    cairo_set_source_rgba (cr, 1, 1, 1, 1);
+    cairo_set_line_width(cr,2.0);
+    break;
+  case GTK_STATE_INSENSITIVE:
+    if(lit)
+        cairo_set_source_rgba (cr, .18, .5, 1, .9);
+      else
+        cairo_set_source_rgba (cr, 1, 1, 1, .5);
+    cairo_set_line_width(cr,1.0);
+    break;
+  }
+
+  cairo_stroke(cr);
+  cairo_destroy(cr);
+
+  return TRUE;
+}
+
+static void rowslider_update_size(rowslider *t){
+  int i,w,h;
+
+  pango_layout_get_pixel_size(t->caption,&w,&h);
+  t->capwidth = w;
+  t->labelheight = h*1.2;
+
+  for(i=0;i<t->label_n;i++){
+    pango_layout_get_pixel_size(t->labels[i],&w,&h);
+    if(w>t->labelwidth)t->labelwidth=w;
+  }
+
+  gtk_widget_set_size_request
+    (t->button,
+     t->capwidth + t->labelwidth*t->label_n*1.5,
+     t->labelheight*2.8);
+
+  while (gtk_events_pending())
+   gtk_main_iteration();
+
+  w = t->w = t->button->allocation.width;
+  h = t->h = t->button->allocation.height;
+
+  int nh = t->labelheight*2.5;
+  t->y0 = (h-nh)/2;
+  t->y1 = t->y0 + nh;
+
+  double capw = t->capwidth;
+  double cellw = t->labelwidth;
+
+  t->radiusS = (nh-t->labelheight)/4;
+  t->radiusL = (nh-t->labelheight)/2-1.5;
+  t->lpad = t->radiusL;
+
+  t->pastdot = (t->radiusL*2-cellw/2 > 0 ?
+                t->radiusL*2-cellw/2 : 0);
+
+  double padw = (w-capw-t->lpad-t->pastdot-1) / t->label_n - cellw;
+
+  t->pipw = cellw+padw;
+  t->pipx = t->lpad+capw+padw+(cellw/2);
+  t->pastdot += cellw/2;
+
+}
+
+static gboolean expose_slider(GtkWidget *widget,
+                                GdkEventExpose *event,
+                                gpointer userdata){
+  if(!widget->window)return TRUE;
+  cairo_t *cr = gdk_cairo_create(widget->window);
+  rowslider *t = (rowslider *)userdata;
+  int w = widget->allocation.width;
+  int h = widget->allocation.height;
+  int x = widget->allocation.x;
+  int y = widget->allocation.y;
+  GtkStateType state = gtk_widget_get_state(widget);
+
+  if(t->w!=w || t->h!=h)
+    rowslider_update_size(t);
+
+  double dif = (t->radiusL-t->radiusS);
+  double pipw = t->pipw;
+  double pipx = x+t->pipx;
+
+  /* rounded slider track */
+  double degrees = M_PI / 180.0;
+  double yy = y + t->y0 + t->labelheight+t->radiusL;
+  double sx = pipx + (t->label_n-1)*pipw;
+
+  clip_to_ancestors(widget,cr);
+
+  cairo_new_sub_path (cr);
+  cairo_arc (cr, pipx+(t->label_n-1)*t->pipw+t->pastdot-t->radiusL,
+               yy, t->radiusS, -90*degrees, 90*degrees);
+  cairo_arc (cr, x+dif+t->radiusS, yy, t->radiusS, 90*degrees, 270*degrees);
+  cairo_close_path (cr);
+
+  cairo_set_source_rgba (cr, .1, .1, .1, 1);
+  cairo_fill_preserve(cr);
+
+  cairo_set_source_rgba (cr, 1, 1, 1, .6);
+  cairo_set_line_width(cr,1.0);
+  cairo_stroke(cr);
+
+  /* pips */
+  int i;
+  cairo_set_source_rgba (cr, 1, 1, 1, .5);
+  for(i=0;i<t->label_n;i++){
+    sx = pipx + i*pipw;
+    cairo_new_sub_path (cr);
+    cairo_move_to(cr,rint(sx)+.5, yy-t->radiusL);
+    cairo_line_to(cr,rint(sx)+.5, yy-t->radiusS);
+    cairo_move_to(cr,rint(sx)+.5, yy+t->radiusL);
+    cairo_line_to(cr,rint(sx)+.5, yy+t->radiusS);
+  }
+  cairo_stroke(cr);
+
+  /* slider grip */
+  sx = pipx + t->delta*pipw;
+
+  cairo_new_sub_path (cr);
+  cairo_arc (cr, sx + t->pastdot - t->radiusL-1, yy,
+             t->radiusL, -90*degrees, 90*degrees);
+  cairo_arc (cr, x+t->radiusL+1, yy, t->radiusL, 90*degrees, 270*degrees);
+  cairo_close_path (cr);
+
+  cairo_set_source_rgba (cr, .1, .3, .6, 1);
+  cairo_fill_preserve(cr);
+
+  if(state!=GTK_STATE_ACTIVE){
+    cairo_pattern_t *pat =
+      cairo_pattern_create_linear(x, y+t->y0+t->labelheight, x, y+t->y1);
+    cairo_pattern_add_color_stop_rgba(pat, 1.0, 0, 0, 0, .3);
+    cairo_pattern_add_color_stop_rgba(pat, 0.6, 0, 0, 0, 0);
+    cairo_pattern_add_color_stop_rgba(pat, 0.4, 1, 1, 1, 0);
+    cairo_pattern_add_color_stop_rgba(pat, 0, 1, 1, 1, .2);
+    cairo_set_source(cr, pat);
+    cairo_fill_preserve (cr);
+    cairo_pattern_destroy(pat);
+  }
+
+  /* slider pips */
+  cairo_save(cr);
+  cairo_clip(cr);
+
+  cairo_set_source_rgba(cr,.9, .9, .9, .8);
+
+  for(i=0;i<t->label_n;i++){
+    double psx = pipx + i*pipw;
+    cairo_move_to(cr,rint(psx)+.5, yy-t->radiusL);
+    cairo_line_to(cr,rint(psx)+.5, yy-t->radiusL*.6);
+
+    cairo_move_to(cr,rint(psx)+.5, yy+t->radiusL);
+    cairo_line_to(cr,rint(psx)+.5, yy+t->radiusL*.6);
+
+  }
+  cairo_stroke(cr);
+  cairo_restore(cr);
+
+  cairo_new_sub_path (cr);
+  cairo_arc (cr, sx + t->pastdot - t->radiusL-1, yy,
+             t->radiusL, -90*degrees, 90*degrees);
+  cairo_arc (cr, x+t->radiusL+1, yy, t->radiusL, 90*degrees, 270*degrees);
+  cairo_close_path (cr);
+
+  switch(state){
+  default:
+  case GTK_STATE_ACTIVE:
+  case GTK_STATE_NORMAL:
+    cairo_set_source_rgba (cr, .8, .8, .8, 1);
+    cairo_set_line_width(cr,2.0);
+    break;
+  case GTK_STATE_PRELIGHT:
+  case GTK_STATE_SELECTED:
+    cairo_set_source_rgba (cr, 1, 1, 1, 1);
+    cairo_set_line_width(cr,2.0);
+    break;
+  }
+
+  cairo_stroke(cr);
+
+  /* slider dot */
+  cairo_new_sub_path (cr);
+  cairo_arc (cr, rint(sx)+.5, yy, 3, 0*degrees, 360*degrees);
+  cairo_fill(cr);
+
+
+  /* labels */
+  {
+    int w,d;
+
+    cairo_set_source_rgba (cr, 1, 1, 1, 1);
+    cairo_move_to(cr,x+t->lpad,y+t->y0);
+    pango_cairo_show_layout(cr,t->caption);
+
+    for(i=0;i<t->label_n;i++){
+      sx = pipx + i*pipw;
+      pango_layout_get_pixel_size(t->labels[i],&w,&d);
+      cairo_move_to(cr,sx-w/2,y+t->y0);
+      pango_cairo_show_layout(cr,t->labels[i]);
+    }
+  }
+
+  cairo_destroy(cr);
+  return TRUE;
+}
+
+static gboolean expose_rowwidget(GtkWidget *widget,
+                                 GdkEventExpose *event,
+                                 gpointer userdata){
+  rowwidget *t = (rowwidget *)userdata;
+
+  /* be sure of the expose order as the widgets overlap */
+  /* toggle button is not a class native expose, call the draw directly */
+  expose_rectarea(t->button,event,t);
+  /* expose labels */
+  expose_a_widget(t->vbox,event);
+
+  return TRUE;
+}
+
+/* modify toggle button behavior that assumes an activated button ==
+   pressed.  I don't want active toggles to be down, just lit */
+static gboolean press_rowtoggle(GtkWidget *widget,
+                                gpointer userdata){
+  rowwidget *t = (rowwidget *)userdata;
+  t->pressed=1;
+  return FALSE;
+}
+static gboolean release_rowtoggle(GtkWidget *widget,
+                                gpointer userdata){
+  rowwidget *t = (rowwidget *)userdata;
+  t->pressed=0;
+  return FALSE;
+}
+
+static gboolean toggle_rowtoggle(GtkWidget *widget,
+                                 gpointer userdata){
+  rowwidget *t = (rowwidget *)userdata;
+  if(t->callback)t->callback(t);
+  return FALSE;
+}
+
+void rowwidget_add_label(rowwidget *t, char *label, int row){
+  if(!t)return;
+  if(row<0 || row>4)return;
+  if(t->label[row] && label==NULL){
+    gtk_widget_destroy(t->label[row]);
+    t->label[row]=NULL;
+  }else if(label){
+    if(!t->label[row]){
+      t->label[row]=gtk_label_new(NULL);
+      gtk_box_pack_start(GTK_BOX(t->vbox),t->label[row],0,0,0);
+      gtk_widget_show(t->label[row]);
+    }
+    gtk_label_set_markup(GTK_LABEL(t->label[row]),label);
+  }
+}
+
+rowwidget *rowtoggle_new(GtkBox *box, char *text,
+                         void (*callback)(rowwidget *)){
+  rowwidget *ret = calloc(1,sizeof(*ret));
+  ret->rounding = 15;
+  ret->radius = 12;
+  ret->parent = box;
+  ret->table = gtk_table_new(1,1,1);
+  ret->button = gtk_toggle_button_new();
+  ret->alignment = gtk_alignment_new(.5,.5,0,0);
+  ret->vbox = gtk_vbox_new(0,2);
+  ret->callback = callback;
+
+  memset(ret->label,0,sizeof(ret->label));
+
+  gtk_box_pack_start(box,ret->table,0,0,0);
+  gtk_table_attach_defaults(GTK_TABLE(ret->table),ret->alignment,0,1,0,1);
+  gtk_container_add(GTK_CONTAINER(ret->alignment),ret->vbox);
+  gtk_table_attach_defaults(GTK_TABLE(ret->table),ret->button,0,1,0,1);
+
+  g_signal_connect(G_OBJECT(ret->button), "expose-event",
+                   G_CALLBACK(expose_rectarea), ret);
+  g_signal_connect(G_OBJECT(ret->button), "pressed",
+                   G_CALLBACK(press_rowtoggle), ret);
+  g_signal_connect(G_OBJECT(ret->button), "released",
+                   G_CALLBACK(release_rowtoggle), ret);
+  g_signal_connect(G_OBJECT(ret->button), "toggled",
+                   G_CALLBACK(toggle_rowtoggle), ret);
+  g_signal_connect(G_OBJECT(ret->table), "expose-event",
+                 G_CALLBACK(expose_rowwidget), ret);
+  gtk_widget_set_size_request(ret->button,
+                              GTK_WIDGET(box)->allocation.height*1.5,-1);
+
+  if(text)
+    rowwidget_add_label(ret, text, 0);
+
+  gtk_widget_show_all(ret->table);
+  return ret;
+}
+
+void rowtoggle_set_active(rowwidget *w, int state){
+  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w->button),state);
+}
+
+int rowtoggle_get_active(rowwidget *w){
+  return gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(w->button));
+}
+
+rowwidget *rowreadout_new(GtkBox *box, char *text){
+  rowwidget *ret = calloc(1,sizeof(*ret));
+  ret->rounding = 0;
+  ret->radius = 6;
+  ret->parent = box;
+  ret->table = gtk_table_new(1,1,1);
+  ret->button = gtk_toggle_button_new();
+  ret->alignment = gtk_alignment_new(.5,.5,0,0);
+  ret->vbox = gtk_vbox_new(0,2);
+
+  memset(ret->label,0,sizeof(ret->label));
+
+  gtk_box_pack_start(box,ret->table,0,0,0);
+  gtk_table_attach_defaults(GTK_TABLE(ret->table),ret->alignment,0,1,0,1);
+  gtk_container_add(GTK_CONTAINER(ret->alignment),ret->vbox);
+  gtk_table_attach_defaults(GTK_TABLE(ret->table),ret->button,0,1,0,1);
+  gtk_widget_set_sensitive(ret->button,FALSE);
+
+  g_signal_connect(G_OBJECT(ret->button), "expose-event",
+                   G_CALLBACK(expose_rectarea), ret);
+  g_signal_connect(G_OBJECT(ret->table), "expose-event",
+                 G_CALLBACK(expose_rowwidget), ret);
+  gtk_widget_set_size_request(ret->button,GTK_WIDGET(box)->allocation.height*1.8,-1);
+
+  if(text)
+    rowwidget_add_label(ret, text, 0);
+
+  gtk_widget_show_all(ret->table);
+  return ret;
+}
+
+void rowreadout_light(rowwidget *t,int state){
+  GdkRectangle rect;
+  t->lit=state;
+  if(t->button->window){
+    rect.x = t->button->allocation.x;
+    rect.y = t->button->allocation.y;
+    rect.width = t->button->allocation.width;
+    rect.height = t->button->allocation.height;
+    gdk_window_invalidate_rect(t->button->window,&rect,1);
+  }
+}
+
+rowwidget *rowspacer_new(GtkBox *box,int pad){
+  rowwidget *ret = calloc(1,sizeof(*ret));
+  ret->parent = box;
+  ret->alignment = gtk_alignment_new(.5,.5,0,0);
+
+  gtk_box_pack_start(box,ret->alignment,0,0,0);
+
+  gtk_widget_set_size_request(ret->alignment,pad,-1);
+
+  gtk_widget_show_all(ret->alignment);
+  return ret;
+}
+
+rowwidget *rowlabel_new(GtkBox *box,char *text){
+  rowwidget *ret = calloc(1,sizeof(*ret));
+  ret->vbox = gtk_vbox_new(1,2);
+  ret->parent = box;
+
+  memset(ret->label,0,sizeof(ret->label));
+  gtk_box_pack_start(box,ret->vbox,0,0,0);
+  gtk_widget_set_name(ret->vbox,"rowlabel");
+
+  if(text)
+    rowwidget_add_label(ret, text, 0);
+  gtk_widget_show_all(ret->vbox);
+  return ret;
+}
+
+static void slider_set(rowslider *t, int ex){
+
+  t->delta = (ex - t->pipx)/t->pipw;
+
+  if(t->delta<0)t->delta=0;
+  if(t->delta>t->label_n-1)t->delta=t->label_n-1;
+
+  int i=floor(t->delta);
+  if(t->stops[i]){
+    if(t->delta-i>.5){
+      t->value=t->values[i+1];
+      t->delta=i+1;
+    }else{
+      t->value=t->values[i];
+      t->delta=i;
+    }
+  }else{
+    if(i==t->label_n-1){
+      t->value=t->values[t->label_n-1];
+    }else{
+      t->value = (i-t->delta+1)*t->values[i] + (t->delta-i)*t->values[i+1];
+    }
+  }
+
+  if(t->button->window){
+    GdkRectangle rect;
+    rect.x = t->button->allocation.x;
+    rect.y = t->button->allocation.y;
+    rect.width = t->button->allocation.width;
+    rect.height = t->button->allocation.height;
+    gdk_window_invalidate_rect(t->button->window,&rect,1);
+  }
+
+  if(t->callback)t->callback(t);
+}
+
+static gboolean motion_slider(GtkWidget *widget,
+                              GdkEvent *event,
+                              gpointer userdata){
+  rowslider *t = (rowslider *)userdata;
+  GdkEventMotion *ev = (GdkEventMotion *)event;
+  GtkStateType state = gtk_widget_get_state(widget);
+
+  if(t->capwidth==0)
+    rowslider_update_size(t);
+
+  if(state==GTK_STATE_ACTIVE)
+    slider_set(t,ev->x);
+
+  t->x = ev->x;
+  return TRUE;
+}
+
+static gboolean press_slider(GtkWidget *widget,
+                             gpointer userdata){
+  rowslider *t = (rowslider *)userdata;
+  slider_set(t,t->x);
+
+  return TRUE;
+}
+
+static gboolean press_slider_logx(GtkWidget *widget,
+                                  GdkEvent *event,
+                                  gpointer userdata){
+  GdkEventButton *ev = (GdkEventButton *)event;
+  rowslider *t = (rowslider *)userdata;
+  t->x = ev->x;
+  return FALSE;
+}
+
+rowslider *rowslider_new(GtkBox *box,
+                         char *caption,
+                         void (*callback)(rowslider *)){
+  rowslider *ret = calloc(1,sizeof(*ret));
+  ret->parent = box;
+  ret->button = gtk_button_new();
+  gtk_widget_set_name(ret->button,"rowslider");
+
+  char buf[256];
+  snprintf(buf,256,"<span weight=\"bold\">%s</span>",caption);
+  ret->caption = gtk_widget_create_pango_layout(ret->button,NULL);
+  pango_layout_set_markup(ret->caption,buf,-1);
+
+  ret->callback = callback;
+  ret->labels = calloc(1,sizeof(*ret->labels));
+  ret->values = calloc(1,sizeof(*ret->values));
+  ret->stops = calloc(1,sizeof(*ret->stops));
+
+  gtk_box_pack_start(GTK_BOX(box),ret->button,1,1,0);
+
+  gtk_widget_add_events (ret->button,
+                         GDK_POINTER_MOTION_MASK);
+
+  g_signal_connect(G_OBJECT(ret->button), "expose-event",
+                   G_CALLBACK(expose_slider), ret);
+  g_signal_connect(G_OBJECT(ret->button), "motion_notify_event",
+                   G_CALLBACK(motion_slider), ret);
+  g_signal_connect(G_OBJECT(ret->button), "button-press-event",
+                   G_CALLBACK(press_slider_logx), ret);
+  g_signal_connect(G_OBJECT(ret->button), "pressed",
+                   G_CALLBACK(press_slider), ret);
+
+  gtk_widget_show_all(ret->button);
+  return ret;
+}
+
+void rowslider_add_stop(rowslider *t,
+                        char *ltext,
+                        float lvalue,
+                        int snap){
+  int n = t->label_n;
+
+  t->labels = realloc(t->labels,sizeof(*t->labels)*(n+1));
+  t->values = realloc(t->values,sizeof(*t->values)*(n+1));
+  t->stops = realloc(t->stops,sizeof(*t->stops)*(n+1));
+  t->labels[n]=gtk_widget_create_pango_layout(t->button,ltext);
+
+  t->values[n]=lvalue;
+  t->stops[n]=snap;
+  t->label_n++;
+
+  rowslider_update_size(t);
+}
+
+void rowslider_set(rowslider *t,float lvalue){
+  int i;
+  int flip=1.0;
+  if(t->values[0] > t->values[t->label_n-1])
+    flip=-1.0;
+
+  lvalue*=flip;
+  if(lvalue<=t->values[0]*flip){
+    t->value = t->values[0];
+    t->delta = 0;
+  } else if(lvalue>=t->values[t->label_n-1]*flip){
+    t->value = t->values[t->label_n-1];
+    t->delta = t->label_n-1;
+  }else{
+    for(i=0;i<t->label_n-1;i++){
+      if(lvalue>=t->values[i]*flip && lvalue<=t->values[i+1]*flip){
+        float delta = (lvalue-t->values[i]*flip) /
+          (t->values[i+1]-t->values[i])*flip;
+        if(t->stops[i]){
+          if(delta<.5){
+            t->value = t->values[i];
+            t->delta = i;
+          }else{
+            t->value = t->values[i+1];
+            t->delta = i+1;
+          }
+        }else{
+          t->value = lvalue*flip;
+          t->delta = i+delta;
+        }
+      }
+    }
+  }
+
+  if(t->button->window){
+    GdkRectangle rect;
+    rect.x = t->button->allocation.x;
+    rect.y = t->button->allocation.y;
+    rect.width = t->button->allocation.width;
+    rect.height = t->button->allocation.height;
+    gdk_window_invalidate_rect(t->button->window,&rect,1);
+  }
+
+  if(t->callback)t->callback(t);
+}
+
+
+/* housekeeping for the up and down buttons, along with the panel
+   scrolling implementation */
+
+static gboolean expose_upbutton(GtkWidget *widget,
+                                GdkEventExpose *event,
+                                gpointer userdata){
+  cairo_t *cr = gdk_cairo_create(widget->window);
+  int w=widget->allocation.width;
+  int h=widget->allocation.height;
+  GtkStateType state = gtk_widget_get_state(widget);
+
+  if(state == GTK_STATE_INSENSITIVE) return TRUE;
+
+  /* rounded arrow path */
+  double
+    radius    = 4.0,
+    scale     = (w/2>h?h:w/2)-radius*2,
+    x         = widget->allocation.x+w/2,        /* parameters like cairo_rectangle */
+    y         = widget->allocation.y+radius+.5;
+  double degrees = M_PI / 180.0;
+
+  cairo_new_sub_path (cr);
+  cairo_arc (cr, x, y, radius, 225*degrees, 315*degrees);
+  cairo_arc (cr, x+scale, y+scale, radius, -45*degrees, 135*degrees);
+  cairo_line_to (cr, x, y+sqrt(2)*radius);
+  cairo_arc (cr, x-scale, y+scale, radius, 45*degrees, 225*degrees);
+  cairo_close_path (cr);
+
+  /* fill translucent light background */
+  cairo_set_source_rgba (cr, .8, .8, .8, .2);
+  cairo_fill_preserve (cr);
+
+  /* stroke border */
+  cairo_set_line_width(cr,1.0);
+  if(state == GTK_STATE_ACTIVE)
+    cairo_set_source_rgba (cr, .5, .6, .83, 1);
+  else
+    cairo_set_source_rgba (cr, .8, .8, .8, 1);
+  cairo_stroke(cr);
+
+  cairo_destroy(cr);
+
+  return TRUE;
+}
+
+static gboolean expose_downbutton(GtkWidget *widget,
+                                  GdkEventExpose *event,
+                                  gpointer userdata){
+  cairo_t *cr = gdk_cairo_create(widget->window);
+  int w=widget->allocation.width;
+  int h=widget->allocation.height;
+  GtkStateType state = gtk_widget_get_state(widget);
+
+  if(state == GTK_STATE_INSENSITIVE) return TRUE;
+
+  /* rounded arrow path */
+  double
+    radius    = 4.0,
+    scale     = (w/2>h?h:w/2)-radius*2,
+    x         = widget->allocation.x+w/2,        /* parameters like cairo_rectangle */
+    y         = widget->allocation.y+h-radius-1.5;
+  double degrees = M_PI / 180.0;
+
+  cairo_new_sub_path (cr);
+  cairo_arc (cr, x, y, radius, -315*degrees, -225*degrees);
+  cairo_arc (cr, x-scale, y-scale, radius, -225*degrees, -45*degrees);
+  cairo_line_to (cr, x, y-sqrt(2)*radius);
+  cairo_arc (cr, x+scale, y-scale, radius, -135*degrees, 45*degrees);
+  cairo_close_path (cr);
+
+  /* fill translucent light background */
+  cairo_set_source_rgba (cr, .8, .8, .8, .2);
+  cairo_fill_preserve (cr);
+
+  /* stroke border */
+  cairo_set_line_width(cr,1.0);
+  if(state == GTK_STATE_ACTIVE)
+    cairo_set_source_rgba (cr, .5, .6, .83, 1);
+  else
+    cairo_set_source_rgba (cr, .8, .8, .8, 1);
+  cairo_stroke(cr);
+
+  cairo_destroy(cr);
+
+  return TRUE;
+}
+
+static void animate_panel(rowpanel *p){
+  int h = p->panel_scrollfix->allocation.height;
+  double new_now = filter_filter(p->current_panel,&p->display_filter);
+  if(fabs(new_now - p->current_panel)*h<1.){
+    p->display_animating=0;
+    p->display_now=p->current_panel;
+  }else{
+    p->display_now=new_now;
+    g_timeout_add(25,(GSourceFunc)animate_panel,p);
+  }
+  gtk_fixed_move(GTK_FIXED(p->panel_scrollfix),p->panel_lefttable,
+                 0,rint(-h*p->display_now));
+}
+
+static void set_current_panel(rowpanel *p, int n){
+  if(p->display_animating){
+    p->current_panel=n;
+  }else{
+    p->display_animating = 1;
+    filter_set(&p->display_filter,p->current_panel);
+    p->current_panel=n;
+    animate_panel(p);
+  }
+
+  if(p->current_panel==0){
+    gtk_widget_set_sensitive(p->upbutton,FALSE);
+  }else{
+    gtk_widget_set_sensitive(p->upbutton,TRUE);
+  }
+  if(p->current_panel+1==p->num_panels){
+    gtk_widget_set_sensitive(p->downbutton,FALSE);
+  }else{
+    gtk_widget_set_sensitive(p->downbutton,TRUE);
+  }
+  if(p->callback)p->callback(p);
+}
+
+static void upbutton_clicked(GtkWidget *widget, gpointer data){
+  rowpanel *p=(rowpanel *)data;
+  if(p->current_panel>0)
+    set_current_panel(p,p->current_panel-1);
+}
+
+static void downbutton_clicked(GtkWidget *widget, gpointer data){
+  rowpanel *p=(rowpanel *)data;
+  if(p->current_panel+1<p->num_panels)
+    set_current_panel(p,p->current_panel+1);
+}
+
+gboolean supports_alpha = FALSE;
+static void screen_changed(GtkWidget *widget,
+                    GdkScreen *old_screen, gpointer userdata){
+
+  /* To check if the display supports alpha channels, get the colormap */
+  GdkScreen *screen = gtk_widget_get_screen(widget);
+  GdkColormap *colormap = gdk_screen_get_rgba_colormap(screen);
+
+  if (!colormap){
+    printf("Your screen does not support alpha channels!\n");
+    colormap = gdk_screen_get_rgb_colormap(screen);
+    supports_alpha = FALSE;
+  }else{
+    supports_alpha = TRUE;
+  }
+
+  gtk_widget_set_colormap(widget, colormap);
+}
+
+static gboolean expose_toplevel(GtkWidget *widget,
+                                GdkEventExpose *event,
+                                gpointer userdata){
+  cairo_t *cr = gdk_cairo_create(widget->window);
+  int w=widget->allocation.width;
+  int h=widget->allocation.height;
+
+  /* clear background to transparent */
+  cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
+  cairo_set_source_rgba (cr, 0, 0, 0, 0); /* transparent */
+  cairo_paint (cr);
+
+  /* rounded rectangle path */
+  double
+    x         = 1,        /* parameters like cairo_rectangle */
+    y         = 1,
+    width     = w-2,
+    height    = h-2,
+    radius    = 8.0;
+  double degrees = M_PI / 180.0;
+
+  cairo_new_sub_path (cr);
+  cairo_arc (cr, x+width-radius, y+radius, radius, -90*degrees, 0);
+  cairo_arc (cr, x+width-radius, y+height-radius, radius, 0, 90*degrees);
+  cairo_arc (cr, x+radius, y+height-radius, radius, 90*degrees, 180*degrees);
+  cairo_arc (cr, x+radius, y+radius, radius, 180*degrees, 270*degrees);
+  cairo_close_path (cr);
+
+  /* fill translucent dark background */
+  cairo_set_source_rgba (cr, 0, 0, 0, .5);
+  cairo_fill_preserve (cr);
+
+  /* stroke border */
+  cairo_set_line_width(cr,2.0);
+  cairo_set_source_rgba (cr, .8, .8, .8, 1);
+  cairo_stroke(cr);
+
+  cairo_destroy(cr);
+
+  return FALSE;
+}
+
+/* hide the X cursor upon entry -- this is a touch tablet app */
+static gboolean hide_mouse(GtkWidget *widget,
+                           GdkEvent *event,
+                           gpointer userdata){
+  GdkCursor *cursor=gdk_cursor_new(GDK_BLANK_CURSOR);
+  gdk_window_set_cursor(widget->window, cursor);
+  gdk_cursor_destroy(cursor);
+  return TRUE;
+}
+
+rowpanel *rowpanel_new(int w, int h, void (*callback)(rowpanel *)){
+  rowpanel *p = calloc(1,sizeof(*p));
+
+  GtkWidget *topbox = gtk_hbox_new(0,0);
+  GtkWidget *rightbox = gtk_vbox_new(1,0);
+  GtkWidget *leftbox = gtk_hbox_new(1,0);
+
+  p->toplevel = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+  p->callback = callback;
+  gtk_widget_set_app_paintable(p->toplevel, TRUE);
+  g_signal_connect(G_OBJECT(p->toplevel), "screen-changed",
+                   G_CALLBACK(screen_changed), p);
+  g_signal_connect(G_OBJECT(p->toplevel), "expose-event",
+                   G_CALLBACK(expose_toplevel), p);
+  g_signal_connect(G_OBJECT(p->toplevel), "enter-notify-event",
+                   G_CALLBACK(hide_mouse), p);
+
+  /* toplevel is a fixed size, meant to be nailed to the screen in one
+     spot */
+  p->w=w;
+  p->h=h;
+  gtk_widget_set_size_request(GTK_WIDGET(p->toplevel),w,h);
+  gtk_window_set_resizable(GTK_WINDOW(p->toplevel),FALSE);
+  gtk_window_set_decorated(GTK_WINDOW(p->toplevel),FALSE);
+  gtk_window_set_gravity(GTK_WINDOW(p->toplevel),GDK_GRAVITY_SOUTH_WEST);
+  gtk_window_move (GTK_WINDOW(p->toplevel),0,gdk_screen_height()-1);
+
+  p->panel_scrollfix = gtk_fixed_new();
+  p->panel_lefttable = gtk_table_new(1,1,1);
+  p->upbutton = gtk_button_new();
+  p->downbutton = gtk_button_new();
+
+  gtk_widget_set_size_request(GTK_WIDGET(p->upbutton),40,25);
+  gtk_widget_set_size_request(GTK_WIDGET(p->downbutton),40,25);
+
+  gtk_container_add(GTK_CONTAINER(p->toplevel),topbox);
+  gtk_container_set_border_width(GTK_CONTAINER(topbox),2);
+  gtk_box_pack_start(GTK_BOX(topbox),leftbox,1,1,0);
+  gtk_box_pack_end(GTK_BOX(topbox),rightbox,0,0,0);
+  gtk_box_pack_start(GTK_BOX(leftbox),p->panel_scrollfix,1,1,0);
+  gtk_box_pack_start(GTK_BOX(rightbox),p->upbutton,0,1,3);
+  gtk_box_pack_end(GTK_BOX(rightbox),p->downbutton,0,1,3);
+
+  g_signal_connect(G_OBJECT(p->upbutton), "expose-event",
+                   G_CALLBACK(expose_upbutton), p);
+  g_signal_connect(G_OBJECT(p->downbutton), "expose-event",
+                   G_CALLBACK(expose_downbutton), p);
+  g_signal_connect(G_OBJECT(p->upbutton), "pressed",
+                   G_CALLBACK(upbutton_clicked), p);
+  g_signal_connect(G_OBJECT(p->downbutton), "pressed",
+                   G_CALLBACK(downbutton_clicked), p);
+
+  filter_make_critical(.07,1,&p->display_filter);
+  gtk_widget_set_size_request(p->panel_lefttable,w-44,-1);
+  gtk_fixed_put(GTK_FIXED(p->panel_scrollfix),p->panel_lefttable,0,0);
+
+  screen_changed(p->toplevel, NULL, NULL);
+  set_current_panel(p,0);
+  gtk_widget_show_all(p->toplevel);
+
+  return p;
+}
+
+GtkBox *rowpanel_new_row(rowpanel *p, int fill_p){
+  int boxborder=8;
+  int n = ++p->num_panels;
+  GtkWidget *heightbox = gtk_hbox_new(0,0);
+  GtkWidget *heightforce = gtk_vbox_new(1,0);
+  GtkWidget *panelframe = gtk_frame_new(NULL);
+  GtkWidget *centerbox = gtk_hbox_new(1,0);
+  GtkWidget *buttonbox = gtk_hbox_new(0,6);
+
+  gtk_table_resize(GTK_TABLE(p->panel_lefttable),n,1);
+
+  gtk_table_attach(GTK_TABLE(p->panel_lefttable),heightbox,0,1,n-1,n,
+                   GTK_EXPAND|GTK_FILL,0,0,0);
+  gtk_box_pack_start(GTK_BOX(heightbox),heightforce,0,0,0);
+  gtk_widget_set_size_request(heightforce,1,p->h-4);
+
+  gtk_widget_set_name(panelframe,"topframe");
+  gtk_frame_set_label_align(GTK_FRAME(panelframe),.5,.5);
+  gtk_container_set_border_width(GTK_CONTAINER(panelframe),boxborder);
+  gtk_frame_set_shadow_type(GTK_FRAME(panelframe),GTK_SHADOW_NONE);
+  gtk_box_pack_start(GTK_BOX(heightbox),panelframe,1,1,0);
+  gtk_container_add(GTK_CONTAINER(panelframe),centerbox);
+  gtk_container_set_border_width(GTK_CONTAINER(buttonbox),boxborder);
+
+  if(fill_p){
+    /* expanding box will fill */
+    gtk_box_pack_start(GTK_BOX(centerbox),buttonbox,1,1,0);
+  }else{
+    /* nonexpanding box will center */
+    gtk_box_pack_start(GTK_BOX(centerbox),buttonbox,0,0,0);
+  }
+  gtk_widget_show_all(heightbox);
+
+  while (gtk_events_pending())
+    gtk_main_iteration();
+
+  return GTK_BOX(buttonbox);
+}

Added: trunk/Xiph-episode-II/bounce/gtk-bounce-widget.h
===================================================================
--- trunk/Xiph-episode-II/bounce/gtk-bounce-widget.h	                        (rev 0)
+++ trunk/Xiph-episode-II/bounce/gtk-bounce-widget.h	2013-02-24 22:04:10 UTC (rev 18812)
@@ -0,0 +1,93 @@
+#ifndef _BOUNCE_WIDGET_H_
+#define _BOUNCE_WIDGET_H_
+
+#include <gtk/gtk.h>
+#include "twopole.h"
+
+typedef struct rowpanel {
+  int w;
+  int h;
+  GtkWidget *toplevel;
+
+  int num_panels;
+  int current_panel;
+  pole2 display_filter;
+  double display_now;
+  int display_animating;
+  GtkWidget *panel_scrollfix;
+  GtkWidget *panel_lefttable;
+  GtkWidget *upbutton;
+  GtkWidget *downbutton;
+  void (*callback)(struct rowpanel *);
+} rowpanel;
+
+typedef struct rowwidget {
+  GtkBox *parent;
+  GtkWidget *table;
+  GtkWidget *button;
+  GtkWidget *vbox;
+  GtkWidget *label[5];
+  GtkWidget *alignment;
+  int radius;
+  int rounding;
+  void (*callback)(struct rowwidget *);
+  int pressed;
+  int lit;
+} rowwidget;
+
+typedef struct rowslider {
+  GtkBox *parent;
+  GtkWidget *button;
+
+  PangoLayout *caption;
+  PangoLayout **labels;
+  float *values;
+  int *stops;
+  int label_n;
+  int capwidth;
+  int labelwidth;
+  int labelheight;
+
+  float value;
+  float delta;
+  int x;
+  void (*callback)(struct rowslider *);
+
+
+  double radiusS;
+  double radiusL;
+  double lpad;
+  double pastdot;
+  double pipw;
+  double pipx;
+  int w;
+  int h;
+  int y0;
+  int y1;
+
+} rowslider;
+
+extern GtkWidget *bounce_toplevel(void);
+
+extern rowwidget *rowtoggle_new(GtkBox *box, char *text,
+                                void (*callback)(rowwidget *));
+extern void rowwidget_add_label(rowwidget *t, char *label, int row);
+
+extern int rowtoggle_get_active(rowwidget *w);
+extern void rowtoggle_set_active(rowwidget *w, int state);
+extern rowwidget *rowreadout_new(GtkBox *box, char *text);
+extern void rowreadout_light(rowwidget *t,int state);
+extern rowwidget *rowspacer_new(GtkBox *box,int pad);
+extern rowwidget *rowlabel_new(GtkBox *box,char *text);
+extern rowslider *rowslider_new(GtkBox *box,
+                                char *caption,
+                                void (*callback)(rowslider *));
+extern void rowslider_add_stop(rowslider *t,
+                               char *ltext,
+                               float lvalue,
+                               int snap);
+extern void rowslider_set(rowslider *t,float lvalue);
+extern rowpanel *rowpanel_new(int w, int h, void (*callback)(rowpanel *));
+extern GtkBox *rowpanel_new_row(rowpanel *p, int fill_p);
+
+#endif

Added: trunk/Xiph-episode-II/bounce/gtk-bounce.c
===================================================================
--- trunk/Xiph-episode-II/bounce/gtk-bounce.c	                        (rev 0)
+++ trunk/Xiph-episode-II/bounce/gtk-bounce.c	2013-02-24 22:04:10 UTC (rev 18812)
@@ -0,0 +1,628 @@
+#include <math.h>
+#include <stdlib.h>
+#include <string.h>
+#include <gtk/gtk.h>
+
+#define PANEL_WIDTH 1024
+#define PANEL_HEIGHT 150
+
+int current_panel=0;
+int num_panels=4;
+
+gboolean supports_alpha = FALSE;
+static void screen_changed(GtkWidget *widget, GdkScreen *old_screen, gpointer userdata)
+{
+  /* To check if the display supports alpha channels, get the colormap */
+  GdkScreen *screen = gtk_widget_get_screen(widget);
+  GdkColormap *colormap = gdk_screen_get_rgba_colormap(screen);
+
+  if (!colormap){
+    printf("Your screen does not support alpha channels!\n");
+    colormap = gdk_screen_get_rgb_colormap(screen);
+    supports_alpha = FALSE;
+  }else{
+    supports_alpha = TRUE;
+  }
+
+  gtk_widget_set_colormap(widget, colormap);
+}
+
+static gboolean expose_toplevel(GtkWidget *widget,
+                                GdkEventExpose *event,
+                                gpointer userdata){
+  cairo_t *cr = gdk_cairo_create(widget->window);
+  int w=widget->allocation.width;
+  int h=widget->allocation.height;
+
+  /* clear background to transparent */
+  cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
+  cairo_set_source_rgba (cr, 0, 0, 0, 0); /* transparent */
+  cairo_paint (cr);
+
+  /* rounded rectangle path */
+  double
+    x         = 1,        /* parameters like cairo_rectangle */
+    y         = 1,
+    width     = w-2,
+    height    = h-2,
+    radius    = 8.0;
+  double degrees = M_PI / 180.0;
+
+  cairo_new_sub_path (cr);
+  cairo_arc (cr, x+width-radius, y+radius, radius, -90*degrees, 0);
+  cairo_arc (cr, x+width-radius, y+height-radius, radius, 0, 90*degrees);
+  cairo_arc (cr, x+radius, y+height-radius, radius, 90*degrees, 180*degrees);
+  cairo_arc (cr, x+radius, y+radius, radius, 180*degrees, 270*degrees);
+  cairo_close_path (cr);
+
+  /* fill translucent dark background */
+  cairo_set_source_rgba (cr, 0, 0, 0, .5);
+  cairo_fill_preserve (cr);
+
+  /* stroke border */
+  cairo_set_line_width(cr,2.0);
+  cairo_set_source_rgba (cr, .8, .8, .8, 1);
+  cairo_stroke(cr);
+
+  cairo_destroy(cr);
+
+  return FALSE;
+}
+
+/* Because we're in a GtkFixed that's being scrolled around, but
+   drawing is being done to the GdkWindow owned by the toplevel, the
+   clip region is set incorrectly to allow us to overdraw things in
+   the toplevel outside our parents' bounds.  Follow the tree up and
+   reclip. */
+static void narrow_clip(GtkWidget *w,GdkRectangle *area){
+  int x1 = w->allocation.x;
+  int y1 = w->allocation.y;
+  int x2 = w->allocation.x+w->allocation.width;
+  int y2 = w->allocation.y+w->allocation.height;
+
+  if(x1>area->x)area->x=x1;
+  if(x2<area->width)area->width=x2;
+  if(y1>area->y)area->y=y1;
+  if(y2<area->height)area->height=y2;
+
+  if(w->parent && w->parent->window == w->window)
+    narrow_clip(w->parent,area);
+}
+
+static void clip_to_ancestors(GtkWidget *w, cairo_t *cr){
+  GdkRectangle a;
+  a.x = w->allocation.x;
+  a.width = a.x + w->allocation.width; /* overload */
+  a.y = w->allocation.y;
+  a.height = a.y + w->allocation.height; /* overload */
+  if(w->parent && w->parent->window == w->window)
+    narrow_clip(w->parent,&a);
+  cairo_rectangle(cr,a.x,a.y,a.width-a.x,a.height-a.y);
+  cairo_clip(cr);
+}
+
+void expose_a_widget(gpointer a, gpointer b){
+  GtkWidget *widget = (GtkWidget *)a;
+  GdkEventExpose *event = (GdkEventExpose *)b;
+  GTK_WIDGET_CLASS(GTK_WIDGET_GET_CLASS(widget))->
+    expose_event (widget, event);
+}
+
+typedef struct {
+  GtkBox *parent;
+  GtkWidget *table;
+  GtkWidget *button;
+  GtkWidget *vbox;
+  GtkWidget *label[5];
+  GtkWidget *alignment;
+  int pressed;
+  int lit;
+} rowwidget;
+
+static gboolean expose_rectarea(GtkWidget *widget,
+                                GdkEventExpose *event,
+                                gpointer userdata){
+  if(!widget->window)return TRUE;
+  cairo_t *cr = gdk_cairo_create(widget->window);
+  rowwidget *t = (rowwidget *)userdata;
+  int w=widget->allocation.width;
+  int h=widget->allocation.height;
+  GtkStateType state = gtk_widget_get_state(widget);
+  int lit=t->lit;
+  /* rounded rectangle path */
+
+  double degrees = M_PI / 180.0;
+  double radius = 12;
+  double height = h-2;
+  double rxd = radius-cos(15*degrees)*radius;
+  double ryd = sin(15*degrees)*radius;
+  double R = (height/2-radius+ryd)/sin(15*degrees);
+  double RxD = R-cos(15*degrees)*R;
+
+  double x = widget->allocation.x+RxD-rxd+1;
+  double width = w - RxD*2 + rxd*2 - 2;
+  double y = widget->allocation.y+1;
+
+  double Rx1 = widget->allocation.x+R+1;
+  double Rx2 = widget->allocation.x+w-R-1;
+
+  clip_to_ancestors(widget,cr);
+
+  cairo_new_sub_path (cr);
+  cairo_arc (cr, x+width-radius, y+radius, radius, -90*degrees, -15*degrees);
+  cairo_arc (cr, Rx2, y+height/2, R, -15*degrees, 15*degrees);
+  cairo_arc (cr, x+width-radius, y+height-radius, radius, 15*degrees, 90*degrees);
+
+  cairo_arc (cr, x+radius, y+height-radius, radius, 90*degrees, 165*degrees);
+  cairo_arc (cr, Rx1, y+height/2, R, 165*degrees, 195*degrees);
+  cairo_arc (cr, x+radius, y+radius, radius, 195*degrees, 270*degrees);
+  cairo_close_path (cr);
+
+  /* fill background */
+  if(state != GTK_STATE_INSENSITIVE){
+    /* won't get here is not togglebutton */
+    if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)))
+      cairo_set_source_rgba (cr, .1, .3, .6, 1);
+    else
+      cairo_set_source_rgba (cr, .1, .1, .1, 1);
+  }else{
+    if(lit)
+      cairo_set_source_rgba (cr, 0, .5, 1, .4);
+    else
+      cairo_set_source_rgba (cr, 0, 0, 0, .5);
+  }
+  cairo_fill_preserve (cr);
+
+  /* gradient */
+  if(state != GTK_STATE_INSENSITIVE){
+    cairo_pattern_t *pat = cairo_pattern_create_linear(x, y, x, y+h);
+    if(!t->pressed){
+      cairo_pattern_add_color_stop_rgba(pat, 1.0, 0, 0, 0, .2);
+      cairo_pattern_add_color_stop_rgba(pat, 0.8, 0, 0, 0, 0);
+      cairo_pattern_add_color_stop_rgba(pat, 0.3, 1, 1, 1, 0);
+      cairo_pattern_add_color_stop_rgba(pat, 0, 1, 1, 1, .15);
+    }
+    cairo_set_source(cr, pat);
+    cairo_fill_preserve (cr);
+    cairo_pattern_destroy(pat);
+  }
+
+  /* stroke border */
+  if(state != GTK_STATE_INSENSITIVE){
+    switch(state){
+    case GTK_STATE_ACTIVE:
+    case GTK_STATE_NORMAL:
+      cairo_set_source_rgba (cr, .8, .8, .8, 1);
+      break;
+    case GTK_STATE_PRELIGHT:
+    case GTK_STATE_SELECTED:
+      cairo_set_source_rgba (cr, 1, 1, 1, 1);
+      break;
+    }
+
+    cairo_set_line_width(cr,2.0);
+    cairo_stroke(cr);
+  }
+  cairo_destroy(cr);
+
+  /* have to do this by hand as returning TRUE below short-circuits
+     the whole expose chain */
+  //GList *children=gtk_container_get_children(GTK_CONTAINER(widget));
+  //g_list_foreach(children,expose_a_widget,event);
+
+  return TRUE;
+}
+
+/* hide the X cursor upon entry -- this is a touch tablet app */
+static gboolean hide_mouse(GtkWidget *widget,
+                           GdkEvent *event,
+                           gpointer userdata){
+  GdkCursor *cursor=gdk_cursor_new(GDK_BLANK_CURSOR);
+  gdk_window_set_cursor(widget->window, cursor);
+  gdk_cursor_destroy(cursor);
+}
+
+static gboolean expose_rowwidget(GtkWidget *widget,
+                                 GdkEventExpose *event,
+                                 gpointer userdata){
+  rowwidget *t = (rowwidget *)userdata;
+  int i;
+
+  /* be sure of the expose order as the widgets overlap */
+  /* toggle button is not a class native expose, call the draw directly */
+  expose_rectarea(t->button,event,t);
+  /* expose labels */
+  expose_a_widget(t->vbox,event);
+
+  return TRUE;
+}
+
+/* modify toggle button behavior that assumes an activated button ==
+   pressed.  I don't want active toggles to be down, just lit */
+static gboolean press_rowtoggle(GtkWidget *widget,
+                                gpointer userdata){
+  rowwidget *t = (rowwidget *)userdata;
+  t->pressed=1;
+  return FALSE;
+}
+static gboolean release_rowtoggle(GtkWidget *widget,
+                                gpointer userdata){
+  rowwidget *t = (rowwidget *)userdata;
+  t->pressed=0;
+  return FALSE;
+}
+
+static rowwidget *rowtoggle_new(GtkBox *box, GCallback callback){
+  rowwidget *ret = calloc(1,sizeof(*ret));
+  ret->parent = box;
+  ret->table = gtk_table_new(1,1,1);
+  ret->button = gtk_toggle_button_new();
+  ret->alignment = gtk_alignment_new(.5,.5,0,0);
+  ret->vbox = gtk_vbox_new(0,2);
+
+  memset(ret->label,0,sizeof(ret->label));
+
+  gtk_box_pack_start(box,ret->table,0,0,10);
+  gtk_table_attach_defaults(GTK_TABLE(ret->table),ret->alignment,0,1,0,1);
+  gtk_container_add(GTK_CONTAINER(ret->alignment),ret->vbox);
+  gtk_table_attach_defaults(GTK_TABLE(ret->table),ret->button,0,1,0,1);
+
+  g_signal_connect(G_OBJECT(ret->button), "expose-event",
+                   G_CALLBACK(expose_rectarea), ret);
+  g_signal_connect(G_OBJECT(ret->button), "pressed",
+                   G_CALLBACK(press_rowtoggle), ret);
+  g_signal_connect(G_OBJECT(ret->button), "released",
+                   G_CALLBACK(release_rowtoggle), ret);
+  g_signal_connect(G_OBJECT(ret->table), "expose-event",
+                 G_CALLBACK(expose_rowwidget), ret);
+  gtk_widget_set_size_request(ret->button,PANEL_HEIGHT,-1);
+
+  return ret;
+}
+
+static rowwidget *rowlabel_new(GtkBox *box){
+  rowwidget *ret = calloc(1,sizeof(*ret));
+  ret->parent = box;
+  ret->table = gtk_table_new(1,1,1);
+  ret->button = gtk_toggle_button_new();
+  ret->alignment = gtk_alignment_new(.5,.5,0,0);
+  ret->vbox = gtk_vbox_new(0,2);
+
+  memset(ret->label,0,sizeof(ret->label));
+
+  gtk_box_pack_start(box,ret->table,0,0,10);
+  gtk_table_attach_defaults(GTK_TABLE(ret->table),ret->alignment,0,1,0,1);
+  gtk_container_add(GTK_CONTAINER(ret->alignment),ret->vbox);
+  gtk_table_attach_defaults(GTK_TABLE(ret->table),ret->button,0,1,0,1);
+  gtk_widget_set_sensitive(ret->button,FALSE);
+
+  g_signal_connect(G_OBJECT(ret->button), "expose-event",
+                   G_CALLBACK(expose_rectarea), ret);
+  g_signal_connect(G_OBJECT(ret->table), "expose-event",
+                 G_CALLBACK(expose_rowwidget), ret);
+  gtk_widget_set_size_request(ret->button,PANEL_HEIGHT,-1);
+
+  return ret;
+}
+
+static rowwidget *rowlabel_light(rowwidget *t,int state){
+  t->lit=state;
+  expose_rectarea(t->button,NULL,t);
+}
+
+static void rowwidget_add_label(rowwidget *t, char *label, int row){
+  if(!t)return;
+  if(row<0 || row>4)return;
+  if(t->label[row] && label==NULL){
+    gtk_widget_destroy(t->label[row]);
+    t->label[row]=NULL;
+  }else{
+    if(!t->label[row]){
+      t->label[row]=gtk_label_new(NULL);
+      gtk_box_pack_start(GTK_BOX(t->vbox),t->label[row],0,0,0);
+    }
+    gtk_label_set_markup(GTK_LABEL(t->label[row]),label);
+  }
+}
+
+static gboolean expose_upbutton(GtkWidget *widget,
+                                GdkEventExpose *event,
+                                gpointer userdata){
+  cairo_t *cr = gdk_cairo_create(widget->window);
+  int w=widget->allocation.width;
+  int h=widget->allocation.height;
+  GtkStateType state = gtk_widget_get_state(widget);
+
+  if(state == GTK_STATE_INSENSITIVE) return TRUE;
+
+  /* rounded arrow path */
+  double
+    radius    = 4.0,
+    scale     = (w/2>h?h:w/2)-radius*2,
+    x         = widget->allocation.x+w/2,        /* parameters like cairo_rectangle */
+    y         = widget->allocation.y+radius;
+  double degrees = M_PI / 180.0;
+
+  cairo_new_sub_path (cr);
+  cairo_arc (cr, x, y, radius, 225*degrees, 315*degrees);
+  cairo_arc (cr, x+scale, y+scale, radius, -45*degrees, 135*degrees);
+  cairo_line_to (cr, x, y+sqrt(2)*radius);
+  cairo_arc (cr, x-scale, y+scale, radius, 45*degrees, 225*degrees);
+  cairo_close_path (cr);
+
+  /* fill translucent light background */
+  if(state == GTK_STATE_PRELIGHT)
+    cairo_set_source_rgba (cr, 1, 1, 1, .5);
+  else
+    cairo_set_source_rgba (cr, .8, .8, .8, .5);
+  cairo_fill_preserve (cr);
+
+  /* stroke border */
+  cairo_set_line_width(cr,1.0);
+  if(state == GTK_STATE_ACTIVE)
+    cairo_set_source_rgba (cr, .5, .6, .83, 1);
+  else
+    cairo_set_source_rgba (cr, .8, .8, .8, 1);
+  cairo_stroke(cr);
+
+  cairo_destroy(cr);
+
+  return TRUE;
+}
+
+static gboolean expose_downbutton(GtkWidget *widget,
+                                  GdkEventExpose *event,
+                                  gpointer userdata){
+  cairo_t *cr = gdk_cairo_create(widget->window);
+  int w=widget->allocation.width;
+  int h=widget->allocation.height;
+  GtkStateType state = gtk_widget_get_state(widget);
+
+  if(state == GTK_STATE_INSENSITIVE) return TRUE;
+
+  /* rounded arrow path */
+  double
+    radius    = 4.0,
+    scale     = (w/2>h?h:w/2)-radius*2,
+    x         = widget->allocation.x+w/2,        /* parameters like cairo_rectangle */
+    y         = widget->allocation.y+h-radius-1;
+  double degrees = M_PI / 180.0;
+
+  cairo_new_sub_path (cr);
+  cairo_arc (cr, x, y, radius, -315*degrees, -225*degrees);
+  cairo_arc (cr, x-scale, y-scale, radius, -225*degrees, -45*degrees);
+  cairo_line_to (cr, x, y-sqrt(2)*radius);
+  cairo_arc (cr, x+scale, y-scale, radius, -135*degrees, 45*degrees);
+  cairo_close_path (cr);
+
+  /* fill translucent light background */
+  if(state == GTK_STATE_PRELIGHT)
+    cairo_set_source_rgba (cr, 1, 1, 1, .5);
+  else
+    cairo_set_source_rgba (cr, .8, .8, .8, .5);
+  cairo_fill_preserve (cr);
+
+  /* stroke border */
+  cairo_set_line_width(cr,1.0);
+  if(state == GTK_STATE_ACTIVE)
+    cairo_set_source_rgba (cr, .5, .6, .83, 1);
+  else
+    cairo_set_source_rgba (cr, .8, .8, .8, 1);
+  cairo_stroke(cr);
+
+  cairo_destroy(cr);
+
+  return TRUE;
+}
+
+typedef struct {
+  double a;
+  double b1;
+  double b2;
+  double x[2];
+  double y[2];
+} pole2;
+
+static void filter_reset(pole2 *p, double val){
+  p->x[0]=p->x[1]=val;
+  p->y[0]=p->y[1]=val;
+}
+
+static void filter_make_critical(double w, pole2 *f){
+  double w0 = tan(M_PI*w*pow(pow(2,.5)-1,-.5));
+  f->a  = w0*w0/(1+(2*w0)+w0*w0);
+  f->b1 = 2*f->a*(1/(w0*w0)-1);
+  f->b2 = 1-(4*f->a+f->b1);
+  filter_reset(f,0);
+}
+
+static double filter_filter(double x, pole2 *p){
+  double y =
+    p->a*x + 2*p->a*p->x[0] + p->a*p->x[1] +
+    p->b1*p->y[0] + p->b2*p->y[1];
+  p->y[1] = p->y[0]; p->y[0] = y;
+  p->x[1] = p->x[0]; p->x[0] = x;
+  return y;
+}
+
+static pole2 display_filter;
+static double display_now=0;
+static int display_animating=0;
+GtkWidget *panel_scrollfix=NULL;
+GtkWidget *panel_lefttable=NULL;
+GtkWidget *upbutton=NULL;
+GtkWidget *downbutton=NULL;
+
+static void animate_panel(){
+  int h = panel_scrollfix->allocation.height;
+  double new_now = filter_filter(current_panel,&display_filter);
+  if(fabs(new_now - current_panel)*h<1.){
+    display_animating=0;
+    display_now=current_panel;
+  }else{
+    display_now=new_now;
+    g_timeout_add(25,(GSourceFunc)animate_panel,NULL);
+  }
+  gtk_fixed_move(GTK_FIXED(panel_scrollfix),panel_lefttable,
+                 0,rint(-h*display_now));
+}
+
+static void set_current_panel(int n){
+  if(display_animating){
+    current_panel=n;
+  }else{
+    display_animating = 1;
+    filter_reset(&display_filter,current_panel);
+    current_panel=n;
+    animate_panel();
+  }
+
+  if(current_panel==0){
+    gtk_widget_set_sensitive(upbutton,FALSE);
+  }else{
+    gtk_widget_set_sensitive(upbutton,TRUE);
+  }
+  if(current_panel+1==num_panels){
+    gtk_widget_set_sensitive(downbutton,FALSE);
+  }else{
+    gtk_widget_set_sensitive(downbutton,TRUE);
+  }
+}
+
+static void upbutton_clicked(GtkWidget *widget, gpointer data){
+  if(current_panel>0)
+    set_current_panel(current_panel-1);
+}
+
+static void downbutton_clicked(GtkWidget *widget, gpointer data){
+  if(current_panel+1<num_panels)
+    set_current_panel(current_panel+1);
+}
+
+static void make_panel(void){
+  int w=PANEL_WIDTH;
+  int h=PANEL_HEIGHT;
+
+  GtkWidget *toplevel=NULL;
+  GtkWidget *topbox=NULL;
+  GtkWidget *rightbox=NULL;
+  GtkWidget *leftbox=NULL;
+  GtkWidget *panels[num_panels];
+
+  int i;
+
+  toplevel = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+  gtk_widget_set_app_paintable(toplevel, TRUE);
+  g_signal_connect(G_OBJECT(toplevel), "screen-changed", G_CALLBACK(screen_changed), NULL);
+  g_signal_connect(G_OBJECT(toplevel), "expose-event", G_CALLBACK(expose_toplevel), NULL);
+  g_signal_connect(G_OBJECT(toplevel), "enter-notify-event", G_CALLBACK(hide_mouse), NULL);
+
+  /* toplevel is a fixed size, meant to be nailed to the screen in one
+     spot */
+  gtk_widget_set_size_request(GTK_WIDGET(toplevel),w,h);
+  gtk_window_set_resizable(GTK_WINDOW(toplevel),FALSE);
+  gtk_window_set_decorated(GTK_WINDOW(toplevel),FALSE);
+  gtk_window_set_gravity(GTK_WINDOW(toplevel),GDK_GRAVITY_SOUTH_WEST);
+  gtk_window_move (GTK_WINDOW(toplevel),0,gdk_screen_height()-1);
+
+  /* multiple sliding panes within */
+  topbox = gtk_hbox_new(0,0);
+  rightbox = gtk_vbox_new(1,0);
+  leftbox = gtk_hbox_new(1,0);
+  panel_scrollfix = gtk_fixed_new();
+  panel_lefttable = gtk_table_new(num_panels,1,1);
+  upbutton = gtk_button_new();
+  downbutton = gtk_button_new();
+
+  gtk_widget_set_size_request(GTK_WIDGET(upbutton),60,25);
+  gtk_widget_set_size_request(GTK_WIDGET(downbutton),60,25);
+
+  gtk_container_add(GTK_CONTAINER(toplevel),topbox);
+  gtk_container_set_border_width(GTK_CONTAINER(topbox),2);
+  gtk_box_pack_start(GTK_BOX(topbox),leftbox,1,1,0);
+  gtk_box_pack_end(GTK_BOX(topbox),rightbox,0,0,0);
+  gtk_box_pack_start(GTK_BOX(leftbox),panel_scrollfix,1,1,0);
+  gtk_box_pack_start(GTK_BOX(rightbox),upbutton,0,1,3);
+  gtk_box_pack_end(GTK_BOX(rightbox),downbutton,0,1,3);
+
+  g_signal_connect(G_OBJECT(upbutton), "expose-event", G_CALLBACK(expose_upbutton), NULL);
+  g_signal_connect(G_OBJECT(downbutton), "expose-event", G_CALLBACK(expose_downbutton), NULL);
+  g_signal_connect(G_OBJECT(upbutton), "pressed", G_CALLBACK(upbutton_clicked), NULL);
+  g_signal_connect(G_OBJECT(downbutton), "pressed", G_CALLBACK(downbutton_clicked), NULL);
+
+  filter_make_critical(.05,&display_filter);
+  gtk_widget_set_size_request(panel_lefttable,w-4,-1);
+  gtk_fixed_put(GTK_FIXED(panel_scrollfix),panel_lefttable,0,0);
+
+
+  /* build the sliding frame table */
+  int boxborder=10;
+  for(i=0;i<num_panels;i++){
+    GtkWidget *heightbox = gtk_hbox_new(0,0);
+    GtkWidget *heightforce = gtk_vbox_new(1,0);
+    GtkWidget *panelframe = gtk_frame_new("foo label");
+    GtkWidget *buttonbox = gtk_hbox_new(0,0);
+
+    gtk_table_attach(GTK_TABLE(panel_lefttable),heightbox,0,1,i,i+1,
+                     GTK_EXPAND|GTK_FILL,0,0,0);
+    gtk_box_pack_start(GTK_BOX(heightbox),heightforce,0,0,0);
+    gtk_widget_set_size_request(heightforce,1,h-4);
+
+    gtk_widget_set_name(panelframe,"topframe");
+    gtk_frame_set_label_align(GTK_FRAME(panelframe),.5,.5);
+    gtk_container_set_border_width(GTK_CONTAINER(panelframe),boxborder);
+    gtk_frame_set_shadow_type(GTK_FRAME(panelframe),GTK_SHADOW_NONE);
+    gtk_box_pack_start(GTK_BOX(heightbox),panelframe,1,1,0);
+    gtk_container_add(GTK_CONTAINER(panelframe),buttonbox);
+    gtk_container_set_border_width(GTK_CONTAINER(buttonbox),boxborder);
+
+    switch(i){
+    default:
+      {
+        rowwidget *t = rowtoggle_new(GTK_BOX(buttonbox), NULL);
+        rowwidget_add_label(t,"foo",0);
+      }
+      break;
+    case 1:
+      {
+        rowwidget *t = rowlabel_new(GTK_BOX(buttonbox));
+        rowwidget_add_label(t,"foo",0);
+      }
+      break;
+    case 2:
+      {
+        rowwidget *t = rowlabel_new(GTK_BOX(buttonbox));
+        rowwidget_add_label(t,"foo",0);
+        rowlabel_light(t,1);
+      }
+      break;
+    }
+  }
+
+
+  screen_changed(toplevel, NULL, NULL);
+  set_current_panel(0);
+
+  gtk_widget_show_all(toplevel);
+}
+
+int main(int argc, char **argv){
+  gtk_init (&argc, &argv);
+
+  gtk_rc_parse_string
+    ("style \"panel\" {"
+     "  fg[NORMAL]=\"#ffffff\""
+     "}"
+     "style \"topframe\" {"
+     "  font_name = \"sans 10 bold\""
+     "  fg[NORMAL]=\"#cccccc\""
+     "}"
+     "class \"*\" style \"panel\""
+     "widget \"*.topframe.GtkLabel\" style \"topframe\""
+     );
+
+  make_panel();
+  gtk_main();
+
+  return 0;
+}

Added: trunk/Xiph-episode-II/bounce/gtk-bounce.c.old
===================================================================
--- trunk/Xiph-episode-II/bounce/gtk-bounce.c.old	                        (rev 0)
+++ trunk/Xiph-episode-II/bounce/gtk-bounce.c.old	2013-02-24 22:04:10 UTC (rev 18812)
@@ -0,0 +1,393 @@
+#include <math.h>
+#include <gtk/gtk.h>
+
+int current_panel=0;
+
+gboolean supports_alpha = FALSE;
+static void screen_changed(GtkWidget *widget, GdkScreen *old_screen, gpointer userdata)
+{
+  /* To check if the display supports alpha channels, get the colormap */
+  GdkScreen *screen = gtk_widget_get_screen(widget);
+  GdkColormap *colormap = gdk_screen_get_rgba_colormap(screen);
+
+  if (!colormap){
+    printf("Your screen does not support alpha channels!\n");
+    colormap = gdk_screen_get_rgb_colormap(screen);
+    supports_alpha = FALSE;
+  }else{
+    supports_alpha = TRUE;
+  }
+
+  gtk_widget_set_colormap(widget, colormap);
+}
+
+static gboolean expose_toplevel(GtkWidget *widget,
+                                GdkEventExpose *event,
+                                gpointer userdata){
+  cairo_t *cr = gdk_cairo_create(widget->window);
+  int w=widget->allocation.width;
+  int h=widget->allocation.height;
+
+  /* clear background to transparent */
+  cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
+  cairo_set_source_rgba (cr, 0, 0, 0, 0); /* transparent */
+  cairo_paint (cr);
+
+  /* rounded rectangle path */
+  double
+    x         = 1,        /* parameters like cairo_rectangle */
+    y         = 1,
+    width     = w-2,
+    height    = h-2,
+    radius    = 8.0;
+  double degrees = M_PI / 180.0;
+
+  cairo_new_sub_path (cr);
+  cairo_arc (cr, x+width-radius, y+radius, radius, -90*degrees, 0);
+  cairo_arc (cr, x+width-radius, y+height-radius, radius, 0, 90*degrees);
+  cairo_arc (cr, x+radius, y+height-radius, radius, 90*degrees, 180*degrees);
+  cairo_arc (cr, x+radius, y+radius, radius, 180*degrees, 270*degrees);
+  cairo_close_path (cr);
+
+  /* fill translucent dark background */
+  cairo_set_source_rgba (cr, 0, 0, 0, .5);
+  cairo_fill_preserve (cr);
+
+  /* stroke border */
+  cairo_set_line_width(cr,2.0);
+  cairo_set_source_rgba (cr, .8, .8, .8, 1);
+  cairo_stroke(cr);
+
+  cairo_destroy(cr);
+
+  return FALSE;
+}
+
+void expose_a_widget(gpointer a, gpointer b){
+  GtkWidget *widget = (GtkWidget *)a;
+  GdkEventExpose *event = (GdkEventExpose *)b;
+  GTK_WIDGET_CLASS(GTK_WIDGET_GET_CLASS(widget))->
+    expose_event (widget, event);
+}
+
+static gboolean expose_toggle_button(GtkWidget *widget,
+                                     GdkEventExpose *event,
+                                     gpointer userdata){
+  cairo_t *cr = gdk_cairo_create(widget->window);
+  int w=widget->allocation.width;
+  int h=widget->allocation.height;
+  GtkStateType state = gtk_widget_get_state(widget);
+  int lit = userdata ? *(int *)userdata : 0;
+  /* rounded rectangle path */
+  double
+    x         = widget->allocation.x,
+    y         = widget->allocation.y,
+    width     = w-2,
+    height    = h-2,
+    radius    = 4.0;
+  double degrees = M_PI / 180.0;
+
+  cairo_new_sub_path (cr);
+  cairo_arc (cr, x+width-radius, y+radius, radius, -90*degrees, 0);
+  cairo_arc (cr, x+width-radius, y+height-radius, radius, 0, 90*degrees);
+  cairo_arc (cr, x+radius, y+height-radius, radius, 90*degrees, 180*degrees);
+  cairo_arc (cr, x+radius, y+radius, radius, 180*degrees, 270*degrees);
+  cairo_close_path (cr);
+
+  /* fill background */
+  switch(state){
+  case GTK_STATE_NORMAL:
+    cairo_set_source_rgba (cr, .1, .1, .1, 1);
+    break;
+  case GTK_STATE_ACTIVE:
+    cairo_set_source_rgba (cr, .1, .3, .6, 1);
+    break;
+  case GTK_STATE_SELECTED:
+    cairo_set_source_rgba (cr, .2, .4, .7, 1);
+    break;
+  case GTK_STATE_PRELIGHT:
+    if(gtk_toggle_button_get_active)
+      cairo_set_source_rgba (cr, .1, .3, .6, 1);
+    else
+      cairo_set_source_rgba (cr, .1, .1, .1, 1);
+    break;
+  case GTK_STATE_INSENSITIVE:
+    if(lit)
+      cairo_set_source_rgba (cr, .1, .3, .6, .5);
+    else
+      cairo_set_source_rgba (cr, 0, 0, 0, .5);
+    break;
+  }
+  cairo_fill_preserve (cr);
+
+  /* stroke border */
+  if(state != GTK_STATE_INSENSITIVE){
+    cairo_set_line_width(cr,2.0);
+    cairo_set_source_rgba (cr, .8, .8, .8, 1);
+    cairo_stroke(cr);
+  }
+  cairo_destroy(cr);
+
+  /* have to do this by hand as returning TRUE below short-circuits
+     the whole expose chain */
+  GList *children=gtk_container_get_children(GTK_CONTAINER(widget));
+  g_list_foreach(children,expose_a_widget,event);
+
+  return TRUE;
+}
+
+static gboolean expose_upbutton(GtkWidget *widget,
+                                GdkEventExpose *event,
+                                gpointer userdata){
+  cairo_t *cr = gdk_cairo_create(widget->window);
+  int w=widget->allocation.width;
+  int h=widget->allocation.height;
+  GtkStateType state = gtk_widget_get_state(widget);
+
+  if(state == GTK_STATE_INSENSITIVE) return TRUE;
+
+  /* rounded arrow path */
+  double
+    radius    = 4.0,
+    scale     = (w/2>h?h:w/2)-radius*2,
+    x         = widget->allocation.x+w/2,        /* parameters like cairo_rectangle */
+    y         = widget->allocation.y+radius;
+  double degrees = M_PI / 180.0;
+
+  cairo_new_sub_path (cr);
+  cairo_arc (cr, x, y, radius, 225*degrees, 315*degrees);
+  cairo_arc (cr, x+scale, y+scale, radius, -45*degrees, 135*degrees);
+  cairo_line_to (cr, x, y+sqrt(2)*radius);
+  cairo_arc (cr, x-scale, y+scale, radius, 45*degrees, 225*degrees);
+  cairo_close_path (cr);
+
+  /* fill translucent light background */
+  if(state == GTK_STATE_PRELIGHT)
+    cairo_set_source_rgba (cr, 1, 1, 1, .5);
+  else
+    cairo_set_source_rgba (cr, .8, .8, .8, .5);
+  cairo_fill_preserve (cr);
+
+  /* stroke border */
+  cairo_set_line_width(cr,1.0);
+  if(state == GTK_STATE_ACTIVE)
+    cairo_set_source_rgba (cr, .5, .6, .83, 1);
+  else
+    cairo_set_source_rgba (cr, .8, .8, .8, 1);
+  cairo_stroke(cr);
+
+  cairo_destroy(cr);
+
+  return TRUE;
+}
+
+static gboolean expose_downbutton(GtkWidget *widget,
+                                  GdkEventExpose *event,
+                                  gpointer userdata){
+  cairo_t *cr = gdk_cairo_create(widget->window);
+  int w=widget->allocation.width;
+  int h=widget->allocation.height;
+  GtkStateType state = gtk_widget_get_state(widget);
+
+  if(state == GTK_STATE_INSENSITIVE) return TRUE;
+
+  /* rounded arrow path */
+  double
+    radius    = 4.0,
+    scale     = (w/2>h?h:w/2)-radius*2,
+    x         = widget->allocation.x+w/2,        /* parameters like cairo_rectangle */
+    y         = widget->allocation.y+h-radius-1;
+  double degrees = M_PI / 180.0;
+
+  cairo_new_sub_path (cr);
+  cairo_arc (cr, x, y, radius, -315*degrees, -225*degrees);
+  cairo_arc (cr, x-scale, y-scale, radius, -225*degrees, -45*degrees);
+  cairo_line_to (cr, x, y-sqrt(2)*radius);
+  cairo_arc (cr, x+scale, y-scale, radius, -135*degrees, 45*degrees);
+  cairo_close_path (cr);
+
+  /* fill translucent light background */
+  if(state == GTK_STATE_PRELIGHT)
+    cairo_set_source_rgba (cr, 1, 1, 1, .5);
+  else
+    cairo_set_source_rgba (cr, .8, .8, .8, .5);
+  cairo_fill_preserve (cr);
+
+  /* stroke border */
+  cairo_set_line_width(cr,1.0);
+  if(state == GTK_STATE_ACTIVE)
+    cairo_set_source_rgba (cr, .5, .6, .83, 1);
+  else
+    cairo_set_source_rgba (cr, .8, .8, .8, 1);
+  cairo_stroke(cr);
+
+  cairo_destroy(cr);
+
+  return TRUE;
+}
+
+static void make_panel(void){
+  int w=1024;
+  int h=150;
+  int rows = 4;
+
+  GtkWidget *toplevel=NULL;
+  GtkWidget *topbox=NULL;
+  GtkWidget *rightbox=NULL;
+  GtkWidget *leftbox=NULL;
+  GtkWidget *scrollfix=NULL;
+  GtkWidget *lefttable=NULL;
+  GtkWidget *upbutton=NULL;
+  GtkWidget *downbutton=NULL;
+  GtkWidget *panels[rows];
+
+  int i;
+
+  toplevel = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+  gtk_widget_set_app_paintable(toplevel, TRUE);
+  g_signal_connect(G_OBJECT(toplevel), "screen-changed", G_CALLBACK(screen_changed), NULL);
+  g_signal_connect(G_OBJECT(toplevel), "expose-event", G_CALLBACK(expose_toplevel), NULL);
+
+  /* toplevel is a fixed size, meant to be nailed to the screen in one
+     spot */
+  gtk_widget_set_size_request(GTK_WIDGET(toplevel),w,h);
+  gtk_window_set_resizable(GTK_WINDOW(toplevel),FALSE);
+  gtk_window_set_decorated(GTK_WINDOW(toplevel),FALSE);
+  gtk_window_set_gravity(GTK_WINDOW(toplevel),GDK_GRAVITY_SOUTH_WEST);
+  gtk_window_move (GTK_WINDOW(toplevel),0,gdk_screen_height()-1);
+
+  /* multiple sliding panes within */
+  topbox = gtk_hbox_new(0,0);
+  rightbox = gtk_vbox_new(0,0);
+  leftbox = gtk_hbox_new(1,0);
+  scrollfix = gtk_fixed_new();
+  lefttable = gtk_table_new(rows,1,1);
+  upbutton = gtk_button_new_with_label("^");
+  downbutton = gtk_button_new_with_label("v");
+
+  gtk_widget_set_size_request(GTK_WIDGET(upbutton),60,25);
+  gtk_widget_set_size_request(GTK_WIDGET(downbutton),60,25);
+
+  gtk_container_add(GTK_CONTAINER(toplevel),topbox);
+  gtk_container_set_border_width(GTK_CONTAINER(topbox),2);
+  gtk_box_pack_start(GTK_BOX(topbox),leftbox,1,1,0);
+  gtk_box_pack_end(GTK_BOX(topbox),rightbox,0,0,0);
+  gtk_box_pack_start(GTK_BOX(leftbox),scrollfix,1,1,0);
+  gtk_box_pack_start(GTK_BOX(rightbox),upbutton,0,0,0);
+  gtk_box_pack_end(GTK_BOX(rightbox),downbutton,0,0,0);
+
+  g_signal_connect(G_OBJECT(upbutton), "expose-event", G_CALLBACK(expose_upbutton), NULL);
+  g_signal_connect(G_OBJECT(downbutton), "expose-event", G_CALLBACK(expose_downbutton), NULL);
+
+  gtk_widget_set_size_request(lefttable,w-4,-1);
+  gtk_fixed_put(GTK_FIXED(scrollfix),lefttable,0,0);
+
+
+  /* build the sliding frame table */
+  int boxborder=10;
+  for(i=0;i<rows;i++){
+    GtkWidget *heightbox = gtk_hbox_new(0,0);
+    GtkWidget *heightforce = gtk_vbox_new(1,0);
+    GtkWidget *panelframe = gtk_frame_new("foo label");
+    GtkWidget *buttonbox = gtk_hbox_new(0,0);
+
+    gtk_table_attach(GTK_TABLE(lefttable),heightbox,0,1,i,i+1,
+                     GTK_EXPAND|GTK_FILL,0,0,0);
+    gtk_box_pack_start(GTK_BOX(heightbox),heightforce,0,0,0);
+    gtk_widget_set_size_request(heightforce,1,h-4);
+
+    gtk_widget_set_name(panelframe,"topframe");
+    gtk_frame_set_label_align(GTK_FRAME(panelframe),.5,.5);
+    gtk_container_set_border_width(GTK_CONTAINER(panelframe),boxborder);
+    gtk_frame_set_shadow_type(GTK_FRAME(panelframe),GTK_SHADOW_NONE);
+    gtk_box_pack_start(GTK_BOX(heightbox),panelframe,1,1,0);
+    gtk_container_add(GTK_CONTAINER(panelframe),buttonbox);
+    gtk_container_set_border_width(GTK_CONTAINER(buttonbox),boxborder);
+
+    GtkWidget *tmp=gtk_toggle_button_new_with_label("foo");
+    g_signal_connect(G_OBJECT(tmp), "expose-event", G_CALLBACK(expose_rectarea), NULL);
+    gtk_widget_set_size_request(tmp,h,-1);
+    gtk_box_pack_start(GTK_BOX(buttonbox),tmp,0,0,10);
+  }
+
+
+  screen_changed(toplevel, NULL, NULL);
+  gtk_widget_show_all(toplevel);
+}
+
+main(int argc, char **argv){
+  gtk_init (&argc, &argv);
+
+  gtk_rc_parse_string
+    ("style \"panel\" {"
+     "  bg[NORMAL]=\"#000000\""
+     "  bg[ACTIVE]=\"#000000\""
+     "  bg[PRELIGHT]=\"#000000\""
+     "  bg[SELECTED]=\"#000000\""
+     "  bg[INSENSITIVE]=\"#000000\""
+
+     "  fg[NORMAL]=\"#ffffff\""
+     "  fg[ACTIVE]=\"#ffffff\""
+     "  fg[PRELIGHT]=\"#ffffff\""
+     "  fg[SELECTED]=\"#000000\""
+     "  fg[INSENSITIVE]=\"#000000\""
+
+     "  base[NORMAL]=\"#000000\""
+     "  base[ACTIVE]=\"#000000\""
+     "  base[PRELIGHT]=\"#000000\""
+     "  base[SELECTED]=\"#000000\""
+     "  base[INSENSITIVE]=\"#000000\""
+
+     "  text[NORMAL]=\"#ffffff\""
+     "  text[ACTIVE]=\"#ffffff\""
+     "  text[PRELIGHT]=\"#ffffff\""
+     "  text[SELECTED]=\"#000000\""
+     "  text[INSENSITIVE]=\"#000000\""
+     "}"
+
+     "style \"topframe\" {"
+     "  font_name = \"sans 10 bold\""
+     "  fg[NORMAL]=\"#cccccc\""
+     "}"
+
+     "style \"button\" {"
+     "  font_name = \"sans 8\""
+     "  GtkButton::focus-padding = 0"
+     "  GtkButton::focus-line-width = 0"
+     "  GtkButton::interior-focus = 0"
+     "  bg[NORMAL]=\"#406090\""
+     "  bg[ACTIVE]=\"#6080a0\""
+     "  bg[PRELIGHT]=\"#6080a0\""
+     "  bg[SELECTED]=\"#d0e6ff\""
+     "  bg[INSENSITIVE]=\"#b0c0d8\""
+
+     "  fg[NORMAL]=\"#ffffff\""
+     "  fg[ACTIVE]=\"#ffffff\""
+     "  fg[PRELIGHT]=\"#ffffff\""
+     "  fg[SELECTED]=\"#000000\""
+     "  fg[INSENSITIVE]=\"#000000\""
+
+     "  base[NORMAL]=\"#000000\""
+     "  base[ACTIVE]=\"#000000\""
+     "  base[PRELIGHT]=\"#000000\""
+     "  base[SELECTED]=\"#000000\""
+     "  base[INSENSITIVE]=\"#000000\""
+
+     "  text[NORMAL]=\"#ffffff\""
+     "  text[ACTIVE]=\"#ffffff\""
+     "  text[PRELIGHT]=\"#ffffff\""
+     "  text[SELECTED]=\"#000000\""
+     "  text[INSENSITIVE]=\"#000000\""
+     "}"
+
+     "class \"*\" style \"panel\""
+     "widget \"*.topframe\" style \"topframe\""
+     "widget \"*.topframe.GtkLabel\" style \"topframe\""
+     "class \"GtkButton\" style \"button\""
+     );
+
+  make_panel();
+  gtk_main();
+
+
+}

Added: trunk/Xiph-episode-II/bounce/gtk-bounce.h
===================================================================
--- trunk/Xiph-episode-II/bounce/gtk-bounce.h	                        (rev 0)
+++ trunk/Xiph-episode-II/bounce/gtk-bounce.h	2013-02-24 22:04:10 UTC (rev 18812)
@@ -0,0 +1,52 @@
+#define _GNU_SOURCE
+#define _LARGEFILE_SOURCE
+#define _LARGEFILE64_SOURCE
+#define _FILE_OFFSET_BITS 64
+
+#ifndef _REENTRANT
+# define _REENTRANT
+#endif
+
+#include <math.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <gtk/gtk.h>
+
+extern sig_atomic_t exiting;
+extern int eventpipe[2];
+
+extern void *io_thread(void *dummy);
+
+extern sig_atomic_t request_bits;
+//extern sig_atomic_t request_ch;
+extern sig_atomic_t request_rate;
+
+extern sig_atomic_t request_ch1_quant;
+extern sig_atomic_t request_ch2_quant;
+extern sig_atomic_t prime_1kHz_notch;
+extern sig_atomic_t request_1kHz_notch;
+extern sig_atomic_t request_1kHz_sine;
+extern sig_atomic_t request_5kHz_sine;
+extern sig_atomic_t request_1kHz_sine2;
+extern sig_atomic_t request_1kHz_amplitude;
+extern sig_atomic_t request_1kHz_modulate;
+extern sig_atomic_t request_1kHz_notch;
+extern sig_atomic_t request_dither;
+extern sig_atomic_t request_dither_shaped;
+extern sig_atomic_t request_dither_amplitude;
+extern sig_atomic_t request_output_noise;
+extern sig_atomic_t request_output_sweep;
+extern sig_atomic_t request_output_logsweep;
+extern sig_atomic_t request_output_silence;
+
+extern sig_atomic_t request_lp_filter;
+extern sig_atomic_t request_1kHz_square;
+extern sig_atomic_t request_1kHz_square_offset;
+extern sig_atomic_t request_1kHz_square_spread;
+extern sig_atomic_t request_output_tone;
+extern sig_atomic_t request_output_duallisten;
+
+#define fromdB(x) (exp((x)*.11512925f))

Added: trunk/Xiph-episode-II/bounce/twopole.c
===================================================================
--- trunk/Xiph-episode-II/bounce/twopole.c	                        (rev 0)
+++ trunk/Xiph-episode-II/bounce/twopole.c	2013-02-24 22:04:10 UTC (rev 18812)
@@ -0,0 +1,80 @@
+/* specific filter recipes with thanks to Alex Matulich
+   http://unicorn.us.com/alex/2polefilters.html */
+
+#include <math.h>
+#include "twopole.h"
+
+void filter_make_bessel2(double w, int passes, pole2 *f){
+  double c = pow(pow(pow(2,(1./passes))-.75,-.5)-.5,-.5)/sqrt(3);
+  double wc = c*w;
+  double w0 = tan(M_PI*wc);
+  double g = 3;
+  double p = 3;
+  double K1 = p*w0;
+  double K2 = g*w0*w0;
+  double A0 = K2/(1+K1+K2);
+  double A1 = 2*A0;
+  double A2 = A0;
+  double B1 = 2*A0*(1/K2-1);
+  double B2 = 1-(A0+A1+A2+B1);
+
+  f->a = A0;
+  f->b1 = B1;
+  f->b2 = B2;
+  filter_set(f,0);
+}
+
+void filter_make_critical(double w, int passes, pole2 *f){
+  double c = pow(pow(2,(1./2*passes))-1,-.5);
+  double wc = c*w;
+  double w0 = tan(M_PI*wc);
+  double g = 1;
+  double p = 2;
+  double K1 = p*w0;
+  double K2 = g*w0*w0;
+  double A0 = K2/(1+K1+K2);
+  double A1 = 2*A0;
+  double A2 = A0;
+  double B1 = 2*A0*(1/K2-1);
+  double B2 = 1-(A0+A1+A2+B1);
+
+  f->a = A0;
+  f->b1 = B1;
+  f->b2 = B2;
+  filter_set(f,0);
+}
+
+void filter_make_butterworth(double w, int passes, pole2 *f){
+  double c = pow(pow(2,(1./passes))-1,-.25);
+  double wc = c*w;
+  double w0 = tan(M_PI*wc);
+  double g = 1;
+  double p = sqrt(2);
+  double K1 = p*w0;
+  double K2 = g*w0*w0;
+  double A0 = K2/(1+K1+K2);
+  double A1 = 2*A0;
+  double A2 = A0;
+  double B1 = 2*A0*(1/K2-1);
+  double B2 = 1-(A0+A1+A2+B1);
+
+  f->a = A0;
+  f->b1 = B1;
+  f->b2 = B2;
+  filter_set(f,0);
+}
+
+void filter_set(pole2 *p, double val){
+  p->x[0]=p->x[1]=val;
+  p->y[0]=p->y[1]=val;
+}
+
+
+double filter_filter(double x, pole2 *p){
+  double a = p->a;
+  double  y = a*x + 2*a*p->x[0] + a*p->x[1] + p->b1*p->y[0] + p->b2*p->y[1];
+  p->y[1] = p->y[0]; p->y[0] = y;
+  p->x[1] = p->x[0]; p->x[0] = x;
+
+  return y;
+}

Added: trunk/Xiph-episode-II/bounce/twopole.h
===================================================================
--- trunk/Xiph-episode-II/bounce/twopole.h	                        (rev 0)
+++ trunk/Xiph-episode-II/bounce/twopole.h	2013-02-24 22:04:10 UTC (rev 18812)
@@ -0,0 +1,21 @@
+/* specific filter recipes with thanks to Alex Matulich
+   http://unicorn.us.com/alex/2polefilters.html */
+
+#ifndef _TWOPOLE_H_
+#define _TWOPOLE_H_
+
+typedef struct {
+  double a;
+  double b1;
+  double b2;
+  double x[2];
+  double y[2];
+} pole2;
+
+extern void filter_set(pole2 *p, double val);
+extern void filter_make_bessel2(double w, int passes, pole2 *f);
+extern void filter_make_critical(double w, int passes, pole2 *f);
+extern void filter_make_butterworth(double w, int passes, pole2 *f);
+extern double filter_filter(double x, pole2 *p);
+
+#endif

Added: trunk/Xiph-episode-II/cairo/README
===================================================================
--- trunk/Xiph-episode-II/cairo/README	                        (rev 0)
+++ trunk/Xiph-episode-II/cairo/README	2013-02-24 22:04:10 UTC (rev 18812)
@@ -0,0 +1,14 @@
+These C files are quick little Cairo applications used to generate
+images for the various animations in the Xiph Episode II video
+"Digital Show & Tell".  Each one generates a series of PNG images that
+are then stitched togetehr into an animation using a utility like
+ffmpeg or mplayer, or an editor like Cinelerra.
+
+Compile each with the commandline:
+
+gcc inname.c -o outname -lm -lcairo -lfftw3f
+
+ch4-samplequant.c also requires the 'squishyio' library from Xiph.org
+SVN and the commandline:
+
+gcc ch4-samplequant.c -o ch4-samplequant -lm -lcairo -lfftw3f -lsquishyio
\ No newline at end of file

Added: trunk/Xiph-episode-II/cairo/ch3_convenient.c
===================================================================
--- trunk/Xiph-episode-II/cairo/ch3_convenient.c	                        (rev 0)
+++ trunk/Xiph-episode-II/cairo/ch3_convenient.c	2013-02-24 22:04:10 UTC (rev 18812)
@@ -0,0 +1,192 @@
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <math.h>
+#include <cairo/cairo.h>
+
+#define W 1920
+#define H 1080
+#define CR 540
+#define WH 300
+
+static int pstep=64;
+static int xstart=317;
+static int xbound=350;
+static int xend=1408;
+static int ystart=179;
+static int ybound=186;
+static int yend=910;
+
+#define OUT         48
+#define HOLD        72
+#define CIRCLE      24
+#define CIRCLEHOLD  72
+#define IN          18
+#define POST        48
+
+cairo_surface_t *read_frame(char *filename){
+  cairo_surface_t *ps=cairo_image_surface_create_from_png(filename);
+  cairo_status_t status = cairo_surface_status(ps);
+  if(!ps || status!=CAIRO_STATUS_SUCCESS){
+    fprintf(stderr,"CAIRO ERROR: Unable to load PNG file %s: %s\n\n",filename,cairo_status_to_string(status));
+    exit(1);
+  }
+  return ps;
+}
+
+void write_frame(cairo_surface_t *surface, int frameno){
+  char buffer[80];
+  cairo_status_t ret;
+
+  snprintf(buffer,80,"ch3_zero-%04d.png",frameno);
+  ret = cairo_surface_write_to_png(surface,buffer);
+  if(ret != CAIRO_STATUS_SUCCESS){
+    fprintf(stderr,"Could not write %s: %s\n",
+	    buffer,cairo_status_to_string(ret));
+    exit(1);
+  }
+}
+
+void clear_surface(cairo_surface_t *cs){
+  cairo_t *c = cairo_create(cs);
+  cairo_set_source_rgb(c,1.,1.,1.);
+  cairo_paint(c);
+  cairo_destroy(c);
+}
+
+void copy_surface(cairo_surface_t *s,cairo_surface_t *d){
+  cairo_t *c = cairo_create(d);
+  cairo_set_source_surface(c,s,0,0);
+  cairo_paint(c);
+  cairo_destroy(c);
+}
+
+void draw_squares(cairo_surface_t *cs, float delta){
+  cairo_t *c = cairo_create(cs);
+  double max = pstep*.5-1;
+  double this = max*delta;
+  int x,y;
+
+  cairo_set_line_width(c,this);
+  cairo_set_source_rgb(c,0,0,0);
+
+  for(x=xbound-pstep;x<xend;x+=pstep){
+    int x1=(x<xstart?xstart:x);
+    int x2=(x+pstep>xend?xend:x+pstep);
+    for(y=ybound-pstep;y<yend;y+=pstep){
+      int y1=(y<ystart?ystart:y);
+      int y2=(y+pstep>yend?yend:y+pstep);
+
+      cairo_save(c);
+
+      /* set up clip area */
+      cairo_rectangle(c,x1,y1,x2-x1,y2-y1);
+      cairo_clip(c);
+
+      /* draw */
+      cairo_rectangle(c, x+this/2., y+this/2. ,pstep-this,pstep-this);
+      cairo_stroke(c);
+
+      cairo_restore(c);
+    }
+  }
+
+  cairo_destroy(c);
+}
+
+void draw_circles(cairo_surface_t *cs, float delta){
+  cairo_t *c = cairo_create(cs);
+  int x,y;
+
+  cairo_set_line_width(c,3);
+  cairo_set_source_rgba(c,.6,.2,.2,delta);
+
+  for(x=xbound-pstep;x<xend;x+=pstep){
+    int x1=(x<xstart?xstart:x);
+    int x2=(x+pstep>xend?xend:x+pstep);
+    for(y=ybound-pstep;y<yend;y+=pstep){
+      int y1=(y<ystart?ystart:y);
+      int y2=(y+pstep>yend?yend:y+pstep);
+
+      cairo_save(c);
+
+      /* set up clip area */
+      cairo_rectangle(c,x1,y1,x2-x1,y2-y1);
+      cairo_clip(c);
+
+      /* draw */
+      cairo_arc(c,x+pstep/2.,y+pstep/2.,15,0,2*M_PI);
+      cairo_stroke(c);
+
+      cairo_restore(c);
+    }
+  }
+
+  cairo_destroy(c);
+}
+
+int main(){
+  cairo_surface_t *cs = cairo_image_surface_create (CAIRO_FORMAT_RGB24,
+						    W,H);
+  cairo_surface_t *ps = read_frame("../framegrabs/convenient-squares.png");
+  int i,count=0;
+
+  /* shrink squares */
+  for(i=0;i<OUT;i++){
+    copy_surface(ps,cs);
+    draw_squares(cs,(double)i/OUT);
+    write_frame(cs,count++);
+  }
+
+  /* hold */
+  copy_surface(ps,cs);
+  draw_squares(cs,1.);
+  for(i=0;i<HOLD;i++)
+    write_frame(cs,count++);
+
+  /* fade in circles */
+  for(i=0;i<CIRCLE;i++){
+    copy_surface(ps,cs);
+    draw_squares(cs,1.);
+    draw_circles(cs,(double)i/CIRCLE);
+    write_frame(cs,count++);
+  }
+
+  /* hold */
+  copy_surface(ps,cs);
+  draw_squares(cs,1.);
+  draw_circles(cs,1.);
+  for(i=0;i<HOLD;i++)
+    write_frame(cs,count++);
+
+  /* fade out circles */
+  for(i=0;i<CIRCLE;i++){
+    copy_surface(ps,cs);
+    draw_squares(cs,1.);
+    draw_circles(cs,1.-(double)i/CIRCLE);
+    write_frame(cs,count++);
+  }
+
+  /* hold */
+  copy_surface(ps,cs);
+  draw_squares(cs,1.);
+  for(i=0;i<HOLD;i++)
+    write_frame(cs,count++);
+
+  /* grow squares */
+  for(i=0;i<IN;i++){
+    copy_surface(ps,cs);
+    draw_squares(cs,1-(double)i/IN);
+    write_frame(cs,count++);
+  }
+
+  /* post */
+  copy_surface(ps,cs);
+  for(i=0;i<POST;i++)
+    write_frame(cs,count++);
+
+
+  cairo_surface_destroy(cs);
+  cairo_surface_destroy(ps);
+  return 0;
+}

Added: trunk/Xiph-episode-II/cairo/ch3_minutely.c
===================================================================
--- trunk/Xiph-episode-II/cairo/ch3_minutely.c	                        (rev 0)
+++ trunk/Xiph-episode-II/cairo/ch3_minutely.c	2013-02-24 22:04:10 UTC (rev 18812)
@@ -0,0 +1,310 @@
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <math.h>
+#include <cairo/cairo.h>
+
+#define W 1920
+#define H 1080
+#define CR 540
+#define WH 300
+
+#define TEXTY 90
+#define TEXTY2 75
+#define TEXT_FONT_SIZE 60
+
+#define SAMPLES 18
+#define CYCLES  1
+
+#define PRE        108
+#define DRAW       72
+#define DRAWHOLD   2
+#define CIRCLE     6
+#define CIRCLEHOLD 12
+
+
+#define POST       240
+
+
+#define AXIS_LINE_WIDTH 6
+#define AXIS_COLOR 0,0,0,1
+#define AXIS_FONT_SIZE 54
+
+#define LINE_WIDTH 15
+#define COLOR .4,.4,.4,1
+
+#define ARROW_LINE_WIDTH 6
+#define ARROW_COLOR 1,0,0
+
+#define WLINE_WIDTH 15
+#define WCOLOR 1,.2,.2,.8
+#define DCOLOR .2,.2,1,1
+#define CCOLOR .8,0,0,1
+
+
+void write_frame(cairo_surface_t *surface, int frameno){
+  char buffer[80];
+  cairo_status_t ret;
+
+  snprintf(buffer,80,"ch3_minute-%04d.png",frameno);
+  ret = cairo_surface_write_to_png(surface,buffer);
+  if(ret != CAIRO_STATUS_SUCCESS){
+    fprintf(stderr,"Could not write %s: %s\n",
+	    buffer,cairo_status_to_string(ret));
+    exit(1);
+  }
+}
+
+void clear_surface(cairo_surface_t *cs){
+  cairo_t *c = cairo_create(cs);
+  cairo_set_source_rgb(c,1.,1.,1.);
+  cairo_paint(c);
+  cairo_destroy(c);
+}
+
+void transparent_surface(cairo_surface_t *cs){
+  cairo_t *c = cairo_create(cs);
+  cairo_set_operator(c,CAIRO_OPERATOR_CLEAR);
+  cairo_paint(c);
+  cairo_destroy(c);
+}
+
+double get_y(double x,double f){
+  return CR + WH*sin((double)x/W*CYCLES*f*2*M_PI);
+}
+
+int tosample(double x){
+  double SW = (double)W/SAMPLES;
+  return rint(x/SW);
+}
+
+int tox(int sample){
+  double SW = (double)W/SAMPLES;
+  return rint(sample*SW);
+}
+
+double get_dy(double x,double f){
+  double s = (double)x*SAMPLES/W;
+  double y = sin((double)x/W*CYCLES*f*2*M_PI);
+  if(s>10 && s<11){
+    double d = sin((s-10)*M_PI);
+    y += .1 * d*d;
+  }
+  return CR+WH*y;
+}
+
+void draw_axis(cairo_surface_t *cs, double del){
+  int i;
+  cairo_t *c = cairo_create(cs);
+  double SW = 105;
+  cairo_text_extents_t extents;
+  cairo_font_face_t *ff;
+
+  cairo_set_source_rgba(c,AXIS_COLOR*del);
+  cairo_set_line_width(c,AXIS_LINE_WIDTH);
+  cairo_move_to(c,0,CR);
+  cairo_line_to(c,W,CR);
+  cairo_move_to(c,W-SW/2,CR-SW/3);
+  cairo_line_to(c,W,CR);
+  cairo_line_to(c,W-SW/2,CR+SW/3);
+  cairo_stroke(c);
+
+  cairo_set_line_width(c,AXIS_LINE_WIDTH*.75);
+
+  for(i=0;i<SAMPLES;i++){
+    double x = tox(i);
+    cairo_move_to(c,x,CR-AXIS_LINE_WIDTH*2);
+    cairo_line_to(c,x,CR+AXIS_LINE_WIDTH*2);
+  }
+  cairo_stroke(c);
+
+
+  ff = cairo_toy_font_face_create ("Adobe Garamond",
+                                   CAIRO_FONT_SLANT_ITALIC,
+                                   CAIRO_FONT_WEIGHT_NORMAL);
+  if(!ff){
+    fprintf(stderr,"Unable to create axis font");
+    exit(1);
+  }
+  cairo_set_font_face(c,ff);
+  cairo_set_font_size(c, AXIS_FONT_SIZE);
+  cairo_text_extents(c, "time", &extents);
+  cairo_move_to(c, W-extents.width-SW*.6, CR + AXIS_FONT_SIZE);
+  cairo_show_text(c, "time");
+
+  cairo_font_face_destroy(ff);
+  cairo_destroy(c);
+}
+
+void draw_waveform(cairo_surface_t *cs, double del, double f){
+  int i;
+  cairo_t *c = cairo_create(cs);
+
+  /* draw sine */
+  cairo_set_source_rgba(c,WCOLOR);
+  cairo_set_line_cap(c,CAIRO_LINE_CAP_ROUND);
+  cairo_set_line_join(c,CAIRO_LINE_JOIN_ROUND);
+  cairo_set_line_width(c,WLINE_WIDTH);
+
+  for(i=-WLINE_WIDTH;i<(W*del)+WLINE_WIDTH;i++){
+    double y = get_y(i,f);
+    if(!i)
+      cairo_move_to(c,i,y);
+    else
+      cairo_line_to(c,i,y);
+  }
+  cairo_stroke(c);
+
+  cairo_destroy(c);
+}
+
+void draw_different(cairo_surface_t *cs, double del, double f){
+  int i;
+  cairo_t *c = cairo_create(cs);
+
+  /* draw sine */
+  cairo_set_source_rgba(c,DCOLOR);
+  cairo_set_line_cap(c,CAIRO_LINE_CAP_ROUND);
+  cairo_set_line_join(c,CAIRO_LINE_JOIN_ROUND);
+  cairo_set_line_width(c,WLINE_WIDTH);
+
+  for(i=-WLINE_WIDTH;i<(W*del)+WLINE_WIDTH;i++){
+    double y = get_dy(i,f);
+    if(!i)
+      cairo_move_to(c,i,y);
+    else
+      cairo_line_to(c,i,y);
+  }
+  cairo_stroke(c);
+
+  cairo_destroy(c);
+}
+
+void draw_circle(cairo_surface_t *cs, double del, double f){
+  int i;
+  cairo_t *c = cairo_create(cs);
+
+  /* draw sine */
+  cairo_set_source_rgba(c,CCOLOR);
+  cairo_set_line_cap(c,CAIRO_LINE_CAP_ROUND);
+  cairo_set_line_join(c,CAIRO_LINE_JOIN_ROUND);
+  cairo_set_line_width(c,WLINE_WIDTH/2);
+
+  cairo_translate(c,W*10.5/SAMPLES,CR-WH*.95);
+  cairo_scale(c,.8,1);
+  for(i=0;i<del*1000;i++)
+    cairo_arc_negative(c,0,0,WH*(.3+.05*(i/1000.)),-i/800.*2*M_PI- M_PI/3,-(i+1)/800.*2*M_PI-M_PI/3);
+  cairo_stroke(c);
+
+  cairo_destroy(c);
+}
+
+void draw_stems(cairo_surface_t *cs, double del, double f){
+  int i;
+  cairo_t *c = cairo_create(cs);
+
+  /* draw lollipops */
+  cairo_set_source_rgba(c,COLOR*del*.8);
+
+  for(i=1;i<SAMPLES;i++){
+    int sx = tox(i);
+    double y = get_y(sx,f);
+
+    cairo_set_line_cap(c,CAIRO_LINE_CAP_BUTT);
+    cairo_set_line_width(c,LINE_WIDTH*.75);
+    cairo_move_to(c,sx,CR);
+    cairo_line_to(c,sx,y);
+    cairo_stroke(c);
+  }
+
+  cairo_destroy(c);
+}
+
+void draw_samples(cairo_surface_t *cs, double del, double f){
+  int i;
+  cairo_t *c = cairo_create(cs);
+
+  /* draw lollipops */
+  for(i=1;i<SAMPLES;i++){
+    int sx = tox(i);
+    double y = get_y(sx,f);
+
+    cairo_set_source_rgba(c,COLOR*del*.4);
+    cairo_set_line_width(c,LINE_WIDTH/3);
+    cairo_arc(c,sx,y,LINE_WIDTH*2,0,2*M_PI);
+    cairo_stroke(c);
+
+    cairo_set_source_rgba(c,COLOR*del);
+    cairo_set_line_cap(c,CAIRO_LINE_CAP_ROUND);
+    cairo_set_line_width(c,LINE_WIDTH*2);
+    cairo_line_to(c,sx,y);
+    cairo_line_to(c,sx,y+.001);
+    cairo_stroke(c);
+  }
+
+  cairo_destroy(c);
+}
+
+int main(){
+  cairo_surface_t *cs = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
+						    W,H);
+
+  int i,count=0;
+  double m,base_f = 44100. / SAMPLES * CYCLES;
+  double f = 20000;
+  m = f/base_f;
+
+#if 0
+  /* transparent 20kHz without the waveform */
+
+  transparent_surface(cs);
+  draw_stems(cs,1.,m);
+  draw_axis(cs,1.);
+  draw_samples(cs,1.,m);
+  draw_waveform(cs,1.,m);
+  write_frame(cs,9998);
+
+  transparent_surface(cs);
+  draw_stems(cs,1.,m);
+  draw_axis(cs,1.);
+  draw_samples(cs,1.,m);
+  write_frame(cs,9999);
+
+#else
+
+  /* static preroll */
+  transparent_surface(cs);
+  //draw_stems(cs,1.,m);
+  //draw_axis(cs,1.);
+  //draw_samples(cs,1.,m);
+  //draw_waveform(cs,1.,m);
+  for(i=0;i<PRE;i++){
+    write_frame(cs,count++);
+  }
+
+  /* draw 'different' waveform */
+  int circlecount=0;
+  for(i=0;i<DRAW;i++){
+    double s = (float)i/DRAW*SAMPLES;
+    transparent_surface(cs);
+    //draw_stems(cs,1.,m);
+    //draw_axis(cs,1.);
+    //draw_samples(cs,1.,m);
+    //draw_waveform(cs,1.,m);
+    draw_different(cs,(i+.5)/DRAW,m);
+    if(s>=14){
+      draw_circle(cs,(circlecount+.5)/CIRCLE,m);
+      if(circlecount+1<CIRCLE)
+        circlecount++;
+    }
+    write_frame(cs,count++);
+  }
+
+  for(i=0;i<POST;i++){
+    write_frame(cs,count++);
+  }
+
+#endif
+  cairo_surface_destroy(cs);
+  return 0;
+}

Added: trunk/Xiph-episode-II/cairo/ch3_samples.c
===================================================================
--- trunk/Xiph-episode-II/cairo/ch3_samples.c	                        (rev 0)
+++ trunk/Xiph-episode-II/cairo/ch3_samples.c	2013-02-24 22:04:10 UTC (rev 18812)
@@ -0,0 +1,526 @@
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <math.h>
+#include <cairo/cairo.h>
+#include <pango/pangocairo.h>
+
+#define W 1920
+#define H 1080
+#define CR 540
+#define WH 300
+
+#define TEXTY 140
+#define TEXTY2 140
+#define TEXT_FONT_SIZE 90
+
+#define SAMPLES 18
+#define CYCLES  1
+
+#define PRE       108
+#define FADE      12
+#define ARROW     76
+#define BRACKETS  72
+#define MID       160
+#define WAVEFADE  48
+#define WAVEFORM  48
+#define KHZ_UP    72
+#define KHZ_20    160
+
+#define AXIS_LINE_WIDTH 6
+#define AXIS_COLOR 0,0,0,1
+#define AXIS_FONT_SIZE 54
+
+#define LINE_WIDTH 15
+#define COLOR .4,.4,.4,1
+
+#define ARROW_LINE_WIDTH 6
+#define ARROW_COLOR 1,0,0
+
+#define WLINE_WIDTH 15
+#define WCOLOR 1,.2,.2,.8
+
+
+void write_frame(cairo_surface_t *surface, int frameno){
+  char buffer[80];
+  cairo_status_t ret;
+
+  snprintf(buffer,80,"ch3_samples-%04d.png",frameno);
+  ret = cairo_surface_write_to_png(surface,buffer);
+  if(ret != CAIRO_STATUS_SUCCESS){
+    fprintf(stderr,"Could not write %s: %s\n",
+	    buffer,cairo_status_to_string(ret));
+    exit(1);
+  }
+}
+
+void clear_surface(cairo_surface_t *cs){
+  cairo_t *c = cairo_create(cs);
+  cairo_set_source_rgb(c,1.,1.,1.);
+  cairo_paint(c);
+  cairo_destroy(c);
+}
+
+double get_y(double x,double f){
+  return CR + WH*sin((double)x/W*CYCLES*f*2*M_PI);
+}
+
+int tosample(double x){
+  double SW = (double)W/SAMPLES;
+  return rint(x/SW);
+}
+
+int tox(int sample){
+  double SW = (double)W/SAMPLES;
+  return rint(sample*SW);
+}
+
+void draw_axis(cairo_surface_t *cs, double del){
+  int i;
+  cairo_t *c = cairo_create(cs);
+  double SW = 105;
+  cairo_text_extents_t extents;
+  cairo_font_face_t *ff;
+
+  cairo_set_source_rgba(c,AXIS_COLOR*del);
+  cairo_set_line_width(c,AXIS_LINE_WIDTH);
+  cairo_move_to(c,0,CR);
+  cairo_line_to(c,W,CR);
+  cairo_move_to(c,W-SW/2,CR-SW/3);
+  cairo_line_to(c,W,CR);
+  cairo_line_to(c,W-SW/2,CR+SW/3);
+  cairo_stroke(c);
+
+  cairo_set_line_width(c,AXIS_LINE_WIDTH*.75);
+
+  for(i=0;i<SAMPLES;i++){
+    double x = tox(i);
+    cairo_move_to(c,x,CR-AXIS_LINE_WIDTH*2);
+    cairo_line_to(c,x,CR+AXIS_LINE_WIDTH*2);
+  }
+  cairo_stroke(c);
+
+
+  ff = cairo_toy_font_face_create ("Adobe Garamond",
+                                   CAIRO_FONT_SLANT_ITALIC,
+                                   CAIRO_FONT_WEIGHT_NORMAL);
+  if(!ff){
+    fprintf(stderr,"Unable to create axis font");
+    exit(1);
+  }
+  cairo_set_font_face(c,ff);
+  cairo_set_font_size(c, AXIS_FONT_SIZE);
+  cairo_text_extents(c, "time", &extents);
+  cairo_move_to(c, W-extents.width-SW*.6, CR + AXIS_FONT_SIZE);
+  cairo_show_text(c, "time");
+
+  cairo_font_face_destroy(ff);
+  cairo_destroy(c);
+}
+
+void draw_waveform(cairo_surface_t *cs, double del, double f){
+  int i;
+  cairo_t *c = cairo_create(cs);
+
+  /* draw sine */
+  cairo_set_source_rgba(c,WCOLOR);
+  cairo_set_line_cap(c,CAIRO_LINE_CAP_ROUND);
+  cairo_set_line_join(c,CAIRO_LINE_JOIN_ROUND);
+  cairo_set_line_width(c,WLINE_WIDTH);
+
+  for(i=-WLINE_WIDTH;i<(W*del)+WLINE_WIDTH;i++){
+    double y = get_y(i,f);
+    if(!i)
+      cairo_move_to(c,i,y);
+    else
+      cairo_line_to(c,i,y);
+  }
+  cairo_stroke(c);
+
+  cairo_destroy(c);
+}
+
+void draw_stems(cairo_surface_t *cs, double del, double f){
+  int i;
+  cairo_t *c = cairo_create(cs);
+
+  /* draw lollipops */
+  cairo_set_source_rgba(c,COLOR*del*.8);
+
+  for(i=1;i<SAMPLES;i++){
+    int sx = tox(i);
+    double y = get_y(sx,f);
+
+    cairo_set_line_cap(c,CAIRO_LINE_CAP_BUTT);
+    cairo_set_line_width(c,LINE_WIDTH*.75);
+    cairo_move_to(c,sx,CR);
+    cairo_line_to(c,sx,y);
+    cairo_stroke(c);
+  }
+
+  cairo_destroy(c);
+}
+
+void draw_samples(cairo_surface_t *cs, double del, double f){
+  int i;
+  cairo_t *c = cairo_create(cs);
+
+  /* draw lollipops */
+  for(i=1;i<SAMPLES;i++){
+    int sx = tox(i);
+    double y = get_y(sx,f);
+
+    cairo_set_source_rgba(c,COLOR*del*.4);
+    cairo_set_line_width(c,LINE_WIDTH/3);
+    cairo_arc(c,sx,y,LINE_WIDTH*2,0,2*M_PI);
+    cairo_stroke(c);
+
+    cairo_set_source_rgba(c,COLOR*del);
+    cairo_set_line_cap(c,CAIRO_LINE_CAP_ROUND);
+    cairo_set_line_width(c,LINE_WIDTH*2);
+    cairo_line_to(c,sx,y);
+    cairo_line_to(c,sx,y+.001);
+    cairo_stroke(c);
+  }
+
+  cairo_destroy(c);
+}
+
+void draw_arrows(cairo_surface_t *cs,double del){
+  int i;
+  cairo_t *c = cairo_create(cs);
+
+  cairo_set_line_cap(c,CAIRO_LINE_CAP_BUTT);
+  cairo_set_line_join(c,CAIRO_LINE_JOIN_MITER);
+  cairo_set_source_rgba(c,ARROW_COLOR,del);
+
+  for(i=1;i<SAMPLES;i++){
+    int x = tox(i);
+    double y = get_y(x,1.);
+
+    //if(y>CR)y=CR;
+
+
+    cairo_set_line_width(c,ARROW_LINE_WIDTH+3);
+    cairo_set_source_rgba(c,1.,1.,1.,del);
+    cairo_move_to(c,x,y-LINE_WIDTH*5);
+    cairo_line_to(c,x,y-LINE_WIDTH*5-ARROW_LINE_WIDTH*10-1.5);
+
+    cairo_move_to(c,x-ARROW_LINE_WIDTH*3,
+                  y-LINE_WIDTH*5-ARROW_LINE_WIDTH*4);
+    cairo_line_to(c,x,y-LINE_WIDTH*5);
+    cairo_line_to(c,x+ARROW_LINE_WIDTH*3,
+                  y-LINE_WIDTH*5-ARROW_LINE_WIDTH*4);
+
+
+    cairo_stroke(c);
+
+    cairo_set_line_width(c,ARROW_LINE_WIDTH);
+    cairo_set_source_rgba(c,ARROW_COLOR,del);
+    cairo_move_to(c,x,y-LINE_WIDTH*5);
+    cairo_line_to(c,x,y-LINE_WIDTH*5-ARROW_LINE_WIDTH*10);
+
+    cairo_move_to(c,x-ARROW_LINE_WIDTH*3,
+                  y-LINE_WIDTH*5-ARROW_LINE_WIDTH*4);
+    cairo_line_to(c,x,y-LINE_WIDTH*5);
+    cairo_line_to(c,x+ARROW_LINE_WIDTH*3,
+                  y-LINE_WIDTH*5-ARROW_LINE_WIDTH*4);
+
+    cairo_stroke(c);
+  }
+
+  cairo_set_line_cap(c,CAIRO_LINE_CAP_ROUND);
+  cairo_set_line_width(c,LINE_WIDTH*2);
+
+  for(i=1;i<SAMPLES;i++){
+    int x = tox(i);
+    double y = get_y(x,1.);
+
+    cairo_move_to(c,x,y);
+    cairo_line_to(c,x,y+.001);
+  }
+  cairo_stroke(c);
+
+  cairo_destroy(c);
+}
+
+void draw_brackets(cairo_surface_t *cs, double del){
+  int i;
+  cairo_t *c = cairo_create(cs);
+  double r = 10;
+  cairo_font_face_t *ff;
+  cairo_text_extents_t extents;
+
+  cairo_set_line_cap(c,CAIRO_LINE_CAP_BUTT);
+  cairo_set_line_join(c,CAIRO_LINE_JOIN_MITER);
+  cairo_set_line_width(c,ARROW_LINE_WIDTH);
+  cairo_set_source_rgba(c,ARROW_COLOR,del);
+
+  for(i=0;i<SAMPLES;i++){
+    double x0 = tox(i)+LINE_WIDTH;
+    double x1 = tox(i+1)-LINE_WIDTH;
+    double y = CR+WH+ARROW_LINE_WIDTH*10;
+
+    cairo_move_to(c,x0,y);
+    cairo_line_to(c,x1,y);
+
+    cairo_move_to(c,x0+ARROW_LINE_WIDTH*4,y-ARROW_LINE_WIDTH*2.5);
+    cairo_line_to(c,x0,y);
+    cairo_line_to(c,x0+ARROW_LINE_WIDTH*4,y+ARROW_LINE_WIDTH*2.5);
+
+    cairo_move_to(c,x1-ARROW_LINE_WIDTH*4,y-ARROW_LINE_WIDTH*2.5);
+    cairo_line_to(c,x1,y);
+    cairo_line_to(c,x1-ARROW_LINE_WIDTH*4,y+ARROW_LINE_WIDTH*2.5);
+    cairo_stroke(c);
+  }
+
+  ff = cairo_toy_font_face_create ("Adobe Garamond",
+                                   CAIRO_FONT_SLANT_NORMAL,
+                                   CAIRO_FONT_WEIGHT_BOLD);
+  if(!ff){
+    fprintf(stderr,"Unable to create cursor font");
+    exit(1);
+  }
+  cairo_set_font_face(c,ff);
+  cairo_set_font_size(c, TEXT_FONT_SIZE);
+  cairo_text_extents(c, "undefined", &extents);
+  double TH = (TEXTY-extents.height)*2 + extents.height;
+  cairo_move_to(c, (W-extents.x_advance)/2, H-TH+TEXTY2+ARROW_LINE_WIDTH*5);
+
+  cairo_show_text(c, "unde\xEF\xAC\x81ned");
+
+  cairo_destroy(c);
+}
+
+void draw_regions(cairo_surface_t *cs, double del){
+  int i;
+  cairo_t *c = cairo_create(cs);
+
+  cairo_set_line_width(c,2);
+
+  for(i=0;i<SAMPLES;i++){
+    double x0 = tox(i)+LINE_WIDTH/2;
+    double x1 = tox(i+1)-LINE_WIDTH/2;
+    double y0 = CR-WH-ARROW_LINE_WIDTH*15;
+    double y1 = CR+WH+ARROW_LINE_WIDTH*15;
+
+    cairo_set_source_rgba(c,ARROW_COLOR,del*.1);
+    cairo_move_to(c,x0,y0);
+    cairo_line_to(c,x0,y1);
+    cairo_line_to(c,x1,y1);
+    cairo_line_to(c,x1,y0);
+    cairo_close_path(c);
+    cairo_fill(c);
+
+    cairo_set_source_rgba(c,ARROW_COLOR,del);
+    cairo_move_to(c,x0,y0);
+    cairo_line_to(c,x0,y1);
+    cairo_move_to(c,x1,y0);
+    cairo_line_to(c,x1,y1);
+    cairo_stroke(c);
+  }
+
+  cairo_destroy(c);
+
+}
+
+void draw_readout(cairo_surface_t *cs, double alpha, double f){
+  char buffer[80];
+
+  cairo_t *c = cairo_create(cs);
+  cairo_font_face_t *ff;
+  cairo_font_face_t *ft;
+  cairo_text_extents_t extents;
+  cairo_text_extents_t extents2;
+  cairo_text_extents_t extents3;
+
+  ff = cairo_toy_font_face_create ("Liberation Sans",
+                                   CAIRO_FONT_SLANT_NORMAL,
+                                   CAIRO_FONT_WEIGHT_BOLD);
+  ft = cairo_toy_font_face_create ("Adobe Garamond",
+                                   CAIRO_FONT_SLANT_NORMAL,
+                                   CAIRO_FONT_WEIGHT_NORMAL);
+  if(!ff){
+    fprintf(stderr,"Unable to create cursor font");
+    exit(1);
+  }
+  cairo_set_font_face(c,ft);
+  cairo_set_font_size(c, TEXT_FONT_SIZE);
+
+  cairo_text_extents(c, "frequency: ", &extents);
+  cairo_text_extents(c, "kHz", &extents3);
+
+  cairo_set_font_face(c,ff);
+  cairo_text_extents(c, "10", &extents2);
+
+  double TH = (TEXTY-extents2.height)*2 + extents2.height;
+
+  cairo_set_line_width(c,2);
+
+  cairo_set_font_face(c,ft);
+  cairo_move_to(c, (W-extents.x_advance-extents2.x_advance-extents3.x_advance)/2, TEXTY);
+  cairo_set_source_rgba(c,0,0,0,alpha);
+  cairo_show_text(c, "frequency: ");
+
+  cairo_set_font_face(c,ff);
+  snprintf(buffer, 80,"%d",(int)(f+.001));
+  cairo_set_source_rgba(c,.8,0,0,alpha);
+  cairo_show_text(c, buffer);
+  cairo_set_source_rgba(c,0,0,0,alpha);
+  cairo_set_font_face(c,ft);
+  cairo_show_text(c, "kHz");
+
+  cairo_text_extents(c, "sampling rate: 44.1kHz", &extents);
+
+  cairo_move_to(c, (W-extents.x_advance)/2, H-TH+TEXTY2);
+  cairo_show_text(c, "sampling rate: 44.1kHz");
+
+  cairo_font_face_destroy(ff);
+  cairo_destroy(c);
+}
+
+int main(){
+  cairo_surface_t *cs = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
+						    W,H);
+
+  int i,count=0;
+
+  /* static preroll */
+  for(i=0;i<PRE;i++){
+    clear_surface(cs);
+    draw_stems(cs,1.,1.);
+    draw_axis(cs,1.);
+    draw_samples(cs,1.,1.);
+    write_frame(cs,count++);
+  }
+
+  /* fade in arrows */
+  for(i=0;i<FADE;i++){
+    clear_surface(cs);
+    draw_stems(cs,1.,1.);
+    draw_axis(cs,1.);
+    draw_samples(cs,1.,1.);
+    draw_arrows(cs,(double)i/FADE);
+    write_frame(cs,count++);
+  }
+
+  /* hold arrows */
+  for(i=0;i<ARROW;i++){
+    clear_surface(cs);
+    draw_stems(cs,1.,1.);
+    draw_axis(cs,1.);
+    draw_samples(cs,1.,1.);
+    draw_arrows(cs,1.);
+    write_frame(cs,count++);
+  }
+
+  /* fade out arrows, fade in brackets */
+  for(i=0;i<FADE;i++){
+    clear_surface(cs);
+    draw_brackets(cs,(double)i/FADE);
+    draw_stems(cs,1.,1.);
+    draw_axis(cs,1.);
+    draw_samples(cs,1.,1.);
+    draw_arrows(cs,1-(double)i/FADE);
+    write_frame(cs,count++);
+  }
+
+  /* fade in regions */
+  for(i=0;i<FADE;i++){
+    clear_surface(cs);
+    draw_regions(cs,(double)i/FADE);
+    draw_brackets(cs,1.);
+    draw_stems(cs,1.,1.);
+    draw_axis(cs,1.);
+    draw_samples(cs,1.,1.);
+    write_frame(cs,count++);
+  }
+
+  /* hold */
+  for(i=0;i<BRACKETS;i++){
+    clear_surface(cs);
+    draw_regions(cs,1.);
+    draw_brackets(cs,1.);
+    draw_stems(cs,1.,1.);
+    draw_axis(cs,1.);
+    draw_samples(cs,1.,1.);
+    write_frame(cs,count++);
+  }
+
+  /* fade out regions, brackets */
+  for(i=0;i<FADE;i++){
+    clear_surface(cs);
+    draw_regions(cs,1.-(double)i/FADE);
+    draw_brackets(cs,1.-(double)i/FADE);
+    draw_stems(cs,1.,1.);
+    draw_axis(cs,1.);
+    draw_samples(cs,1.,1.);
+    write_frame(cs,count++);
+  }
+
+  /* mid */
+  for(i=0;i<MID;i++){
+    clear_surface(cs);
+    draw_stems(cs,1.,1.);
+    draw_axis(cs,1.);
+    draw_samples(cs,1.,1.);
+    write_frame(cs,count++);
+  }
+
+  /* fade in waveform */
+  for(i=0;i<WAVEFADE;i++){
+    clear_surface(cs);
+    draw_stems(cs,1.,1.);
+    draw_axis(cs,1.);
+    draw_samples(cs,1.,1.);
+    draw_waveform(cs,(double)i/WAVEFADE,1.);
+    write_frame(cs,count++);
+  }
+
+  /* hold waveform */
+  for(i=0;i<WAVEFORM;i++){
+    clear_surface(cs);
+    draw_stems(cs,1.,1.);
+    draw_axis(cs,1.);
+    draw_samples(cs,1.,1.);
+    draw_waveform(cs,1.,1.);
+    write_frame(cs,count++);
+  }
+
+  /* fade in and advance to 20kHz */
+  for(i=0;i<KHZ_UP;i++){
+    double alpha = (double)i/FADE;
+    double m,base_f = 44100. / SAMPLES * CYCLES;
+    double f = sin((i+.5)/KHZ_UP*M_PI/2.);
+    f = sin(f*M_PI/2.);
+    f = base_f + (20000.-base_f)*f*f;
+    m = f/base_f;
+    if(alpha>1.)alpha=1.;
+
+    clear_surface(cs);
+    draw_stems(cs,1,m);
+    draw_axis(cs,1.0);
+    draw_readout(cs,alpha,rint(f/1000.));
+    draw_samples(cs,1,m);
+    draw_waveform(cs,1.0,m);
+    write_frame(cs,count++);
+  }
+
+  /* hold 20kHz */
+  for(i=0;i<KHZ_20;i++){
+    double m,base_f = 44100. / SAMPLES * CYCLES;
+    double f = 20000;
+    m = f/base_f;
+
+    clear_surface(cs);
+    draw_stems(cs,1.,m);
+    draw_axis(cs,1.);
+    draw_readout(cs,1.,rint(f/1000.));
+    draw_samples(cs,1.,m);
+    draw_waveform(cs,1.,m);
+    write_frame(cs,count++);
+  }
+
+  cairo_surface_destroy(cs);
+  return 0;
+}

Added: trunk/Xiph-episode-II/cairo/ch3_stairsteps.c
===================================================================
--- trunk/Xiph-episode-II/cairo/ch3_stairsteps.c	                        (rev 0)
+++ trunk/Xiph-episode-II/cairo/ch3_stairsteps.c	2013-02-24 22:04:10 UTC (rev 18812)
@@ -0,0 +1,378 @@
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <math.h>
+#include <cairo/cairo.h>
+
+#define W 1920
+#define H 1080
+#define CR 540
+#define WH 300
+
+#define TEXTY 140
+#define TEXT_FONT_SIZE 90
+
+#define SAMPLES 18
+#define CYCLES  1
+
+#define PRE     96
+#define FRAMES  128
+#define POST    48
+#define FADE          24
+#define FADEMARGIN    2
+
+#define AXIS_LINE_WIDTH 6
+#define AXIS_COLOR 0,0,0,1
+#define AXIS_FONT_SIZE 54
+
+#define FIRST_LINE_WIDTH 15
+#define FIRST_COLOR .6,.6,.6,1
+
+#define SECOND_LINE_WIDTH 15
+#define SECOND_COLOR .8,.4,.4,1
+#define HALO_TAIL 60
+#define HALO 15
+
+void write_frame(cairo_surface_t *surface, int frameno){
+  char buffer[80];
+  cairo_status_t ret;
+
+  snprintf(buffer,80,"ch3_stairstep-%04d.png",frameno);
+  ret = cairo_surface_write_to_png(surface,buffer);
+  if(ret != CAIRO_STATUS_SUCCESS){
+    fprintf(stderr,"Could not write %s: %s\n",
+	    buffer,cairo_status_to_string(ret));
+    exit(1);
+  }
+}
+
+void clear_surface(cairo_surface_t *cs){
+  cairo_t *c = cairo_create(cs);
+  cairo_set_source_rgb(c,1.,1.,1.);
+  cairo_paint(c);
+  cairo_destroy(c);
+}
+
+double get_y(double x){
+  return CR + rint(WH*sin((double)x/W*CYCLES*2*M_PI));
+}
+
+int tox(int sample){
+  double SW = (double)W/SAMPLES;
+  return rint(sample*SW);
+}
+
+void draw_axis(cairo_surface_t *cs){
+  int i;
+  cairo_t *c = cairo_create(cs);
+  double SW = 105;
+  cairo_text_extents_t extents;
+  cairo_font_face_t *ff;
+
+  cairo_set_source_rgba(c,AXIS_COLOR);
+  cairo_set_line_width(c,AXIS_LINE_WIDTH);
+  cairo_move_to(c,0,CR);
+  cairo_line_to(c,W,CR);
+  cairo_move_to(c,W-SW/2,CR-SW/3);
+  cairo_line_to(c,W,CR);
+  cairo_line_to(c,W-SW/2,CR+SW/3);
+  cairo_stroke(c);
+
+  cairo_set_line_width(c,AXIS_LINE_WIDTH*.75);
+
+  for(i=0;i<SAMPLES;i++){
+    double x = tox(i);
+    cairo_move_to(c,x,CR-AXIS_LINE_WIDTH*2);
+    cairo_line_to(c,x,CR+AXIS_LINE_WIDTH*2);
+  }
+  cairo_stroke(c);
+
+
+  ff = cairo_toy_font_face_create ("Adobe Garamond",
+                                   CAIRO_FONT_SLANT_ITALIC,
+                                   CAIRO_FONT_WEIGHT_NORMAL);
+  if(!ff){
+    fprintf(stderr,"Unable to create axis font");
+    exit(1);
+  }
+  cairo_set_font_face(c,ff);
+  cairo_set_font_size(c, AXIS_FONT_SIZE);
+  cairo_text_extents(c, "time", &extents);
+  cairo_move_to(c, W-extents.width-SW*.6, CR + AXIS_FONT_SIZE);
+  cairo_show_text(c, "time");
+
+  cairo_font_face_destroy(ff);
+  cairo_destroy(c);
+}
+
+double sadj(double x){
+  if(x<.2)
+    return sin(x*2.5*M_PI);
+  if(x>.8)
+    return sin((1-x)*2.5*M_PI);
+  return 1.;
+}
+
+void draw_stairstep_trace(cairo_surface_t *cs,int frame){
+int i,p,flag=0;
+  double sx,y,y1;
+  cairo_t *c = cairo_create(cs);
+  double SW = (double)W/SAMPLES;
+
+  /* draw trace and halo in successive passes */
+  cairo_set_source_rgba(c,FIRST_COLOR);
+  cairo_set_line_width(c,FIRST_LINE_WIDTH);
+  cairo_set_line_join(c,CAIRO_LINE_JOIN_ROUND);
+  cairo_set_line_cap(c,CAIRO_LINE_CAP_ROUND);
+
+  for(i=frame * W / FRAMES;i<W;i++){
+    if(i>=0){
+      sx = floor((double)i / SW) * SW;
+      y1 = get_y(sx);
+      if(!flag){
+        y=y1;
+        cairo_move_to(c,i,y);
+        flag=1;
+      }
+      cairo_line_to(c,i,y);
+      if(i>=W)break;
+      if(y!=y1){
+        cairo_stroke(c);
+        cairo_set_source_rgba(c,FIRST_COLOR*.5);
+        cairo_move_to(c,i,y);
+        cairo_line_to(c,i,y1);
+        cairo_stroke(c);
+        cairo_set_source_rgba(c,FIRST_COLOR);
+      }
+      y=y1;
+    }
+  }
+  cairo_stroke(c);
+
+  cairo_set_source_rgba(c,SECOND_COLOR);
+  cairo_set_line_width(c,SECOND_LINE_WIDTH);
+
+  for(i=0;i<frame * W / FRAMES;i++){
+    sx = floor((double)i / SW) * SW;
+    y1 = get_y(sx);
+    if(!i){
+      y=y1;
+      cairo_move_to(c,i,y);
+    }
+    cairo_line_to(c,i,y);
+    if(i>=W)break;
+    if(y!=y1)
+      cairo_line_to(c,i,y1);
+    y=y1;
+  }
+  cairo_stroke(c);
+
+  /* halos */
+  cairo_save(c);
+  cairo_set_line_join(c,CAIRO_LINE_JOIN_ROUND);
+  cairo_set_line_cap(c,CAIRO_LINE_CAP_ROUND);
+
+  for(p=HALO_TAIL;p>=0;p--){
+
+    int start = (double)(frame-HALO_TAIL+p) * W / FRAMES;
+    int end = (double)frame * W / FRAMES;
+
+    for(i=start;i<=end;i++){
+      double y0 = get_y(floor((double)(i-1) / SW) * SW);
+      double y1 = get_y(floor((double)i / SW) * SW);
+      double fp = 1.-(end-i)/(double)(end-start);
+
+      if(p==0){
+        cairo_set_source_rgba(c,1,.2,.1,.25*fp*fp);
+      }else{
+        double dp = (HALO_TAIL-p+1.)/(HALO_TAIL+1);
+        cairo_set_source_rgba(c,1,.2,.1,.003*fp*fp+.01*dp*fp);
+      }
+
+      if(i>0){
+        if(p==0)
+          cairo_set_line_width(c,SECOND_LINE_WIDTH);
+        else
+          cairo_set_line_width(c,SECOND_LINE_WIDTH+2+HALO*
+                               ((double)(p+3)/(HALO_TAIL+1))*
+                               ((double)(p+3)/(HALO_TAIL+1)));
+
+
+        cairo_move_to(c,i-1,y0);
+        cairo_line_to(c,i,y0);
+        if(i<W && y0 != y1){
+          int j;
+          if(y0>y1){
+            int t = y0;
+            y0 = (int)y1;
+            y1 = t;
+          }
+          for(j=y0+1;j<y1;j++){
+            double adj = sadj((j-y0)/(y1-y0));
+            adj = 1.- .5*adj;
+
+            cairo_stroke(c);
+
+            if(p==0)
+              cairo_set_line_width(c,SECOND_LINE_WIDTH*adj);
+            else
+              cairo_set_line_width(c,(SECOND_LINE_WIDTH+2+HALO*
+                                      ((double)(p+3)/(HALO_TAIL+1))*
+                                      ((double)(p+3)/(HALO_TAIL+1)))*adj);
+
+            if(p==0){
+              cairo_set_source_rgba(c,1,.2,.1,.25*fp*fp*adj);
+            }else{
+              double dp = (HALO_TAIL-p+1.)/(HALO_TAIL+1);
+              cairo_set_source_rgba(c,1,.2,.1,(.003*fp*fp+.01*dp*fp)*adj);
+            }
+
+            cairo_move_to(c,i,j-1);
+            cairo_line_to(c,i,j);
+          }
+        }
+        cairo_stroke(c);
+      }
+
+      if(i>=W)break;
+    }
+  }
+  cairo_restore(c);
+
+  cairo_destroy(c);
+}
+
+void draw_cursor(cairo_surface_t *cs,int frame, double alpha){
+  char buffer[80];
+  double SW = (double)W/SAMPLES;
+  int x = (double)frame * W / FRAMES;
+  int y = get_y(floor((double)x / SW + .01) * SW);
+
+  cairo_t *c = cairo_create(cs);
+  cairo_font_face_t *ff,*ft;
+  cairo_text_extents_t extents;
+  cairo_text_extents_t extents2;
+  cairo_text_extents_t extents3;
+  cairo_text_extents_t extents4;
+
+  ff = cairo_toy_font_face_create ("Liberation Sans",
+                                   CAIRO_FONT_SLANT_NORMAL,
+                                   CAIRO_FONT_WEIGHT_BOLD);
+  ft = cairo_toy_font_face_create ("Adobe Garamond",
+                                   CAIRO_FONT_SLANT_NORMAL,
+                                   CAIRO_FONT_WEIGHT_NORMAL);
+  if(!ff){
+    fprintf(stderr,"Unable to create cursor font");
+    exit(1);
+  }
+  cairo_set_font_face(c,ft);
+  cairo_set_font_size(c, TEXT_FONT_SIZE);
+  cairo_text_extents(c, "time: ", &extents);
+  cairo_text_extents(c, "value: ", &extents3);
+  cairo_set_font_face(c,ff);
+  cairo_text_extents(c, "00.00  ", &extents2);
+  cairo_text_extents(c, "-100", &extents4);
+
+  double TH = (TEXTY-extents.height)*2 + extents.height;
+
+  cairo_set_line_width(c,2);
+
+  cairo_set_source_rgba(c,.8,0,0,alpha);
+  cairo_move_to(c,x,TH);
+  cairo_line_to(c,x,H-TH);
+  cairo_stroke(c);
+
+#if 0
+  cairo_set_source_rgba(c,.9,.9,.9,alpha);
+  cairo_rectangle(c,0,0,W,TH);
+  cairo_fill_preserve(c);
+  cairo_set_source_rgba(c,.5,.5,.5,alpha);
+  cairo_stroke(c);
+
+  cairo_set_source_rgba(c,.9,.9,.9,alpha);
+  cairo_rectangle(c,0,H-TH,W,TH);
+  cairo_fill_preserve(c);
+  cairo_set_source_rgba(c,.5,.5,.5,alpha);
+  cairo_stroke(c);
+#endif
+
+  cairo_move_to(c, (W-extents.x_advance-extents2.x_advance-
+                    extents3.x_advance-extents4.x_advance)/2, TEXTY);
+
+  cairo_set_source_rgba(c,0,0,0,alpha);
+  cairo_set_font_face(c,ft);
+  cairo_show_text(c, "time: ");
+  snprintf(buffer, 80,"%2.2f  ",
+           (double)frame/FRAMES*SAMPLES);
+  cairo_set_source_rgba(c,.8,0,0,alpha);
+  cairo_set_font_face(c,ff);
+  cairo_show_text(c, buffer);
+
+  cairo_move_to(c, (W+extents.x_advance+extents2.x_advance-
+                    extents3.x_advance-extents4.x_advance)/2, TEXTY);
+
+  cairo_set_source_rgba(c,0,0,0,alpha);
+  cairo_set_font_face(c,ft);
+  cairo_show_text(c, "value: ");
+  snprintf(buffer, 80,"%d",(int)rint((CR-(int)y)/2.4));
+  cairo_set_source_rgba(c,.8,0,0,alpha);
+  cairo_set_font_face(c,ff);
+  cairo_show_text(c, buffer);
+
+  cairo_font_face_destroy(ft);
+  cairo_font_face_destroy(ff);
+  cairo_destroy(c);
+}
+
+int main(){
+  cairo_surface_t *cs = cairo_image_surface_create (CAIRO_FORMAT_RGB24,
+						    W,H);
+
+  int i;
+
+  /* static preroll */
+  for(i=-PRE;i<FADEMARGIN;i++){
+    clear_surface(cs);
+    draw_axis(cs);
+    draw_stairstep_trace(cs,i);
+    write_frame(cs,i+PRE);
+  }
+
+  /* fade in */
+  for(;i<FADE+FADEMARGIN;i++){
+    clear_surface(cs);
+    draw_axis(cs);
+    draw_cursor(cs,i,(double)(i-FADEMARGIN)/FADE);
+    draw_stairstep_trace(cs,i);
+    write_frame(cs,i+PRE);
+  }
+
+  /* normal */
+  for(;i<FRAMES-FADE-FADEMARGIN;i++){
+    clear_surface(cs);
+    draw_axis(cs);
+    draw_cursor(cs,i,1.);
+    draw_stairstep_trace(cs,i);
+    write_frame(cs,i+PRE);
+  }
+
+  /* fade out */
+  for(;i<FRAMES-FADEMARGIN;i++){
+    clear_surface(cs);
+    draw_axis(cs);
+    draw_cursor(cs,i,(double)(FRAMES-i-FADEMARGIN)/FADE);
+    draw_stairstep_trace(cs,i);
+    write_frame(cs,i+PRE);
+  }
+
+  /* static outro */
+  for(;i<FRAMES+POST;i++){
+    clear_surface(cs);
+    draw_axis(cs);
+    draw_stairstep_trace(cs,i);
+    write_frame(cs,i+PRE);
+  }
+
+  cairo_surface_destroy(cs);
+  return 0;
+}

Added: trunk/Xiph-episode-II/cairo/ch3_zerohold.c
===================================================================
--- trunk/Xiph-episode-II/cairo/ch3_zerohold.c	                        (rev 0)
+++ trunk/Xiph-episode-II/cairo/ch3_zerohold.c	2013-02-24 22:04:10 UTC (rev 18812)
@@ -0,0 +1,462 @@
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <math.h>
+#include <cairo/cairo.h>
+
+#define W 1920
+#define H 1080
+#define CR 540
+#define WH 300
+
+#define TEXTY 140
+#define TEXTY2 140
+#define TEXT_FONT_SIZE 90
+
+#define SAMPLES 18
+#define CYCLES  1
+
+#define PRE        128
+#define FADE       24
+#define OFFSET     1
+#define ARROW      64
+#define ZERO_FADE  24
+#define ZERO       256
+
+
+#define AXIS_LINE_WIDTH 6
+#define AXIS_COLOR 0,0,0,1
+#define AXIS_FONT_SIZE 54
+
+#define FIRST_LINE_WIDTH 15
+#define FIRST_COLOR .6,.6,.6,1
+
+#define SECOND_LINE_WIDTH 15
+#define SECOND_COLOR .8,.4,.4,1
+#define HALO_TAIL 10
+#define HALO 15
+
+
+#define LINE_WIDTH 15
+#define COLOR .4,.4,.4,1
+
+#define ARROW_LINE_WIDTH 6
+#define ARROW_COLOR 1,0,0
+
+
+void write_frame(cairo_surface_t *surface, int frameno){
+  char buffer[80];
+  cairo_status_t ret;
+
+  snprintf(buffer,80,"ch3_zero-%04d.png",frameno);
+  ret = cairo_surface_write_to_png(surface,buffer);
+  if(ret != CAIRO_STATUS_SUCCESS){
+    fprintf(stderr,"Could not write %s: %s\n",
+	    buffer,cairo_status_to_string(ret));
+    exit(1);
+  }
+}
+
+void clear_surface(cairo_surface_t *cs){
+  cairo_t *c = cairo_create(cs);
+  cairo_set_source_rgb(c,1.,1.,1.);
+  cairo_paint(c);
+  cairo_destroy(c);
+}
+
+double get_y(double x){
+  return CR + WH*sin((double)x/W*CYCLES*2*M_PI);
+}
+
+int tosample(double x){
+  double SW = (double)W/SAMPLES;
+  return floor(x/SW+.001);
+}
+
+int tox(int sample){
+  double SW = (double)W/SAMPLES;
+  return ceil(sample*SW);
+}
+
+void draw_axis(cairo_surface_t *cs, double del){
+  int i;
+  cairo_t *c = cairo_create(cs);
+  double SW = 105;
+  cairo_text_extents_t extents;
+  cairo_font_face_t *ff;
+
+  cairo_set_source_rgba(c,AXIS_COLOR*del);
+  cairo_set_line_width(c,AXIS_LINE_WIDTH);
+  cairo_move_to(c,0,CR);
+  cairo_line_to(c,W,CR);
+  cairo_move_to(c,W-SW/2,CR-SW/3);
+  cairo_line_to(c,W,CR);
+  cairo_line_to(c,W-SW/2,CR+SW/3);
+  cairo_stroke(c);
+
+  cairo_set_line_width(c,AXIS_LINE_WIDTH*.75);
+
+  for(i=0;i<SAMPLES;i++){
+    double x = tox(i);
+    cairo_move_to(c,x,CR-AXIS_LINE_WIDTH*2);
+    cairo_line_to(c,x,CR+AXIS_LINE_WIDTH*2);
+  }
+  cairo_stroke(c);
+
+
+  ff = cairo_toy_font_face_create ("Adobe Garamond",
+                                   CAIRO_FONT_SLANT_ITALIC,
+                                   CAIRO_FONT_WEIGHT_NORMAL);
+  if(!ff){
+    fprintf(stderr,"Unable to create axis font");
+    exit(1);
+  }
+  cairo_set_font_face(c,ff);
+  cairo_set_font_size(c, AXIS_FONT_SIZE);
+  cairo_text_extents(c, "time", &extents);
+  cairo_move_to(c, W-extents.width-SW*.6, CR + AXIS_FONT_SIZE);
+  cairo_show_text(c, "time");
+
+  cairo_font_face_destroy(ff);
+  cairo_destroy(c);
+}
+
+void draw_stems(cairo_surface_t *cs, double del, int frame){
+  int i;
+  cairo_t *c = cairo_create(cs);
+  /* draw lollipops */
+
+  cairo_set_line_cap(c,CAIRO_LINE_CAP_BUTT);
+  for(i=1;i<SAMPLES;i++){
+    int sx = tox(i);
+    double y = get_y(sx);
+    double y0 = get_y(tox(i-1));
+
+    double frameoff = frame - (i*OFFSET) - ((ARROW-SAMPLES*OFFSET)/2);
+    double dp = 1.-frameoff/((ARROW-SAMPLES*OFFSET)/2);
+    if(dp<0)dp=0;
+    if(dp>1)dp=1;
+
+    cairo_set_line_cap(c,CAIRO_LINE_CAP_BUTT);
+    cairo_set_source_rgba(c,COLOR*del*(.5+dp*.5)*.8);
+    cairo_set_line_width(c,LINE_WIDTH*.75);
+    cairo_move_to(c,sx,CR);
+    cairo_line_to(c,sx,y);
+    cairo_stroke(c);
+
+
+    cairo_set_line_cap(c,CAIRO_LINE_CAP_ROUND);
+    cairo_set_source_rgba(c,FIRST_COLOR*(1-dp)*del);
+    cairo_set_line_width(c,FIRST_LINE_WIDTH);
+    cairo_move_to(c,sx,y0);
+    cairo_line_to(c,sx,y);
+    cairo_stroke(c);
+
+
+
+  }
+
+  cairo_destroy(c);
+}
+
+void draw_samples(cairo_surface_t *cs, double del){
+  int i;
+  cairo_t *c = cairo_create(cs);
+
+  /* draw lollipops */
+  for(i=1;i<SAMPLES;i++){
+    int sx = tox(i);
+    double y = get_y(sx);
+
+    cairo_set_source_rgba(c,COLOR*del*.4);
+    cairo_set_line_width(c,LINE_WIDTH/3);
+    cairo_arc(c,sx,y,LINE_WIDTH*2,0,2*M_PI);
+    cairo_stroke(c);
+
+    cairo_set_source_rgba(c,COLOR*del);
+    cairo_set_line_cap(c,CAIRO_LINE_CAP_ROUND);
+    cairo_set_line_width(c,LINE_WIDTH*2);
+    cairo_line_to(c,sx,y);
+    cairo_line_to(c,sx,y+.001);
+    cairo_stroke(c);
+  }
+
+  cairo_destroy(c);
+}
+
+void draw_stairstep_full(cairo_surface_t *cs,double del){
+  int i,flag=0;
+  double y;
+  cairo_t *c = cairo_create(cs);
+
+  cairo_set_source_rgba(c,FIRST_COLOR*del);
+  cairo_set_line_width(c,FIRST_LINE_WIDTH);
+  cairo_set_line_join(c,CAIRO_LINE_JOIN_ROUND);
+  cairo_set_line_cap(c,CAIRO_LINE_CAP_ROUND);
+
+  for(i=0;i<W;i++){
+    double s = tosample(i);
+    double sx = tox(s);
+    double y1 = get_y(sx);
+    if(!flag){
+      y=y1;
+      cairo_move_to(c,i,y);
+      flag=1;
+    }
+    cairo_line_to(c,i,y);
+    if(i>=W)break;
+    if(y!=y1)
+      cairo_line_to(c,i,y1);
+    y=y1;
+  }
+  cairo_stroke(c);
+  cairo_destroy(c);
+}
+
+void draw_arrows(cairo_surface_t *cs,double del,int frame){
+  int i,p,s;
+  cairo_t *c = cairo_create(cs);
+
+  /* draw trace and halo in successive passes */
+  cairo_set_line_join(c,CAIRO_LINE_JOIN_MITER);
+  cairo_set_line_cap(c,CAIRO_LINE_CAP_ROUND);
+
+
+  for(s=0;s<SAMPLES;s++){
+    int sx = tox(s);
+    double end = tox(s+1);
+    int y = get_y(sx);
+
+    if(end>sx){
+      /* sample highlight */
+      cairo_set_source_rgba(c,.8,0,0,del);
+      cairo_set_line_width(c,LINE_WIDTH*2);
+      cairo_line_to(c,sx,y);
+      cairo_line_to(c,sx,y+.001);
+      cairo_stroke(c);
+    }
+  }
+
+  /* trace */
+  for(s=SAMPLES-1;s>=0;s--){
+    int sx = tox(s);
+    double end = tox(s+1);
+    int y = get_y(sx);
+    double frameoff = frame - (s*OFFSET);
+    double dp = frameoff/(ARROW-(SAMPLES*OFFSET));
+    if(dp<0)dp=0;
+    if(dp>1)dp=1;
+    dp = sin(dp*M_PI/2);
+    dp = dp*dp;
+    dp = sin(dp*M_PI/2);
+    dp = dp*dp;
+    end = (end-sx)*dp+sx;
+
+    if(end>sx){
+
+#if 0
+      /* trace background */
+      cairo_set_line_width(c,LINE_WIDTH);
+      cairo_set_source_rgba(c,SECOND_COLOR*del*dp);
+      cairo_move_to(c,sx,y);
+      cairo_line_to(c,end,y);
+      cairo_stroke(c);
+
+      /* sample highlight */
+      cairo_set_source_rgba(c,.8,0,0,del);
+      cairo_set_line_width(c,LINE_WIDTH*2);
+      cairo_line_to(c,sx,y);
+      cairo_line_to(c,sx,y+.001);
+      cairo_stroke(c);
+
+      for(p=HALO_TAIL;p>=0;p--){
+        double sdp = (frameoff-HALO_TAIL+p)/(ARROW-(SAMPLES*OFFSET));
+        if(sdp>=0 && sdp<=1){
+          sdp = sin(sdp*M_PI/2);
+          sdp = sdp*sdp;
+          sdp = sin(sdp*M_PI/2);
+          sdp = sdp*sdp;
+          double start = (tox(s+1)-sx)*sdp+sx;
+
+          for(i=start;i<=end;i++){
+            double fp = 1.-(end-i)/(double)(end-start+1);
+
+            if(p==0){
+              cairo_set_source_rgba(c,1,.2,.1,.25*fp*fp*del);
+              cairo_set_line_width(c,SECOND_LINE_WIDTH);
+            }else{
+              double dp = (HALO_TAIL-p+1.)/(HALO_TAIL+1);
+              cairo_set_source_rgba(c,1,.2,.1,(.003*fp*fp+.01*dp*fp)*del);
+              cairo_set_line_width(c,SECOND_LINE_WIDTH+2+HALO*
+                                   ((double)(p+3)/(HALO_TAIL+1))*
+                                   ((double)(p+3)/(HALO_TAIL+1)));
+            }
+
+            /* squarewave halo */
+            cairo_move_to(c,i-1,y);
+            cairo_line_to(c,i,y);
+            cairo_stroke(c);
+            if(i>=W)break;
+          }
+        }
+      }
+#endif
+
+      /* arrow_clear */
+      double ddp = (double)(end-sx)/(ARROW_LINE_WIDTH*5);
+      if(ddp<0)ddp=0;
+      if(ddp>1.)ddp=1.;
+      cairo_set_line_width(c,SECOND_LINE_WIDTH+4);
+      cairo_set_source_rgba(c,1,1,1,1*del*ddp);
+
+      cairo_move_to(c,sx,y);
+      cairo_line_to(c,end-SECOND_LINE_WIDTH*.75,y);
+      cairo_stroke(c);
+      cairo_set_line_width(c,ARROW_LINE_WIDTH+4);
+      cairo_move_to(c,end-ARROW_LINE_WIDTH*5,y + ARROW_LINE_WIDTH*3);
+      cairo_line_to(c,end,y);
+      cairo_line_to(c,end-ARROW_LINE_WIDTH*5,y - ARROW_LINE_WIDTH*3);
+      cairo_stroke(c);
+
+      /* arrow */
+      cairo_set_line_width(c,SECOND_LINE_WIDTH);
+      cairo_set_source_rgba(c,.8,0,0,1*del*ddp);
+
+      cairo_move_to(c,sx,y);
+      cairo_line_to(c,end-SECOND_LINE_WIDTH*.75,y);
+      cairo_stroke(c);
+      cairo_set_line_width(c,ARROW_LINE_WIDTH);
+      cairo_move_to(c,end-ARROW_LINE_WIDTH*5,y + ARROW_LINE_WIDTH*3);
+      cairo_line_to(c,end,y);
+      cairo_line_to(c,end-ARROW_LINE_WIDTH*5,y - ARROW_LINE_WIDTH*3);
+      cairo_stroke(c);
+    }
+  }
+
+  cairo_destroy(c);
+}
+
+void draw_readout(cairo_surface_t *cs, double alpha){
+  cairo_t *c = cairo_create(cs);
+  cairo_font_face_t *ff;
+  cairo_text_extents_t extents;
+  ff = cairo_toy_font_face_create ("Adobe Garamond",
+                                   CAIRO_FONT_SLANT_NORMAL,
+                                   CAIRO_FONT_WEIGHT_NORMAL);
+  if(!ff){
+    fprintf(stderr,"Unable to create cursor font");
+    exit(1);
+  }
+  cairo_set_font_face(c,ff);
+  cairo_set_font_size(c, TEXT_FONT_SIZE);
+  cairo_text_extents(c, "\xE2\x80\x9CZero-order hold\xE2\x80\x9D", &extents);
+  double TH = (TEXTY-extents.height)*2 + extents.height;
+
+  cairo_move_to(c, (W-extents.width)/2, TEXTY);
+
+  cairo_set_source_rgba(c,0,0,0,alpha);
+  cairo_show_text(c, "\xE2\x80\x9CZero-order hold\xE2\x80\x9D");
+
+  cairo_font_face_destroy(ff);
+
+  cairo_destroy(c);
+}
+
+void draw_alignment(cairo_surface_t *cs,double del){
+  int i;
+  cairo_t *c = cairo_create(cs);
+  cairo_font_face_t *ff;
+  cairo_text_extents_t extents;
+
+  ff = cairo_toy_font_face_create ("Adobe Garamond",
+                                   CAIRO_FONT_SLANT_NORMAL,
+                                   CAIRO_FONT_WEIGHT_NORMAL);
+  if(!ff){
+    fprintf(stderr,"Unable to create cursor font");
+    exit(1);
+  }
+  cairo_set_font_face(c,ff);
+  cairo_set_font_size(c, TEXT_FONT_SIZE);
+  cairo_text_extents(c, "\xE2\x80\x9CZero-order hold\xE2\x80\x9D", &extents);
+  double TH = (TEXTY-extents.height)*2 + extents.height;
+
+  cairo_set_source_rgba(c,.6,.8,1,del);
+  cairo_set_line_width(c,2);
+
+  for(i=0;i<=SAMPLES;i++){
+    double x = tox(i);
+    cairo_move_to(c,x,TH);
+    cairo_line_to(c,x,H-TH);
+  }
+  cairo_stroke(c);
+
+  cairo_font_face_destroy(ff);
+  cairo_destroy(c);
+}
+
+int main(){
+  cairo_surface_t *cs = cairo_image_surface_create (CAIRO_FORMAT_RGB24,
+						    W,H);
+  int i,count=0;
+
+  /* static preroll */
+  for(i=0;i<PRE;i++){
+    clear_surface(cs);
+    draw_stems(cs,1.,0);
+    draw_axis(cs,1.);
+    draw_samples(cs,1.);
+    write_frame(cs,count++);
+  }
+
+  /* fade in alignment lines */
+  for(i=0;i<FADE;i++){
+    clear_surface(cs);
+    draw_alignment(cs,(double)i/FADE);
+    draw_stems(cs,1.,0);
+    draw_axis(cs,1.);
+    draw_samples(cs,1.);
+    //draw_readout(cs,(double)i/FADE);
+    write_frame(cs,count++);
+  }
+
+  /* arrows */
+  for(i=0;i<ARROW;i++){
+    clear_surface(cs);
+    draw_alignment(cs,1.);
+    draw_stems(cs,1.,i);
+    draw_axis(cs,1.);
+    draw_samples(cs,1.);
+    draw_arrows(cs,1.,i);
+    //draw_readout(cs,1.);
+    write_frame(cs,count++);
+  }
+
+  /* fade in zero-hold, */
+  for(i=0;i<ZERO_FADE;i++){
+    clear_surface(cs);
+    draw_alignment(cs,1.);
+    draw_stems(cs,1.,i+ARROW);
+    draw_axis(cs,1.);
+    draw_samples(cs,1.);
+    draw_stairstep_full(cs,(double)i/ZERO_FADE);
+    draw_arrows(cs,1.,ARROW+i);
+    draw_readout(cs,(double)i/ZERO_FADE);
+    write_frame(cs,count++);
+  }
+
+ /* zero-hold */
+  for(i=0;i<ZERO;i++){
+    clear_surface(cs);
+    draw_alignment(cs,1.);
+    draw_stems(cs,1.,i+ARROW);
+    draw_axis(cs,1.);
+    draw_samples(cs,1.);
+    draw_stairstep_full(cs,1.);
+    draw_arrows(cs,1.,ARROW+i);
+    draw_readout(cs,1.);
+    write_frame(cs,count++);
+  }
+
+  cairo_surface_destroy(cs);
+  return 0;
+}

Added: trunk/Xiph-episode-II/cairo/ch4-samplequant.c
===================================================================
--- trunk/Xiph-episode-II/cairo/ch4-samplequant.c	                        (rev 0)
+++ trunk/Xiph-episode-II/cairo/ch4-samplequant.c	2013-02-24 22:04:10 UTC (rev 18812)
@@ -0,0 +1,354 @@
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <cairo/cairo.h>
+#include <fftw3.h>
+#include <squishyio/squishyio.h>
+
+#define W 1920
+#define H 1080
+#define CR 540
+#define WH 200
+#define SPACING 44
+#define QUANT 6
+
+#define LINE_WIDTH 6
+#define WAVE_WIDTH 12
+
+#define WAVEORIG .2,1,.2,.7
+#define WAVENOISE 1,.3,.3,.8
+#define WAVEQUANT 1,.7,0,.8
+#define STEM .0,.0,.0,.3
+#define LOLLI .0,.0,.0,1
+
+#define QSTEM .8,.0,.0,.3
+#define QLOLLI .8,.0,.0,1
+
+#define LINE .6,.9,1.,1
+
+#define AXIS_LINE_WIDTH 6
+#define AXIS_COLOR 0,0,0,1
+#define AXIS_FONT_SIZE 54
+
+void write_frame(cairo_surface_t *surface, char *base){
+  char buffer[80];
+  cairo_status_t ret;
+
+  snprintf(buffer,80,"ch4_%s.png",base);
+  ret = cairo_surface_write_to_png(surface,buffer);
+  if(ret != CAIRO_STATUS_SUCCESS){
+    fprintf(stderr,"Could not write %s: %s\n",
+	    buffer,cairo_status_to_string(ret));
+    exit(1);
+  }
+}
+
+void transparent_surface(cairo_surface_t *cs){
+  cairo_t *c = cairo_create(cs);
+  cairo_set_source_rgba(c,1.,1.,1.,1.);
+  cairo_set_operator(c,CAIRO_OPERATOR_CLEAR);
+  cairo_paint(c);
+  cairo_destroy(c);
+}
+
+void draw_lines(cairo_surface_t *cs){
+  cairo_t *c = cairo_create(cs);
+  int i=-SPACING;
+
+  cairo_set_source_rgba(c,LINE);
+  cairo_set_line_cap(c,CAIRO_LINE_CAP_BUTT);
+  cairo_set_line_width(c,2);
+
+  for(;i<W+SPACING;i+=SPACING){
+    cairo_move_to(c,i-8,CR-WH);
+    cairo_line_to(c,i-8,CR+WH);
+  }
+
+  cairo_stroke(c);
+  cairo_destroy(c);
+}
+
+void draw_quant(cairo_surface_t *cs){
+  cairo_t *c = cairo_create(cs);
+  int i;
+
+  cairo_set_source_rgba(c,LINE);
+  cairo_set_line_cap(c,CAIRO_LINE_CAP_BUTT);
+  cairo_set_line_width(c,2);
+
+  for(i=-QUANT;i<=QUANT;i++){
+    float y = (float)WH*i/QUANT+CR;
+    cairo_move_to(c,0,y);
+    cairo_line_to(c,W,y);
+  }
+
+  cairo_stroke(c);
+  cairo_destroy(c);
+}
+
+void draw_axis(cairo_surface_t *cs,int mark){
+  int i;
+  cairo_t *c = cairo_create(cs);
+  cairo_text_extents_t extents;
+  cairo_font_face_t *ff;
+  double SW = 105;
+
+  cairo_set_source_rgba(c,AXIS_COLOR);
+  cairo_set_line_width(c,AXIS_LINE_WIDTH);
+  cairo_move_to(c,0,CR);
+  cairo_line_to(c,W,CR);
+  cairo_move_to(c,W-SW/2,CR-SW/3);
+  cairo_line_to(c,W,CR);
+  cairo_line_to(c,W-SW/2,CR+SW/3);
+  cairo_stroke(c);
+
+  cairo_set_line_width(c,AXIS_LINE_WIDTH*.75);
+
+  if(mark){
+    for(i=1;i*SPACING<W;i++){
+      double x = i*SPACING-8;
+      cairo_move_to(c,x,CR-AXIS_LINE_WIDTH*2);
+      cairo_line_to(c,x,CR+AXIS_LINE_WIDTH*2);
+    }
+    cairo_stroke(c);
+  }
+
+  ff = cairo_toy_font_face_create ("Adobe Garamond",
+                                   CAIRO_FONT_SLANT_ITALIC,
+                                   CAIRO_FONT_WEIGHT_NORMAL);
+  if(!ff){
+    fprintf(stderr,"Unable to create axis font");
+    exit(1);
+  }
+  cairo_set_font_face(c,ff);
+  cairo_set_font_size(c, AXIS_FONT_SIZE);
+  cairo_text_extents(c, "time", &extents);
+  cairo_move_to(c, W-extents.width-SW*.6, CR + AXIS_FONT_SIZE);
+  cairo_show_text(c, "time");
+
+  cairo_font_face_destroy(ff);
+  cairo_destroy(c);
+}
+
+void draw_waveform(cairo_surface_t *cs, float *data, float r, float g, float b, float a){
+  int i;
+  cairo_t *c = cairo_create(cs);
+  int first=0;
+  cairo_set_source_rgba(c,r,g,b,a);
+  cairo_set_line_cap(c,CAIRO_LINE_CAP_ROUND);
+  cairo_set_line_join(c,CAIRO_LINE_JOIN_ROUND);
+  cairo_set_line_width(c,WAVE_WIDTH);
+  for(i=1;i-SPACING<W+LINE_WIDTH;i++){
+    float x = i-SPACING-8;
+    if(x-WAVE_WIDTH>0 && x+WAVE_WIDTH<W){
+      if(!first){
+        cairo_move_to(c,x,CR-data[i]*WH);
+        first=1;
+      }else{
+        cairo_line_to(c,x,CR-data[i]*WH);
+      }
+    }
+  }
+  cairo_stroke(c);
+  cairo_destroy(c);
+}
+
+void draw_samples(cairo_surface_t *cs, float *data){
+  int i;
+  cairo_t *c = cairo_create(cs);
+
+  /* draw lollipops */
+  for(i=0;(i-1)*SPACING<=W+10;i++){
+    int x = (i-1)*SPACING-8;
+    double y = CR-WH*data[i*SPACING];
+
+    if(x-LINE_WIDTH*2>0 && x+LINE_WIDTH*2<W){
+
+      cairo_set_line_cap(c,CAIRO_LINE_CAP_BUTT);
+      cairo_set_source_rgba(c,STEM);
+      cairo_set_line_width(c,LINE_WIDTH*.75);
+      cairo_move_to(c,x,CR);
+      cairo_line_to(c,x,y);
+      cairo_stroke(c);
+
+      cairo_set_source_rgba(c,LOLLI*.5);
+      cairo_set_line_width(c,LINE_WIDTH/3);
+      cairo_arc(c,x,y,LINE_WIDTH*2,0,2*M_PI);
+      cairo_stroke(c);
+
+      cairo_set_source_rgba(c,LOLLI);
+      cairo_set_line_cap(c,CAIRO_LINE_CAP_ROUND);
+      cairo_set_line_width(c,LINE_WIDTH*2);
+      cairo_line_to(c,x,y);
+      cairo_line_to(c,x,y+.001);
+      cairo_stroke(c);
+    }
+  }
+
+  cairo_destroy(c);
+}
+
+void draw_qsamples(cairo_surface_t *cs, float *data, float *qdata){
+  int i;
+  cairo_t *c = cairo_create(cs);
+
+  /* draw lollipops */
+  for(i=0;(i-1)*SPACING<=W+10;i++){
+    int x = (i-1)*SPACING-8;
+    double y1 = CR-WH*data[i*SPACING];
+    double y2 = CR-WH*qdata[i*SPACING];
+
+    if(x-LINE_WIDTH*2>0 && x+LINE_WIDTH*2<W){
+      cairo_set_line_cap(c,CAIRO_LINE_CAP_BUTT);
+      cairo_set_source_rgba(c,QSTEM);
+      cairo_set_line_width(c,LINE_WIDTH*.75);
+      cairo_move_to(c,x,y1);
+      cairo_line_to(c,x,y2);
+      cairo_stroke(c);
+
+      cairo_set_source_rgba(c,QLOLLI*.5);
+      cairo_set_line_width(c,LINE_WIDTH/3);
+      cairo_arc(c,x,y2,LINE_WIDTH*2,0,2*M_PI);
+      cairo_stroke(c);
+
+      cairo_set_source_rgba(c,QLOLLI);
+      cairo_set_line_cap(c,CAIRO_LINE_CAP_ROUND);
+      cairo_set_line_width(c,LINE_WIDTH*2);
+      cairo_line_to(c,x,y2);
+      cairo_line_to(c,x,y2+.001);
+      cairo_stroke(c);
+    }
+  }
+
+  cairo_destroy(c);
+}
+
+/* cheap, dirty, circular, insufficient for high-fidelity, but this is just an illustration */
+float *oversample_data(float *in,int n){
+  int overn = n*SPACING*4;
+  int i;
+
+  float *data=calloc(overn+2,sizeof(*data));
+  fftwf_plan plans = fftwf_plan_dft_r2c_1d(n*4,data,
+                                           (fftwf_complex *)data,
+                                           FFTW_ESTIMATE);
+  fftwf_plan plani = fftwf_plan_dft_c2r_1d(overn,(fftwf_complex *)data,
+                                           data,
+                                           FFTW_ESTIMATE);
+
+  memset(data,0,sizeof(*data)*(overn+2));
+  memcpy(data,in,sizeof(*in)*n);
+
+  fftwf_execute(plans);
+  for(i=0;i<overn+2;i+=2){
+    data[i]*=.25/n;
+    data[i+1]*=.25/n;
+  }
+  fftwf_execute(plani);
+  fftwf_destroy_plan(plani);
+  fftwf_destroy_plan(plans);
+
+  return data;
+}
+
+float *quantize_data(float *in,int n){
+  int i;
+  float *ret = calloc(n,sizeof(*ret));
+  for(i=0;i<n;i++)
+    ret[i] = rint(in[i]*QUANT)/QUANT;
+  return ret;
+}
+
+void clear_surface(cairo_surface_t *cs){
+  cairo_t *c = cairo_create(cs);
+  cairo_set_source_rgb(c,1.,1.,1.);
+  cairo_paint(c);
+  cairo_destroy(c);
+}
+
+int main(){
+  int i;
+  cairo_surface_t *cs = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
+						    W,H);
+
+  pcm_t *wave = squishyio_load_file("../frames/ch4-short-sample.wav");
+  if(wave->samples<((W+SPACING)/SPACING)){
+    fprintf(stderr,"Not enough input samples\n");
+    exit(1);
+  }
+  for(i=0;i<wave->samples;i++)
+    wave->data[0][i] = wave->data[0][i] * 30.;
+
+  /* oversample original wave into new storage */
+  float *over = oversample_data(wave->data[0],wave->samples);
+
+  /* quantize original wave into new storage */
+  float *quant = quantize_data(wave->data[0],wave->samples);
+
+  /* oversample quantized data into new storage */
+  float *overquant = oversample_data(quant,wave->samples);
+
+  /* compute noise */
+  float *noise = calloc(W+SPACING*2,sizeof(float));
+  for(i=0;i<W+SPACING*2;i++)
+    noise[i] = overquant[i]-over[i];
+
+  /* draw oversampled original waveform */
+  clear_surface(cs);
+  draw_axis(cs,0);
+  draw_waveform(cs,over,WAVEORIG);
+  write_frame(cs,"origcaption");
+
+  /* draw oversampled original waveform with lines and samples */
+  clear_surface(cs);
+  draw_lines(cs);
+  draw_axis(cs,1);
+  draw_waveform(cs,over,WAVEORIG);
+  draw_samples(cs,over);
+  write_frame(cs,"sample");
+
+  /* draw oversample with quantization lines */
+  clear_surface(cs);
+  draw_lines(cs);
+  draw_quant(cs);
+  draw_axis(cs,1);
+  draw_waveform(cs,over,WAVEORIG);
+  draw_samples(cs,over);
+  write_frame(cs,"qlines");
+
+  /* draw highlighted quantization samples */
+  clear_surface(cs);
+  draw_lines(cs);
+  draw_quant(cs);
+  draw_axis(cs,1);
+  draw_waveform(cs,over,WAVEORIG);
+  draw_samples(cs,over);
+  draw_qsamples(cs,over,overquant);
+  write_frame(cs,"hiquant");
+
+  /* draw unhighlighted quantization samples+waveforms */
+  clear_surface(cs);
+  draw_lines(cs);
+  draw_quant(cs);
+  draw_axis(cs,1);
+  draw_waveform(cs,over,WAVEORIG);
+  draw_waveform(cs,overquant,WAVEQUANT);
+  draw_samples(cs,overquant);
+  write_frame(cs,"quant");
+
+  /* draw all waveforms */
+  clear_surface(cs);
+  draw_lines(cs);
+  draw_quant(cs);
+  draw_axis(cs,1);
+  draw_waveform(cs,over,WAVEORIG);
+  draw_waveform(cs,overquant,WAVEQUANT);
+  draw_samples(cs,overquant);
+  draw_waveform(cs,noise,WAVENOISE);
+  write_frame(cs,"noise");
+
+  cairo_surface_destroy(cs);
+  return 0;
+}

Added: trunk/Xiph-episode-II/cairo/ch5-quantize.c
===================================================================
--- trunk/Xiph-episode-II/cairo/ch5-quantize.c	                        (rev 0)
+++ trunk/Xiph-episode-II/cairo/ch5-quantize.c	2013-02-24 22:04:10 UTC (rev 18812)
@@ -0,0 +1,336 @@
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <cairo/cairo.h>
+
+#define W 1920
+#define H 1080
+#define CR 540
+#define WH 250
+#define QUANT 4
+#define CYCLES 1
+#define SPACING 120
+
+#define FRAMES 12
+
+#define LINE_WIDTH 12
+
+#define STEM .0,.0,.0,.3
+#define LOLLI .0,.0,.0,1
+#define ARROW .9,.0,.0,.1
+#define LINE .6,.9,1.,1
+
+#define AXIS_LINE_WIDTH 6
+#define AXIS_COLOR 0,0,0,1
+#define AXIS_FONT_SIZE 54
+
+#define ARROW_LINE_WIDTH 6
+#define ARROW_COLOR 1,0,0
+
+void write_frame(cairo_surface_t *surface, char *base, int o){
+  char buffer[80];
+  cairo_status_t ret;
+
+  snprintf(buffer,80,"ch5_%s-%04d.png",base,o);
+  ret = cairo_surface_write_to_png(surface,buffer);
+  if(ret != CAIRO_STATUS_SUCCESS){
+    fprintf(stderr,"Could not write %s: %s\n",
+	    buffer,cairo_status_to_string(ret));
+    exit(1);
+  }
+}
+
+void clear_surface(cairo_surface_t *cs){
+  cairo_t *c = cairo_create(cs);
+  cairo_set_source_rgb(c,1.,1.,1.);
+  cairo_paint(c);
+  cairo_destroy(c);
+}
+
+void draw_quant(cairo_surface_t *cs){
+  cairo_t *c = cairo_create(cs);
+  int i;
+
+  cairo_set_source_rgba(c,LINE);
+  cairo_set_line_cap(c,CAIRO_LINE_CAP_BUTT);
+  cairo_set_line_width(c,2);
+
+  for(i=-QUANT;i<=QUANT;i++){
+    float y = (float)WH*i/QUANT+CR;
+    cairo_move_to(c,0,y);
+    cairo_line_to(c,W,y);
+  }
+
+  cairo_stroke(c);
+  cairo_destroy(c);
+}
+
+void draw_axis(cairo_surface_t *cs,int mark){
+  int i;
+  cairo_t *c = cairo_create(cs);
+  cairo_text_extents_t extents;
+  cairo_font_face_t *ff;
+  double SW = 105;
+
+  cairo_set_source_rgba(c,AXIS_COLOR);
+  cairo_set_line_width(c,AXIS_LINE_WIDTH);
+  cairo_move_to(c,0,CR);
+  cairo_line_to(c,W,CR);
+  cairo_move_to(c,W-SW/2,CR-SW/3);
+  cairo_line_to(c,W,CR);
+  cairo_line_to(c,W-SW/2,CR+SW/3);
+  cairo_stroke(c);
+
+  cairo_set_line_width(c,AXIS_LINE_WIDTH*.75);
+
+  if(mark){
+    for(i=1;i*SPACING<W;i++){
+      double x = i*SPACING-8;
+      cairo_move_to(c,x,CR-AXIS_LINE_WIDTH*2);
+      cairo_line_to(c,x,CR+AXIS_LINE_WIDTH*2);
+    }
+    cairo_stroke(c);
+  }
+
+  ff = cairo_toy_font_face_create ("Adobe Garamond",
+                                   CAIRO_FONT_SLANT_ITALIC,
+                                   CAIRO_FONT_WEIGHT_NORMAL);
+  if(!ff){
+    fprintf(stderr,"Unable to create axis font");
+    exit(1);
+  }
+  cairo_set_font_face(c,ff);
+  cairo_set_font_size(c, AXIS_FONT_SIZE);
+  cairo_text_extents(c, "time", &extents);
+  cairo_move_to(c, W-extents.width-SW*.6, CR + AXIS_FONT_SIZE);
+  cairo_show_text(c, "time");
+
+  cairo_font_face_destroy(ff);
+  cairo_destroy(c);
+}
+
+void draw_samples(cairo_surface_t *cs, float *data, float ldel){
+  int i;
+  cairo_t *c = cairo_create(cs);
+
+  /* draw lollipops */
+  for(i=0;(i-1)*SPACING<=W+10;i++){
+    int x = (i-1)*SPACING-8;
+    double yf = CR-WH*data[i];
+    double yi = CR-rint(data[i]*QUANT)/QUANT*WH;
+
+    if(x-LINE_WIDTH*2>0 && x+LINE_WIDTH*2<W){
+      float y;
+      y = ldel<0 ? yf : ldel > 1. ? yi : ldel*(yi-yf)+yf;
+
+      cairo_set_line_cap(c,CAIRO_LINE_CAP_BUTT);
+      cairo_set_source_rgba(c,STEM);
+      cairo_set_line_width(c,LINE_WIDTH*.75);
+      cairo_move_to(c,x,CR);
+      cairo_line_to(c,x,y);
+      cairo_stroke(c);
+
+      cairo_set_source_rgba(c,LOLLI*.5);
+      cairo_set_line_width(c,LINE_WIDTH/3);
+      cairo_arc(c,x,y,LINE_WIDTH*2,0,2*M_PI);
+      cairo_stroke(c);
+
+      cairo_set_source_rgba(c,LOLLI);
+      cairo_set_line_cap(c,CAIRO_LINE_CAP_ROUND);
+      cairo_set_line_width(c,LINE_WIDTH*2);
+      cairo_line_to(c,x,y);
+      cairo_line_to(c,x,y+.001);
+      cairo_stroke(c);
+
+    }
+  }
+
+  cairo_destroy(c);
+}
+
+void draw_targets(cairo_surface_t *cs, float *data, float adel){
+  int i;
+  cairo_t *c = cairo_create(cs);
+
+  /* draw lollipops */
+  for(i=0;(i-1)*SPACING<=W+10;i++){
+    int x = (i-1)*SPACING-8;
+    //double yf = CR-WH*data[i];
+    double yi = CR-rint(data[i]*QUANT)/QUANT*WH;
+    //double yi2 = yi>yf ? CR-(rint(data[i]*QUANT)+1)/QUANT*WH : CR-(rint(data[i]*QUANT)-1)/QUANT*WH;
+
+    if(x-LINE_WIDTH*2>0 && x+LINE_WIDTH*2<W){
+
+      cairo_set_source_rgba(c,ARROW_COLOR,adel*.5);
+      cairo_set_line_width(c,LINE_WIDTH/3);
+      cairo_arc(c,x,yi,LINE_WIDTH*2,0,2*M_PI);
+      cairo_stroke(c);
+
+      cairo_set_source_rgba(c,ARROW_COLOR,adel);
+      cairo_set_line_cap(c,CAIRO_LINE_CAP_ROUND);
+      cairo_set_line_width(c,LINE_WIDTH*2);
+      cairo_line_to(c,x,yi);
+      cairo_line_to(c,x,yi+.001);
+      cairo_stroke(c);
+
+    }
+  }
+  cairo_destroy(c);
+}
+
+void draw_arrows(cairo_surface_t *cs, float *data, float ldel, float adel){
+  int i;
+  cairo_t *c = cairo_create(cs);
+
+  /* draw lollipops */
+  for(i=0;(i-1)*SPACING<=W+10;i++){
+    int x = (i-1)*SPACING-8;
+    double yf = CR-WH*data[i];
+    double yi = CR-rint(data[i]*QUANT)/QUANT*WH;
+
+    if(x-LINE_WIDTH*2>0 && x+LINE_WIDTH*2<W){
+      float y;
+      y = ldel<0 ? yf : ldel > 1. ? yi : ldel*(yi-yf)+yf;
+
+
+      if(yi<yf){  /* down arrow */
+        //if(yi > CR)
+        //y -= LINE_WIDTH*10+ARROW_LINE_WIDTH*10;
+
+        cairo_set_line_width(c,ARROW_LINE_WIDTH+3);
+        cairo_set_source_rgba(c,1.,1.,1.,adel);
+        cairo_move_to(c,x,y+LINE_WIDTH*5);
+        cairo_line_to(c,x,y+LINE_WIDTH*5+ARROW_LINE_WIDTH*10+1.5);
+
+        cairo_move_to(c,x-ARROW_LINE_WIDTH*3,
+                      y+LINE_WIDTH*5+ARROW_LINE_WIDTH*4);
+        cairo_line_to(c,x,y+LINE_WIDTH*5);
+        cairo_line_to(c,x+ARROW_LINE_WIDTH*3,
+                      y+LINE_WIDTH*5+ARROW_LINE_WIDTH*4);
+
+
+        cairo_stroke(c);
+
+        cairo_set_line_width(c,ARROW_LINE_WIDTH);
+        cairo_set_source_rgba(c,ARROW_COLOR,adel);
+        cairo_move_to(c,x,y+LINE_WIDTH*5);
+        cairo_line_to(c,x,y+LINE_WIDTH*5+ARROW_LINE_WIDTH*10);
+
+        cairo_move_to(c,x-ARROW_LINE_WIDTH*3,
+                      y+LINE_WIDTH*5+ARROW_LINE_WIDTH*4);
+        cairo_line_to(c,x,y+LINE_WIDTH*5);
+        cairo_line_to(c,x+ARROW_LINE_WIDTH*3,
+                      y+LINE_WIDTH*5+ARROW_LINE_WIDTH*4);
+
+        cairo_stroke(c);
+
+
+      }
+      if(yi>yf){
+        //if(yi < CR )
+        //y += LINE_WIDTH*10+ARROW_LINE_WIDTH*10;
+
+        cairo_set_line_width(c,ARROW_LINE_WIDTH+3);
+        cairo_set_source_rgba(c,1.,1.,1.,adel);
+        cairo_move_to(c,x,y-LINE_WIDTH*5);
+        cairo_line_to(c,x,y-LINE_WIDTH*5-ARROW_LINE_WIDTH*10-1.5);
+
+        cairo_move_to(c,x-ARROW_LINE_WIDTH*3,
+                      y-LINE_WIDTH*5-ARROW_LINE_WIDTH*4);
+        cairo_line_to(c,x,y-LINE_WIDTH*5);
+        cairo_line_to(c,x+ARROW_LINE_WIDTH*3,
+                      y-LINE_WIDTH*5-ARROW_LINE_WIDTH*4);
+
+
+        cairo_stroke(c);
+
+        cairo_set_line_width(c,ARROW_LINE_WIDTH);
+        cairo_set_source_rgba(c,ARROW_COLOR,adel);
+        cairo_move_to(c,x,y-LINE_WIDTH*5);
+        cairo_line_to(c,x,y-LINE_WIDTH*5-ARROW_LINE_WIDTH*10);
+
+        cairo_move_to(c,x-ARROW_LINE_WIDTH*3,
+                      y-LINE_WIDTH*5-ARROW_LINE_WIDTH*4);
+        cairo_line_to(c,x,y-LINE_WIDTH*5);
+        cairo_line_to(c,x+ARROW_LINE_WIDTH*3,
+                      y-LINE_WIDTH*5-ARROW_LINE_WIDTH*4);
+
+        cairo_stroke(c);
+
+      }
+    }
+  }
+
+  cairo_destroy(c);
+}
+
+int main(){
+  int i,count=0;
+  cairo_surface_t *cs = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
+						    W,H);
+
+  int s=(W+SPACING-1)/SPACING+1;
+  float *data = calloc((W+SPACING-1)/SPACING,sizeof(*data));
+  for(i=0;i<s;i++)
+    data[i] = -sin((i-.35)/s*2*M_PI)*.9;
+
+  clear_surface(cs);
+  draw_quant(cs);
+  draw_axis(cs,0);
+  draw_samples(cs,data,0);
+  write_frame(cs,"quant",count++);
+
+  /* fade in lines */
+  for(i=0;i<FRAMES;i++){
+    clear_surface(cs);
+    draw_quant(cs);
+    draw_axis(cs,0);
+    draw_targets(cs,data,(float)i/FRAMES);
+    draw_samples(cs,data,0);
+    write_frame(cs,"quant",count++);
+  }
+
+  /* fade in arrows */
+  for(i=0;i<FRAMES;i++){
+    clear_surface(cs);
+    draw_quant(cs);
+    draw_axis(cs,0);
+    draw_targets(cs,data,1);
+    draw_samples(cs,data,0);
+    draw_arrows(cs,data,0,(float)i/FRAMES);
+    write_frame(cs,"quant",count++);
+  }
+
+  /* move */
+  for(i=0;i<FRAMES;i++){
+    clear_surface(cs);
+    draw_quant(cs);
+    draw_axis(cs,0);
+    draw_targets(cs,data,1);
+    draw_samples(cs,data,(float)i/FRAMES);
+    draw_arrows(cs,data,(float)i/FRAMES,1);
+    write_frame(cs,"quant",count++);
+  }
+
+  /* fade out all */
+  for(i=0;i<FRAMES;i++){
+    clear_surface(cs);
+    draw_quant(cs);
+    draw_axis(cs,0);
+    draw_targets(cs,data,1.-(float)i/FRAMES);
+    draw_samples(cs,data,1);
+    draw_arrows(cs,data,1,1-(float)i/FRAMES);
+    write_frame(cs,"quant",count++);
+  }
+
+  clear_surface(cs);
+  draw_quant(cs);
+  draw_axis(cs,0);
+  draw_samples(cs,data,1);
+  write_frame(cs,"quant",count++);
+
+  cairo_surface_destroy(cs);
+  return 0;
+}

Added: trunk/Xiph-episode-II/cairo/ch6-overlay.c
===================================================================
--- trunk/Xiph-episode-II/cairo/ch6-overlay.c	                        (rev 0)
+++ trunk/Xiph-episode-II/cairo/ch6-overlay.c	2013-02-24 22:04:10 UTC (rev 18812)
@@ -0,0 +1,114 @@
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <math.h>
+#include <cairo/cairo.h>
+#include <fftw3.h>
+
+#define W 1920
+#define H 1080
+#define CR 540
+#define WH 330
+#define SAMPLES 44
+#define CYCLES 1.1
+#define OFFSET -.05
+
+#define LINE_WIDTH 14
+#define COLOR .6,0,0,1
+
+void write_frame(cairo_surface_t *surface, int frameno){
+  char buffer[80];
+  cairo_status_t ret;
+
+  snprintf(buffer,80,"ch6_overlay-%04d.png",frameno);
+  ret = cairo_surface_write_to_png(surface,buffer);
+  if(ret != CAIRO_STATUS_SUCCESS){
+    fprintf(stderr,"Could not write %s: %s\n",
+	    buffer,cairo_status_to_string(ret));
+    exit(1);
+  }
+}
+
+void transparent_surface(cairo_surface_t *cs){
+  cairo_t *c = cairo_create(cs);
+  cairo_set_source_rgba(c,1.,1.,1.,1.);
+  cairo_set_operator(c,CAIRO_OPERATOR_CLEAR);
+  cairo_paint(c);
+  cairo_destroy(c);
+}
+
+void clear_surface(cairo_surface_t *cs){
+  cairo_t *c = cairo_create(cs);
+  cairo_set_source_rgb(c,1.,1.,1.);
+  cairo_paint(c);
+  cairo_destroy(c);
+}
+
+void draw_overlay(cairo_surface_t *cs,float *data){
+  int i;
+  cairo_t *c = cairo_create(cs);
+
+  cairo_set_source_rgba(c,COLOR);
+  cairo_set_line_cap(c,CAIRO_LINE_CAP_ROUND);
+  cairo_set_line_join(c,CAIRO_LINE_JOIN_ROUND);
+  cairo_set_line_width(c,LINE_WIDTH);
+
+  for(i=0;i<W;i++){
+    double y = data[i]*WH+CR;
+    if(i==0)
+      cairo_move_to(c,i,y);
+    else
+      cairo_line_to(c,i,y);
+  }
+
+  cairo_stroke(c);
+  cairo_destroy(c);
+}
+
+int main(){
+  cairo_surface_t *cs = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
+						    W,H);
+  int period=SAMPLES;
+  int coeffs = period/4;
+  float *square_coeffs;
+  float *square_phases;
+  int square_coeffs_n;
+  int i,j;
+  double phase=OFFSET*2*M_PI - (M_PI*CYCLES/period);
+  float waveform[W];
+  float *work = fftwf_malloc((period+2)*sizeof(*work));
+  fftwf_plan plan = fftwf_plan_dft_r2c_1d(period,work,
+                                          (fftwf_complex *)work,
+                                          FFTW_ESTIMATE);
+  for(i=0;i<period/2;i++)
+    work[i]=1.;
+  for(;i<period;i++)
+    work[i]=-1.;
+  fftwf_execute(plan);
+
+  square_coeffs_n = coeffs;
+  square_coeffs = calloc(coeffs,sizeof(*square_coeffs));
+  square_phases = calloc(coeffs,sizeof(*square_phases));
+
+  for(i=1,j=0;j<square_coeffs_n;i+=2,j++){
+    square_coeffs[j] = hypotf(work[i<<1],work[(i<<1)+1]) / period;
+    square_phases[j] = atan2f(work[(i<<1)+1],work[i<<1]);
+  }
+
+  for(i=0;i<W;i++){
+    float acc=0.;
+    int k;
+    for(j=0,k=1;j<square_coeffs_n;j++,k+=2)
+      acc += square_coeffs[j] *
+        cos( square_phases[j] + k*phase);
+    waveform[i]=acc;
+    phase += 2*M_PI*CYCLES/W;
+    if(phase>=2*M_PI)phase-=2*M_PI;
+  }
+
+  transparent_surface(cs);
+  draw_overlay(cs,waveform);
+  write_frame(cs,0);
+  cairo_surface_destroy(cs);
+  return 0;
+}

Added: trunk/Xiph-episode-II/cairo/ch6-right.c
===================================================================
--- trunk/Xiph-episode-II/cairo/ch6-right.c	                        (rev 0)
+++ trunk/Xiph-episode-II/cairo/ch6-right.c	2013-02-24 22:04:10 UTC (rev 18812)
@@ -0,0 +1,324 @@
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <cairo/cairo.h>
+#include <fftw3.h>
+
+#define W 3840
+#define H 2160
+#define CR1 907.5
+#define CR2 1237.5
+#define DE  .2
+#define WH 225
+#define SAMPLES 3840
+#define SPACING 100
+#define IMPULSE 80
+
+#define MULT 1.
+#define RATE 48000
+
+#define TIME 8.5
+#define PER 36
+
+
+#define LINE_WIDTH 6
+#define WAVE_WIDTH 15
+#define BAND .6,0,0,1
+
+#define WAVETOP .0,.0,.0,1
+#define WAVEBOTTOM 0,.0,.0,.25
+#define STEM .0,.0,.0,.3
+#define LOLLI .0,.0,.0,1
+
+#define LINE .6,.9,1.,1
+
+void write_frame(cairo_surface_t *surface, char *base, int frameno){
+  char buffer[80];
+  cairo_status_t ret;
+
+  snprintf(buffer,80,"ch6_%s-%04d.png",base,frameno);
+  ret = cairo_surface_write_to_png(surface,buffer);
+  if(ret != CAIRO_STATUS_SUCCESS){
+    fprintf(stderr,"Could not write %s: %s\n",
+	    buffer,cairo_status_to_string(ret));
+    exit(1);
+  }
+}
+
+void transparent_surface(cairo_surface_t *cs){
+  cairo_t *c = cairo_create(cs);
+  cairo_set_source_rgba(c,1.,1.,1.,1.);
+  cairo_set_operator(c,CAIRO_OPERATOR_CLEAR);
+  cairo_paint(c);
+  cairo_destroy(c);
+}
+
+void draw_lines(cairo_surface_t *cs){
+  cairo_t *c = cairo_create(cs);
+  double o;
+
+  cairo_set_source_rgba(c,LINE);
+  cairo_set_line_cap(c,CAIRO_LINE_CAP_BUTT);
+  cairo_set_line_width(c,LINE_WIDTH);
+
+  cairo_move_to(c,W/2.,CR2-WH);
+  cairo_line_to(c,W/2.,CR2+WH*DE);
+  o=SPACING*MULT;
+
+  while(o<W+LINE_WIDTH/2.){
+    double x0=W/2.-o;
+    double x1=W/2.+o;
+
+    cairo_move_to(c,x0,CR2-WH);
+    cairo_line_to(c,x0,CR2+WH*DE);
+    cairo_move_to(c,x1,CR2-WH);
+    cairo_line_to(c,x1,CR2+WH*DE);
+
+    o+=SPACING*MULT;
+  }
+
+  cairo_stroke(c);
+  cairo_destroy(c);
+}
+
+void draw_right(cairo_surface_t *cs,float *data, float center, float shift){
+  int i;
+  cairo_t *c = cairo_create(cs);
+
+  float center_sample = center*RATE+shift;
+  float samples = (float)W/MULT;
+  int start_sample = rint(center_sample-(samples/2));
+  int end_sample = rint(start_sample+samples);
+  float hold = 0;
+  float thresh=0;
+  double o=SPACING*MULT;
+
+  while(o+LINE_WIDTH<W/2.){
+    thresh=W/2.-o;
+    o+=SPACING*MULT;
+  }
+
+  fprintf(stderr,"%d %d %f\n",start_sample,end_sample,shift);
+
+  cairo_set_source_rgba(c,WAVETOP);
+
+  cairo_set_line_cap(c,CAIRO_LINE_CAP_ROUND);
+  cairo_set_line_join(c,CAIRO_LINE_JOIN_ROUND);
+  cairo_set_line_width(c,WAVE_WIDTH);
+
+  for(i=start_sample;i<end_sample;i++){
+    double t = (double)(i-shift)/RATE;
+    double x = (double)W/2. + (t-center)*RATE*MULT;
+    double y = CR1-data[i]*WH;
+
+    if(i==start_sample)
+      cairo_move_to(c,x,y);
+    else
+      cairo_line_to(c,x,y);
+  }
+
+  cairo_stroke(c);
+
+  cairo_set_source_rgba(c,WAVEBOTTOM);
+
+  cairo_set_line_cap(c,CAIRO_LINE_CAP_ROUND);
+  cairo_set_line_join(c,CAIRO_LINE_JOIN_ROUND);
+  cairo_set_line_width(c,WAVE_WIDTH);
+
+  for(i=start_sample;i<end_sample;i++){
+    double t = (double)(i-shift)/RATE;
+    double x = (double)W/2. + (t-center)*RATE*MULT;
+    double y = CR2-data[i]*WH;
+
+    if(i==start_sample)
+      cairo_move_to(c,x,y);
+    else
+      cairo_line_to(c,x,y);
+  }
+
+  cairo_stroke(c);
+
+  cairo_set_line_width(c,LINE_WIDTH+2);
+  cairo_set_source_rgba(c,STEM);
+
+  for(i=start_sample;i<end_sample;i++){
+    double tp = (double)(i-shift-1)/RATE;
+    double t = (double)(i-shift)/RATE;
+    double xp = (double)W/2. + (tp-center)*RATE*MULT;
+    double x = (double)W/2. + (t-center)*RATE*MULT;
+
+    if(i==start_sample || (xp<thresh && x>=thresh)){
+      hold=CR2-data[i]*WH;
+      cairo_move_to(c,thresh,CR2);
+      cairo_line_to(c,thresh,hold);
+      thresh+=SPACING*MULT;
+    }
+  }
+  cairo_stroke(c);
+
+  o=SPACING*MULT;
+
+  while(o+LINE_WIDTH<W/2.){
+    thresh=W/2.-o;
+    o+=SPACING*MULT;
+  }
+
+  cairo_set_line_width(c,WAVE_WIDTH/2.);
+
+  for(i=start_sample;i<end_sample;i++){
+    double tp = (double)(i-shift-1)/RATE;
+    double t = (double)(i-shift)/RATE;
+    double xp = (double)W/2. + (tp-center)*RATE*MULT;
+    double x = (double)W/2. + (t-center)*RATE*MULT;
+
+    if(i==start_sample || (xp<thresh && x>=thresh)){
+      hold=CR2-data[i]*WH;
+
+      cairo_set_source_rgba(c,LOLLI);
+      cairo_move_to(c,thresh,hold);
+      cairo_arc(c,thresh,hold,LINE_WIDTH*2,0,2*M_PI);
+      cairo_fill(c);
+
+      cairo_set_source_rgba(c,STEM);
+      cairo_arc(c,thresh,hold,LINE_WIDTH*5,0,2*M_PI);
+      cairo_stroke(c);
+      thresh+=SPACING*MULT;
+    }
+  }
+
+
+  cairo_stroke(c);
+  cairo_destroy(c);
+}
+
+int main(){
+  cairo_surface_t *cs = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
+						    W,H);
+  float *data=calloc(5*48000,sizeof(float));
+
+  fftwf_plan planf = fftwf_plan_dft_r2c_1d(5*48000-2,data,
+                                           (fftwf_complex *)data,
+                                           FFTW_ESTIMATE);
+  fftwf_plan plani = fftwf_plan_dft_c2r_1d(5*48000-2,(fftwf_complex *)data,
+                                           data,
+                                           FFTW_ESTIMATE);
+
+  int i,count=0;
+
+  int start=2.5*48000-IMPULSE/2;
+  for(i=0;i<IMPULSE;i++){
+    float v = sin((i+.5)/IMPULSE*M_PI);
+    data[i+start]= v*v      * (2./(5*48000));
+  }
+  /* this is just an illustration, so quick hack the bandlimit; the
+     below is _WRONG_ for audio use, but it will be visually OK */
+  fftwf_execute(planf);
+  for(i=5*48000/2/100*2;i<5*48000;i++)
+    data[i]=0;
+  fftwf_execute(plani);
+#define WIN 3840
+
+  for(i=0;i<(int)(2.5*48000-WIN/2);i++)
+    data[i]=0;
+  for(i=0;i<WIN;i++){
+    float v=sin((i+.5)/WIN*M_PI);
+    data[i+(int)(2.5*48000-WIN/2)]*=v*v;
+  }
+  for(i=(int)(2.5*48000+WIN/2);i<(int)(5*48000);i++)
+    data[i]=0;
+
+  float shift=0;
+  for(i=0;i<24;i++){
+    transparent_surface(cs);
+    draw_lines(cs);
+    draw_right(cs,data, 2.5, shift);
+    write_frame(cs,"impulse-right",count++);
+    shift -= SPACING/PER*sin((i+.5)/48.*M_PI)*sin((i+.5)/48.*M_PI);
+  }
+
+  for(;i<TIME*24;i++){
+    transparent_surface(cs);
+    draw_lines(cs);
+    draw_right(cs,data, 2.5, shift);
+    write_frame(cs,"impulse-right",count++);
+    shift-=SPACING/PER;
+  }
+
+#define OFFSET 0
+
+  int period = 200;
+  int coeffs = period/4;
+  float *square_coeffs;
+  float *square_phases;
+  int square_coeffs_n;
+  int j;
+  double phase = 0;
+  float *work = fftwf_malloc((period+2)*sizeof(*work));
+  fftwf_plan plan = fftwf_plan_dft_r2c_1d(period,work,
+                                          (fftwf_complex *)work,
+                                          FFTW_ESTIMATE);
+  for(i=0;i<period/2;i++)
+    work[i]=1.;
+  for(;i<period;i++)
+    work[i]=-1.;
+  fftwf_execute(plan);
+
+  square_coeffs_n = coeffs;
+  square_coeffs = calloc(coeffs,sizeof(*square_coeffs));
+  square_phases = calloc(coeffs,sizeof(*square_phases));
+
+  for(i=1,j=0;j<square_coeffs_n;i+=2,j++){
+    square_coeffs[j] = hypotf(work[i<<1],work[(i<<1)+1]) / period;
+    square_phases[j] = atan2f(work[(i<<1)+1],work[i<<1]);
+  }
+
+  for(i=0;i<5*48000;i++){
+    float acc=0.;
+    int k;
+    for(j=0,k=1;j<square_coeffs_n;j++,k+=2)
+      acc += square_coeffs[j] *
+        cos( square_phases[j] + k*phase);
+    data[i]=(acc+.5)*.8;
+    phase += 2*M_PI/period/SPACING;
+    if(phase>=2*M_PI)phase-=2*M_PI;
+  }
+
+  for(i=0;i<(int)(2.5*48000-WIN/2);i++)
+    data[i]=0;
+  for(i=0;i<WIN/2;i++){
+    float v=sin((i+.5)/WIN*M_PI);
+    data[i+(int)(2.5*48000-WIN/2)]*=v*v;
+  }
+  for(;i<WIN;i++){
+    float v=sin((i+.5)/WIN*M_PI);
+    data[i+(int)(2.5*48000-WIN/2)] *= v*v;
+    data[i+(int)(2.5*48000-WIN/2)] += .8*(1.-v*v);
+  }
+  for(i=(int)(2.5*48000+WIN/2);i<(int)(5*48000);i++)
+    data[i]=.8;
+
+  count=0;
+  shift=0;
+  for(i=0;i<24;i++){
+    transparent_surface(cs);
+    draw_lines(cs);
+    draw_right(cs,data, 2.5, shift);
+    write_frame(cs,"box-right",count++);
+    shift -= SPACING/PER*sin((i+.5)/48.*M_PI)*sin((i+.5)/48.*M_PI);
+  }
+  for(;i<TIME*24;i++){
+    transparent_surface(cs);
+    draw_lines(cs);
+    draw_right(cs,data, 2.5, shift);
+    write_frame(cs,"box-right",count++);
+    shift-=SPACING/PER;
+  }
+
+
+
+
+  cairo_surface_destroy(cs);
+  return 0;
+}

Added: trunk/Xiph-episode-II/cairo/ch6-squareband.c
===================================================================
--- trunk/Xiph-episode-II/cairo/ch6-squareband.c	                        (rev 0)
+++ trunk/Xiph-episode-II/cairo/ch6-squareband.c	2013-02-24 22:04:10 UTC (rev 18812)
@@ -0,0 +1,594 @@
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <math.h>
+#include <cairo/cairo.h>
+
+#define W 1920
+#define H 1080
+#define CR 300
+#define WH 120
+#define CYCLES 3.1
+#define SAMPLES (3.1*4)
+#define OFFSET .2
+
+#define TEXTY 600
+#define TEXTH 500
+#define XMARGINL 90
+#define XMARGINR 60
+#define TEXTSIZE 60
+#define REDUCA .8
+#define REDUCB .78
+
+#define OVER 20
+#define YUP (24*4)
+
+#define PRE 48
+#define FHOLD 16
+#define FFADE 24
+#define BANDHOLD 72
+#define BANDFADE 18
+#define POST 48
+
+#define PLUS 1
+#define ACCEL .01
+
+#define AXIS_LINE_WIDTH 6
+#define AXIS_COLOR 0,0,0,1
+#define AXIS_FONT_SIZE 54
+
+#define LINE_WIDTH 15
+#define COLOR .4,.4,.4,1
+
+#define ARROW_LINE_WIDTH 6
+#define ARROW_COLOR 1,0,0
+
+#define LINE_WIDTH 15
+
+#define FIRST_COLOR .6,.6,.6,1
+#define SECOND_COLOR .7,.0,.0,1
+
+void write_frame(cairo_surface_t *surface, int frameno){
+  char buffer[80];
+  cairo_status_t ret;
+
+  snprintf(buffer,80,"ch6_squareband-%04d.png",frameno);
+  ret = cairo_surface_write_to_png(surface,buffer);
+  if(ret != CAIRO_STATUS_SUCCESS){
+    fprintf(stderr,"Could not write %s: %s\n",
+	    buffer,cairo_status_to_string(ret));
+    exit(1);
+  }
+}
+
+void clear_surface(cairo_surface_t *cs){
+  cairo_t *c = cairo_create(cs);
+  cairo_set_source_rgb(c,1.,1.,1.);
+  cairo_paint(c);
+  cairo_destroy(c);
+}
+
+void transparent_surface(cairo_surface_t *cs){
+  cairo_t *c = cairo_create(cs);
+  cairo_set_source_rgba(c,1.,1.,1.,1.);
+  cairo_set_operator(c,CAIRO_OPERATOR_CLEAR);
+  cairo_paint(c);
+  cairo_destroy(c);
+}
+
+void draw_axis(cairo_surface_t *cs){
+  int i;
+  cairo_t *c = cairo_create(cs);
+  cairo_text_extents_t extents;
+  cairo_font_face_t *ff;
+  double SW = 105;
+
+  cairo_set_source_rgba(c,AXIS_COLOR);
+  cairo_set_line_width(c,AXIS_LINE_WIDTH);
+  cairo_move_to(c,0,CR);
+  cairo_line_to(c,W,CR);
+  cairo_move_to(c,W-SW/2,CR-SW/3);
+  cairo_line_to(c,W,CR);
+  cairo_line_to(c,W-SW/2,CR+SW/3);
+  cairo_stroke(c);
+
+  cairo_set_line_width(c,AXIS_LINE_WIDTH*.75);
+
+  for(i=-2;i<SAMPLES;i++){
+    double x = tox(i);
+    cairo_move_to(c,x,CR-AXIS_LINE_WIDTH*2);
+    cairo_line_to(c,x,CR+AXIS_LINE_WIDTH*2);
+  }
+  cairo_stroke(c);
+
+
+  ff = cairo_toy_font_face_create ("Adobe Garamond",
+                                   CAIRO_FONT_SLANT_ITALIC,
+                                   CAIRO_FONT_WEIGHT_NORMAL);
+  if(!ff){
+    fprintf(stderr,"Unable to create axis font");
+    exit(1);
+  }
+  cairo_set_font_face(c,ff);
+  cairo_set_font_size(c, AXIS_FONT_SIZE);
+  cairo_text_extents(c, "time", &extents);
+  cairo_move_to(c, W-extents.width-SW*.6, CR + AXIS_FONT_SIZE);
+  cairo_show_text(c, "time");
+
+  cairo_font_face_destroy(ff);
+  cairo_destroy(c);
+}
+
+double sign(double x){
+  if(x<0)return -1;
+  if(x>0)return 1;
+  return 0;
+}
+
+double get_y(double x){
+  float v = sin(((double)x/W*CYCLES+OFFSET)*2*M_PI);
+  return CR + WH*sign(v);
+}
+
+
+double memo[(W+LINE_WIDTH*2)*OVER];
+static int lastmemo=-1;
+void compute_ys(int p){
+  int i,j;
+  if(lastmemo==-1 || p<lastmemo){
+    for(j=-LINE_WIDTH*OVER;j<(W+LINE_WIDTH)*OVER;j++){
+      double v=0.;
+      for(i=0;i<p;i++)
+        v += (4/((i*2+1)*M_PI))*sin(((j*(1./OVER)/W*CYCLES+OFFSET)*(i*2+1))*2*M_PI);
+      memo[j+LINE_WIDTH*OVER]=v;
+    }
+  }else if(p>lastmemo){
+    for(j=-LINE_WIDTH*OVER;j<(W+LINE_WIDTH)*OVER;j++){
+      double v=memo[j+LINE_WIDTH*OVER];
+      for(i=lastmemo;i<p;i++)
+        v += (4/((i*2+1)*M_PI))*sin(((j*(1./OVER)/W*CYCLES+OFFSET)*(i*2+1))*2*M_PI);
+      memo[j+LINE_WIDTH*OVER]=v;
+    }
+  }
+  lastmemo=p;
+}
+
+double get_ys(int x){
+  int i;
+  float v=0;
+  x+=LINE_WIDTH;
+  x*=OVER;
+  for(i=x-OVER+1;i<x+OVER;i++){
+    float d = 1.-fabs(x-i)/OVER;
+    v+=d*memo[i];
+  }
+  return CR+WH*v/OVER;
+}
+
+int tosample(double x){
+  double SW = (double)W/SAMPLES;
+  return rint(x/SW);
+}
+
+int tox(int sample){
+  double SW = (double)W/SAMPLES;
+  return rint((sample-OFFSET*SAMPLES/CYCLES)*SW);
+}
+
+void draw_square(cairo_surface_t *cs, float alpha){
+  int i;
+  cairo_t *c = cairo_create(cs);
+  double y0;
+
+  cairo_set_source_rgba(c,FIRST_COLOR*.5*alpha);
+  cairo_set_line_cap(c,CAIRO_LINE_CAP_ROUND);
+  cairo_set_line_join(c,CAIRO_LINE_JOIN_ROUND);
+  cairo_set_line_width(c,LINE_WIDTH);
+
+  for(i=-LINE_WIDTH;i<W+LINE_WIDTH;i++){
+    double y = get_y(i);
+    cairo_move_to(c,i,y0);
+    cairo_line_to(c,i,y);
+    cairo_stroke(c);
+    y0=y;
+  }
+
+  cairo_destroy(c);
+}
+
+void draw_waveform(cairo_surface_t *cs,int p, double alpha){
+  int i;
+  cairo_t *c = cairo_create(cs);
+  double ym=0,yM=0;
+  int x=-LINE_WIDTH;
+  if(p<1)return;
+
+  cairo_set_source_rgba(c,SECOND_COLOR*alpha);
+  cairo_set_line_cap(c,CAIRO_LINE_CAP_ROUND);
+  cairo_set_line_join(c,CAIRO_LINE_JOIN_ROUND);
+  cairo_set_line_width(c,LINE_WIDTH);
+
+  compute_ys(p);
+  for(i=-LINE_WIDTH;i<W+LINE_WIDTH;i++){
+    double y = get_ys(i);
+    cairo_line_to(c,i,y);
+  }
+
+
+  cairo_stroke(c);
+  cairo_destroy(c);
+}
+
+#define MAX(x,y) ((x)>(y)?(x):(y))
+
+double draw_formula_Y(cairo_t *c,int size,double *xx, double y, int show){
+  cairo_text_extents_t extents;
+  cairo_text_extents_t extentsB;
+  cairo_font_face_t *fi,*ff;
+  double x=*xx;
+  char *text;
+
+  fi = cairo_toy_font_face_create ("Liberation Serif",
+                                   CAIRO_FONT_SLANT_OBLIQUE,
+                                   CAIRO_FONT_WEIGHT_NORMAL);
+  ff = cairo_toy_font_face_create ("Liberation Sans",
+                                   CAIRO_FONT_SLANT_NORMAL,
+                                   CAIRO_FONT_WEIGHT_NORMAL);
+  if(!fi){
+    fprintf(stderr,"Unable to create formula font");
+    exit(1);
+  }
+
+  cairo_set_font_size(c, size);
+
+  cairo_set_font_face(c,fi);
+  text = "y";
+  cairo_text_extents(c, text, &extents);
+  cairo_move_to(c,x,y-extents.y_bearing/2);
+  if(show)
+    cairo_show_text(c,text);
+  x += extents.x_advance;
+
+  cairo_set_font_face(c,ff);
+  text = "(";
+  cairo_text_extents(c, text, &extents);
+  cairo_move_to(c,x,y-extents.y_bearing/2);
+  if(show)
+    cairo_show_text(c,text);
+  x += extents.x_advance;
+
+
+  cairo_set_font_face(c,fi);
+  text = "t";
+  cairo_text_extents(c, text, &extents);
+  cairo_move_to(c,x,y-extents.y_bearing/2);
+  if(show)
+    cairo_show_text(c,text);
+  x += extents.x_advance;
+
+  cairo_set_font_face(c,ff);
+  text = ") = ";
+  cairo_text_extents(c, text, &extents);
+  cairo_move_to(c,x,y-extents.y_bearing/2);
+  if(show)
+    cairo_show_text(c,text);
+  x += extents.x_advance;
+
+  cairo_font_face_destroy(fi);
+  cairo_font_face_destroy(ff);
+  *xx=x;
+  return extents.height;
+}
+
+double draw_formula_T(cairo_t *c,int n,int size,double *xx,double y, int plus, int show){
+  cairo_text_extents_t extents;
+  cairo_text_extents_t extentsB;
+  cairo_font_face_t *fi,*ff,*fb;
+  double x=*xx;
+  char *text;
+  char buf[80];
+  double th;
+
+  fi = cairo_toy_font_face_create ("Liberation Serif",
+                                   CAIRO_FONT_SLANT_OBLIQUE,
+                                   CAIRO_FONT_WEIGHT_NORMAL);
+  ff = cairo_toy_font_face_create ("Liberation Sans",
+                                   CAIRO_FONT_SLANT_NORMAL,
+                                   CAIRO_FONT_WEIGHT_NORMAL);
+  fb = cairo_toy_font_face_create ("Liberation Sans",
+                                   CAIRO_FONT_SLANT_NORMAL,
+                                   CAIRO_FONT_WEIGHT_BOLD);
+  if(!ff){
+    fprintf(stderr,"Unable to create formula font");
+    exit(1);
+  }
+  if(!fi){
+    fprintf(stderr,"Unable to create formula font");
+    exit(1);
+  }
+
+  /* fraction */
+
+  cairo_set_font_face(c,fb);
+  cairo_set_font_size(c, size*.5);
+  text = "4";
+  cairo_text_extents(c, text, &extents);
+  th = extents.height;
+
+  text = "\xE2\x80\x94";
+  cairo_text_extents(c, text, &extentsB);
+  if(extents.x_advance>extentsB.x_advance)extentsB.x_advance=extents.x_advance;
+  if(n>1){
+    snprintf(buf,80,"%d\xCF\x80",(n*2-1));
+  }else{
+    snprintf(buf,80,"\xCF\x80");
+  }
+  cairo_text_extents(c, buf, &extents);
+  if(extents.x_advance>extentsB.x_advance)extentsB.x_advance=extents.x_advance;
+  cairo_move_to(c,x + (extentsB.x_advance - extents.x_advance)/2,y+th-extents.y_bearing/2);
+  if(show)
+    cairo_show_text(c,buf);
+
+  text = "\xE2\x80\x94";
+  cairo_text_extents(c, text, &extents);
+  cairo_move_to(c,x + (extentsB.x_advance - extents.x_advance)/2,y-extents.y_bearing);
+  if(show)
+    cairo_show_text(c,text);
+
+  text="4";
+  cairo_text_extents(c, text, &extents);
+  cairo_move_to(c,x + (extentsB.x_advance - extents.x_advance)/2,y-th-extents.y_bearing/2);
+  if(show)
+    cairo_show_text(c,text);
+  x+= extentsB.x_advance+size/10;
+
+  cairo_set_font_size(c, size);
+  cairo_set_font_face(c,ff);
+
+  if(n>1)
+    snprintf(buf,80,"sin(%d",n*2-1);
+  else
+    snprintf(buf,80,"sin(");
+  text=buf;
+  cairo_text_extents(c, text, &extents);
+  cairo_move_to(c,x,y-extents.y_bearing/2);
+  if(show)
+    cairo_show_text(c,text);
+  x+=extents.x_advance;
+
+  cairo_set_font_face(c,fi);
+  text="\xCF\x89t";
+  cairo_text_extents(c, text, &extents);
+  cairo_move_to(c,x,y-extents.y_bearing/2);
+  if(show)
+    cairo_show_text(c,text);
+  x+=extents.x_advance;
+
+  cairo_set_font_face(c,ff);
+  text=")";
+  cairo_text_extents(c, text, &extents);
+  cairo_move_to(c,x,y-extents.y_bearing/2);
+  if(show)
+    cairo_show_text(c,text);
+  x+=extents.x_advance;
+
+  cairo_set_font_face(c,ff);
+  text=" + ";
+  cairo_text_extents(c, text, &extentsB);
+  cairo_move_to(c,x,y-extentsB.y_bearing/2);
+  if(plus)
+    cairo_show_text(c,text);
+  x+=extentsB.x_advance;
+
+  cairo_font_face_destroy(ff);
+  cairo_font_face_destroy(fi);
+  *xx=x;
+  return extents.height;
+}
+
+double draw_formula_K(cairo_t *c,int n,int size,double *xx,double y, int show){
+  cairo_text_extents_t extents;
+  cairo_font_face_t *ff;
+  double x=*xx;
+  char *text;
+  char buf[80];
+  double th;
+
+  ff = cairo_toy_font_face_create ("Liberation Sans",
+                                   CAIRO_FONT_SLANT_NORMAL,
+                                   CAIRO_FONT_WEIGHT_BOLD);
+  if(!ff){
+    fprintf(stderr,"Unable to create formula font");
+    exit(1);
+  }
+
+  cairo_set_font_size(c, size*1.2);
+  cairo_set_font_face(c,ff);
+
+  snprintf(buf,80,"%dkHz + ",n*2-1);
+  text=buf;
+  cairo_text_extents(c, text, &extents);
+  cairo_move_to(c,x,y-extents.y_bearing/2);
+  if(show){
+    snprintf(buf,80,"%dkHz",n*2-1);
+    cairo_show_text(c,text);
+  }
+  x+=extents.x_advance;
+
+  cairo_font_face_destroy(ff);
+  *xx=x;
+  return extents.height;
+}
+
+void draw_formula(cairo_t *c, int from, int to, int plusp, int fracp, int kHzp){
+  double y = TEXTY;
+  double size = TEXTSIZE;
+  int i = 1;
+  int alignw=0;
+  float reduce = REDUCA;
+  double lw = W-(XMARGINL+XMARGINR);
+
+  while(y<TEXTY+TEXTH){
+
+    double w = alignw;
+    double ww = alignw;
+    int ii=i;
+    double x=XMARGINL;
+    while(w < lw){
+      ww=w;
+      if(ii==1){
+        draw_formula_Y(c,size,&w, 0, 0);
+        alignw=w;
+      }
+      draw_formula_T(c,ii,size,&w, 0, 0, 0);
+      ii++;
+    }
+
+    lw=ww-8;
+    ii--;
+
+    double yh=0;
+    if(i>1)
+      x+=alignw;
+
+    for(;i<ii;i++){
+      if(i==1){
+        draw_formula_Y(c,size,&x, y, from==0);
+        if(to==0)return;
+      }
+
+      double wT=x;
+      double wK=0;
+      double hh = draw_formula_T(c,i,size, &wT, y, plusp && i>=from && i<to, fracp && i>=from && i<to);
+      wT-=x;
+      if(hh>yh)yh=hh;
+      draw_formula_K(c,i,size,&wK, y, 0);
+      if(i>=from && i<to && kHzp){
+        wK = x + (wT-wK)/2;
+        draw_formula_K(c,i,size,&wK, y, 1);
+      }
+
+      x+=wT;
+      if(hh>yh)yh=hh;
+      if(i>=to)return;
+    }
+
+    size *= reduce;
+    reduce = REDUCB;
+    yh*=1.5;
+    y += yh+10;
+    if(size<1)size=1;
+  }
+}
+
+int main(){
+  cairo_surface_t *cs = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
+						    W,H);
+  int i,j;
+  int count=0;
+
+  transparent_surface(cs);
+  draw_waveform(cs,10,1);
+  write_frame(cs,count++);
+
+  clear_surface(cs);
+  draw_square(cs,1);
+  draw_axis(cs);
+
+  cairo_t *c=cairo_create(cs);
+  cairo_set_source_rgba(c,0,0,0,1);
+  draw_formula(c,0,0,0,0,0);
+  cairo_set_source_rgba(c,FIRST_COLOR);
+  draw_formula(c,1,1000,1,1,0);
+  cairo_destroy(c);
+
+  for(i=0;i<PRE;i++){
+    write_frame(cs,count++);
+  }
+
+  /* kHz markers */
+  int hold = FHOLD;
+  for(j=1;j<20;j+=2){
+    int jj = (j+1)/2;
+
+    for(i=1;i<=hold;i++){
+
+      clear_surface(cs);
+      draw_square(cs,1);
+      draw_axis(cs);
+
+      /* new waveform */
+      draw_waveform(cs,jj,1);
+
+      cairo_t *c=cairo_create(cs);
+      /* draw Y and all pluses */
+      cairo_set_source_rgba(c,0,0,0,1);
+      draw_formula(c,0,0,0,0,0);
+      cairo_set_source_rgba(c,FIRST_COLOR);
+      draw_formula(c,1,1000,1,0,0);
+
+      /* draw fractions to date */
+      cairo_set_source_rgba(c,0,0,0,1);
+      draw_formula(c,1,jj,0,1,0);
+
+      /* draw fractions past */
+      cairo_set_source_rgba(c,FIRST_COLOR);
+      draw_formula(c,jj+1,1000,0,1,0);
+
+      cairo_set_source_rgba(c,SECOND_COLOR);
+      draw_formula(c,jj,jj+1,0,0,1);
+
+      write_frame(cs,count++);
+    }
+    hold--;
+  }
+
+  for(i=1;i<=FHOLD-hold;i++)
+    write_frame(cs,count++);
+
+  for(i=1;i<=FFADE;i++){
+    clear_surface(cs);
+    draw_square(cs,(1-(float)i/FFADE));
+    draw_axis(cs);
+
+    /* waveform */
+    draw_waveform(cs,10,1);
+
+    cairo_t *c=cairo_create(cs);
+    /* draw Y */
+    cairo_set_source_rgba(c,0,0,0,1);
+    draw_formula(c,0,0,0,0,0);
+
+    /* draw fractions to date */
+    cairo_set_source_rgba(c,FIRST_COLOR);
+    draw_formula(c,1,10,1,0,0);
+    cairo_set_source_rgba(c,0,0,0,1);
+    draw_formula(c,1,10,0,1,0);
+
+    /* fade in last fraction */
+    cairo_set_source_rgba(c,0,0,0,(float)i/FFADE);
+    draw_formula(c,10,11,0,1,0);
+
+    /* fade out last marker */
+    cairo_set_source_rgba(c,SECOND_COLOR*(1-(float)i/FFADE));
+    draw_formula(c,10,11,0,0,1);
+
+    /* fade pluses past */
+    cairo_set_source_rgba(c,FIRST_COLOR*(1-(float)i/FFADE));
+    draw_formula(c,9,1000,1,0,0);
+
+    /* fade fractions past */
+    draw_formula(c,11,1000,0,1,0);
+
+    write_frame(cs,count++);
+  }
+
+  for(i=1;i<=BANDHOLD;i++)
+    write_frame(cs,count++);
+
+
+
+
+  cairo_surface_destroy(cs);
+  return 0;
+}

Added: trunk/Xiph-episode-II/cairo/ch6-squarefreq.c
===================================================================
--- trunk/Xiph-episode-II/cairo/ch6-squarefreq.c	                        (rev 0)
+++ trunk/Xiph-episode-II/cairo/ch6-squarefreq.c	2013-02-24 22:04:10 UTC (rev 18812)
@@ -0,0 +1,542 @@
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <math.h>
+#include <cairo/cairo.h>
+
+#define W 1920
+#define H 1080
+#define CR 300
+#define WH 120
+#define CYCLES 3.1
+#define SAMPLES (3.1*4)
+#define OFFSET .2
+
+#define TEXT0 750
+#define TEXTY 600
+#define TEXTH 500
+#define XMARGINL 90
+#define XMARGINR 60
+#define TEXTSIZE 60
+#define REDUCA .8
+#define REDUCB .78
+
+#define OVER 20
+#define YUP (24*4)
+
+#define PRE 240
+#define FADE 12
+#define FUNDAMENTAL 33
+#define PLUS (1./12)
+#define ACCEL 1.00
+#define ACCELB .0005
+#define APLUS 120
+#define APLUSPLUS 128
+#define POST 32
+
+#define AXIS_LINE_WIDTH 6
+#define AXIS_COLOR 0,0,0,1
+#define AXIS_FONT_SIZE 54
+
+#define LINE_WIDTH 15
+#define COLOR .4,.4,.4,1
+
+#define ARROW_LINE_WIDTH 6
+#define ARROW_COLOR 1,0,0
+
+#define LINE_WIDTH 15
+
+#define FIRST_COLOR .6,.6,.6,1
+#define SECOND_COLOR .7,.0,.0,1
+
+void write_frame(cairo_surface_t *surface, int frameno){
+  char buffer[80];
+  cairo_status_t ret;
+
+  snprintf(buffer,80,"ch6_squarefreq-%04d.png",frameno);
+  ret = cairo_surface_write_to_png(surface,buffer);
+  if(ret != CAIRO_STATUS_SUCCESS){
+    fprintf(stderr,"Could not write %s: %s\n",
+	    buffer,cairo_status_to_string(ret));
+    exit(1);
+  }
+}
+
+void clear_surface(cairo_surface_t *cs){
+  cairo_t *c = cairo_create(cs);
+  cairo_set_source_rgb(c,1.,1.,1.);
+  cairo_paint(c);
+  cairo_destroy(c);
+}
+
+void draw_axis(cairo_surface_t *cs){
+  int i;
+  cairo_t *c = cairo_create(cs);
+  cairo_text_extents_t extents;
+  cairo_font_face_t *ff;
+  double SW = 105;
+
+  cairo_set_source_rgba(c,AXIS_COLOR);
+  cairo_set_line_width(c,AXIS_LINE_WIDTH);
+  cairo_move_to(c,0,CR);
+  cairo_line_to(c,W,CR);
+  cairo_move_to(c,W-SW/2,CR-SW/3);
+  cairo_line_to(c,W,CR);
+  cairo_line_to(c,W-SW/2,CR+SW/3);
+  cairo_stroke(c);
+
+  cairo_set_line_width(c,AXIS_LINE_WIDTH*.75);
+
+  for(i=-2;i<SAMPLES;i++){
+    double x = tox(i);
+    cairo_move_to(c,x,CR-AXIS_LINE_WIDTH*2);
+    cairo_line_to(c,x,CR+AXIS_LINE_WIDTH*2);
+  }
+  cairo_stroke(c);
+
+
+  ff = cairo_toy_font_face_create ("Adobe Garamond",
+                                   CAIRO_FONT_SLANT_ITALIC,
+                                   CAIRO_FONT_WEIGHT_NORMAL);
+  if(!ff){
+    fprintf(stderr,"Unable to create axis font");
+    exit(1);
+  }
+  cairo_set_font_face(c,ff);
+  cairo_set_font_size(c, AXIS_FONT_SIZE);
+  cairo_text_extents(c, "time", &extents);
+  cairo_move_to(c, W-extents.width-SW*.6, CR + AXIS_FONT_SIZE);
+  cairo_show_text(c, "time");
+
+  cairo_font_face_destroy(ff);
+  cairo_destroy(c);
+}
+
+double sign(double x){
+  if(x<0)return -1;
+  if(x>0)return 1;
+  return 0;
+}
+
+double get_y(double x){
+  float v = sin(((double)x/W*CYCLES+OFFSET)*2*M_PI);
+  return CR + WH*sign(v);
+}
+
+
+double memo[(W+LINE_WIDTH*2)*OVER];
+static int lastmemo=-1;
+void compute_ys(int p){
+  int i,j;
+  if(lastmemo==-1 || p<lastmemo){
+    for(j=-LINE_WIDTH*OVER;j<(W+LINE_WIDTH)*OVER;j++){
+      double v=0.;
+      for(i=0;i<p;i++)
+        v += (4/((i*2+1)*M_PI))*sin(((j*(1./OVER)/W*CYCLES+OFFSET)*(i*2+1))*2*M_PI);
+      memo[j+LINE_WIDTH*OVER]=v;
+    }
+  }else if(p>lastmemo){
+    for(j=-LINE_WIDTH*OVER;j<(W+LINE_WIDTH)*OVER;j++){
+      double v=memo[j+LINE_WIDTH*OVER];
+      for(i=lastmemo;i<p;i++)
+        v += (4/((i*2+1)*M_PI))*sin(((j*(1./OVER)/W*CYCLES+OFFSET)*(i*2+1))*2*M_PI);
+      memo[j+LINE_WIDTH*OVER]=v;
+    }
+  }
+  lastmemo=p;
+}
+
+double get_ys(int x){
+  int i;
+  float v=0;
+  x+=LINE_WIDTH;
+  x*=OVER;
+  for(i=x-OVER+1;i<x+OVER;i++){
+    float d = 1.-fabs(x-i)/OVER;
+    v+=d*memo[i];
+  }
+  return CR+WH*v/OVER;
+}
+
+int tosample(double x){
+  double SW = (double)W/SAMPLES;
+  return rint(x/SW);
+}
+
+int tox(int sample){
+  double SW = (double)W/SAMPLES;
+  return rint((sample-OFFSET*SAMPLES/CYCLES)*SW);
+}
+
+void draw_square(cairo_surface_t *cs){
+  int i;
+  cairo_t *c = cairo_create(cs);
+  double y0;
+
+  cairo_set_source_rgba(c,FIRST_COLOR);
+  cairo_set_line_cap(c,CAIRO_LINE_CAP_ROUND);
+  cairo_set_line_join(c,CAIRO_LINE_JOIN_ROUND);
+  cairo_set_line_width(c,LINE_WIDTH);
+
+  for(i=-LINE_WIDTH;i<W+LINE_WIDTH;i++){
+    double y = get_y(i);
+    cairo_move_to(c,i,y0);
+    cairo_set_source_rgba(c,FIRST_COLOR*.5);
+    cairo_line_to(c,i,y);
+    cairo_stroke(c);
+    y0=y;
+  }
+
+  cairo_destroy(c);
+}
+
+void draw_waveform(cairo_surface_t *cs,int p, double alpha){
+  int i;
+  cairo_t *c = cairo_create(cs);
+  double ym=0,yM=0;
+  int x=-LINE_WIDTH;
+  if(p<1)return;
+
+  cairo_set_source_rgba(c,SECOND_COLOR*alpha);
+  cairo_set_line_cap(c,CAIRO_LINE_CAP_ROUND);
+  cairo_set_line_join(c,CAIRO_LINE_JOIN_ROUND);
+  cairo_set_line_width(c,LINE_WIDTH);
+
+  compute_ys(p);
+  for(i=-LINE_WIDTH;i<W+LINE_WIDTH;i++){
+    double y = get_ys(i);
+    cairo_line_to(c,i,y);
+  }
+
+
+  cairo_stroke(c);
+  cairo_destroy(c);
+}
+
+#define MAX(x,y) ((x)>(y)?(x):(y))
+
+double draw_formula_Y(cairo_t *c,int size,double *xx, double y, double alpha){
+  cairo_text_extents_t extents;
+  cairo_text_extents_t extentsB;
+  cairo_font_face_t *fi,*ff;
+  double x=*xx;
+  char *text;
+
+  fi = cairo_toy_font_face_create ("Liberation Serif",
+                                   CAIRO_FONT_SLANT_OBLIQUE,
+                                   CAIRO_FONT_WEIGHT_NORMAL);
+  ff = cairo_toy_font_face_create ("Liberation Sans",
+                                   CAIRO_FONT_SLANT_NORMAL,
+                                   CAIRO_FONT_WEIGHT_NORMAL);
+  if(!fi){
+    fprintf(stderr,"Unable to create formula font");
+    exit(1);
+  }
+
+  cairo_set_source_rgba(c,0,0,0,alpha);
+  cairo_set_font_size(c, size);
+
+  cairo_set_font_face(c,fi);
+  text = "y";
+  cairo_text_extents(c, text, &extents);
+  cairo_move_to(c,x,y-extents.y_bearing/2);
+  cairo_show_text(c,text);
+  x += extents.x_advance;
+
+  cairo_set_font_face(c,ff);
+  text = "(";
+  cairo_text_extents(c, text, &extents);
+  cairo_move_to(c,x,y-extents.y_bearing/2);
+  cairo_show_text(c,text);
+  x += extents.x_advance;
+
+
+  cairo_set_font_face(c,fi);
+  text = "t";
+  cairo_text_extents(c, text, &extents);
+  cairo_move_to(c,x,y-extents.y_bearing/2);
+  cairo_show_text(c,text);
+  x += extents.x_advance;
+
+  cairo_set_font_face(c,ff);
+  text = ") = ";
+  cairo_text_extents(c, text, &extents);
+  cairo_move_to(c,x,y-extents.y_bearing/2);
+  cairo_show_text(c,text);
+  x += extents.x_advance;
+
+  cairo_font_face_destroy(fi);
+  cairo_font_face_destroy(ff);
+  *xx=x;
+  return extents.height;
+}
+
+double draw_formula_T(cairo_t *c,int n,int size,double *xx,double y, int color, double alpha){
+  cairo_text_extents_t extents;
+  cairo_text_extents_t extentsB;
+  cairo_font_face_t *fi,*ff,*fb;
+  double x=*xx;
+  char *text;
+  char buf[80];
+  double th;
+
+  fi = cairo_toy_font_face_create ("Liberation Serif",
+                                   CAIRO_FONT_SLANT_OBLIQUE,
+                                   CAIRO_FONT_WEIGHT_NORMAL);
+  ff = cairo_toy_font_face_create ("Liberation Sans",
+                                   CAIRO_FONT_SLANT_NORMAL,
+                                   CAIRO_FONT_WEIGHT_NORMAL);
+  fb = cairo_toy_font_face_create ("Liberation Sans",
+                                   CAIRO_FONT_SLANT_NORMAL,
+                                   CAIRO_FONT_WEIGHT_BOLD);
+  if(!ff){
+    fprintf(stderr,"Unable to create formula font");
+    exit(1);
+  }
+  if(!fi){
+    fprintf(stderr,"Unable to create formula font");
+    exit(1);
+  }
+
+
+  switch(color){
+  case 0:
+    cairo_set_source_rgba(c,0,0,0,0);
+    break;
+  case 1:
+  case 2:
+    cairo_set_source_rgba(c,SECOND_COLOR*alpha);
+    break;
+  }
+
+  /* fraction */
+
+  cairo_set_font_face(c,fb);
+  cairo_set_font_size(c, size*.5);
+  text = "4";
+  cairo_text_extents(c, text, &extents);
+  th = extents.height;
+
+  text = "\xE2\x80\x94";
+  cairo_text_extents(c, text, &extentsB);
+  if(extents.x_advance>extentsB.x_advance)extentsB.x_advance=extents.x_advance;
+  if(n>1){
+    snprintf(buf,80,"%d\xCF\x80",(n*2-1));
+  }else{
+    snprintf(buf,80,"\xCF\x80");
+  }
+  cairo_text_extents(c, buf, &extents);
+  if(extents.x_advance>extentsB.x_advance)extentsB.x_advance=extents.x_advance;
+  cairo_move_to(c,x + (extentsB.x_advance - extents.x_advance)/2,y+th-extents.y_bearing/2);
+  cairo_show_text(c,buf);
+
+  text = "\xE2\x80\x94";
+  cairo_text_extents(c, text, &extents);
+  cairo_move_to(c,x + (extentsB.x_advance - extents.x_advance)/2,y-extents.y_bearing);
+  cairo_show_text(c,text);
+
+  text="4";
+  cairo_text_extents(c, text, &extents);
+  cairo_move_to(c,x + (extentsB.x_advance - extents.x_advance)/2,y-th-extents.y_bearing/2);
+  cairo_show_text(c,text);
+  x+= extentsB.x_advance+size/10;
+
+  cairo_set_font_size(c, size);
+  cairo_set_font_face(c,ff);
+
+  if(n>1)
+    snprintf(buf,80,"sin(%d",n*2-1);
+  else
+    snprintf(buf,80,"sin(");
+  text=buf;
+  cairo_text_extents(c, text, &extents);
+  cairo_move_to(c,x,y-extents.y_bearing/2);
+  cairo_show_text(c,text);
+  x+=extents.x_advance;
+
+  cairo_set_font_face(c,fi);
+  text="\xCF\x89t";
+  cairo_text_extents(c, text, &extents);
+  cairo_move_to(c,x,y-extents.y_bearing/2);
+  cairo_show_text(c,text);
+  x+=extents.x_advance;
+
+  cairo_set_font_face(c,ff);
+  text=")";
+  cairo_text_extents(c, text, &extents);
+  cairo_move_to(c,x,y-extents.y_bearing/2);
+  cairo_show_text(c,text);
+  x+=extents.x_advance;
+
+  switch(color){
+  case 0:
+  case 1:
+    cairo_set_source_rgba(c,0,0,0,0);
+    break;
+  case 2:
+    cairo_set_source_rgba(c,FIRST_COLOR*alpha);
+    break;
+  }
+
+  cairo_set_font_face(c,ff);
+  text=" + ";
+  cairo_text_extents(c, text, &extentsB);
+  cairo_move_to(c,x,y-extentsB.y_bearing/2);
+  cairo_show_text(c,text);
+  x+=extentsB.x_advance;
+
+  cairo_font_face_destroy(ff);
+  cairo_font_face_destroy(fi);
+  *xx=x;
+  return extents.height;
+}
+
+static int startframe=-1;
+
+void draw_formula(cairo_surface_t *cs, double pp, int frame, double alpha){
+  cairo_t *c = cairo_create(cs);
+  int p = pp;
+  double y = TEXTY;
+  double size = TEXTSIZE;
+  int i = 1;
+  int alignw=0;
+  float reduce = REDUCA;
+  double lw = W-(XMARGINL+XMARGINR);
+
+  while(y<TEXTY+TEXTH){
+
+    double w = alignw;
+    double ww = alignw;
+    int ii=i;
+    double x=XMARGINL;
+    while(w < lw){
+      ww=w;
+      if(ii==1){
+        draw_formula_Y(c,size,&w, 0, 0);
+        alignw=w;
+      }
+      draw_formula_T(c,ii,size,&w, 0, 0,0);
+      if(i==1 && ii>p && w<lw){
+        ii++;
+        x = (W-XMARGINL-XMARGINR-ww)/2+XMARGINL;
+        y = TEXT0;
+        break;
+      }
+      ii++;
+    }
+
+    if(i==1 && ii-1>p && w>=lw && startframe==-1){
+      startframe=frame;
+    }
+    if(i==1 && startframe>-1){
+      float del = (float)(frame-startframe)/YUP;
+      if(del<=1){
+        del = sin(del*.5*M_PI);
+        del *= del;
+        y = del*TEXTY + (1-del)*TEXT0;
+      }else{
+        y = TEXTY;
+      }
+    }
+
+    lw=ww-8;
+    ii--;
+
+
+    double yh=0;
+
+    if(i>1)
+      x+=alignw;
+
+    for(;i<ii;i++){
+      if(i==1)
+        draw_formula_Y(c,size,&x, y, alpha);
+
+      double hh=draw_formula_T(c,i,size,&x, y, i<p?2:(p&&i<=p?1:0), alpha);
+      if(hh>yh)yh=hh;
+    }
+
+    size *= reduce;
+    reduce = REDUCB;
+    yh*=1.5;
+    y += yh+10;
+    if(size<1)size=1;
+  }
+  cairo_destroy(c);
+}
+
+int main(){
+  cairo_surface_t *cs = cairo_image_surface_create (CAIRO_FORMAT_RGB24,
+						    W,H);
+  int i;
+  int count=0;
+
+  clear_surface(cs);
+  draw_square(cs);
+  draw_axis(cs);
+  for(i=0;i<PRE;i++){
+    write_frame(cs,count++);
+  }
+
+  /* fade to fundamental */
+  for(i=0;i<FADE;i++){
+    clear_surface(cs);
+    draw_square(cs);
+    draw_axis(cs);
+    draw_waveform(cs,1,(float)i/FADE);
+    draw_formula(cs,1,0,(float)i/FADE);
+    write_frame(cs,count++);
+  }
+
+  /* hold fundamental */
+  clear_surface(cs);
+  draw_square(cs);
+  draw_axis(cs);
+  draw_waveform(cs,1,1);
+  draw_formula(cs,1,0,1);
+  for(i=0;i<FUNDAMENTAL;i++){
+    write_frame(cs,count++);
+  }
+
+  /* count up */
+  float p = 2;
+  float a = ACCEL;
+  float a2 = PLUS;
+  int lastinc=0;
+  int cont=0;
+  for(i=1;i<=APLUS;i++){
+    clear_surface(cs);
+    draw_square(cs);
+    draw_axis(cs);
+    draw_waveform(cs,(int)p,1);
+    draw_formula(cs,p,i,1);
+    write_frame(cs,count++);
+    a+=(i<APLUS/2)?ACCELB:ACCELB*(1+20*((float)(i-APLUS/2)/(APLUS/2)));
+    a2*=a;
+    int inc = (int)(p+a2) - (int)(p);
+    if(inc>0 && lastinc>0)cont=1;
+    if(cont && inc<lastinc){
+      p+=inc=lastinc;
+    }else{
+      p+=a2;
+    }
+    lastinc=inc;
+    fprintf(stderr,"p=%f, togo=%d\n",p,APLUSPLUS-i);
+  }
+
+ for(;i<=APLUSPLUS;i++){
+    clear_surface(cs);
+    draw_square(cs);
+    draw_axis(cs);
+    draw_waveform(cs,(int)p,1);
+    draw_formula(cs,p,i,1);
+    write_frame(cs,count++);
+    fprintf(stderr,"p=%f, togo=%d\n",p,APLUSPLUS-i);
+  }
+
+  for(i=0;i<POST;i++)
+    write_frame(cs,count++);
+
+  cairo_surface_destroy(cs);
+  return 0;
+}

Added: trunk/Xiph-episode-II/cairo/ch6-squaretime.c
===================================================================
--- trunk/Xiph-episode-II/cairo/ch6-squaretime.c	                        (rev 0)
+++ trunk/Xiph-episode-II/cairo/ch6-squaretime.c	2013-02-24 22:04:10 UTC (rev 18812)
@@ -0,0 +1,509 @@
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <math.h>
+#include <cairo/cairo.h>
+
+#define W 1920
+#define H 1080
+#define CR 300
+#define WH 120
+#define CYCLES 3.1
+#define SAMPLES (3.1*4)
+#define OFFSET .2
+
+#define TEXTY 750
+#define TEXTSIZE 80
+
+/*
+-1  0 < t mod T <= .5T
+  1  .5T < t mod T <= T
+*/
+
+
+#define AXIS_LINE_WIDTH 6
+#define AXIS_COLOR 0,0,0,1
+#define AXIS_FONT_SIZE 54
+
+#define LINE_WIDTH 15
+#define COLOR .4,.4,.4,1
+
+#define ARROW_LINE_WIDTH 6
+#define ARROW_COLOR 1,0,0
+
+#define LINE_WIDTH 15
+
+#define FIRST_COLOR .6,.6,.6,1
+#define SECOND_COLOR .7,.0,.0,1
+
+void write_frame(cairo_surface_t *surface, int frameno){
+  char buffer[80];
+  cairo_status_t ret;
+
+  snprintf(buffer,80,"ch6_squaretime-%04d.png",frameno);
+  ret = cairo_surface_write_to_png(surface,buffer);
+  if(ret != CAIRO_STATUS_SUCCESS){
+    fprintf(stderr,"Could not write %s: %s\n",
+	    buffer,cairo_status_to_string(ret));
+    exit(1);
+  }
+}
+
+void clear_surface(cairo_surface_t *cs){
+  cairo_t *c = cairo_create(cs);
+  cairo_set_source_rgb(c,1.,1.,1.);
+  cairo_paint(c);
+  cairo_destroy(c);
+}
+
+void draw_axis(cairo_surface_t *cs){
+  int i;
+  cairo_t *c = cairo_create(cs);
+  cairo_text_extents_t extents;
+  cairo_font_face_t *ff;
+  double SW = 105;
+
+  cairo_set_source_rgba(c,AXIS_COLOR);
+  cairo_set_line_width(c,AXIS_LINE_WIDTH);
+  cairo_move_to(c,0,CR);
+  cairo_line_to(c,W,CR);
+  cairo_move_to(c,W-SW/2,CR-SW/3);
+  cairo_line_to(c,W,CR);
+  cairo_line_to(c,W-SW/2,CR+SW/3);
+  cairo_stroke(c);
+
+  cairo_set_line_width(c,AXIS_LINE_WIDTH*.75);
+
+  for(i=-2;i<SAMPLES;i++){
+    double x = tox(i);
+    cairo_move_to(c,x,CR-AXIS_LINE_WIDTH*2);
+    cairo_line_to(c,x,CR+AXIS_LINE_WIDTH*2);
+  }
+  cairo_stroke(c);
+
+
+  ff = cairo_toy_font_face_create ("Adobe Garamond",
+                                   CAIRO_FONT_SLANT_ITALIC,
+                                   CAIRO_FONT_WEIGHT_NORMAL);
+  if(!ff){
+    fprintf(stderr,"Unable to create axis font");
+    exit(1);
+  }
+  cairo_set_font_face(c,ff);
+  cairo_set_font_size(c, AXIS_FONT_SIZE);
+  cairo_text_extents(c, "time", &extents);
+  cairo_move_to(c, W-extents.width-SW*.6, CR + AXIS_FONT_SIZE);
+  cairo_show_text(c, "time");
+
+  cairo_font_face_destroy(ff);
+  cairo_destroy(c);
+}
+
+double sign(double x){
+  if(x<0)return -1;
+  if(x>0)return 1;
+  return 0;
+}
+
+double get_y(double x){
+  float v = sin(((double)x/W*CYCLES+OFFSET)*2*M_PI);
+  return CR + WH*sign(v);
+}
+
+int tosample(double x){
+  double SW = (double)W/SAMPLES;
+  return rint(x/SW);
+}
+
+int tox(int sample){
+  double SW = (double)W/SAMPLES;
+  return rint((sample-OFFSET*SAMPLES/CYCLES)*SW);
+}
+
+void draw_waveform(cairo_surface_t *cs,int p){
+  int i;
+  cairo_t *c = cairo_create(cs);
+  double y0;
+
+  cairo_set_source_rgba(c,FIRST_COLOR);
+  cairo_set_line_cap(c,CAIRO_LINE_CAP_ROUND);
+  cairo_set_line_join(c,CAIRO_LINE_JOIN_ROUND);
+  cairo_set_line_width(c,LINE_WIDTH);
+
+  for(i=-LINE_WIDTH;i<W+LINE_WIDTH;i++){
+    double y = get_y(i);
+    if(i==-LINE_WIDTH){
+    }else{
+      cairo_move_to(c,i,y0);
+      if(y!=y0){
+        cairo_set_source_rgba(c,FIRST_COLOR*.5);
+      }else{
+        if( (y<CR && p==1) || (y>CR && p==2) )
+          cairo_set_source_rgba(c,SECOND_COLOR*.5);
+        else
+          cairo_set_source_rgba(c,FIRST_COLOR*.5);
+      }
+      cairo_line_to(c,i,y);
+      cairo_stroke(c);
+    }
+    y0=y;
+  }
+
+  cairo_destroy(c);
+}
+
+#define MAX(x,y) ((x)>(y)?(x):(y))
+void draw_formula(cairo_surface_t *cs,int p){
+  cairo_text_extents_t extents;
+  cairo_text_extents_t extentsB;
+  cairo_text_extents_t extentsT;
+  cairo_font_face_t *fi,*ff,*fb;
+  cairo_t *c = cairo_create(cs);
+
+  ff = cairo_toy_font_face_create ("Liberation Sans",
+                                   CAIRO_FONT_SLANT_NORMAL,
+                                   CAIRO_FONT_WEIGHT_NORMAL);
+  fi = cairo_toy_font_face_create ("Liberation Serif",
+                                   CAIRO_FONT_SLANT_OBLIQUE,
+                                   CAIRO_FONT_WEIGHT_NORMAL);
+  fb = cairo_toy_font_face_create ("Adobe Garamond",
+                                   CAIRO_FONT_SLANT_NORMAL,
+                                   CAIRO_FONT_WEIGHT_NORMAL);
+  if(!ff){
+    fprintf(stderr,"Unable to create formula font");
+    exit(1);
+  }
+  if(!fi){
+    fprintf(stderr,"Unable to create formula font");
+    exit(1);
+  }
+
+  int th=0,tw=0,bw=0,bh=0,w5=0,wT;
+
+  cairo_set_font_face(c,fi);
+  cairo_set_font_size(c, TEXTSIZE);
+
+  cairo_text_extents(c, "y(t) = ", &extents);
+  tw += extents.x_advance;
+  cairo_text_extents(c, " t (mod T)", &extents);
+  tw += extents.x_advance;
+
+  cairo_text_extents(c, "T", &extentsT);
+  wT = extentsT.x_advance;
+  th = extentsT.height;
+
+  cairo_set_font_face(c,ff);
+  cairo_text_extents(c, " +1,  ", &extents);
+  cairo_text_extents(c, " -1,  ", &extentsB);
+
+  tw += MAX(extents.x_advance, extentsB.x_advance);
+
+  cairo_set_font_size(c, TEXTSIZE/2);
+  cairo_text_extents(c, "\xE2\x80\x94", &extents);
+  w5 = extents.x_advance + extentsT.x_advance;
+  tw += w5+w5;
+
+  cairo_set_font_size(c, TEXTSIZE);
+  cairo_text_extents(c, " \x3C", &extents);
+  tw += extents.x_advance;
+  cairo_text_extents(c, " \xE2\x89\xA4 ", &extents);
+  tw += extents.x_advance;
+
+  cairo_set_font_face(c,fb);
+  cairo_set_font_size(c, TEXTSIZE*4);
+  cairo_text_extents(c, "{", &extents);
+  tw += extents.x_advance;
+
+  cairo_set_font_face(c,fi);
+  cairo_set_font_size(c, TEXTSIZE);
+  cairo_set_source_rgba(c,0,0,0,1);
+
+  int x = W/2-(tw/2)*1.05;
+  int y = (th*1.5);
+  char *text;
+
+  cairo_set_source_rgba(c,0,0,0,1);
+  text="y(t) = ";
+  cairo_text_extents(c, text, &extents);
+  cairo_move_to(c, x, TEXTY-extents.y_bearing/2);
+  cairo_show_text(c,text);
+  x += extents.x_advance;
+
+  cairo_set_font_face(c,fb);
+  cairo_set_font_size(c, TEXTSIZE*4);
+  text="{";
+  cairo_text_extents(c, text, &extents);
+  int d = extents.height+extents.y_bearing;
+
+  cairo_move_to(c,x,TEXTY+extents.height/2-d);
+  cairo_show_text(c,text);
+  x += extents.x_advance;
+
+  switch(p){
+  case 0:
+    cairo_set_source_rgba(c,0,0,0,1);
+    break;
+  case 1:
+    cairo_set_source_rgba(c,SECOND_COLOR);
+    break;
+  case 2:
+    cairo_set_source_rgba(c,FIRST_COLOR);
+    break;
+  }
+  cairo_set_font_face(c,ff);
+  cairo_set_font_size(c, TEXTSIZE);
+  text=" +1,  ";
+  cairo_text_extents(c, text, &extents);
+  cairo_move_to(c,x,TEXTY-y-extents.y_bearing/2);
+  cairo_show_text(c,text);
+
+  switch(p){
+  case 0:
+    cairo_set_source_rgba(c,0,0,0,1);
+    break;
+  case 1:
+   cairo_set_source_rgba(c,FIRST_COLOR);
+    break;
+  case 2:
+    cairo_set_source_rgba(c,SECOND_COLOR);
+    break;
+  }
+  text=" -1,  ";
+  cairo_text_extents(c, text, &extentsB);
+  cairo_move_to(c,x+(extents.x_advance-extentsB.x_advance),TEXTY+y-extentsB.y_bearing/2);
+  cairo_show_text(c,text);
+  x += extents.x_advance;
+
+
+  switch(p){
+  case 0:
+    cairo_set_source_rgba(c,0,0,0,1);
+    break;
+  case 1:
+    cairo_set_source_rgba(c,SECOND_COLOR);
+    break;
+  case 2:
+    cairo_set_source_rgba(c,FIRST_COLOR);
+    break;
+  }
+  text="0";
+  cairo_text_extents(c, text, &extents);
+  cairo_move_to(c,x + (w5 - extents.x_advance)/2,TEXTY-y-extents.y_bearing/2);
+  cairo_show_text(c,text);
+
+  switch(p){
+  case 0:
+    cairo_set_source_rgba(c,0,0,0,1);
+    break;
+  case 1:
+   cairo_set_source_rgba(c,FIRST_COLOR);
+    break;
+  case 2:
+    cairo_set_source_rgba(c,SECOND_COLOR);
+    break;
+  }
+  cairo_set_font_size(c, TEXTSIZE/2);
+  text="1";
+  cairo_text_extents(c, text, &extents);
+  cairo_move_to(c,x + (w5 - wT - extents.x_advance)/2,TEXTY+y-th*.5-extents.y_bearing/2);
+  cairo_show_text(c,text);
+
+  text="\xE2\x80\x94";
+  cairo_text_extents(c, text, &extents);
+  cairo_move_to(c,x + (w5 - wT - extents.x_advance)/2,TEXTY+y-extents.y_bearing);
+  cairo_show_text(c,text);
+
+  text="2";
+  cairo_text_extents(c, text, &extents);
+  cairo_move_to(c,x + (w5 - wT - extents.x_advance)/2,TEXTY+y+th*.5-extents.y_bearing/2);
+  cairo_show_text(c,text);
+
+  cairo_set_font_size(c, TEXTSIZE);
+  cairo_set_font_face(c,fi);
+  text="T";
+  cairo_text_extents(c, text, &extents);
+  cairo_move_to(c,x + (w5 - wT),TEXTY+y-extents.y_bearing/2);
+  cairo_show_text(c,text);
+  x += w5;
+
+  switch(p){
+  case 0:
+    cairo_set_source_rgba(c,0,0,0,1);
+    break;
+  case 1:
+    cairo_set_source_rgba(c,SECOND_COLOR);
+    break;
+  case 2:
+    cairo_set_source_rgba(c,FIRST_COLOR);
+    break;
+  }
+  cairo_set_font_face(c,ff);
+  text=" \x3C";
+  cairo_text_extents(c, text, &extents);
+  cairo_move_to(c,x,TEXTY-y-extents.y_bearing/2);
+  cairo_show_text(c,text);
+  cairo_text_extents(c, text, &extents);
+  cairo_move_to(c,x,TEXTY+y-extents.y_bearing/2);
+  switch(p){
+  case 0:
+    cairo_set_source_rgba(c,0,0,0,1);
+    break;
+  case 1:
+   cairo_set_source_rgba(c,FIRST_COLOR);
+    break;
+  case 2:
+    cairo_set_source_rgba(c,SECOND_COLOR);
+    break;
+  }
+  cairo_show_text(c,text);
+  x += extents.x_advance;
+
+  switch(p){
+  case 0:
+    cairo_set_source_rgba(c,0,0,0,1);
+    break;
+  case 1:
+    cairo_set_source_rgba(c,SECOND_COLOR);
+    break;
+  case 2:
+    cairo_set_source_rgba(c,FIRST_COLOR);
+    break;
+  }
+  cairo_set_font_face(c,fi);
+  text=" t (mod T)";
+  cairo_text_extents(c, text, &extents);
+  cairo_move_to(c,x,TEXTY-y-extents.y_bearing/2);
+  cairo_show_text(c,text);
+  cairo_text_extents(c, text, &extents);
+  switch(p){
+  case 0:
+    cairo_set_source_rgba(c,0,0,0,1);
+    break;
+  case 1:
+   cairo_set_source_rgba(c,FIRST_COLOR);
+    break;
+  case 2:
+    cairo_set_source_rgba(c,SECOND_COLOR);
+    break;
+  }
+  cairo_move_to(c,x,TEXTY+y-extents.y_bearing/2);
+  cairo_show_text(c,text);
+  x += extents.x_advance;
+
+  switch(p){
+  case 0:
+    cairo_set_source_rgba(c,0,0,0,1);
+    break;
+  case 1:
+    cairo_set_source_rgba(c,SECOND_COLOR);
+    break;
+  case 2:
+    cairo_set_source_rgba(c,FIRST_COLOR);
+    break;
+  }
+  cairo_set_font_face(c,ff);
+  text=" \xE2\x89\xA4 ";
+  cairo_text_extents(c, text, &extents);
+  cairo_move_to(c,x,TEXTY-y-extents.y_bearing/2);
+  cairo_show_text(c,text);
+  cairo_text_extents(c, text, &extents);
+  switch(p){
+  case 0:
+    cairo_set_source_rgba(c,0,0,0,1);
+    break;
+  case 1:
+   cairo_set_source_rgba(c,FIRST_COLOR);
+    break;
+  case 2:
+    cairo_set_source_rgba(c,SECOND_COLOR);
+    break;
+  }
+  cairo_move_to(c,x,TEXTY+y-extents.y_bearing/2);
+  cairo_show_text(c,text);
+  x += extents.x_advance;
+
+
+  switch(p){
+  case 0:
+    cairo_set_source_rgba(c,0,0,0,1);
+    break;
+  case 1:
+   cairo_set_source_rgba(c,FIRST_COLOR);
+    break;
+  case 2:
+    cairo_set_source_rgba(c,SECOND_COLOR);
+    break;
+  }
+  cairo_set_font_face(c,fi);
+  text="T";
+  cairo_text_extents(c, text, &extents);
+  cairo_move_to(c,x + (w5 - extents.x_advance)/2,TEXTY+y-extents.y_bearing/2);
+  cairo_show_text(c,text);
+
+  switch(p){
+  case 0:
+    cairo_set_source_rgba(c,0,0,0,1);
+    break;
+  case 1:
+    cairo_set_source_rgba(c,SECOND_COLOR);
+    break;
+  case 2:
+    cairo_set_source_rgba(c,FIRST_COLOR);
+    break;
+  }
+  cairo_set_font_face(c,ff);
+  cairo_set_font_size(c, TEXTSIZE/2);
+  text="1";
+  cairo_text_extents(c, text, &extents);
+  cairo_move_to(c,x + (w5 - wT - extents.x_advance)/2,TEXTY-y-th*.5-extents.y_bearing/2);
+  cairo_show_text(c,text);
+
+  text="\xE2\x80\x94";
+  cairo_text_extents(c, text, &extents);
+  cairo_move_to(c,x + (w5 - wT - extents.x_advance)/2,TEXTY-y-extents.y_bearing);
+  cairo_show_text(c,text);
+
+  text="2";
+  cairo_text_extents(c, text, &extents);
+  cairo_move_to(c,x + (w5 - wT - extents.x_advance)/2,TEXTY-y+th*.5-extents.y_bearing/2);
+  cairo_show_text(c,text);
+
+  cairo_set_font_size(c, TEXTSIZE);
+  cairo_set_font_face(c,fi);
+  text="T";
+  cairo_text_extents(c, text, &extents);
+  cairo_move_to(c,x + (w5 - wT),TEXTY-y-extents.y_bearing/2);
+  cairo_show_text(c,text);
+  x += w5;
+
+  cairo_font_face_destroy(ff);
+  cairo_font_face_destroy(fi);
+  cairo_font_face_destroy(fb);
+}
+
+
+int main(){
+  cairo_surface_t *cs = cairo_image_surface_create (CAIRO_FORMAT_RGB24,
+						    W,H);
+
+  clear_surface(cs);
+  draw_waveform(cs,0);
+  draw_axis(cs);
+  draw_formula(cs,0);
+  write_frame(cs,0);
+
+  clear_surface(cs);
+  draw_waveform(cs,1);
+  draw_axis(cs);
+  draw_formula(cs,1);
+  write_frame(cs,1);
+
+  clear_surface(cs);
+  draw_waveform(cs,2);
+  draw_axis(cs);
+  draw_formula(cs,2);
+  write_frame(cs,2);
+
+  cairo_surface_destroy(cs);
+  return 0;
+}

Added: trunk/Xiph-episode-II/cairo/ch6-wrong.c
===================================================================
--- trunk/Xiph-episode-II/cairo/ch6-wrong.c	                        (rev 0)
+++ trunk/Xiph-episode-II/cairo/ch6-wrong.c	2013-02-24 22:04:10 UTC (rev 18812)
@@ -0,0 +1,180 @@
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <cairo/cairo.h>
+
+#define W 3840
+#define H 2160
+#define CR1 907.5
+#define CR2 1237.5
+#define DE  .2
+#define WH 225
+#define SAMPLES 3840
+#define SPACING 100
+#define IMPULSE 80
+
+#define MULT 1.
+#define RATE 48000
+
+#define TIME 25
+#define PER 36
+
+
+#define LINE_WIDTH 6
+#define WAVE_WIDTH 15
+#define BAND .6,0,0,1
+#define WAVETOP .0,.0,.0,1
+#define WAVEBOTTOM .0,.0,.0,.25
+#define LINE .6,.9,1.,1
+
+void write_frame(cairo_surface_t *surface, char *base, int frameno){
+  char buffer[80];
+  cairo_status_t ret;
+
+  snprintf(buffer,80,"ch6_%s-%04d.png",base,frameno);
+  ret = cairo_surface_write_to_png(surface,buffer);
+  if(ret != CAIRO_STATUS_SUCCESS){
+    fprintf(stderr,"Could not write %s: %s\n",
+	    buffer,cairo_status_to_string(ret));
+    exit(1);
+  }
+}
+
+void transparent_surface(cairo_surface_t *cs){
+  cairo_t *c = cairo_create(cs);
+  cairo_set_source_rgba(c,1.,1.,1.,1.);
+  cairo_set_operator(c,CAIRO_OPERATOR_CLEAR);
+  cairo_paint(c);
+  cairo_destroy(c);
+}
+
+void draw_lines(cairo_surface_t *cs){
+  cairo_t *c = cairo_create(cs);
+  double o;
+
+  cairo_set_source_rgba(c,LINE);
+  cairo_set_line_cap(c,CAIRO_LINE_CAP_BUTT);
+  cairo_set_line_width(c,LINE_WIDTH);
+
+  cairo_move_to(c,W/2.,CR2-WH);
+  cairo_line_to(c,W/2.,CR2+WH*DE);
+  o=SPACING*MULT;
+
+  while(o<W+LINE_WIDTH/2.){
+    double x0=W/2.-o;
+    double x1=W/2.+o;
+
+    cairo_move_to(c,x0,CR2-WH);
+    cairo_line_to(c,x0,CR2+WH*DE);
+    cairo_move_to(c,x1,CR2-WH);
+    cairo_line_to(c,x1,CR2+WH*DE);
+
+    o+=SPACING*MULT;
+  }
+
+  cairo_stroke(c);
+  cairo_destroy(c);
+}
+
+void draw_wrong(cairo_surface_t *cs,float *data, float center, float shift){
+  int i;
+  cairo_t *c = cairo_create(cs);
+
+  float center_sample = center*RATE+shift;
+  float samples = (float)W/MULT;
+  int start_sample = rint(center_sample-(samples/2));
+  int end_sample = rint(start_sample+samples);
+  float hold = CR2;
+  float thresh=0;
+  double o=SPACING*MULT;
+
+  while(o+LINE_WIDTH<W/2.){
+    thresh=W/2.-o;
+    o+=SPACING*MULT;
+  }
+
+  fprintf(stderr,"%d %d %f\n",start_sample,end_sample,shift);
+
+  cairo_set_source_rgba(c,WAVETOP);
+  cairo_set_line_cap(c,CAIRO_LINE_CAP_ROUND);
+  cairo_set_line_join(c,CAIRO_LINE_JOIN_ROUND);
+  cairo_set_line_width(c,WAVE_WIDTH);
+
+  for(i=start_sample;i<end_sample;i++){
+    double t = (double)(i-shift)/RATE;
+    double x = (double)W/2. + (t-center)*RATE*MULT;
+    double y = CR1-data[i]*WH;
+
+    if(i==start_sample)
+      cairo_move_to(c,x,y);
+    else
+      cairo_line_to(c,x,y);
+  }
+
+  cairo_stroke(c);
+
+  cairo_set_source_rgba(c,WAVEBOTTOM);
+
+  cairo_move_to(c,thresh,CR2);
+  for(i=start_sample;i<end_sample;i++){
+    double tp = (double)(i-shift-1)/RATE;
+    double t = (double)(i-shift)/RATE;
+    double xp = (double)W/2. + (tp-center)*RATE*MULT;
+    double x = (double)W/2. + (t-center)*RATE*MULT;
+
+    if(xp<thresh && x>=thresh){
+      cairo_line_to(c,thresh,hold);
+
+      hold=CR2-data[i]*WH;
+
+      cairo_line_to(c,thresh,hold);
+      thresh += SPACING*MULT;
+    }
+
+    cairo_line_to(c,x,hold);
+  }
+
+  cairo_stroke(c);
+  cairo_destroy(c);
+}
+
+int main(){
+  cairo_surface_t *cs = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
+						    W,H);
+  float *data=calloc(5*48000,sizeof(float));
+  int i,count=0;
+
+  int start=2.5*48000-IMPULSE/2;
+  for(i=0;i<IMPULSE;i++){
+    float v = sin((i+.5)/IMPULSE*M_PI);
+    data[i+start]= v*v*.8;
+  }
+
+  for(i=0;i<360;i++){
+    transparent_surface(cs);
+    draw_lines(cs);
+    draw_wrong(cs,data, 2.5, (TIME*24/2.+2.2*24-i)*SPACING/PER);
+    write_frame(cs,"impulse-wrong",count++);
+  }
+
+  memset(data,0,sizeof(*data)*48000*5);
+
+  for(i=0;i<2.5*48000;i++)
+    data[i]=0;
+  for(;i<5*48000;i++)
+    data[i]=.8;
+
+  count=0;
+
+  for(i=0;i<360;i++){
+    transparent_surface(cs);
+    draw_lines(cs);
+    draw_wrong(cs,data, 2.5, (TIME*24/2.+2.2*24-i)*SPACING/PER);
+    write_frame(cs,"box-wrong",count++);
+  }
+
+  cairo_surface_destroy(cs);
+  return 0;
+}



More information about the commits mailing list