// $Id: cd.cc 1907 2007-02-07 02:53:00Z flaterco $
// Fill a CD with random selections.
//
// Usage:
//
// Pipe the list of wav files to standard input.  E.g.,
//
//   find /share/audio -name *.wav -print | /usr/local/bin/cd
//
// Tracks are left in /tmp as NN.wav.
//
// g++ -O2 -Wall -Wextra -pedantic -s -o cd cd.cc -ldstr
//
// Based on jukebox.cc,v 1.1 2005/07/10 01:47:49

#include <stdio.h>
#include <Dstr>
#include <vector>
#include <time.h>
#include <stdlib.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <math.h>

// Length tolerances in seconds.  There are enough songs between 3 and
// 4 minutes but not many shorter than 3.
// 77.5 minutes
#define min_length 4650
// 81.5 minutes
#define max_length 4890

#define tmptmpname "/tmp/cd_tmp_delme.wav"

// Return length in seconds.
double lengthofwav (char *fname) {
  struct stat s;
  assert (stat (fname, &s) == 0);
  assert (s.st_size > 44);
  // wav files from sox and normalize have 44 bytes of header.
  // 1 second = 44100 samples * 2 channels * 16 bits = 176400 bytes.
  return (double)(s.st_size - 44) / 176400.0;
}

// Hook to allow printing of the commands that get executed, if desired.
void shellcmd (char *cmd) {
  // printf ("shell> %s\n", cmd);
  system (cmd);
}

// Adaptively trim silence from start and end of song.  Some songs
// have silent bits in the middle, so they require a longer interval
// for detecting the end.

// Toofar:  if change in length exceeds toofar seconds, then you have
// screwed up.
#define toofar 20

double trim (char *from, char *to, double fromlen) {
  char cmd[1000];
  double interval=0.1, tolen=0.0;
  do {
    printf ("  Trimming with interval %3.1f\n", interval);
    sprintf (cmd, "sox %s %s silence 1 0:0:0.01 -50d 1 0:0:%3.1f -60d",
	     from, to, interval);
    shellcmd (cmd);
    tolen = lengthofwav(to);
    printf ("  %02lu:%05.2f  trimmed length",
      (unsigned long)(tolen/60), fmod(tolen,60.0));
    if (tolen < fromlen - toofar) {
      printf (" -- OOPS");
      interval *= 2;
    }
    printf ("\n");
  } while (tolen < fromlen - toofar);
  return tolen;
}

int main (int argc __attribute__ ((unused)),
          char **argv __attribute__ ((unused))) {
  Dstr buf;
  std::vector<Dstr> playlist;
  while (!(buf.getline(stdin).isNull()))
    playlist.push_back (buf);
  srand (time(NULL));
  unsigned long songnum=1;
  double length = 0.0;
  while (length < min_length) {
    assert (songnum < 99);
    assert (playlist.size());

    printf ("%d songs to choose from.\n", playlist.size());

    unsigned long nextup = (unsigned long)((double)rand()/(1.0+(double)RAND_MAX) * playlist.size());
    Dstr nextnam;
    {
      std::vector<Dstr>::iterator it = playlist.begin() + nextup;
      nextnam = *it;
      playlist.erase (it);
    }

    double newlength = lengthofwav(nextnam.aschar());
    printf ("Trying %s\n  %02lu:%05.2f  orig length\n", 
      nextnam.aschar(),
      (unsigned long)(newlength/60), fmod(newlength,60.0));

    // Delete silence at start and end.
    newlength = trim (nextnam.aschar(), tmptmpname, newlength);

    if (newlength + length < max_length) {
      char cmd[1000];

      // Normalize volume without limiting (the limiter in normalize
      // seems ineffective).
      sprintf (cmd, "normalize -q -a -13dB --clipping %s", tmptmpname);
      shellcmd (cmd);

      // Bring up the quiet bits but don't squash the loud bits.
      char fname[80];
      sprintf (fname, "/tmp/%02lu.wav", songnum);
      sprintf (cmd, "sox %s %s compand .05,.5 -25,-15,-13,-13 0 -25 .05",
	       tmptmpname, fname);
      shellcmd (cmd);
      printf ("Wrote to %s.\n", fname);

      length += newlength;
      printf ("CD length now %02lu:%05.2f.\n\n", (unsigned long)(length/60),
        fmod(length,60.0));
      songnum++;
    } else
      printf ("Too long.  Try again.\n\n");
  }
  unlink (tmptmpname);
  printf ("Done.  Burn tracks /tmp/NN.wav to a CD.\n");
  return 0;
}
