--- /dev/null
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <termios.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/wait.h>
+#include <vorbis/vorbisfile.h>
+#include <alsa/asoundlib.h>
+#include <sndfile.h>
+
+static void __attribute__((noreturn)) die(char *msg, ...)
+{
+ va_list args;
+ va_start(args, msg);
+ vfprintf(stderr, msg, args);
+ fputc('\n', stderr);
+ exit(1);
+}
+
+typedef long long s64;
+typedef short s16;
+#define MIN(a,b) ((a)<(b) ? (a) : (b))
+#define MAX(a,b) ((a)>(b) ? (a) : (b))
+#define TRIPLE(pos) (int)(((pos)/rate)/60), (int)(((pos)/rate)%60), (int)((pos)%rate)
+
+static struct termios tios, tios_old;
+
+static void key_init(void)
+{
+ if (tcgetattr(0, &tios_old) < 0)
+ die("tcgetattr failed: %m");
+ tios = tios_old;
+ tios.c_iflag = IGNBRK;
+ tios.c_lflag = 0;
+ tios.c_cc[VTIME] = 0;
+ tios.c_cc[VMIN] = 1;
+ if (tcsetattr(0, 0, &tios) < 0)
+ die("tcsetattr failed: %m");
+ fcntl(0, F_SETFL, O_NONBLOCK);
+}
+
+static void key_cleanup(void)
+{
+ tcsetattr(0, 0, &tios_old);
+}
+
+static int key_get(void)
+{
+ char keybuf[1];
+ static int esc_state, esc_num;
+
+ for (;;)
+ {
+ int e = read(0, keybuf, 1);
+ if (e != 1)
+ return 0;
+
+ int key = keybuf[0];
+ switch (esc_state)
+ {
+ case 0:
+ if (key != '\e')
+ return key;
+ esc_state = 1;
+ esc_num = 0;
+ break;
+ case 1:
+ if (key == '[')
+ esc_state = 2;
+ else
+ {
+ esc_state = 0;
+ return 0x0100 + key;
+ }
+ break;
+ case 2:
+ if (key >= '0' && key <= '9')
+ esc_num = 10*esc_num + key - '0';
+ else
+ {
+ esc_state = 0;
+ return (esc_num << 16) + 0x200 + key;
+ }
+ break;
+ }
+ }
+}
+
+static FILE *infile;
+enum { IN_WAV, IN_OGG } inmode;
+static OggVorbis_File vf;
+static SNDFILE *sndf;
+static unsigned int rate;
+static char *find_title = "Toulky ceskou minulosti";
+static s64 total_samples = 0;
+static s64 start_pos = -1;
+static s64 end_pos = -1;
+double prefade = 1, postfade = 1;
+
+static void scan_streams(void)
+{
+ int nstr = ov_streams(&vf);
+ printf("OGG: Scanning %d logical streams:\n", nstr);
+ if (!nstr)
+ die("No streams found");
+ for (int i=0; i<nstr; i++)
+ {
+ vorbis_info *vi;
+ vi = ov_info(&vf, i);
+ if (!vi)
+ die("ov_info failed");
+ if (vi->channels != 2)
+ die("Stream %d has %d channels, which is not supported", i, vi->channels);
+ if ((unsigned int) vi->rate != rate)
+ {
+ if (rate)
+ die("Stream %d has sample rate %d, while the previous had %d", i, vi->rate, rate);
+ else
+ rate = vi->rate;
+ }
+
+ vorbis_comment *vc;
+ vc = ov_comment(&vf, i);
+ if (!vc)
+ die("ov_comment failed");
+ char title[1024];
+ title[0] = 0;
+ for (int j=0; j<vc->comments; j++)
+ if (vc->comment_lengths[j] > 6 && !strncasecmp(vc->user_comments[j], "title=", 6))
+ {
+ int l = vc->comment_lengths[j] - 6;
+ memcpy(title, vc->user_comments[j]+6, l);
+ title[l] = 0;
+ }
+ if (!title[0])
+ strcpy(title, "<none>");
+
+ s64 samples = ov_pcm_total(&vf, i);
+ int sec = (samples + rate - 1) / rate;
+ printf(" %d: `%s' (%d:%02d, %d bits/sec) @%Ld\n", i, title, sec/60, sec%60, (int)vi->bitrate_nominal, total_samples);
+
+ if (find_title && strstr(title, find_title))
+ {
+ if (start_pos < 0)
+ start_pos = total_samples;
+ else if (end_pos != total_samples)
+ printf("WARNING: Gap encountered!\n");
+ end_pos = total_samples + samples;
+ }
+
+ total_samples += samples;
+ }
+
+ if (start_pos < 0)
+ {
+ if (find_title)
+ printf("WARNING: Title not found, marking whole file");
+ start_pos = 0;
+ end_pos = total_samples;
+ }
+}
+
+static void in_open(char *name)
+{
+ infile = fopen(name, "r");
+ if (!infile)
+ die("Cannot open %s: %m", name);
+
+ char s[4];
+ if (fread(s, 1, 4, infile) != 4)
+ die("Input file too short");
+ rewind(infile);
+ if (!memcmp(s, "RIFF", 4))
+ {
+ puts("INPUT: WAV file detected");
+ inmode = IN_WAV;
+ }
+ else if (!memcmp(s, "OggS", 4))
+ {
+ puts("INPUT: OGG file detected");
+ inmode = IN_OGG;
+ }
+ else if (!memcmp(s, "HTTP", 4))
+ {
+ puts("INPUT: HTTP header detected, expecting OGG inside");
+ inmode = IN_OGG;
+ }
+ else
+ die("Unable to identify input format");
+
+ if (inmode == IN_WAV)
+ {
+ SF_INFO si;
+ bzero(&si, sizeof(si));
+ lseek(fileno(infile), 0, SEEK_SET);
+ sndf = sf_open_fd(fileno(infile), O_RDONLY, &si, 0);
+ if (!sndf)
+ die("sf_open_fd() failed: %s", sf_strerror(NULL));
+ total_samples = si.frames;
+ rate = si.samplerate;
+ if (si.channels != 2)
+ die("Got %d channels instead of 2", si.channels);
+ if (si.sections != 1)
+ die("Found %d sections, what does it mean?", si.sections);
+ printf("WAV: format id=%08x\n", si.format);
+ start_pos = 0;
+ end_pos = total_samples;
+ }
+ else
+ {
+ int err = ov_open(infile, &vf, NULL, 0);
+ if (err)
+ die("ov_open: error %d", err);
+ if (!ov_seekable(&vf))
+ die("Input is not seekable, how come?");
+ scan_streams();
+ }
+
+ printf("INPUT: length %3d:%02d.%05d, %Ld samples at rate %d\n", TRIPLE(total_samples), total_samples, rate);
+}
+
+static void in_goto(s64 go)
+{
+ if (inmode == IN_OGG)
+ ov_pcm_seek(&vf, go);
+ else
+ sf_seek(sndf, go, SEEK_SET);
+}
+
+static int in_read(s16 *buf, s64 pos, int nsamp)
+{
+ if (inmode == IN_OGG)
+ {
+ if (ov_pcm_tell(&vf) != pos)
+ printf("!!! CONFUSED POSITION\n");
+ for (;;)
+ {
+ int bp;
+ int e = ov_read(&vf, (char*)buf, 4*nsamp, 0, 2, 1, &bp);
+ if (e == OV_HOLE)
+ printf("!!! HOLE DETECTED\n");
+ else if (e == OV_EBADLINK)
+ printf("!!! BAD LINK\n");
+ else if (e % 4)
+ die("ov_read returned %d bytes, which means non-integer number of samples. Huh.", e);
+ else if (e/4 <= nsamp)
+ return e/4;
+ else
+ die("ov_read returned %d samples, although we wanted %d", e/4, nsamp);
+ }
+ }
+ else
+ {
+ int e = sf_readf_short(sndf, buf, nsamp);
+ if (e != nsamp)
+ printf("!!!! sf_readf_short mismatch: %d != %d\n", e, nsamp);
+ return e;
+ }
+}
+
+static void in_close(void)
+{
+ if (inmode == IN_OGG)
+ ov_clear(&vf);
+ else
+ sf_close(sndf);
+}
+
+static s16 *prefader, *postfader;
+
+static s16 *calc_fader(int len, int rev)
+{
+ if (!len)
+ return NULL;
+ s16 *x = malloc(2*len);
+ for (int i=0; i<len; i++)
+ x[rev ? len-1-i : i] = (s64)i*32767/len;
+ return x;
+}
+
+static void recalc_faders(void)
+{
+ // printf("FADERS: set to %g pre, %g post\n", prefade, postfade);
+ free(prefader);
+ prefader = calc_fader(prefade*rate, 0);
+ free(postfader);
+ postfader = calc_fader(postfade*rate, 1);
+}
+
+static void apply_fader(s16 *sig, int nsamp, s64 pos, s64 fstart, int flen, s16 *fader)
+{
+ s64 relpos = pos - fstart;
+ if (relpos <= -nsamp || relpos >= flen)
+ return;
+ int rel = relpos;
+ if (rel < 0)
+ {
+ rel = -rel;
+ sig += 2*rel;
+ nsamp -= rel;
+ rel = 0;
+ }
+ while (nsamp > 0 && rel < flen)
+ {
+ sig[0] = (sig[0] * fader[rel]) >> 15;
+ sig[1] = (sig[1] * fader[rel]) >> 15;
+ sig += 2;
+ nsamp--;
+ rel++;
+ }
+}
+
+static int cooked_read(s16 *buf, s64 pos, int nsamp, int apply_pre, int apply_post)
+{
+ nsamp = in_read(buf, pos, nsamp);
+ int presamp = prefade * rate;
+ int postsamp = postfade * rate;
+ if (apply_pre)
+ apply_fader(buf, nsamp, pos, start_pos, presamp, prefader);
+ if (apply_post)
+ apply_fader(buf, nsamp, pos, end_pos-postsamp, postsamp, postfader);
+ return nsamp;
+}
+
+static char *cuename;
+
+static int cue_load(void)
+{
+ if (cuename)
+ {
+ FILE *f = fopen(cuename, "r");
+ if (f)
+ {
+ if (fscanf(f, "%Ld%lf%Ld%lf", &start_pos, &prefade, &end_pos, &postfade) != 4)
+ die("CUE: Invalid syntax");
+ fclose(f);
+ printf("CUE: Loaded\n");
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static void cue_save(void)
+{
+ if (cuename)
+ {
+ FILE *f = fopen(cuename, "w");
+ if (!f)
+ die("CUE: Cannot write");
+ fprintf(f, "%Ld %g %Ld %g\n", start_pos, prefade, end_pos, postfade);
+ fclose(f);
+ }
+}
+
+static void editor(void)
+{
+ printf("Initializing ALSA\n");
+ snd_pcm_t *pcm;
+ snd_pcm_hw_params_t *apars;
+ int err;
+#define ALSACALL(f,args) if ((err = f args) < 0) die(#f " failed: %s", snd_strerror(err))
+ ALSACALL(snd_pcm_open, (&pcm, "default", SND_PCM_STREAM_PLAYBACK, 0));
+ ALSACALL(snd_pcm_hw_params_malloc, (&apars));
+ ALSACALL(snd_pcm_hw_params_any, (pcm, apars));
+ ALSACALL(snd_pcm_hw_params_set_access, (pcm, apars, SND_PCM_ACCESS_RW_INTERLEAVED));
+ ALSACALL(snd_pcm_hw_params_set_format, (pcm, apars, SND_PCM_FORMAT_S16_LE));
+ unsigned int xrate = rate;
+ int dir = 0;
+ ALSACALL(snd_pcm_hw_params_set_rate_near, (pcm, apars, &xrate, &dir));
+ if (xrate != rate)
+ printf("WARNING: Rate set to %d instead of %d\n", xrate, rate);
+ ALSACALL(snd_pcm_hw_params_set_channels, (pcm, apars, 2));
+ ALSACALL(snd_pcm_hw_params, (pcm, apars));
+ snd_pcm_hw_params_free(apars);
+ ALSACALL(snd_pcm_prepare, (pcm));
+
+ s64 go = start_pos;
+ s64 pos = -1;
+
+#define CLAMP(x) (((x) < 0) ? 0 : ((x) >= total_samples) ? total_samples-1 : (x))
+
+ s16 buf[2048];
+ key_init();
+
+ enum {
+ M_START,
+ M_END,
+ M_PRE,
+ M_POST,
+ } mode = M_START;
+ int silence = 0;
+ int step = rate;
+ double fst = 1;
+ double fsil = 1;
+ int lback = 3*rate;
+ for(;;)
+ {
+ if (go >= 0)
+ {
+ if (pos != go)
+ {
+ in_goto(go);
+ pos = go;
+ }
+ go = -1;
+ }
+
+ int key = key_get();
+ switch (key)
+ {
+ case ' ':
+ if (mode == M_START || mode == M_PRE)
+ {
+ start_pos = pos;
+ go = start_pos;
+ mode = M_PRE;
+ }
+ else if (mode == M_END || mode == M_POST)
+ {
+ end_pos = pos;
+ go = CLAMP(end_pos - lback);
+ mode = M_POST;
+ }
+ break;
+ case 0x241:
+ fst *= 2;
+ step = rate * fst;
+ break;
+ case 0x242:
+ fst /= 2;
+ step = rate * fst;
+ break;
+ case '1' ... '4':
+ fst = 1. / (16 >> (key - '0'));
+ step = rate * fst;
+ break;
+ case '5' ... '9':
+ fst = 1 << (key - '4');
+ step = rate * fst;
+ break;
+ case 0x244:
+ if (mode == M_PRE)
+ {
+ start_pos = CLAMP(start_pos - step);
+ go = start_pos;
+ }
+ else if (mode == M_POST)
+ {
+ end_pos = CLAMP(end_pos - step);
+ go = CLAMP(end_pos - lback);
+ }
+ else
+ go = CLAMP(pos - step);
+ break;
+ case 0x243:
+ if (mode == M_PRE)
+ {
+ start_pos = CLAMP(start_pos + step);
+ go = start_pos;
+ }
+ else if (mode == M_POST)
+ {
+ end_pos = CLAMP(end_pos + step);
+ go = CLAMP(end_pos - lback);
+ }
+ else
+ go = CLAMP(pos + step);
+ break;
+ case '\r':
+ case '\n':
+ if (mode == M_PRE)
+ {
+ go = start_pos;
+ silence = fsil*rate;
+ }
+ else if (mode == M_POST)
+ {
+ go = CLAMP(end_pos - lback);
+ silence = fsil*rate;
+ }
+ else
+ silence = 0;
+ break;
+ case 0x7027e: // Home
+ if (mode <= M_END)
+ go = 0;
+ break;
+ case 0x8027e: // End
+ if (mode <= M_END)
+ go = CLAMP(total_samples - lback);
+ break;
+ case 0x5027e: // PgUp
+ if (mode <= M_END)
+ go = CLAMP(pos - 60*rate);
+ break;
+ case 0x6027e: // PgUp
+ if (mode <= M_END)
+ go = CLAMP(pos + 60*rate);
+ break;
+ case 0x2027e: // Insert
+ mode = M_START;
+ break;
+ case 0x3027e: // Delete
+ mode = M_END;
+ break;
+ case '[':
+ mode = M_PRE;
+ go = start_pos;
+ break;
+ case ']':
+ mode = M_POST;
+ go = CLAMP(end_pos - lback);
+ break;
+ case 0x7f:
+ silence = -1;
+ break;
+ case '+':
+ case '-':
+ case '/':
+ case '*':
+ if (mode == M_PRE || mode == M_POST)
+ {
+ double *f = (mode == M_PRE ? &prefade : &postfade);
+ if (key == '/')
+ *f = 0;
+ else if (key == '*')
+ *f = MIN(fst, 10);
+ else if (key == '+')
+ *f = MIN(*f + fst, 10);
+ else
+ *f = MAX(*f - fst, 0);
+ recalc_faders();
+ if (mode == 1)
+ go = start_pos;
+ else
+ go = CLAMP(end_pos - lback);
+ }
+ break;
+ case 'q':
+ cue_save();
+ /* fall-thru */
+ case 'Q':
+ case 3:
+ goto done;
+ default:
+ printf("KEY <%x>\n", key);
+ case 0: ;
+ }
+ if (go >= 0)
+ continue;
+
+ printf("%3d:%02d.%05d [%s %3d:%02d.%05d/%g -> %s %3d:%02d.%05d\\%g] step=%g%s\e[K\r",
+ TRIPLE(pos),
+ (mode == M_START) ? "START" : (mode == M_PRE) ? "START>" : "start", TRIPLE(start_pos), prefade,
+ (mode == M_END) ? "END" : (mode == M_POST) ? ">END" : "end", TRIPLE(end_pos), postfade,
+ fst,
+ (silence < 0) ? " [STOP]" : "");
+ fflush(stdout);
+
+ s64 end = (mode == M_POST) ? end_pos : total_samples;
+ int nsamp, nread;
+ if (pos >= end || silence)
+ {
+ bzero(buf, 1024);
+ nsamp = 1024/4;
+ if (silence > 0)
+ {
+ nsamp = MIN(nsamp, silence);
+ silence -= nsamp;
+ }
+ nread = 0;
+ }
+ else
+ {
+ nsamp = sizeof(buf)/4;
+ if (pos + nsamp > end_pos)
+ nsamp = end_pos - pos;
+ nsamp = cooked_read(buf, pos, nsamp, (mode == M_PRE), (mode == M_POST));
+ nread = nsamp;
+ }
+
+ int err = snd_pcm_writei(pcm, buf, nsamp);
+ if (err == -EPIPE)
+ {
+ puts("[xrun detected]");
+ err = snd_pcm_prepare(pcm);
+ if (err < 0)
+ die("xrun recovery failed: error %d", err);
+ }
+ if (err < 0)
+ die("snd_pcm_writei failed: error %d", err);
+ pos += nread;
+ if (pos > end)
+ pos = end;
+ }
+
+ done:
+ key_cleanup();
+}
+
+static int nchildren;
+
+static int add_filter(int fd, char **args)
+{
+ int p[2];
+ if (pipe(p) < 0)
+ die("pipe: %m");
+ pid_t pid = fork();
+ if (pid < 0)
+ die("fork: %m");
+ if (!pid)
+ {
+ close(p[1]);
+ dup2(p[0], 0);
+ close(p[0]);
+ dup2(fd, 1);
+ close(fd);
+ execvp(args[0], args);
+ die("execvp(%s) failed: %m", args[0]);
+ }
+ else
+ {
+ nchildren++;
+ printf("FILTER: Forked pid %d for %s\n", pid, args[0]);
+ close(p[0]);
+ close(fd);
+ return p[1];
+ }
+}
+
+static int orig_stdout;
+
+static void render(char *name)
+{
+ char *sfix;
+ int fd;
+ if (!strcmp(name, "-"))
+ {
+ fd = orig_stdout;
+ sfix = ".wav";
+ }
+ else
+ {
+ sfix = strrchr(name, '.') ? : "";
+ fd = open(name, O_WRONLY | O_CREAT | O_TRUNC, 0666);
+ if (fd < 0)
+ die("Unable to create %s: %m", name);
+ }
+ if (!strcmp(sfix, ".WAV"))
+ printf("RENDER: WAV output without sample rate conversion\n");
+ else if (!strcmp(sfix, ".wav"))
+ {
+ printf("RENDER: WAV output\n");
+ if (rate != 44100)
+ {
+ printf("RENDER: Adding resample filter\n");
+ char *f[] = { "sox", "-twav", "-", "-r44100", "-twav", "-", "vol", "0.9", "resample", NULL };
+ fd = add_filter(fd, f);
+ }
+ }
+ else
+ die("Unknown output file suffix");
+
+ s64 outlen = end_pos - start_pos;
+
+#if 0
+ /* GRRRR! libsndfile is unable to write WAV files to pipes! */
+ SF_INFO si = {
+ .frames = 0,
+ .samplerate = rate,
+ .channels = 2,
+ .format = SF_FORMAT_WAV | SF_FORMAT_PCM_16,
+ .sections = 1,
+ .seekable = 0
+ };
+ SNDFILE *sf = sf_open_fd(fd, SFM_WRITE, &si, 0);
+ if (!sf)
+ die("Output sf_open_fd() failed: %s", sf_strerror(NULL));
+#else
+ FILE *of = fdopen(fd, "w");
+ void wt(char *x) { fwrite(x, 4, 1, of); }
+ void wr(unsigned int x) { fwrite(&x, sizeof(x), 1, of); }
+ wt("RIFF");
+ wr(8 + 0x10 + 8 + 4*outlen);
+ wt("WAVE");
+ wt("fmt ");
+ wr(0x10);
+ wr(0x00020001); // 2 channels, not compressed
+ wr(rate);
+ wr(4*rate);
+ wr(0x00100004); // 16-bit
+ wt("data");
+ wr(4*outlen);
+#endif
+
+ s64 pos = start_pos;
+ s64 outpos = 0;
+ s16 buf[65536];
+ in_goto(pos);
+ printf("RENDER: Generating %d:%02d.%05d of output\n", TRIPLE(outlen));
+ for (;;)
+ {
+ int n = sizeof(buf) / 4;
+ if (pos + n > end_pos)
+ n = end_pos - pos;
+ if (!n)
+ break;
+ n = cooked_read(buf, pos, n, 1, 1);
+ pos += n;
+ outpos += n;
+#if 0
+ if (sf_writef_short(sf, buf, n) != n)
+#else
+ if ((int)fwrite(buf, 4, n, of) != n)
+#endif
+ die("Short write, oops!");
+ printf("RENDER: %3d:%02d.%05d\r", TRIPLE(outpos));
+ fflush(stdout);
+ }
+
+#if 0
+ sf_close(sf);
+ close(fd);
+#else
+ fclose(of);
+#endif
+ while (nchildren)
+ {
+ int st;
+ pid_t p = wait(&st);
+ if (p < 0)
+ printf("wait(): %m\n");
+ else
+ {
+ nchildren--;
+ if (WIFEXITED(st))
+ {
+ if (WEXITSTATUS(st))
+ printf("!!! pid %d failed with exit code %d\n", p, WEXITSTATUS(st));
+ else
+ printf("FILTER: pid %d finished OK\n", p);
+ }
+ else
+ printf("!!! pid %d failed with status %x\n", p, st);
+ }
+ }
+ printf("RENDER: Rendering successfully completed.\n");
+}
+
+int main(int argc, char **argv)
+{
+ if (argc < 2 || argc > 4)
+ die("Usage: vorbiscut <infile> [<cuefile> [<outfile>|-]]");
+
+ if (argc >= 4)
+ {
+ orig_stdout = dup(1);
+ dup2(2, 1);
+ }
+
+ in_open(argv[1]);
+ if (argc >= 3)
+ cuename = argv[2];
+ int cueok = cue_load();
+ recalc_faders();
+ if (argc >= 4)
+ {
+ if (!cueok)
+ printf("!!!!! Cue sheet not present, rendering with defaults !!!!!\n");
+ render(argv[3]);
+ }
+ else
+ {
+ editor();
+ }
+ in_close();
+ return 0;
+}