mirror of
https://github.com/deater/dos33fsprogs.git
synced 2024-11-01 01:06:33 +00:00
622 lines
13 KiB
C
622 lines
13 KiB
C
/* This is a two-track format to play on the simple 1-bit speaker */
|
|
/* Interface available on the Apple II */
|
|
|
|
/* https://sourceforge.net/p/ed2midi/code/ */
|
|
/* From a spreadsheet from ed2midi */
|
|
/* Octave 1 2 3 4 5
|
|
A 255 128 64 32 16
|
|
A# 240 120 60 30 15
|
|
B 228 114 57 28 14
|
|
C 216 108 54 27 13
|
|
C# 204 102 51 25 12
|
|
D 192 96 48 24 12
|
|
D# 180 90 45 22 11
|
|
E 172 86 43 21 10
|
|
F 160 80 40 20 10
|
|
F# 152 76 38 19 9
|
|
G 144 72 36 18 9
|
|
G# 136 68 34 17 8
|
|
*/
|
|
|
|
/* ed file format */
|
|
|
|
/* First byte 0: ??? (0,0,0 = exit) */
|
|
|
|
/* First byte 1: Voice */
|
|
/* byte1 = voice1 instrument */
|
|
/* byte2 = voice2 instrument */
|
|
/* Varies, bigger than 8 seem to make no difference */
|
|
/* custom hack, negative means do action */
|
|
|
|
/* Otherwise, byte0 = duration (20=quarter, 40=half) */
|
|
/* byte1 = voice1 note */
|
|
/* byte2 = voice2 note */
|
|
|
|
#define VERSION "1.0"
|
|
|
|
#include <stdio.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
|
|
#include "notes.h"
|
|
|
|
static int debug=0;
|
|
static int offset=0;
|
|
|
|
static int bpm=120;
|
|
static int baselen=80;
|
|
static int frames_per_line;
|
|
static int octave_adjust=0;
|
|
|
|
static int line=0;
|
|
|
|
static int header_version=0;
|
|
|
|
static int note_to_length(int length) {
|
|
|
|
int len=1;
|
|
|
|
switch(length) {
|
|
case 0: len=(baselen*5)/2; break; // 0 = 2.5
|
|
case 1: len=baselen; break; // 1 = 1 whole
|
|
case 2: len=baselen/2; break; // 2 = 1/2 half
|
|
case 3: len=(baselen*3)/8; break; // 3 = 3/8 dotted quarter
|
|
case 4: len=baselen/4; break; // 4 = 1/4 quarter
|
|
case 5: len=(baselen*5)/8; break; // 5 = 5/8 ?
|
|
case 8: len=baselen/8; break; // 8 = 1/8 eighth
|
|
case 9: len=(baselen*3)/16; break; // 9 = 3/16 dotted eighth
|
|
case 6: len=baselen/16; break; // 6 = 1/16 sixteenth
|
|
case 10: len=(baselen*3)/4; break; // : = 3/4 dotted half
|
|
case 11: len=(baselen*9)/8; break; // ; = 9/8 dotted half + dotted quarter
|
|
case 12: len=(baselen*3)/2; break; // < = 3/2 dotted whole
|
|
case 13: len=(baselen*2); break; // = = 2 double whole
|
|
case 14: len=(baselen/32); break; // > = 1/32
|
|
case 15: len=(baselen/32)*3; break; // ? = 3/32 dotted sixteenth
|
|
default:
|
|
fprintf(stderr,"Unknown length %d, line %d\n",
|
|
length,line);
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
struct note_type {
|
|
unsigned char which;
|
|
unsigned char note;
|
|
int sharp,flat;
|
|
int octave;
|
|
int len;
|
|
int enabled;
|
|
int freq;
|
|
int length;
|
|
int left;
|
|
int ed_freq;
|
|
};
|
|
|
|
|
|
|
|
static int get_note(char *string, int sp, struct note_type *n, int line) {
|
|
|
|
int freq;
|
|
int ch;
|
|
|
|
|
|
/* Skip white space */
|
|
while((string[sp]==' ' || string[sp]=='\t')) sp++;
|
|
|
|
if (string[sp]=='\n') return -1;
|
|
|
|
/* return early if no change */
|
|
ch=string[sp];
|
|
|
|
if (ch=='-') {
|
|
if (header_version==0) return sp+6;
|
|
else {
|
|
return sp+11;
|
|
}
|
|
}
|
|
|
|
/* get note info */
|
|
n->sharp=0;
|
|
n->flat=0;
|
|
n->ed_freq=0;
|
|
n->note=ch;
|
|
sp++;
|
|
if (string[sp]==' ') ;
|
|
else if (string[sp]=='#') n->sharp=1;
|
|
else if (string[sp]=='-') n->flat=1;
|
|
else if (string[sp]=='=') n->flat=2;
|
|
else {
|
|
fprintf(stderr,"Unknown note modifier %c, line %d:%d\n",
|
|
string[sp],line,sp);
|
|
fprintf(stderr,"String: %s\n",string);
|
|
}
|
|
// printf("Sharp=%d Flat=%d\n",n->sharp,n->flat);
|
|
sp++;
|
|
n->octave=string[sp]-'0';
|
|
sp++;
|
|
sp++;
|
|
n->len=string[sp]-'0';
|
|
sp++;
|
|
|
|
|
|
if (n->note!='-') {
|
|
|
|
freq=note_to_ed(n->note,n->flat,n->sharp,
|
|
n->octave+octave_adjust);
|
|
|
|
if (debug) printf("(%c) %c%c L=%d O=%d f=%d\n",
|
|
n->which,
|
|
n->note,
|
|
n->sharp?'#':' ',
|
|
n->len,
|
|
n->octave,
|
|
freq);
|
|
|
|
n->ed_freq=freq;
|
|
n->enabled=1;
|
|
n->length=note_to_length(n->len);
|
|
n->left=n->length-1;
|
|
|
|
if (n->length<=0) {
|
|
printf("Error line %d\n",line);
|
|
exit(-1);
|
|
}
|
|
}
|
|
else {
|
|
n->ed_freq=0;
|
|
}
|
|
|
|
if (header_version==2) sp+=6;
|
|
|
|
return sp;
|
|
}
|
|
|
|
static int get_string(char *string, char *key, char *output, int strip_linefeed) {
|
|
|
|
char *found;
|
|
|
|
found=strstr(string,key);
|
|
found=found+strlen(key);
|
|
|
|
/* get rid of leading whitespace */
|
|
while(1) {
|
|
if ((*found==' ') || (*found=='\t')) found++;
|
|
else break;
|
|
}
|
|
|
|
strcpy(output,found);
|
|
|
|
/* remove trailing linefeed */
|
|
if (strip_linefeed) output[strlen(output)-1]=0;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
static void update_voice(FILE *ed_file,int voice1,int voice2) {
|
|
|
|
fprintf(ed_file,"%c%c%c",1,voice1,voice2);
|
|
|
|
}
|
|
|
|
static void print_help(int just_version, char *exec_name) {
|
|
|
|
printf("\ntext_to_ed version %s by Vince Weaver <vince@deater.net>\n\n",VERSION);
|
|
if (just_version) exit(0);
|
|
|
|
printf("This created Electric Duet files\n\n");
|
|
|
|
printf("Usage:\n");
|
|
printf("\t%s [-h] [-v] [-d] [-o X] [-i X] [-l] textfile outbase\n\n",
|
|
exec_name);
|
|
printf("\t-h: this help message\n");
|
|
printf("\t-v: version info\n");
|
|
printf("\t-d: print debug messages\n");
|
|
printf("\t-o: Offset octave by X\n");
|
|
printf("\t-i: set second instrument to X\n");
|
|
printf("\t-l: generate lyrics file\n");
|
|
|
|
exit(0);
|
|
}
|
|
|
|
|
|
int main(int argc, char **argv) {
|
|
|
|
char string[BUFSIZ];
|
|
char *result;
|
|
char ed_filename[BUFSIZ],lyrics_filename[BUFSIZ],*in_filename;
|
|
char temp[BUFSIZ];
|
|
FILE *ed_file,*lyrics_file=NULL,*in_file=NULL;
|
|
//int attributes=0;
|
|
int loop=0,i;
|
|
int sp,external_frequency,irq;
|
|
struct note_type a,b,c;
|
|
int copt;
|
|
int generate_lyrics=0;
|
|
|
|
// Instruments 0=square
|
|
int voice1=0,voice2=0;
|
|
int newvoice1,newvoice2;
|
|
|
|
char song_name[BUFSIZ];
|
|
char author_name[BUFSIZ];
|
|
char comments[BUFSIZ];
|
|
char *comments_ptr=comments;
|
|
|
|
unsigned char sharp_char[]=" #-=";
|
|
|
|
/* Parse command line arguments */
|
|
while ((copt = getopt(argc, argv, "dhvo:i:"))!=-1) {
|
|
switch (copt) {
|
|
case 'd':
|
|
/* Debug messages */
|
|
printf("Debug enabled\n");
|
|
debug=1;
|
|
break;
|
|
case 'h':
|
|
/* help */
|
|
print_help(0,argv[0]);
|
|
break;
|
|
case 'v':
|
|
/* version */
|
|
print_help(1,argv[0]);
|
|
break;
|
|
case 'o':
|
|
/* octave offset */
|
|
octave_adjust=atoi(optarg);
|
|
break;
|
|
case 'i':
|
|
/* instrument to use */
|
|
voice1=atoi(optarg);
|
|
break;
|
|
case 'l':
|
|
/* generate lyrics */
|
|
generate_lyrics=1;
|
|
break;
|
|
default:
|
|
print_help(0,argv[0]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (argv[optind]!=NULL) {
|
|
/* Open the input file */
|
|
if (argv[optind][0]=='-') {
|
|
in_file=stdin;
|
|
}
|
|
else {
|
|
in_filename=strdup(argv[optind]);
|
|
in_file=fopen(in_filename,"r");
|
|
if (in_file==NULL) {
|
|
fprintf(stderr,"Couldn't open %s\n",in_filename);
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (optind+1>=argc) {
|
|
fprintf(stderr,"Error, need outfile\n");
|
|
exit(0);
|
|
}
|
|
|
|
|
|
|
|
/* Open the output/lyrics files */
|
|
sprintf(ed_filename,"%s.ed",argv[optind+1]);
|
|
sprintf(lyrics_filename,"%s.edlyrics",argv[optind+1]);
|
|
|
|
ed_file=fopen(ed_filename,"w");
|
|
if (ed_file==NULL) {
|
|
fprintf(stderr,"Couldn't open %s\n",ed_filename);
|
|
return -1;
|
|
}
|
|
|
|
if (generate_lyrics) {
|
|
lyrics_file=fopen(lyrics_filename,"w");
|
|
if (lyrics_file==NULL) {
|
|
fprintf(stderr,"Couldn't open %s\n",lyrics_filename);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* Get the info for the header */
|
|
|
|
while(1) {
|
|
result=fgets(string,BUFSIZ,in_file);
|
|
if (result==NULL) break;
|
|
line++;
|
|
if (strstr(string,"ENDHEADER")) break;
|
|
if (strstr(string,"HEADER:")) {
|
|
get_string(string,"HEADER:",temp,1);
|
|
header_version=atoi(temp);
|
|
printf("Found header version %d\n",header_version);
|
|
}
|
|
if (strstr(string,"TITLE:")) {
|
|
get_string(string,"TITLE:",song_name,1);
|
|
}
|
|
if (strstr(string,"AUTHOR:")) {
|
|
get_string(string,"AUTHOR:",author_name,1);
|
|
}
|
|
if (strstr(string,"COMMENTS:")) {
|
|
get_string(string,"COMMENTS:",comments_ptr,0);
|
|
comments_ptr=&comments[strlen(comments)];
|
|
}
|
|
if (strstr(string,"BPM:")) {
|
|
get_string(string,"BPM:",temp,1);
|
|
bpm=atoi(temp);
|
|
}
|
|
if (strstr(string,"FREQ:")) {
|
|
get_string(string,"FREQ:",temp,1);
|
|
external_frequency=atoi(temp);
|
|
}
|
|
if (strstr(string,"IRQ:")) {
|
|
get_string(string,"IRQ:",temp,1);
|
|
irq=atoi(temp);
|
|
}
|
|
if (strstr(string,"LOOP:")) {
|
|
get_string(string,"LOOP:",temp,1);
|
|
loop=atoi(temp);
|
|
}
|
|
|
|
}
|
|
|
|
if (bpm==115) {
|
|
baselen=80;
|
|
}
|
|
else if (bpm==120) { // 2Hz, 500ms, 80x=500, x=6.25, should be 80
|
|
baselen=120;
|
|
}
|
|
else if (bpm==136) { // 2.3Hz, 440ms, should be 70
|
|
baselen=70;
|
|
}
|
|
else if (bpm==160) {// 2.66Hz, 375ms, should be 60
|
|
// baselen=60;
|
|
baselen=64; // multiple of 16?
|
|
}
|
|
else if (bpm==180) {// 3.33Hz, 333ms,
|
|
baselen=64; // multiple of 16?
|
|
}
|
|
else if (bpm==250) {
|
|
baselen=48; // eyeballed
|
|
}
|
|
else if (bpm==300) { //40 is too fast
|
|
baselen=42;
|
|
}
|
|
else if (bpm==320) {// 2.66Hz, 375ms, should be 60
|
|
// baselen=60;
|
|
baselen=32; // multiple of 16?
|
|
}
|
|
else {
|
|
fprintf(stderr,"Warning! Unusual BPM of %d\n",bpm);
|
|
baselen=80;
|
|
}
|
|
|
|
frames_per_line=baselen/16;
|
|
|
|
a.which='A'; b.which='B'; c.which='C';
|
|
|
|
int first=1;
|
|
int a_last=0,b_last=0,same_count=0;
|
|
int a_len=0,b_len=0,a_freq=0,b_freq=0;
|
|
int frame=0,lyric=0;
|
|
int lyric_line=1;
|
|
|
|
update_voice(ed_file,voice1,voice2);
|
|
offset+=3;
|
|
|
|
while(1) {
|
|
result=fgets(string,BUFSIZ,in_file);
|
|
if (result==NULL) break;
|
|
line++;
|
|
|
|
a.ed_freq=0;
|
|
b.ed_freq=0;
|
|
a.length=0;
|
|
b.length=0;
|
|
|
|
/* skip comments */
|
|
if (string[0]=='\'') continue;
|
|
if (string[0]=='-') continue;
|
|
if (string[0]=='*') continue;
|
|
|
|
if (string[0]=='@') {
|
|
sscanf(string,"@%d %d",&newvoice1,&newvoice2);
|
|
/* special case */
|
|
if (newvoice1>128) {
|
|
update_voice(ed_file,0xff,0xff);
|
|
}
|
|
else {
|
|
voice1=newvoice1;
|
|
voice2=newvoice2;
|
|
update_voice(ed_file,voice1,voice2);
|
|
}
|
|
|
|
offset+=3;
|
|
|
|
|
|
/* Avoid changing instrument by accident */
|
|
/* Also keep lyrics in sync */
|
|
if (same_count<2) {
|
|
same_count=2;
|
|
}
|
|
|
|
fprintf(ed_file,"%c%c%c",same_count,a_last,b_last);
|
|
offset+=3;
|
|
if (debug) {
|
|
printf("%04X: %x %x %x\n",offset,same_count,a_last,b_last);
|
|
}
|
|
same_count=0;
|
|
|
|
|
|
continue;
|
|
}
|
|
|
|
sp=0;
|
|
|
|
/* Skip line number */
|
|
while((string[sp]!=' ' && string[sp]!='\t')) sp++;
|
|
|
|
sp=get_note(string,sp,&a,line);
|
|
if (sp!=-1) sp=get_note(string,sp,&b,line);
|
|
if (sp!=-1) sp=get_note(string,sp,&c,line);
|
|
|
|
/* handle lyrics */
|
|
if (generate_lyrics) {
|
|
if ((sp!=-1) && (string[sp]!='\n') && (string[sp]!=0)) {
|
|
fprintf(lyrics_file,"; %d: %s",frame,&string[sp]);
|
|
while((string[sp]==' ' || string[sp]=='\t')) sp++;
|
|
if (string[sp]!='\n') {
|
|
fprintf(lyrics_file,".byte\t$%02X",lyric_line&0xff);
|
|
|
|
/* get to first quote */
|
|
while(string[sp]!='\"') sp++;
|
|
sp++;
|
|
|
|
/* stop at second quote */
|
|
while(string[sp]!='\"') {
|
|
if (string[sp]=='\\') {
|
|
sp++;
|
|
/* Ignore if we have LED panel */
|
|
if (string[sp]=='i') {
|
|
//printf(",$%02X",10);
|
|
}
|
|
/* form feed */
|
|
else if (string[sp]=='f') {
|
|
fprintf(lyrics_file,",$%02X",12);
|
|
}
|
|
/* Vertical tab */
|
|
else if (string[sp]=='v') {
|
|
fprintf(lyrics_file,",$%02X",11);
|
|
}
|
|
else if (string[sp]=='n') {
|
|
fprintf(lyrics_file,",$%02X",13|0x80);
|
|
}
|
|
else if ((string[sp]>='0') &&
|
|
(string[sp]<=':')) {
|
|
fprintf(lyrics_file,",$%02X",string[sp]-'0');
|
|
}
|
|
else {
|
|
printf("UNKNOWN ESCAPE %d\n",string[sp]);
|
|
}
|
|
sp++;
|
|
continue;
|
|
}
|
|
fprintf(lyrics_file,",$%02X",string[sp]|0x80);
|
|
sp++;
|
|
}
|
|
fprintf(lyrics_file,",$00\n");
|
|
|
|
// fprintf(lyrics_file," %s",&string[sp]);
|
|
// printf("%s",&string[sp]);
|
|
}
|
|
lyric=1;
|
|
|
|
}
|
|
}
|
|
|
|
if ((a.ed_freq!=0)||(b.ed_freq!=0)) {
|
|
if (debug) {
|
|
printf("%d: ",frame);
|
|
printf("%c%c%d %d (%d,%d) |%d %d %d %c|\t",
|
|
a.note,sharp_char[a.sharp+2*a.flat],a.octave,
|
|
a.len,a.ed_freq,a.length,
|
|
a.sharp,a.flat,a.sharp+2*a.flat,
|
|
sharp_char[2]);
|
|
|
|
printf("%c%c%d %d (%d,%d)\n",
|
|
b.note,sharp_char[b.sharp+2*b.flat],b.octave,
|
|
b.len,b.ed_freq,b.length);
|
|
}
|
|
}
|
|
|
|
if (a.length) a_len=a.length;
|
|
if (b.length) b_len=b.length;
|
|
if (a.ed_freq) {
|
|
a_freq=a.ed_freq;
|
|
}
|
|
if (b.ed_freq) {
|
|
b_freq=b.ed_freq;
|
|
}
|
|
|
|
if (first) {
|
|
a_last=a_freq;
|
|
b_last=b_freq;
|
|
first=0;
|
|
}
|
|
|
|
for(i=0;i<frames_per_line;i++) {
|
|
|
|
if ( (a_len==0) || (b_len==0) ) {
|
|
// fprintf(ed_file,"%c%c%c",same_count*(baselen/16),a_last,b_last);
|
|
// printf("*** %x %x %x\n",same_count*(baselen/16),a_last,b_last);
|
|
if (a_len==0) {
|
|
a_freq=0;
|
|
// printf("A hit zero\n");
|
|
}
|
|
if (b_len==0) {
|
|
b_freq=0;
|
|
// printf("B hit zero\n");
|
|
}
|
|
// same_count=0;
|
|
|
|
}
|
|
else {
|
|
// printf("%d\n",a_len);
|
|
}
|
|
|
|
if ((a_freq!=a_last) ||
|
|
(b_freq!=b_last) ||
|
|
(lyric) ||
|
|
(same_count>250)) {
|
|
|
|
|
|
/* Avoid changing instrument by accident */
|
|
/* Also keep lyrics in sync */
|
|
if (same_count<2) {
|
|
same_count=2;
|
|
}
|
|
lyric_line++;
|
|
|
|
fprintf(ed_file,"%c%c%c",same_count,a_last,b_last);
|
|
offset+=3;
|
|
if (debug) {
|
|
printf("%04X: %x %x %x\n",offset,same_count,a_last,b_last);
|
|
}
|
|
same_count=0;
|
|
|
|
}
|
|
|
|
same_count++;
|
|
|
|
if (a_len) a_len--;
|
|
if (b_len) b_len--;
|
|
|
|
a_last=a_freq;
|
|
b_last=b_freq;
|
|
lyric=0;
|
|
|
|
}
|
|
frame++;
|
|
|
|
}
|
|
if (same_count==1) same_count++;
|
|
fprintf(ed_file,"%c%c%c",same_count,a_last,b_last);
|
|
fprintf(ed_file,"%c%c%c",0,0,0); // EOF?
|
|
|
|
fclose(ed_file);
|
|
|
|
if (generate_lyrics) {
|
|
fclose(lyrics_file);
|
|
}
|
|
|
|
(void) irq;
|
|
(void) loop;
|
|
(void) external_frequency;
|
|
|
|
return 0;
|
|
}
|
|
|