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