Skip to content

Instantly share code, notes, and snippets.

@RavuAlHemio
Created July 27, 2013 19:36
Show Gist options
  • Save RavuAlHemio/6096006 to your computer and use it in GitHub Desktop.
Save RavuAlHemio/6096006 to your computer and use it in GitHub Desktop.
Magnetophon: simulates the frequency oscillation in a broken tape deck or damaged tape using SoundTouch and SoX.
/**
* Simulates the frequency oscillation in a broken tape deck or damaged tape
* using SoundTouch and SoX.
*
* Released into the public domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
#include <string>
#include <cassert>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <sox.h>
#include <soundtouch/SoundTouch.h>
/** The buffer size when reading or writing a file. */
static const size_t BUFSIZE = 1024;
/** The name of the program, generally as provided in argv[0]. */
static const char *progname = "magnetophon";
/** How to bend the signal? */
enum class BendMode
{
INVALID,
LINEAR,
SINE
};
/** Parameters for Magnetophon operation. */
struct Opts
{
/** How to bend the signal? */
BendMode bm;
/** How often should the function oscillate? */
double period;
/** The file name from which to read. */
const char *srcfn;
/** The file name in which to write. */
const char *dstfn;
/** Construct with a few sane defaults. */
Opts() : bm(BendMode::SINE), period(1.0), srcfn(nullptr), dstfn(nullptr) {}
};
/**
* Convert the bend mode from a string into the corresponding enum
* value.
*/
BendMode parseBendMode(const std::string & s)
{
if (s == "lin" || s == "linear")
{
return BendMode::LINEAR;
}
else if (s == "sin" || s == "sine")
{
return BendMode::SINE;
}
return BendMode::INVALID;
}
/** Output a message about how this program is to be called. */
static void usage(void)
{
fprintf(stderr, "Usage: %s [-m lin|sin] [-p PERIOD] SOURCE TARGET\n", progname);
}
/**
* Parses a double-precision floating-point value.
*
* Uses strtod.
*
* @param str The string to parse.
* @param out The value is stored in the target of this pointer.
* @return Whether the conversion was successful. An error message is also
* printed on stderr if not.
*/
static bool parseDouble(const char *str, double *out)
{
assert(out != NULL);
char *end;
*out = strtod(str, &end);
if (*out == HUGE_VAL && errno == ERANGE)
{
fprintf(stderr, "%s: '%s' is too large\n", progname, str);
return false;
}
else if (*out == -HUGE_VAL && errno == ERANGE)
{
fprintf(stderr, "%s: '%s' is too small\n", progname, str);
return false;
}
else if (*out == 0.0 && errno == ERANGE)
{
fprintf(stderr, "%s: '%s' is too minuscule\n", progname, str);
return false;
}
else if (end[0] != '\0')
{
fprintf(stderr, "%s: '%s' contains invalid characters: '%s'\n", progname, str, end);
return false;
}
return true;
}
/**
* Parse the command-line arguments.
* @param opts Reference to structure in which to store options.
* @param argc Number of arguments.
* @param argv Array of argument strings.
* @param Whether the parsing was successful.
*/
static bool parseArgs(Opts *opts, int argc, char **argv)
{
assert(opts != nullptr);
if (argc > 0)
{
progname = argv[0];
}
int c;
while ((c = getopt(argc, argv, "m:p:")) != -1)
{
switch (c)
{
case 'm':
opts->bm = parseBendMode(optarg);
break;
case 'p':
if (!parseDouble(optarg, &opts->period))
{
usage();
return false;
}
break;
case '?':
usage();
return false;
default:
assert(0 && "Unexpected option.");
return false;
}
}
if (opts->bm == BendMode::INVALID)
{
fprintf(stderr, "%s: invalid bend mode\n", progname);
usage();
return false;
}
if (opts->period <= 0.0)
{
fprintf(stderr, "%s: period must be greater than 0.0\n", progname);
usage();
return false;
}
if (argc - optind != 2)
{
usage();
return false;
}
opts->srcfn = argv[optind + 0];
opts->dstfn = argv[optind + 1];
/* FIXME: doesn't canonicalize paths */
if (strcmp(opts->srcfn, opts->dstfn) == 0)
{
fprintf(stderr, "%s: source and target must not be the same\n", progname);
return false;
}
return true;
}
struct SoxFormats
{
SoxFormats()
{
sox_format_init();
}
~SoxFormats()
{
sox_format_quit();
}
};
struct SoxRead
{
sox_format_t *fmt;
SoxRead(const char *path) : fmt(nullptr)
{
fmt = sox_open_read(path, NULL, NULL, NULL);
}
~SoxRead()
{
if (fmt != nullptr)
{
sox_close(fmt);
}
}
};
struct SoxWrite
{
sox_signalinfo_t sig;
sox_format_t *fmt;
SoxWrite(const char *path, double samrate, unsigned chans, unsigned bps) : sig(), fmt(nullptr)
{
sig.rate = samrate;
sig.channels = chans;
sig.precision = bps;
sig.length = 0;
sig.mult = nullptr;
fmt = sox_open_write(path, &sig, NULL, NULL, NULL, NULL);
}
~SoxWrite()
{
if (fmt != nullptr)
{
sox_close(fmt);
}
}
};
/**
* Calculates a correction factor according to the number of bits per sample.
*/
__attribute__((pure))
static inline float corrFactor(unsigned bps)
{
assert(bps > 0);
float ret = 128.0f;
--bps;
while (--bps > 0)
{
ret *= 256.0f;
}
return ret;
}
/**
* Converts an array of SoX samples into an array of floating-point samples (in
* the range [-1, 1]).
* @param flts Pointer to the destination array.
* @param sams Pointer to the source array.
* @param bps Bits per sample actually used.
* @param sz Number of elements -- must not be greater than the size of flts
* and sams.
*/
static void soxSamplesToFloats(float *flts, const sox_sample_t *sams, unsigned bps, size_t sz)
{
size_t i;
for (i = 0; i < sz; ++i)
{
flts[i] = ((float)sams[i]) / corrFactor(bps);
}
}
/**
* Converts an array of SoX samples into an array of floating-point samples (in
* the range [-1, 1]).
* @param flts Pointer to the destination array.
* @param sams Pointer to the source array.
* @param bps Bits per sample to actually use.
* @param sz Number of elements -- must not be greater than the size of flts
* and sams.
*/
static void floatsToSoxSamples(sox_sample_t *sams, const float *flts, unsigned bps, size_t sz)
{
size_t i;
for (i = 0; i < sz; ++i)
{
sams[i] = (sox_sample_t)(flts[i] * corrFactor(bps));
}
}
/**
* The main entry point of Magnetophon.
* @param argc Number of arguments.
* @param argv Array of argument strings.
* @return Zero on success, non-zero on failure.
*/
int main(int argc, char **argv)
{
// parse options
Opts opts;
if (!parseArgs(&opts, argc, argv))
{
return 1;
}
// start SoX
SoxFormats sfs;
// start SoundTouch
soundtouch::SoundTouch st;
st.setRate(1);
st.setTempo(1);
// prepare some values
unsigned chans;
unsigned bps;
double samrate;
// with the file
{
SoxRead rd = SoxRead(opts.srcfn);
if (rd.fmt == nullptr)
{
fprintf(stderr, "%s: error opening %s\n", progname, opts.srcfn);
return 1;
}
// store important signal info
samrate = rd.fmt->signal.rate;
bps = rd.fmt->signal.precision;
chans = rd.fmt->signal.channels;
// configure SoundTouch
st.setChannels(chans);
st.setSampleRate(samrate);
// read the file's contents, feeding them to SoundTouch
{
float flts[BUFSIZE];
sox_sample_t sams[BUFSIZE];
size_t rdsz;
float n = 0.0;
for (;;)
{
rdsz = sox_read(rd.fmt, sams, BUFSIZE);
if (rdsz == 0)
{
break;
}
soxSamplesToFloats(flts, sams, bps, rdsz);
st.putSamples(flts, rdsz / chans);
st.setRate(0.875f + (cosf(1e-10f * n) / 8.0f));
n += rdsz * samrate * opts.period;
}
}
}
st.flush();
// with the other file
{
SoxWrite wr = SoxWrite(opts.dstfn, samrate, chans, bps);
if (wr.fmt == nullptr)
{
fprintf(stderr, "%s: error opening %s\n", progname, opts.dstfn);
return 1;
}
// read from SoundTouch, write into file
{
float flts[BUFSIZE];
sox_sample_t sams[BUFSIZE];
size_t wrsz;
for (;;)
{
wrsz = st.receiveSamples(flts, BUFSIZE / chans) * chans;
if (wrsz == 0)
{
break;
}
floatsToSoxSamples(sams, flts, bps, wrsz);
sox_write(wr.fmt, sams, wrsz);
}
}
}
// done, I guess
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment