view ringtools/imy/convert.c @ 887:3e398f9c31a0

fc-imy2pwt: overhaul melody error handling, report position
author Mychaela Falconia <falcon@freecalypso.org>
date Sun, 03 Apr 2022 04:17:08 +0000
parents fd4c9bc7835d
children
line wrap: on
line source

/*
 * This module implements the second pass of fc-imy2pwt processing:
 * stepping through the captured melody and converting it to PWT.
 */

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>

extern char melody_str_buf[];
extern unsigned tdma_durations[6][4];
extern FILE *outF;

static int cur_octave = 4;

static char *pwt_note_names[12] = {"c", "cs", "d", "ds", "e", "f", "fs",
				   "g", "gs", "a", "as", "b"};

static void
melody_error(cp, msg)
	char *cp, *msg;
{
	unsigned pos;

	pos = cp - melody_str_buf;
	fprintf(stderr, "melody error at offset %u: %s\n", pos, msg);
	exit(1);
}

static void
process_octave_cmd(cp)
	char *cp;
{
	if (!isdigit(cp[1]))
		melody_error(cp, "'*' octave prefix not followed by digit");
	cur_octave = cp[1] - '0';
}

static int
process_note(str, type)
	char *str;
{
	int note, dur_basic, dur_mod;

	switch (*str) {
	case 'c':
		note = 0;
		break;
	case 'd':
		note = 2;
		break;
	case 'e':
		note = 4;
		break;
	case 'f':
		note = 5;
		break;
	case 'g':
		note = 7;
		break;
	case 'a':
		note = 9;
		break;
	case 'b':
		note = 11;
		break;
	default:
		melody_error(str, "note letter expected after '&' or '#'");
	}
	switch (type) {
	case 1:
		if (note == 0 || note == 5)
			melody_error(str, "invalid flat note");
		note--;
		break;
	case 2:
		if (note == 4 || note == 11)
			melody_error(str, "invalid sharp note");
		note++;
		break;
	}
	if (str[1] < '0' || str[1] > '5')
		melody_error(str, "missing expected note duration digit");
	dur_basic = str[1] - '0';
	switch (str[2]) {
	case '.':
		dur_mod = 1;
		break;
	case ':':
		dur_mod = 2;
		break;
	case ';':
		dur_mod = 3;
		break;
	default:
		dur_mod = 0;
		break;
	}
	fprintf(outF, "%s%d\t64\t%u\n", pwt_note_names[note], cur_octave + 1,
		tdma_durations[dur_basic][dur_mod]);
	if (dur_mod)
		return 3;
	else
		return 2;
}

static int
process_rest(str)
	char *str;
{
	int dur_basic, dur_mod;

	if (str[1] < '0' || str[1] > '5')
		melody_error(str, "missing expected rest duration digit");
	dur_basic = str[1] - '0';
	switch (str[2]) {
	case '.':
		dur_mod = 1;
		break;
	case ':':
		dur_mod = 2;
		break;
	case ';':
		dur_mod = 3;
		break;
	default:
		dur_mod = 0;
		break;
	}
	fprintf(outF, "rest\t\t%u\n", tdma_durations[dur_basic][dur_mod]);
	if (dur_mod)
		return 3;
	else
		return 2;
}

melody_convert_pass()
{
	char *cp, *repeat_start_ptr;
	int repeat_start_octave, repeat_count, rpt_set;

	repeat_start_ptr = 0;
	for (cp = melody_str_buf; *cp; ) {
		/* skip junk first */
		if (!strncmp(cp, "vibeon", 6)) {
			cp += 6;
			continue;
		}
		if (!strncmp(cp, "vibeoff", 7)) {
			cp += 7;
			continue;
		}
		if (!strncmp(cp, "ledon", 5)) {
			cp += 5;
			continue;
		}
		if (!strncmp(cp, "ledoff", 6)) {
			cp += 6;
			continue;
		}
		if (!strncmp(cp, "backon", 6)) {
			cp += 6;
			continue;
		}
		if (!strncmp(cp, "backoff", 7)) {
			cp += 7;
			continue;
		}
		/* real stuff */
		switch (*cp) {
		case '*':
			process_octave_cmd(cp);
			cp += 2;
			continue;
		case 'c':
		case 'd':
		case 'e':
		case 'f':
		case 'g':
		case 'a':
		case 'b':
			cp += process_note(cp, 0);
			continue;
		case '&':
			cp++;
			cp += process_note(cp, 1);
			continue;
		case '#':
			cp++;
			cp += process_note(cp, 2);
			continue;
		case 'r':
			cp += process_rest(cp);
			continue;
		case 'V':
			/* skip unimplemented volume control */
			cp++;
			if (*cp == '+' || *cp == '-') {
				cp++;
				continue;
			}
			if (!isdigit(*cp))
				melody_error(cp, "invalid character after 'V'");
			if (*cp == '1' && cp[1] >= '0' && cp[1] <= '5')
				cp += 2;
			else
				cp++;
			continue;
		case '(':
			if (repeat_start_ptr)
				melody_error(cp, "nested repeat");
			cp++;
			repeat_start_ptr = cp;
			repeat_start_octave = cur_octave;
			repeat_count = 0;
			continue;
		case '@':
			if (!repeat_start_ptr)
				melody_error(cp, "'@' not in repeat block");
			cp++;
			if (!isdigit(*cp))
				melody_error(cp, "'@' not followed by digit");
			rpt_set = *cp - '0';
			if (!rpt_set)
				melody_error(cp,
					"infinite repeat not supported");
			cp++;
			if (!repeat_count)
				repeat_count = rpt_set;
			continue;
		case ')':
			if (!repeat_start_ptr)
				melody_error(cp, "')' without opening '('");
			if (!repeat_count)
				melody_error(cp, "repeat block without count");
			repeat_count--;
			if (repeat_count) {
				cp = repeat_start_ptr;
				cur_octave = repeat_start_octave;
			} else {
				cp++;
				repeat_start_ptr = 0;
			}
			continue;
		default:
			melody_error(cp, "non-understood character");
		}
	}
}