/* 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 #include #include #include #include #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 \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;i250)) { /* 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; }