Crossfade Loop

Crossfade Loop

A simple crossfade loop generator commandline interface. It's all bundled up in a single tangled file called "cfloop.c":

<<cfloop.c>>=
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "dr_wav/dr_wav.h"

<<cfloop_top>>
<<main>>

CLI Usage

cfloop in.wav out.wav loop_amt

in.wav is the input filename.

out.wav is the output filename.

loop_amt is the loop length (percentage, value between 0 and 1).

Command line arguments are parsed with the function run_cfloop:

CLI function

The CLI can be instantiated with run_cfloop. It expects arguments from main.

<<main>>=
int run_cfloop(int argc, char *argv[])
{
    if(argc < 4) {
        printf("%s: in.wav out.wav cf\n", argv[0]);
        return 1;
    }

    return do_cfloop(argv[1], argv[2], atof(argv[3]));
}

Algorithm

The recipe for a basic crossfade loop is pretty simple: Apply a linear fade in N seconds long at the beginning of the sample. Fade out the last N seconds. Take those last N seconds and mix it into the beginning of the file. Boom. Crossfade loop.

In my version of this crossfade algorith, the loop time is relative to the sample. More often than not, it makes more musical sense to think it relative terms than in precise seconds. When the crossfade loop time is 100% of the file, it means that the fade in/out times add up to the total duration of the file, making the fade time half the file. A 50% loop time would therefore be quarter the file duration for the fade times.

<<cfloop_top>>=
#define BUFSIZE 1024

int do_cfloop(const char *in, const char *out, float cf)
{
    float buf[BUFSIZE];
    float endbuf[BUFSIZE];
    drwav infile;
    drwav *outfile;
    drwav_data_format format;
    int rc;
    int count;
    unsigned long dur;
    unsigned long fade;
    unsigned long pos;
    unsigned long n;
    unsigned long endpos;

    rc = drwav_init_file(&infile, in);

    if (!rc) {
        fprintf(stderr, "Error opening %s\n", in);
        return 1;
    }

    format.container = drwav_container_riff;
    format.format = DR_WAVE_FORMAT_IEEE_FLOAT;
    /* format.format = DR_WAVE_FORMAT_PCM; */
    format.channels = 1;
    format.sampleRate = infile.sampleRate;
    format.bitsPerSample = 32;
    outfile = drwav_open_file_write(out, &format);

    dur = infile.totalSampleCount;
    fade = floor(dur * cf * 0.5);
    dur -= fade;
    endpos = dur;

    pos = 0;

    while (1) {
        count = drwav_read_f32(&infile, BUFSIZE, buf);

        if (pos < fade) {
            <<read_end>>
        }

        for (n = 0; n < count; n++) {
            if (pos < fade) {
                <<scale_and_sum>>
            }
            pos++;
        }

        if (pos >= dur) {
            count -= (pos - dur);
            drwav_write(outfile, count, buf);
            break;
        } else {
            drwav_write(outfile, count, buf);
        }
    }

    drwav_uninit(&infile);
    drwav_close(outfile);
    return 0;
}

Crossfading

Up next are the slightly non-trivial bits of this program. Overall read position is kept track of. While this position is in the fade region, the file is read from the beginning and simultaneously. This requires an extra buffer, and a way to jump back and forth between positions in the files.

If it happens that the read position is still in the fade region, the file will seek to the outfile end position plus the read position as an offset, and fill up a buffer that is COUNT samples long.

<<read_end>>=
drwav_seek_to_sample(&infile, endpos + pos);
count = drwav_read_f32(&infile, BUFSIZE, endbuf);
<<snap_it_back>>

This buffer will then be scaled and summed into the output buffer.

Scaling is done using a normalized alpha value, which is the current sample position, divided by the fade time. The end block gets multiplied by alpha. Beginning block is one minus alpha.

<<scale_and_sum>>=
float beg, end;
float a;
beg = buf[n];
end = endbuf[n];
a = (float)pos / fade;
buf[n] = (1 - a) * end + a * beg;

With the end read done, the file can jump back to the previous position and go COUNT samples further, in order to get ready for the next read.

<<snap_it_back>>=
drwav_seek_to_sample(&infile, pos + count);