/* dab.c - Decode Aiken Biphase
   Copyright (c) 2004-2005 Joseph Battaglia <redbird@2600.com>
   Released under the MIT License.
   Compiling
     cc dab.c -o dab -lsndfile

Translator's Note:
This code was printed with an article called "Magnetic Stripe Reading" by 
Redbird in 2600 Volume 22, Number 1 (Spring 2005).
It is useful for decoding Magstripe using a computer sound card.
I have tested it with a microphone and ensured the accuracy of this 
reproduction to a fair degree. I have not tried it with a magnetic sensor yet.
Since it was released under the MIT License, I have modified it very slightly 
and am re-releasing it.

Download it here: https://altsci.com/concepts/dab.c
*/

#include <fcntl.h>
#include <getopt.h>
#include <sndfile.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/soundcard.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#define DEVICE        "/dev/dsp" /* default sound card device */
#define SAMPLE_RATE   192000
#define SILENCE_THRES 5000

/* #define DISABLE_VC */

#define AUTO_THRES    30
#define BUF_SIZE      1024
#define END_LENGTH    200
#define FREQ_THRES    60
#define MAX_TERM      60
#define VERSION       "0.6"

short int *sample = NULL;
int sample_size = 0;

void *xmalloc(size_t size)
{
	void *ptr;
	
	ptr = malloc(size);
	if (ptr == NULL) {
		fprintf(stderr, "Out of memory.\n");
		exit(EXIT_FAILURE);
	}
	return ptr;
}

void *xrealloc(void *ptr, size_t size)
{
	void *nptr;
	
	nptr = realloc(ptr, size);
	if (nptr == NULL) {
		fprintf(stderr, "Out of memory.\n");
		exit(EXIT_FAILURE);
	}
	return nptr;
}

char *xstrdup(char *string)
{
	char *ptr;
	
	ptr = xmalloc(strlen(string) + 1);
	strcpy(ptr, string);
	return ptr;
}

ssize_t xread(int fd, void *buf, size_t count)
{
	int retval;
	
	retval = read(fd, buf, count);
	if (retval == -1) {
		perror("read()");
		exit(EXIT_FAILURE);
	}
	return retval;
}

/* prints version
   [stream]     output stream */
void print_version(FILE *stream)
{
	fprintf(stream, "dab - Decode Aiken Biphase\n");
	fprintf(stream, "Version %s\n", VERSION);
	fprintf(stream, "Copyright (c) 2004-2005  ");
	fprintf(stream, "Joseph Battaglia <redbird@2600.com>\n");
}

/* prints version and help
   [stream]      output stream
   [exec]        string containing the name of the executable */
void print_help(FILE *stream, char *exec)
{
	print_version(stream);
	fprintf(stream, "Usage: %s [OPTIONS]\n\n", exec);
	fprintf(stream, "    -a,  --auto-thres    Set auto-thres percentage\n");
	fprintf(stream, "                         (default: %d)\n", AUTO_THRES);
	fprintf(stream, "    -d,  --device        Device to read audio data from\n");
	fprintf(stream, "                         (default: %s)\n", DEVICE);
	fprintf(stream, "    -f,  --file          File to read audio data from\n");
	fprintf(stream, "                         (use instead of -d)\n");
	fprintf(stream, "    -h,  --help          Print help information\n");
	fprintf(stream, "    -m,  --max-level     Show maximum level\n");
	fprintf(stream, "                         (use to determine threshold)\n");
	fprintf(stream, "    -s,  --silent        No verbose messages\n");
	fprintf(stream, "    -t,  --device        Set silence threshold\n");
	fprintf(stream, "                         (default: automatic detect)\n");
	fprintf(stream, "    -v,  --version       Print version information\n");
}

/* sets the device parameters
   [fd]
   [verbose]
   returns        sample rate */
int dsp_init(int fd, int verbose)
{
	int ch, fmt, sr;
	
	if (verbose)
		fprintf(stderr, "*** setting audio device parameters:\n");
	
	if (verbose)
		fprintf(stderr, "      Format: AFMT_S16_LE\n");
	fmt= AFMT_S16_LE;
	if (ioctl(fd, SNDCTL_DSP_SETFMT, &fmt) == -1) {
		perror("SNDCTL_DSP_SETFMT");
		exit(EXIT_FAILURE);
	}
	
	if (verbose)
		fprintf(stderr, "      Channels: 1\n");
	ch = 0;
	if (ioctl(fd, SNDCTL_DSP_STEREO, &ch) == -1) {
		perror("SNDCTL_DSP_STEREO");
		exit(EXIT_FAILURE);
	}
	
	if (verbose)
		fprintf(stderr, "      Sample rate: %i\n", SAMPLE_RATE);
	sr = SAMPLE_RATE;
	if (ioctl(fd, SNDCTL_DSP_SPEED, &sr) == -1) {
		perror("SNDCTL_DSP_SPEED");
		exit(EXIT_FAILURE);
	}
	if (sr != SAMPLE_RATE)
		fprintf(stderr, "*** Warning: Highest supported sample rate is %d\n", sr);
	
	return sr;
	
	
}

