view ringtools/fc-e1gen.c @ 605:5b286f910148

CHANGES: scripts addition documented
author Mychaela Falconia <falcon@freecalypso.org>
date Mon, 10 Feb 2020 03:55:13 +0000
parents 2133c475f5bd
children
line wrap: on
line source

/*
 * This program is an experimental compiler for TI's Melody E1 format
 * based on the description given in the L1M_AS001_1.pdf document
 * found in the Peek/FGW drop.
 */

#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <strings.h>

#define	MAX_FIELDS	17

char *infname, *outfname;
FILE *inf, *outf;
char linebuf[512], *fields[MAX_FIELDS+1];
int lineno, nfields;

int global_osc_set;
int start_time, osc_mask;
u_short osc_words[8][4];

get_input_line()
{
	char *cp;
	int n;

	if (!fgets(linebuf, sizeof linebuf, inf)) {
		fprintf(stderr, "%s: unexpected EOF\n", infname);
		exit(1);
	}
	cp = index(linebuf, '\n');
	if (!cp) {
		fprintf(stderr, "%s line %d: too long or missing newline\n",
			infname, lineno);
		exit(1);
	}
	*cp = '\0';
	/* parse it into fields */
	cp = linebuf;
	n = 0;
	for (;;) {
		while (isspace(*cp))
			cp++;
		if (*cp == '\0' || *cp == '#')
			break;
		if (n >= MAX_FIELDS) {
			fprintf(stderr, "%s line %d: too many fields\n",
				infname, lineno);
			exit(1);
		}
		fields[n++] = cp;
		while (*cp && !isspace(*cp))
			cp++;
		if (*cp)
			*cp++ = '\0';
	}
	fields[n] = 0;
	nfields = n;
}

input_number(str, min, max)
	char *str;
{
	char *endp;
	long val;

	val = strtol(str, &endp, 10);
	if (*endp) {
		fprintf(stderr,
			"%s line %d: \"%s\" is not a valid decimal number\n",
			infname, lineno, str);
		exit(1);
	}
	if (val < min || val > max) {
		fprintf(stderr, "%s line %d: number %ld is out of range\n",
			infname, lineno, val);
		exit(1);
	}
	return val;
}

input_paren_number(str, min, max)
	char *str;
{
	char *cp;

	if (str[0] != '(') {
badparen:	fprintf(stderr,
			"%s line %d: bad parenthesized argument \"%s\"\n",
			infname, lineno, str);
		exit(1);
	}
	cp = index(str, ')');
	if (!cp || cp[1])
		goto badparen;
	*cp = '\0';
	return input_number(str + 1, min, max);
}

handle_time_line()
{
	if (nfields != 2) {
		fprintf(stderr, "%s line %d: time header takes 1 argument\n",
			infname, lineno);
		exit(1);
	}
	start_time = input_number(fields[1], 1, 255);
}

check_req_field(n)
{
	if (n >= nfields) {
		fprintf(stderr, "%s line %d: too few fields\n",
			infname, lineno);
		exit(1);
	}
}

process_osc_line()
{
	int p = 1;
	int oscn, osc_bit;
	u_short word0, word1, word2, word3;
	int amp, freq, length;
	int tremT0, tremFreq;
	int sustain, t1, t2, t3, t5;
	int bit0;

	check_req_field(p);
	oscn = input_number(fields[p], 0, 7);
	p++;
	osc_bit = 1 << oscn;
	if (osc_mask & osc_bit) {
		fprintf(stderr, "%s line %d: osc %d defined more than once\n",
			infname, lineno, oscn);
		exit(1);
	}
	osc_mask |= osc_bit;

	/* basic part */
	check_req_field(p);
	if (fields[p][0] == '(') {
		bit0 = input_paren_number(fields[p], 0, 1);
		p++;
		check_req_field(p);
	} else
		bit0 = 0;
	if (!strcmp(fields[p], "df")) {
		p++;
		check_req_field(p);
		freq = input_number(fields[p], -8192, 8191) & 0x3FFF;
		p++;
		check_req_field(p);
		amp = input_number(fields[p], 0, 1023);
		p++;
		word0 = freq << 2 | 2 | bit0;
		word1 = amp << 6;
	} else {
		word0 = bit0;
		if (!strcmp(fields[p], "sq1")) {
			p++;
			word0 |= 4;
			check_req_field(p);
		}
		if (!strcmp(fields[p], "sq2")) {
			p++;
			word0 |= 8;
			check_req_field(p);
		}
		amp = input_number(fields[p], 0, 63);
		p++;
		check_req_field(p);
		freq = input_number(fields[p], 0, 63);
		p++;
		word0 |= (freq << 10) | (amp << 4);
		check_req_field(p);
		length = input_number(fields[p], 0, 1023);
		p++;
		word1 = length << 6;
	}

	/* optional 3rd word */
	if (p < nfields && !strcmp(fields[p], "trem")) {
		p++;
		word1 |= 0x10;
		check_req_field(p);
		tremT0 = input_number(fields[p], 0, 7);
		p++;
		check_req_field(p);
		tremFreq = input_number(fields[p], -16, 15) & 31;
		p++;
		word2 = (tremFreq << 11) | (tremT0 << 8);
	}

	/* optional 4th word */
	if (p < nfields && !strcmp(fields[p], "env")) {
		p++;
		word1 |= 0x20;
		check_req_field(p);
		sustain = input_number(fields[p], 0, 15);
		p++;
		check_req_field(p);
		t1 = input_number(fields[p], 0, 7);
		p++;
		check_req_field(p);
		t2 = input_number(fields[p], 0, 7);
		p++;
		check_req_field(p);
		t3 = input_number(fields[p], 0, 7);
		p++;
		check_req_field(p);
		t5 = input_number(fields[p], 0, 7);
		p++;
		word3 = (t1 << 13) | (t2 << 10) | (t3 << 7) | (t5 << 4) |
			sustain;
	}

	if (p != nfields) {
		fprintf(stderr, "%s line %d: unexpected extra fields\n",
			infname, lineno);
	}
	osc_words[oscn][0] = word0;
	osc_words[oscn][1] = word1;
	osc_words[oscn][2] = word2;
	osc_words[oscn][3] = word3;
}

