#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <stdio.h>
#define MAXTRACK 0x0010
#define MAXVOICE 0x0008
#define MAXINDEX 0x1000
#define RECSIZE 0x20
#define MAPSIZE 0x20
#define MAXINPUTLINE 0x400
#define REST 0
#define CONT 1
#define END 2
int ntracks;
char map[MAPSIZE][RECSIZE];
unsigned char matrix[MAXTRACK][MAXINDEX][MAXVOICE];
/* search key in map
* if key is found, return record index, otherwise return negative int:
* return the index of the first empty record, negated
* if map is full, return -MAPSIZE */
int
map_find(char type, const char *key)
{
int i;
for (i = 0; i < MAPSIZE; i++) {
if (!*map[i])
return -i;
if (*map[i] != type)
continue;
if (!strcmp(&map[i][1], key))
return i;
}
return -i;
}
/* put a key-value pair on the map
* if the key is already on the map, update its value
* return 0 on success, negative value on error:
* -1: key-value pair is too large to fit on a record
* -2: no record available on the map */
int
map_put(char type, const char *key, const char *val)
{
int keylen = strlen(key);
int vallen = strlen(val);
int i;
if (keylen + vallen + 3 > RECSIZE)
return -1;
i = map_find(type, key);
if (i == -MAPSIZE)
return -2;
if (i < 0) {
i = -i;
map[i][0] = type;
strcpy(&map[i][1], key);
}
strcpy(&map[i][keylen+2], val);
return 0;
}
/* get the value associated with the given key on the map
* returns a pointer to the value found or NULL if not found */
char *
map_get(char type, const char *key)
{
int i = map_find(type, key);
if (i < 0)
return NULL;
return &map[i][strlen(key)+2];
}
typedef enum TxtStt {NTRK, MTDT, TKNM, EVNT} TxtStt;
int
load_txt(FILE *fp)
{
char line[MAXINPUTLINE];
char track_key[] = "0";
TxtStt state = NTRK;
char *sep, *nl, *cell;
int track, index, voice;
unsigned char pitch;
while (fgets(line, MAXINPUTLINE, fp)) {
if (line[0] == '\n')
continue;
switch (state) {
case NTRK:
if (strncmp(line, "ntracks:", 8))
return -1;
ntracks = atoi(line + 8);
track = 0;
state = MTDT;
break;
case MTDT:
if ((sep = strchr(line, ':'))) {
nl = strchr(line, '\n');
*sep = *nl = '\0';
map_put('#', line, sep + 1);
break;
} else {
state = TKNM;
}
/* fall through */
case TKNM:
state = EVNT;
track_name:
nl = strchr(line, '\n');
*nl = '\0';
map_put('@', track_key, line);
*track_key = *track_key == '9' ? 'A' : *track_key + 1;
index = 0;
break;
case EVNT:
if (isdigit(line[0])) {
/* noteset */
cell = &line[2];
voice = 0;
while (*cell) {
switch (*cell) {
case '.':
/* matrix[track][index][voice] = REST; */
break;
case '=':
matrix[track][index][voice] = CONT;
break;
default:
pitch = (cell[0] - '0') * 10 + cell[1] - '0';
matrix[track][index][voice] = pitch | 0x80;
}
cell += 3;
voice++;
}
index++;
} else {
if (!strchr(line, ':')) {
matrix[track][index][0] = END;
track++;
goto track_name;
}
printf("ignoring metaevent: %s", line);
}
}
}
matrix[track][index][0] = END;
ntracks = track + 1;
return 0;
}
int
save_txt(FILE *fp)
{
int track, index, voice;
int last_voice;
unsigned char cell;
char track_key[] = "0";
fprintf(fp, "ntracks:%d\n\n", ntracks);
/* TODO: write metadata */
fprintf(fp, "foo:bar\n");
for (track = 0; track < ntracks; track++) {
fprintf(fp, "\n%s\n\n", map_get('@', track_key));
for (index = 0; index < MAXINDEX; index++) {
if (matrix[track][index][0] == END)
break;
for (voice = MAXVOICE-1; voice >= 0; voice--)
if (matrix[track][index][voice])
break;
last_voice = voice;
for (voice = 0; voice <= last_voice; voice++) {
cell = matrix[track][index][voice];
if (cell & 0x80)
fprintf(fp, " %2u", cell & 0x7F);
else if (cell == REST)
fprintf(fp, " ..");
else if (cell == CONT)
fprintf(fp, " ==");
else
fprintf(fp, " ??");
}
fprintf(fp, "\n");
}
*track_key = *track_key == '9' ? 'A' : *track_key + 1;
}
return 0;
}
int
main()
{
if (load_txt(stdin) < 0)
return -1;
if (save_txt(stdout) < 0)
return -2;
return 0;
}