/* prints the maximum dsp level to aid in setting the silence
   [fd]
   [sample_rate]      sample rate of device */
void print_max_level(int fd, int sample_rate)
{
	int i;
	short int buf, last = 0;
	
	printf("Terminating after %d seconds...\n", MAX_TERM);
	
	for (i=0; i< sample_rate * MAX_TERM; i++) {
	
		xread(fd, &buf, sizeof (short int));
		
		if (buf < 0)
			buf = -buf;
		
		if (buf > last) {
			printf("Maximum level: %d\r", buf);
			fflush(stdout);
			last = buf;
		}
	}
	printf("\n");
}

/* find the maximum value in sample
   ** global **
   [sample]       sample
   [sample_size]  number of frames in sample */
short int evaluate_max(void)
{
	int i;
	short int max = 0;
	for (i=0; i < sample_size; i++) {
		if (sample[i] > max)
			max = sample[i];
	}
	
	return max;
}

/* pauses until the dsp is above the silence threshold
   [fd]
   [silence_thres] silence threshold */
void silence_pause(int fd, int silence_thres)
{
	short int buf = 0;
	
	while (buf < silence_thres) {
	
		xread(fd, &buf, sizeof (short int));
		
		if (buf < 0)
			buf = -buf;
	}
}

/* gets a sample, terminating when the input goes below the silence threshold
   [fd]
   [sample_rate]   sample rate of device
   [silence_thres] silence threshold
   ** global **
   [sample]       sample
   [sample_size]  number of frames in sample */
void get_dsp(int fd, int sample_rate, int silence_thres)
{
	int count = 0, eos = 0, i;
	short buf;
	
	sample_size = 0;
	
	silence_pause(fd, silence_thres);
	
	while (!eos) {
	
		sample = xrealloc(sample, sizeof (short int) * (BUF_SIZE * (count + 1)));
		for (i = 0; i < BUF_SIZE; i++) {
			xread(fd, &buf, sizeof (short int));
			sample[i + (count * BUF_SIZE)] = buf;
		}
		count++;
		sample_size = count * BUF_SIZE;
		
		eos = 1;
		if (sample_size > (sample_rate * END_LENGTH) / 1000) {
			for (i = 0; i < (sample_rate * END_LENGTH) / 1000; i++) {
				buf = sample[(count * BUF_SIZE) - i];
				if (buf < 0)
					buf = -buf;
				if (buf > silence_thres)
					eos = 0;
			}
		} else
			eos = 0;
	}
}

/* open the file
   [fd]
   [verbose]
   ** global **
   [sample_size] number of frames in the file */
SNDFILE *sndfile_init(int fd, int verbose)
{
	SNDFILE *sndfile;
	SF_INFO sfinfo;
	
	memset(&sfinfo, 0, sizeof(sfinfo));
	
	sndfile = sf_open_fd(fd, SFM_READ, &sfinfo, 0);
	if (sndfile == NULL) {
		fprintf(stderr, "*** Error: sf_open_fd() failed\n");
		exit(EXIT_FAILURE);
	}
	
	if (verbose) {
		fprintf(stderr, "*** Input file format:\n"
			"    Frames: %i\n"
			"    Sample Rate: %i\n"
			"    Channels: %i\n"
			"    Format: 0x%08x\n"
			"    Sections: %i\n"
			"    Seekable: %i\n",
			(int)sfinfo.frames, sfinfo.samplerate, sfinfo.channels,
			sfinfo.format, sfinfo.sections, sfinfo.seekable);
	}
	
	if (sfinfo.channels != 1) {
		fprintf(stderr, "*** Error: Only monaural files are supported\n");
		exit(EXIT_FAILURE);
	}
	
	sample_size = sfinfo.frames;
	
	return sndfile;
}

/* read in data from libsndfile
   [sndfile]
   ** global **
   [sample]       sample
   [sample_size]  number of frames in sample */
void get_sndfile(SNDFILE *sndfile)
{
	sf_count_t count;
	
	sample = xmalloc(sizeof(short int) * sample_size);
	
	count = sf_read_short(sndfile, sample, sample_size);
	if (count != sample_size) {
		fprintf(stderr, "*** Warning, expected %i frames, read %i\n",
			sample_size, (int)count);
		sample_size = count;
	}
}

/* decodes aiken biphase and prints binary
   [freq_thres]     frequency threshold
   ** global **
   [sample]         sample
   [sample_size]    number of frames in sample */