read_osc_lines()
{
	osc_mask = 0;
	for (;;) {
		get_input_line();
		if (!nfields)
			break;
		if (!strcmp(fields[0], "osc")) {
			process_osc_line();
			continue;
		}
		fprintf(stderr, "%s line %d: osc line expected\n",
			infname, lineno);
		exit(1);
	}
}

emit_16bit_word(word)
	u_short word;
{
	putc(word & 0xFF, outf);
	putc(word >> 8, outf);
}

emit_record()
{
	int oscn, osc_bit;

	putc(start_time, outf);
	putc(osc_mask, outf);
	for (oscn = 0; oscn < 8; oscn++) {
		osc_bit = 1 << oscn;
		if (!(osc_mask & osc_bit))
			continue;
		emit_16bit_word(osc_words[oscn][0]);
		emit_16bit_word(osc_words[oscn][1]);
		if (osc_words[oscn][1] & 0x10)
			emit_16bit_word(osc_words[oscn][2]);
		if (osc_words[oscn][1] & 0x20)
			emit_16bit_word(osc_words[oscn][3]);
	}
}

handle_global_osc_set()
{
	int p;
	int oscn, osc_bit;
	int dummy_time_byte;

	do
		get_input_line();
	while (!nfields);
	if (strcmp(fields[0], "osc-set")) {
		fprintf(stderr, "%s line %d: osc-set line expected\n",
			infname, lineno);
		exit(1);
	}
	if (nfields < 2) {
emptyerr:	fprintf(stderr, "%s line %d: osc-set must be non-empty\n",
			infname, lineno);
		exit(1);
	}
	p = 1;
	if (fields[p][0] == '(') {
		dummy_time_byte = input_paren_number(fields[p], 0, 255);
		p++;
		if (nfields < 3)
			goto emptyerr;
	} else
		dummy_time_byte = 0;
	for (; p < nfields; p++) {
		oscn = input_number(fields[p], 0, 7);
		osc_bit = 1 << oscn;
		global_osc_set |= osc_bit;
	}
	putc(dummy_time_byte, outf);
	putc(global_osc_set, outf);
}

main(argc, argv)
	char **argv;
{
	if (argc != 3) {
		fprintf(stderr, "usage: %s src-file e1-bin-file\n", argv[0]);
		exit(1);
	}
	if (strcmp(argv[1], "-")) {
		infname = argv[1];
		inf = fopen(infname, "r");
		if (!inf) {
			perror(infname);
			exit(1);
		}
	} else {
		infname = "stdin";
		inf = stdin;
	}
	outfname = argv[2];
	outf = fopen(outfname, "w");
	if (!outf) {
		perror(outfname);
		exit(1);
	}

	handle_global_osc_set();
	/* main loop */
	for (;;) {
		do
			get_input_line();
		while (!nfields);
		if (!strcmp(fields[0], "time")) {
			handle_time_line();
			read_osc_lines();
			emit_record();
		} else if (!strcmp(fields[0], "end")) {
			emit_16bit_word(0);
			exit(0);
		} else {
			fprintf(stderr, "%s line %d: expected time or end\n",
				infname, lineno);
			exit(1);
		}
	}
}