Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
branch: master
Fetching contributors…

Cannot retrieve contributors at this time

529 lines (410 sloc) 12.905 kb
/*
* Copyright (C) 2011 Mark Hills <mark@pogo.org.uk>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License version 2 for more details.
*
* You should have received a copy of the GNU General Public License
* version 2 along with this program; if not, write to the Free
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*
*/
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "timecoder.h"
#define ZERO_THRESHOLD 128
#define ZERO_RC 0.001 /* time constant for zero/rumble filter */
#define REF_PEAKS_AVG 48 /* in wave cycles */
/* The number of correct bits which come in before the timecode
* is declared valid. Set this too low, and risk the record skipping around
* (often to blank areas of track) during scratching */
#define VALID_BITS 24
#define MONITOR_DECAY_EVERY 512 /* in samples */
#define SQ(x) ((x)*(x))
/* Timecode definitions */
#define SWITCH_PHASE 0x1 /* tone phase difference of 270 (not 90) degrees */
#define SWITCH_PRIMARY 0x2 /* use left channel (not right) as primary */
#define SWITCH_POLARITY 0x4 /* read bit values in negative (not positive) */
static struct timecode_def_t timecode_def[] = {
{
.name = "serato_2a",
.desc = "Serato 2nd Ed., side A",
.resolution = 1000,
.flags = 0,
.bits = 20,
.seed = 0x59017,
.taps = 0x361e4,
.length = 712000,
.safe = 707000,
.lookup = false
},
{
.name = "serato_2b",
.desc = "Serato 2nd Ed., side B",
.resolution = 1000,
.flags = 0,
.bits = 20,
.seed = 0x8f3c6,
.taps = 0x4f0d8, /* reverse of side A */
.length = 922000,
.safe = 917000,
.lookup = false
},
{
.name = "serato_cd",
.desc = "Serato CD",
.resolution = 1000,
.flags = 0,
.bits = 20,
.seed = 0xd8b40,
.taps = 0x34d54,
.length = 950000,
.safe = 940000,
.lookup = false
},
{
.name = "traktor_a",
.desc = "Traktor Scratch, side A",
.resolution = 2000,
.flags = SWITCH_PRIMARY | SWITCH_POLARITY | SWITCH_PHASE,
.bits = 23,
.seed = 0x134503,
.taps = 0x041040,
.length = 1500000,
.safe = 1480000,
.lookup = false
},
{
.name = "traktor_b",
.desc = "Traktor Scratch, side B",
.resolution = 2000,
.flags = SWITCH_PRIMARY | SWITCH_POLARITY | SWITCH_PHASE,
.bits = 23,
.seed = 0x32066c,
.taps = 0x041040, /* same as side A */
.length = 2110000,
.safe = 2090000,
.lookup = false
},
{
.name = "mixvibes_v2",
.desc = "MixVibes V2",
.resolution = 1300,
.flags = SWITCH_PHASE,
.bits = 20,
.seed = 0x22c90,
.taps = 0x00008,
.length = 950000,
.safe = 923000,
.lookup = false
},
{
.name = "mixvibes_7inch",
.desc = "MixVibes 7\"",
.resolution = 1300,
.flags = SWITCH_PHASE,
.bits = 20,
.seed = 0x22c90,
.taps = 0x00008,
.length = 312000,
.safe = 310000,
.lookup = false
},
{
.name = NULL
}
};
/* Linear Feeback Shift Register in the forward direction. New values
* are generated at the least-significant bit. */
static inline bits_t lfsr(bits_t code, bits_t taps)
{
bits_t taken;
int xrs;
taken = code & taps;
xrs = 0;
while (taken != 0x0) {
xrs += taken & 0x1;
taken >>= 1;
}
return xrs & 0x1;
}
static inline bits_t fwd(bits_t current, struct timecode_def_t *def)
{
bits_t l;
/* New bits are added at the MSB; shift right by one */
l = lfsr(current, def->taps | 0x1);
return (current >> 1) | (l << (def->bits - 1));
}
static inline bits_t rev(bits_t current, struct timecode_def_t *def)
{
bits_t l, mask;
/* New bits are added at the LSB; shift left one and mask */
mask = (1 << def->bits) - 1;
l = lfsr(current, (def->taps >> 1) | (0x1 << (def->bits - 1)));
return ((current << 1) & mask) | l;
}
static struct timecode_def_t* find_definition(const char *name)
{
struct timecode_def_t *def;
def = &timecode_def[0];
while (def->name) {
if (!strcmp(def->name, name))
return def;
def++;
}
return NULL;
}
/* Where necessary, build the lookup table required for this timecode */
static int build_lookup(struct timecode_def_t *def)
{
unsigned int n;
bits_t current, last;
if (def->lookup)
return 0;
fprintf(stderr, "Building LUT for %d bit %dHz timecode (%s)\n",
def->bits, def->resolution, def->desc);
if (lut_init(&def->lut, def->length) == -1)
return -1;
current = def->seed;
for (n = 0; n < def->length; n++) {
/* timecode must not wrap */
assert(lut_lookup(&def->lut, current) == (unsigned)-1);
lut_push(&def->lut, current);
last = current;
current = fwd(current, def);
assert(rev(current, def) == last);
}
def->lookup = true;
return 0;
}
/* Free the timecoder lookup tables when they are no longer needed */
void timecoder_free_lookup(void) {
struct timecode_def_t *def;
def = &timecode_def[0];
while (def->name) {
if (def->lookup)
lut_clear(&def->lut);
def++;
}
}
static void init_channel(struct timecoder_channel_t *ch)
{
ch->positive = 0;
ch->zero = 0;
}
/* Initialise a timecode decoder at the given reference speed */
int timecoder_init(struct timecoder_t *tc, const char *def_name, double speed,
unsigned int sample_rate)
{
/* A definition contains a lookup table which can be shared
* across multiple timecoders */
tc->def = find_definition(def_name);
if (tc->def == NULL) {
fprintf(stderr, "Timecode definition '%s' is not known.\n", def_name);
return -1;
}
if (build_lookup(tc->def) == -1)
return -1;
tc->speed = speed;
tc->dt = 1.0 / sample_rate;
tc->zero_alpha = tc->dt / (ZERO_RC + tc->dt);
tc->forwards = 1;
init_channel(&tc->primary);
init_channel(&tc->secondary);
pitch_init(&tc->pitch, tc->dt);
tc->ref_level = 32768.0;
tc->bitstream = 0;
tc->timecode = 0;
tc->valid_counter = 0;
tc->timecode_ticker = 0;
tc->mon = NULL;
return 0;
}
/* Clear a timecode decoder */
void timecoder_clear(struct timecoder_t *tc)
{
timecoder_monitor_clear(tc);
}
/* The monitor (otherwise known as 'scope' in the interface) is the
* display of the incoming audio. Initialise one for the given
* timecoder */
int timecoder_monitor_init(struct timecoder_t *tc, int size)
{
tc->mon_size = size;
tc->mon = malloc(SQ(tc->mon_size));
if (tc->mon == NULL) {
perror("malloc");
return -1;
}
memset(tc->mon, 0, SQ(tc->mon_size));
tc->mon_counter = 0;
return 0;
}
/* Clear the monitor on the given timecoder */
void timecoder_monitor_clear(struct timecoder_t *tc)
{
if (tc->mon) {
free(tc->mon);
tc->mon = NULL;
}
}
static void detect_zero_crossing(struct timecoder_channel_t *ch,
signed int v, float alpha)
{
ch->crossing_ticker++;
ch->swapped = 0;
if (v > ch->zero + ZERO_THRESHOLD && !ch->positive) {
ch->swapped = 1;
ch->positive = 1;
ch->crossing_ticker = 0;
} else if (v < ch->zero - ZERO_THRESHOLD && ch->positive) {
ch->swapped = 1;
ch->positive = 0;
ch->crossing_ticker = 0;
}
ch->zero += alpha * (v - ch->zero);
}
/* Plot the given sample value in the monitor (scope) */
static void update_monitor(struct timecoder_t *tc, signed int x, signed int y)
{
int px, py, p;
float v, w;
if (!tc->mon)
return;
/* Decay the pixels already in the montior */
if (++tc->mon_counter % MONITOR_DECAY_EVERY == 0) {
for (p = 0; p < SQ(tc->mon_size); p++) {
if (tc->mon[p])
tc->mon[p] = tc->mon[p] * 7 / 8;
}
}
v = (float)x / tc->ref_level / 2;
w = (float)y / tc->ref_level / 2;
px = tc->mon_size / 2 + (v * tc->mon_size / 2);
py = tc->mon_size / 2 + (w * tc->mon_size / 2);
/* Set the pixel value to white */
if (px > 0 && px < tc->mon_size && py > 0 && py < tc->mon_size)
tc->mon[py * tc->mon_size + px] = 0xff;
}
/* Process a single bitstream reading */
static void process_bitstream(struct timecoder_t *tc, signed int m)
{
bits_t b;
b = m > tc->ref_level;
/* Add it to the bitstream, and work out what we were expecting
* (timecode). */
/* tc->bitstream is always in the order it is physically placed on
* the vinyl, regardless of the direction. */
if (tc->forwards) {
tc->timecode = fwd(tc->timecode, tc->def);
tc->bitstream = (tc->bitstream >> 1)
+ (b << (tc->def->bits - 1));
} else {
bits_t mask;
mask = ((1 << tc->def->bits) - 1);
tc->timecode = rev(tc->timecode, tc->def);
tc->bitstream = ((tc->bitstream << 1) & mask) + b;
}
if (tc->timecode == tc->bitstream)
tc->valid_counter++;
else {
tc->timecode = tc->bitstream;
tc->valid_counter = 0;
}
/* Take note of the last time we read a valid timecode */
tc->timecode_ticker = 0;
/* Adjust the reference level based on this new peak */
tc->ref_level = (tc->ref_level * (REF_PEAKS_AVG - 1) + m) / REF_PEAKS_AVG;
#ifdef DEBUG_BITSTREAM
fprintf(stderr, "%+6d zero, %+6d (ref %+6d)\t= %d%c (%5d)\n",
tc->primary.zero,
m,
tc->ref_level,
b,
tc->valid_counter == 0 ? 'x' : ' ',
tc->valid_counter);
#endif
}
/* Process a single sample from the incoming audio */
static void process_sample(struct timecoder_t *tc,
signed int primary, signed int secondary)
{
signed int m; /* pcm sample, sum of two shorts */
detect_zero_crossing(&tc->primary, primary, tc->zero_alpha);
detect_zero_crossing(&tc->secondary, secondary, tc->zero_alpha);
m = abs(primary - tc->primary.zero);
/* If an axis has been crossed, use the direction of the crossing
* to work out the direction of the vinyl */
if (tc->primary.swapped) {
tc->forwards = (tc->primary.positive != tc->secondary.positive);
if (tc->def->flags & SWITCH_PHASE)
tc->forwards = !tc->forwards;
} if (tc->secondary.swapped) {
tc->forwards = (tc->primary.positive == tc->secondary.positive);
if (tc->def->flags & SWITCH_PHASE)
tc->forwards = !tc->forwards;
}
/* If any axis has been crossed, register movement using the pitch
* counters */
if (!tc->primary.swapped && !tc->secondary.swapped)
pitch_dt_observation(&tc->pitch, 0.0);
else {
float dx;
dx = 1.0 / tc->def->resolution / 4;
if (!tc->forwards)
dx = -dx;
pitch_dt_observation(&tc->pitch, dx);
}
/* If we have crossed the primary channel in the right polarity,
* it's time to read off a timecode 0 or 1 value */
if (tc->secondary.swapped &&
tc->primary.positive == ((tc->def->flags & SWITCH_POLARITY) == 0))
{
process_bitstream(tc, m);
}
tc->timecode_ticker++;
}
/* Submit and decode a block of PCM audio data to the timecoder */
void timecoder_submit(struct timecoder_t *tc, signed short *pcm, size_t npcm)
{
while (npcm--) {
signed int primary, secondary;
if (tc->def->flags & SWITCH_PRIMARY) {
primary = pcm[0];
secondary = pcm[1];
} else {
primary = pcm[1];
secondary = pcm[0];
}
process_sample(tc, primary, secondary);
update_monitor(tc, pcm[0], pcm[1]);
pcm += TIMECODER_CHANNELS;
}
}
/* Return the known position in the timecode, or -1 if not known. If
* two few bits have been error-checked, then this also counts as
* invalid. If 'when' is given, return the time, in seconds since this
* value was read. */
signed int timecoder_get_position(struct timecoder_t *tc, float *when)
{
signed int r;
if (tc->valid_counter > VALID_BITS) {
r = lut_lookup(&tc->def->lut, tc->bitstream);
if (r >= 0) {
if (when)
*when = tc->timecode_ticker * tc->dt;
return r;
}
}
return -1;
}
Jump to Line
Something went wrong with that request. Please try again.