// $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 #include #include #include #include #include #include #include #include #include // 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 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::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; }