commit 910af7f
zzo38
·
2022-11-14 06:36:53 +0000 UTC
parent c04360c
Add the MML compiler and the documentation and example files of MML.
6 files changed,
+571,
-0
+359,
-0
1@@ -0,0 +1,359 @@
2+#if 0
3+gcc -s -O2 -o ./mmlmake mmlmake.c
4+exit
5+#endif
6+
7+#include <err.h>
8+#include <stdio.h>
9+#include <stdlib.h>
10+#include <string.h>
11+
12+#define OP_REST 0x03
13+#define OP_WAIT 0x05
14+#define OP_INSTRUMENT 0x07
15+#define OP_GOTO 0x09
16+#define OP_LOOPSTART 0x0B
17+#define OP_LOOPEND 0x0D
18+#define OP_VOLUME 0x0F
19+
20+typedef struct {
21+ FILE*file;
22+ char*data;
23+ size_t size;
24+ unsigned short loop;
25+ unsigned short address;
26+} Track;
27+
28+typedef struct {
29+ const char*name;
30+ void(*call)(char*);
31+} Command;
32+
33+typedef struct {
34+ char*data;
35+ size_t size;
36+ unsigned short address;
37+ unsigned char env[2];
38+ char loop;
39+} Instrument;
40+
41+static const signed char notes[12]={9,11,0,2,4,5,7};
42+
43+static unsigned char*driver_data;
44+static size_t driver_size;
45+static int divisions=120;
46+static char stereo=0;
47+static Track tracks[4];
48+static Instrument instrum[128];
49+static int cur_op=0;
50+static int cur_time=0;
51+static int cur_track;
52+static int rested=0;
53+static int total_time=0;
54+static char debug=0;
55+
56+static void cmd_debug(char*arg) {
57+ debug=1;
58+}
59+
60+static void cmd_divisions(char*arg) {
61+ divisions=strtol(arg,&arg,10);
62+ if(!divisions || (*arg!='\r' && *arg!='\n' && *arg)) errx(1,"Improper argument for #DIVISIONS");
63+}
64+
65+static void cmd_driver(char*arg) {
66+ FILE*fp=fopen(arg,"rb");
67+ if(!fp) err(1,"Cannot open driver file (%s)",arg);
68+ fseek(fp,0,SEEK_END);
69+ driver_size=ftell(fp);
70+ if(driver_size<32 || driver_size>0xFF00) errx(1,"Improper size of driver file");
71+ fseek(fp,0,SEEK_SET);
72+ driver_data=malloc(driver_size);
73+ if(!driver_data) err(1,"Allocation failed");
74+ if(!fread(driver_data,driver_size,1,fp)) err(1,"Error reading driver file");
75+ fclose(fp);
76+ if(memcmp(driver_data,"\xA0\x00\x00\xA0\xF8\x00\xA0\x00\xFA\xA0\x00\x00\xA0\xFC\x00\xA0\x00\xFE\x31\x31\x31\x31",0x16)) errx(1,"Wrong driver file");
77+}
78+
79+static void cmd_stereo(char*arg) {
80+ stereo=strtol(arg,&arg,10);
81+ if(stereo<0 || stereo>15 || (*arg!='\r' && *arg!='\n' && *arg)) errx(1,"Improper argument for #STEREO");
82+}
83+
84+static void cmd_tempo(char*arg) {
85+ int x;
86+ x=strtol(arg,&arg,10);
87+ if(x>600 || x<12 || (*arg!='\r' && *arg!='\n' && *arg)) errx(1,"Improper argument for #TEMPO");
88+ divisions=14400/x;
89+}
90+
91+static const Command commands[]={
92+ {"DEBUG",cmd_debug},
93+ {"DIVISIONS",cmd_divisions},
94+ {"DRIVER",cmd_driver},
95+ {"STEREO",cmd_stereo},
96+ {"TEMPO",cmd_tempo},
97+ {0,0}
98+};
99+
100+static void do_command(char*c) {
101+ int i;
102+ char*s=strchr(c,' ');
103+ if(s) *s++=0; else errx(1,"Missing argument for #%s",c);
104+ for(i=0;commands[i].name;i++) if(!strcmp(commands[i].name,c)) {
105+ commands[i].call(s);
106+ return;
107+ }
108+ errx(1,"Bad command: #%s",c);
109+}
110+
111+static void do_music(char*line) {
112+ char*s=strchr(line,' ');
113+ int ch;
114+ if(!s) errx(1,"Improper music line");
115+ while(line<s) {
116+ if(*line<'A' || *line>'D') errx(1,"Improper music line");
117+ ch=*line++-'A';
118+ if(!tracks[ch].file) {
119+ tracks[ch].file=open_memstream(&tracks[ch].data,&tracks[ch].size);
120+ if(!tracks[ch].file) err(1,"Cannot open memory stream");
121+ }
122+ fprintf(tracks[ch].file,"%s ",s);
123+ }
124+}
125+
126+static void do_instrument(char*s) {
127+ int i;
128+ int n=strtol(s,&s,10);
129+ FILE*fp;
130+ if(n&~127) errx(1,"Instrument number out of range");
131+ if(instrum[n].data) errx(1,"Instrument %d already defined",n);
132+ while(*s==' ' || *s=='\t') ++s;
133+ if(*s=='[') {
134+ ++s;
135+ i=strtol(s,&s,10); if(i&~15) errx(1,"Improper envelope");
136+ instrum[n].env[0]=i>>4;
137+ i=strtol(s,&s,10); if(i&~15) errx(1,"Improper envelope");
138+ instrum[n].env[0]|=i;
139+ i=strtol(s,&s,10); if(i&~15) errx(1,"Improper envelope");
140+ instrum[n].env[1]=i>>4;
141+ i=strtol(s,&s,10); if(i&~15) errx(1,"Improper envelope");
142+ instrum[n].env[1]|=i;
143+ while(*s==' ' || *s=='\t') ++s;
144+ if(*s!=']') errx(1,"Improper instrument definition (%d): %s",n,s);
145+ ++s;
146+ while(*s==' ' || *s=='\t') ++s;
147+ }
148+ if(*s=='$') {
149+ ++s;
150+ instrum[n].loop=0x80;
151+ while(*s==' ' || *s=='\t') ++s;
152+ }
153+ if(*s!='=') errx(1,"Improper instrument definition (%d): %s",n,s);
154+ ++s;
155+ while(*s==' ' || *s=='\t') ++s;
156+ fp=fopen(s,"rb");
157+ if(!fp) err(1,"Cannot open data file for instrument %d",n);
158+ fseek(fp,0,SEEK_END);
159+ i=ftell(fp);
160+ if(i<2 || i>0xFF00) errx(1,"Improper size of data file for instrument %d",n);
161+ instrum[n].data=malloc(instrum[n].size=i);
162+ if(!instrum[n].data) err(1,"Allocation failed");
163+ fseek(fp,0,SEEK_SET);
164+ if(!fread(instrum[n].data,i,1,fp)) err(1,"Cannot read instrument data");
165+ fclose(fp);
166+}
167+
168+static void do_line(char*line) {
169+ char*s=line+strlen(line);
170+ while(s>line && (s[-1]=='\r' || s[-1]=='\n' || s[-1]==' ' || s[-1]=='\t')) *--s=0;
171+ switch(*line) {
172+ case 'A': case 'B': case 'C': case 'D': do_music(line); break;
173+ case '#': do_command(line+1); break;
174+ case '@': do_instrument(line+1); break;
175+ case 0: case ';': break;
176+ default: errx(1,"Bad command: %s",line);
177+ }
178+}
179+
180+static void instrument_address(void) {
181+ int n;
182+ int a=driver_size+0x0100;
183+ for(n=0;n<128;n++) if(instrum[n].data) {
184+ instrum[n].address=a;
185+ a+=instrum[n].size+5;
186+ }
187+}
188+
189+static void send_time(void) {
190+ int i;
191+ if(!cur_time) return;
192+ total_time+=cur_time;
193+ while(cur_time) {
194+ if(cur_time>255) i=255; else i=cur_time;
195+ cur_time-=i;
196+ fputc(cur_op,tracks[cur_track].file);
197+ fputc(-i,tracks[cur_track].file);
198+ cur_op=OP_WAIT;
199+ }
200+}
201+
202+static int note_length(char**ss,int n) {
203+ char*s=*ss;
204+ int i;
205+ if(i=strtol(s,&s,10)) n=divisions/i;
206+ i=n;
207+ while(*s=='.') n+=i/=2;
208+ *ss=s;
209+ if(n<1) errx(1,"Improper note length");
210+ return n;
211+}
212+
213+static char*add_note(char*s,int n,int t) {
214+ int v=notes[s[-1]-'a']+t;
215+ int i;
216+ send_time();
217+ while(*s=='-') s++,v--;
218+ while(*s=='+') s++,v++;
219+ while(*s==',' || *s=='_') s++,v-=12;
220+ while(*s=='\'') s++,v+=12;
221+ n=note_length(&s,n);
222+ if(v<0 || v>127) errx(1,"Note out of range");
223+ cur_op=v+0x80;
224+ cur_time=n;
225+ return s;
226+}
227+
228+static char*add_rest(char*s,int n) {
229+ send_time();
230+ n=note_length(&s,n);
231+ cur_op=OP_REST;
232+ cur_time=n;
233+ return s;
234+}
235+
236+static char*add_wait(char*s,int n) {
237+ n=note_length(&s,n);
238+ cur_time+=n;
239+ return s;
240+}
241+
242+static char*set_instrument(char*s,FILE*fp) {
243+ int n;
244+ if(*s<'0' || *s>'9') errx(1,"Improper music command (@%c)",*s);
245+ n=strtol(s,&s,10);
246+ if(n&~127) errx(1,"Instrument number out of range");
247+ if(!instrum[n].size || !instrum[n].address) errx(1,"Undefined instrument");
248+ n=instrum[n].address;
249+ fputc(OP_INSTRUMENT,fp);
250+ fputc(n>>8,fp);
251+ fputc(n&255,fp);
252+ return s;
253+}
254+
255+static char*set_volume(char*s,FILE*fp,int y) {
256+ int n;
257+ if(*s<'0' || *s>'9') errx(1,"Improper music command (v%c)",*s);
258+ n=strtol(s,&s,10);
259+ if(n&~15) errx(1,"Volume number out of range");
260+ fputc(OP_VOLUME,fp);
261+ fputc(n*0x11-(((stereo*n)/15)<<(y?0:4)),fp);
262+ return s;
263+}
264+
265+static void do_track(int ch) {
266+ int notelen=divisions/4;
267+ int octave=5;
268+ int transpose=0;
269+ int looptime=0;
270+ Track*tr=tracks+ch;
271+ char*s;
272+ if(tr->file) {
273+ fclose(tr->file);
274+ s=tr->data;
275+ } else {
276+ s="Lw1 ";
277+ }
278+ if(!s) err(1,"Unknown error");
279+ tr->data=0;
280+ tr->size=0;
281+ tr->file=open_memstream(&tr->data,&tr->size);
282+ if(!tr->file) err(1,"Cannot open memory stream");
283+ cur_track=ch;
284+ cur_time=total_time=0;
285+ cur_op=OP_WAIT;
286+ rested=0;
287+ while(*s) switch(*s++) {
288+ case ' ': case '\r': case '\n': case '\t': break;
289+ case 'a' ... 'g': rested=0; s=add_note(s,notelen,octave*12+transpose); break;
290+ case 'l': notelen=note_length(&s,notelen); break;
291+ case 'n': send_time(); cur_op=strtol(s,&s,10)+transpose+0x80; cur_time=notelen; break;
292+ case 'o': octave=strtol(s,&s,10); if(octave<0 || octave>9) errx(1,"Improper octave (%d)",octave); break;
293+ case 'r': s=rested?add_wait(s,notelen):add_rest(s,notelen); rested=1; break;
294+ case 'v': send_time(); s=set_volume(s,tr->file,ch&1); break;
295+ case 'w': case '^': s=add_wait(s,notelen); break;
296+ case 'K': transpose=strtol(s,&s,10); break;
297+ case 'L': send_time(); looptime=total_time; tr->loop=ftell(tr->file); rested=0; break;
298+ case 'X': send_time(); fputc(strtol(s,&s,0),tr->file); rested=0; break;
299+ case '<': if(octave) --octave; break;
300+ case '>': if(octave<9) ++octave; break;
301+ case '@': send_time(); s=set_instrument(s,tr->file); break;
302+ case '[': send_time(); rested=0; fputc(OP_LOOPSTART,tr->file); break;
303+ case ']': send_time(); rested=0; fputc(OP_LOOPEND,tr->file); fputc(strtol(s,&s,10)-2,tr->file); break;
304+ default: errx(1,"Improper music command (%c)",s[-1]);
305+ }
306+ send_time();
307+ fprintf(stderr,"%c %d %d\n",ch+'A',looptime,total_time);
308+ fclose(tr->file);
309+ if(!tr->data) err(1,"Allocation failed");
310+ if(tr->size<1 || tr->size>=0xFFFF) errx(1,"Too big output size");
311+}
312+
313+static void do_output(void) {
314+ static const char z[8]={1,5,10,14,2,7,11,16};
315+ int a=driver_size+0x0100;
316+ int n;
317+ if(debug) fprintf(stderr,"Data start address = 0x%04X\n",a);
318+ for(n=0;n<128;n++) if(instrum[n].data) a+=instrum[n].size+5;
319+ for(n=0;n<4;n++) {
320+ if(debug) fprintf(stderr,"Track %c address = 0x%04X\n",n+'A',a);
321+ tracks[n].address=a;
322+ driver_data[z[n]]=a>>8;
323+ driver_data[z[n+4]]=a&255;
324+ a+=tracks[n].size+3;
325+ if(a<0 || a>0xFFFF) errx(1,"Too big output size");
326+ }
327+ if(debug) fprintf(stderr,"Data end address = 0x%04X\n",a);
328+ fwrite(driver_data,1,driver_size,stdout);
329+ for(n=0;n<128;n++) if(instrum[n].data) {
330+ if(debug) fprintf(stderr,"Instrument %d address = 0x%04X\n",n,instrum[n].address);
331+ fwrite(instrum[n].env,1,2,stdout);
332+ putchar(instrum[n].size>>8);
333+ putchar(instrum[n].size&255);
334+ putchar(instrum[n].loop);
335+ fwrite(instrum[n].data,1,instrum[n].size,stdout);
336+ }
337+ for(n=0;n<4;n++) {
338+ fwrite(tracks[n].data,1,tracks[n].size,stdout);
339+ putchar(OP_GOTO);
340+ a=tracks[n].address+tracks[n].loop;
341+ putchar(a>>8);
342+ putchar(a&255);
343+ }
344+}
345+
346+int main(int argc,char**argv) {
347+ char*linebuf=0;
348+ size_t linesize=0;
349+ while(getline(&linebuf,&linesize,stdin)>0) do_line(linebuf);
350+ if(!driver_size) errx(1,"Missing #DRIVER command");
351+ free(linebuf);
352+ instrument_address();
353+ do_track(0);
354+ do_track(1);
355+ do_track(2);
356+ do_track(3);
357+ do_output();
358+ return 0;
359+}
360+
+124,
-0
1@@ -0,0 +1,124 @@
2+MML maker for Uxn
3+
4+Reads the MML text from stdin and write output to stdout.
5+
6+Status is written to stderr. Each line is the number of frames before the
7+loop start and then the total number of frames.
8+
9+
10+=== Outer commands ===
11+
12+;
13+ Comment; does nothing.
14+
15+#DIVISIONS
16+ Set the number of frames per whole note. (Normally, there are sixty
17+ frames in one second.)
18+
19+#DRIVER
20+ The name of the driver file. This command is mandatory.
21+
22+#STEREO
23+ Set the stereo amount, from 0 (mono only; default) to 15.
24+
25+#TEMPO
26+ Set the number of quarter notes per minute. You should specify either
27+ this command or #DIVISIONS, not both; if you specify both then only
28+ the one that comes later in the file will be used.
29+
30+@
31+ Define a instrument; see below section for details.
32+
33+A B C D
34+ Add track commands. You can have any sequence of letters
35+ A, B, C, and D, and then a space and then track commands,
36+ which means the notes for the music; see below section.
37+
38+
39+=== Instrument definition ===
40+
41+The instrument definition format can be one of:
42+
43+@number=filename
44+@number$=filename
45+@number[attack decay sustain release]=filename
46+@number[attack decay sustain release]$=filename
47+
48+The number is 0 to 127.
49+
50+If $ is present then the sample will be looped.
51+
52+If [] is present then they are the time of each section of the envelope,
53+in fifteenths of a second (each can be 0 to 15); if absent, or if all of
54+the numbers are zero, then there is no envelope.
55+
56+The file contains the unsigned 8-bits sample data at 44100 Hz middle C.
57+
58+
59+=== Track commands ===
60+
61+a b c d e f g
62+ Play a note. You can optionally add any combination of of - + ' _ for
63+ flat, sharp, high octave, and low octave, respectively. Optionally can
64+ also specify the note duration (see l command for details). Any such
65+ specifications of octave/duration apply to the current note only.
66+
67+l
68+ Set note length for following notes (until changed by another l command).
69+ It is a number of fraction of the note, e.g. 4 for a quarter note, 8 for
70+ eighth note, etc. You can optionally add dots for longer durations, like
71+ in ordinary music.
72+
73+n
74+ Play a note by the MIDI note number.
75+
76+o
77+ Set the octave; follow by a number 0 to 9 indicating which octave, where
78+ 5 is the default octave and is the octave containing the middle C note.
79+
80+r
81+ Play a rest. Can optionally have the note length, like notes can be.
82+
83+v
84+ Set volume for further notes, from 0 to 15.
85+
86+w
87+ Wait for a specified duration without silencing the current note or
88+ changing anything else.
89+
90+K
91+ Set transpose amount, which is a signed number of semitones. This is
92+ not relative to the previous transpose amount; it is reset each time.
93+
94+L
95+ Set global loop point.
96+
97+X
98+ Follow by a number to emit that byte directly. It is not meaningful
99+ unless you are using a customized driver program which is capable of
100+ interpreting this number.
101+
102+^
103+ Tied note with a specified duration (as for rests and notes). (Actually,
104+ this command is same as w command.)
105+
106+<
107+ Low octave.
108+
109+>
110+ High octave.
111+
112+@
113+ Follow by the instrument number 0 to 127, to set the instrument for any
114+ further notes being played in this channel.
115+
116+[
117+ Begin local loop. Loops cannot be nested.
118+
119+]
120+ End local loop; follow by the repeat count number, which must be at least
121+ 2 and cannot exceed 255. You can have multiple ends for a local loop in
122+ which case later ones may have a higher number, which indicates the total
123+ number of times that it plays from the beginning of this loop, and is
124+ going backward only if the number of times specified is not reached yet.
125+
+81,
-0
1@@ -0,0 +1,81 @@
2+|d0 @ch-loops $8
3+|d8 @ch-loopc $8
4+|e0 @ch-vol $8
5+|e8 @ch-pitch $8
6+|f0 @ch-time $8
7+|f8 @ch-ptr $8
8+
9+|0100
10+ LIT2 0000
11+ LIT2 f800
12+ LIT2 00fa
13+ LIT2 0000
14+ LIT2 fc00
15+ LIT2 00fe
16+ STZ2 STZ2 STZ2 STZ2
17+ [; #20 DEO2 ]@
18+ LIT2r 00f8
19+@command
20+ STHrk #08 SUB LDZ
21+ DUP ,nxchan JCN POP
22+ ,readc JSR
23+ DUP #80 LTH [, JCN #01 ]@ LDR2 JMP2
24+ ( 01 ) :cmd-note
25+ ( 03 ) :cmd-rest
26+ ( 05 ) :cmd-wait
27+ ( 07 ) :cmd-instrument
28+ ( 09 ) :cmd-goto
29+ ( 0b ) :cmd-loopstart
30+ ( 0d ) :cmd-loopend
31+ ( 0f ) :cmd-volume
32+@nxchan
33+ INC STHrk #08 SUB STZ
34+ INCr INCr STHrk
35+ ,command JCN POP2r BRK
36+@readc
37+ OVR2r STH2r LDA2 INC2k OVR2r STH2r STA2 LDA JMP2r
38+@send
39+ OVR2r STH2r #30 SFT ADD ADD DEO2 JMP2r
40+
41+@cmd-note
42+ STHrk #18 SUB LDZ
43+ SWP STHrk #10 SUB LDZ ADD #7e ,send JSR
44+ ,cmd-wait JMP
45+
46+@cmd-rest
47+ #0000 #7e ,send JSR
48+ ( fall through )
49+@cmd-wait
50+ ,readc JSR
51+ STHrk #08 SUB STZ
52+ ,command JMP
53+
54+@cmd-instrument
55+ ,readc JSR ,readc JSR
56+ LDA2k #78 ,send JSR INC2 INC2
57+ LDA2k #7a ,send JSR INC2 INC2
58+ LDAk STHrk #10 SUB STZ INC2
59+ #7c ,send JSR
60+ ;command JMP2
61+
62+@cmd-goto
63+ ,readc JSR ,readc JSR
64+ STHrk STZ2
65+ ;command JMP2
66+
67+@cmd-loopstart
68+ STH2rk #20 SUB STZ
69+ STHrk LDZ2 STHrk #28 SUB STZ2
70+ ;command JMP2
71+
72+@cmd-loopend
73+ STHrk #20 SUB STHk LDZ
74+ INCk STHr STZ
75+ ;readc JSR2 GTH [, JCN STHrk #28 SUB LDZ2 STHrk STZ2 ]@
76+ ;command JMP2
77+
78+@cmd-volume
79+ ;readc JSR2
80+ STHrk #18 SUB STZ
81+ ;command JMP2
82+
+7,
-0
1@@ -0,0 +1,7 @@
2+#DRIVER driver.bin
3+#DIVISIONS 120
4+
5+@0 [0 8 0 12]$ = square50.u8
6+
7+A v15@0L
8+A l4 [aab2]2 [abc'b ab8a8f2 ecef ee8c8<b2>]2 [aab2]2 efb8a8f e2r2
+0,
-0
+0,
-0