void decode_aiken_biphase(int freq_thres, int silence_thres)
{
	int i = 0, peak = 0, ppeak = 0;
	int *peaks = NULL, peaks_size = 0;
	int zerobl;
	
	printf("*** Parsing %d samples\n", sample_size);
	for (i=0; i < sample_size; i++)           /* absolute value */
		if (sample[i] < 0)
			sample[i] = -sample[i];
	
	i = 0;                                    /* store peak differences */
	while (i < sample_size) {
		ppeak = peak;                     /* old peak value */
		while (sample[i] <= silence_thres && i < sample_size)
			i++;
		peak = 0;
		while (sample[i] > silence_thres && i < sample_size) {
			if (sample[i] > sample[peak])
				peak = i;
			i++;
		}
		if (peak - ppeak > 0) {
			peaks = xrealloc(peaks, sizeof(int) * (peaks_size + 1));
			peaks[peaks_size] = peak - ppeak;
			peaks_size++;
		}
	}
	
	printf("Found %d peaks\n", peaks_size);
	/* decode aiken biphase allowing for
	   frequency deviation based on freq_thres */
	/* ignore first two peaks and last peak */
	zerobl = peaks[2];
	for (i = 2; i < peaks_size - 1; i++) {
		if (peaks[i] < ((zerobl / 2) + (freq_thres * (zerobl / 2) / 100)) &&
			peaks[i] > ((zerobl / 2) - (freq_thres * (zerobl / 2) / 100))) {
			if (peaks[i + 1] < ((zerobl / 2) + (freq_thres * (zerobl / 2) / 100)) &&
				peaks[i + 1] > ((zerobl / 2) - (freq_thres * (zerobl / 2) / 100))) {
				printf("1");
				zerobl = peaks[i] + 2;
				i++;
			}
		}
		else if (peaks[i] < ((zerobl / 2) + (freq_thres * (zerobl / 2) / 100)) &&
			peaks[i] > ((zerobl / 2) - (freq_thres * (zerobl / 2) / 100))) {
			printf("0");
#ifndef DISABLE_VC
			zerobl = peaks[i];
#endif
		}
	}
	printf("\n");
}

/* main */
int main(int argc, char *argv[])
{
	int fd;
	SNDFILE *sndfile = NULL;
	
	/* configuration variables */
	char *filename = NULL;
	int auto_thres = AUTO_THRES, max_level = 0, use_sndfile = 0, verbose = 1;
	int sample_rate = SAMPLE_RATE, silence_thres = SILENCE_THRES;
	
	/* getopt variables */
	int ch, option_index;
	static struct option long_options[] = {
		{"auto-thres", 0, 0, 'a'},
		{"device",     1, 0, 'd'},
		{"file",       1, 0, 'f'},
		{"help",       0, 0, 'h'},
		{"max-level",  0, 0, 'm'},
		{"silent",     0, 0, 's'},
		{"threshold",  0, 0, 't'},
		{"version",    0, 0, 'v'},
		{0,            0, 0,  0}
	};
	
	while(1) {
		ch = getopt_long(argc, argv, "a:d:f:hmst:v", long_options, &option_index);
		if (ch == -1)
			break;
		
		switch (ch) {
			case 'a':
				auto_thres = atoi(optarg);
				break;
			case 'd':
				filename = xstrdup(optarg);
				break;
			case 'f':
				filename = xstrdup(optarg);
				use_sndfile = 1;
				break;
			case 'h':
				print_help(stdout, argv[0]);
				exit(EXIT_SUCCESS);
				break;
			case 'm':
				max_level = 1;
				break;
			case 's':
				verbose = 0;
				break;
			case 't':
				auto_thres = 0;
				break;
			case 'v':
				print_version(stdout);
				exit(EXIT_SUCCESS);
				break;
			default:
				print_help(stdout, argv[0]);
				exit(EXIT_FAILURE);
				break;
		}
	}
	
	if (verbose) {
		print_version(stderr);
		fprintf(stderr, "\n");
	}
	
	if (use_sndfile && max_level) {
		fprintf(stderr, "*** Error: -f and -m switches do not mix!\n");
		exit(EXIT_FAILURE);
	}
	
	if (filename == NULL)
		filename = xstrdup(DEVICE);
	
	if (verbose)
		fprintf(stderr, "*** Opening %s\n", filename);
	fd = open(filename, O_RDONLY);
	if (fd == -1) {
		perror("open()");
		exit(EXIT_FAILURE);
	}
	
	if (use_sndfile)
		sndfile = sndfile_init(fd, verbose);
	else
		sample_rate = dsp_init(fd, verbose);
	
	if (max_level) {
		print_max_level(fd, sample_rate);
		exit(EXIT_SUCCESS);
	}
	
	if (!silence_thres) {
		fprintf(stderr, "*** Error: Invalid silence threshold\n");
		exit(EXIT_FAILURE);
	}
	
	if (use_sndfile)
		get_sndfile(sndfile);
	else
		get_dsp(fd, sample_rate, silence_thres);
	
	if (auto_thres)
		silence_thres = auto_thres * evaluate_max() / 100;
	
	if (verbose)
		fprintf(stderr, "*** Silence threshold: %d (%d%% of max)\n",
		silence_thres, auto_thres);
	
	decode_aiken_biphase(FREQ_THRES, silence_thres);
	
	close(fd);
	
	free(sample);
	
	exit(EXIT_SUCCESS);
	
	return 0;
}