/* reads 16-bit stereo PCM on stdin, writes sped up mono mp3 on stdout */ /* adapted from https://github.com/waywardgeek/sonic */ #include #include enum{MAXPERIOD = 672, MINPERIOD = 108, SKIP = 12, INSAMPS = 2048}; s16int input[2 * MAXPERIOD + INSAMPS]; s16int output[2 * MAXPERIOD]; s16int down[2 * MAXPERIOD / SKIP]; float speed = 2.0; int nin, nout, nremain, minΔ, maxΔ, prev, prevminΔ; void cp(s16int *to, s16int *from, int n){ while(n--) *to++ = *from++; } int findperiodlim(s16int *in, int min, int max){ int period, best = 0, worst = 255, Δ, i; s16int *s, *p, sval, pval; minΔ = 1; maxΔ = 0; for(period = min; period <= max; period++){ s = in; p = in + period; for(Δ = i = 0; i < period; i++){ sval = *s++; pval = *p++; Δ += sval >= pval ? sval - pval : pval - sval; } if(Δ * best < minΔ * period){ minΔ = Δ; best = period; } if(Δ * worst > maxΔ * period){ maxΔ = Δ; worst = period; } } minΔ /= best; maxΔ /= worst; return best; } void downsample(s16int *in, s16int *out){ int i, j, val; for(i = 2 * MAXPERIOD / SKIP; i; i--){ for(val = j = 0; j < SKIP; j++) val += *in++; *out++ = val / SKIP; } } int findperiod(s16int *in){ int min, max, period; downsample(in, down); period = findperiodlim(down, MINPERIOD / SKIP, MAXPERIOD / SKIP) * SKIP; min = period - (SKIP << 2); if(min < MINPERIOD) min = MINPERIOD; max = period + (SKIP << 2); if(max > MAXPERIOD) max = MAXPERIOD; period = findperiodlim(in, min, max); /* at abrupt ends of words the previous period can be a better estimate */ max = minΔ == 0 || prev == 0 || maxΔ > minΔ * 3 || minΔ * 2 <= prevminΔ * 3 ? period : prev; prevminΔ = minΔ; prev = period; return max; } void resample(void){ s16int *in, *out, *↓, *↑; int pos = 0, period, n, i; if(nin < 2 * MAXPERIOD) return; do{ if(nremain > 0){ n = nremain < 2 * MAXPERIOD ? nremain : 2 * MAXPERIOD; cp(output + nout, input + pos, n); nout += n; nremain -= n; pos += n; }else{ in = input + pos; period = findperiod(in); if(speed >= 2.0f) n = period / (speed - 1.0f); else{ n = period; nremain = period * (2.0f - speed) / (speed - 1.0f); } out = output + nout; ↓ = in; ↑ = in + period; for(i = 0; i < n; i++) /* overlap add */ *out++ = (*↓++ * (n - i) + *↑++ * i) / n; nout += n; pos += period + n; } }while(pos + 2 * MAXPERIOD <= nin); cp(input, input + pos, nin -= pos); } void main(int i, char *arg[]){ uchar buf[INSAMPS * 4]; int p[2], n, more; if(i > 2) sysfatal("usage: %s [speed]", arg[0]); if(i == 2 && (speed = atof(arg[1])) < 1.0001) sysfatal("I only go fast!"); if(pipe(p) < 0) sysfatal("pipe: %r"); n = fork(); if(n < 0) sysfatal("fork: %r"); if(n == 0){ dup(p[1], 0); close(p[1]); close(p[0]); execl("/bin/audio/mp3enc", "mp3enc", "-r", "-m", "m", nil); sysfatal("exec: %r"); } close(p[1]); do{ i = read(0, buf, sizeof(buf)) & ~3; if(i < 0) sysfatal("read: %r"); n = i >> 2; if(n == 0){/* flush */ i = nelem(input) - nin; while(i--) input[nin + i] = 0; i = nout + nin / speed + 0.5f; nin += 4 * MAXPERIOD; resample(); if(nout > i) nout = i; nin = nremain = 0; }else{ do{ i -= 4; input[nin + (i >> 2)] = (s16int)(buf[i | 1] << 8 | buf[i]) + (s16int)(buf[i | 3] << 8 | buf[i | 2]) >> 1; }while(i); nin += n; resample(); } while(nout){ if(nout <= INSAMPS) more = 0; else{ more = nout - INSAMPS; nout = INSAMPS; } for(i = 0; i < nout; i++){/* mp3enc takes big endian */ buf[i << 1] = output[i] >> 8; buf[i << 1 | 1] = output[i]; } if(i && write(p[0], buf, i << 1) != i << 1) sysfatal("write: %r"); cp(output, output + nout, more); nout = more; } }while(n > 0); close(p[0]); exits(nil); }