Fix an issue where the pattern doesn't advance,
[quincer.git] / src / lex.l
blobe9f8ac0b22c25d73d4d01fdae3e07ec201f654e4
1 %option nodefault warn yylineno
3 %{
4 #include <limits.h>
5 #include "lex.h"
6 #define MAX_FILENAME_LEN 64
8   int comment_caller;
10   qn_config_t* config = NULL;
11   char filename[MAX_FILENAME_LEN+1];
13   static void start_include(int quoted) {
14     memset(filename,0,MAX_FILENAME_LEN);
15     char* start = yytext;
16     int len = strlen(start);
17     if(quoted) {
18       start += 1;
19       len -= 2;
20     }
21     strncpy(filename,start,
22             (len > MAX_FILENAME_LEN) ? MAX_FILENAME_LEN : len);
23     // fprintf(stderr,"Just lexed a filename: %s\n",filename);
25     yyin = fopen(filename,"r");
26     if(!yyin) {
27       fprintf(stderr,"Couldn't open include file %s\n",filename);
28       exit(2);
29     }
30     yypush_buffer_state(yy_create_buffer(yyin,YY_BUF_SIZE));
31   }
33   // TODO: other channel mode messages:
34   // aftertouch (both kinds), program change, pitch bend
35   enum EventType {
36     NoneYet = -1,
37     NoteOn,
38     ControlChange,
39     ProgramChange
40   };
42   struct eventdef {
43     int is_set;
44     enum EventType type;
45     char data[NBYTES_MIDI-1];
46   };
48 # define MAX_EVENT_DEFS 128
49   struct eventdef eventdefs[MAX_EVENT_DEFS];
51   enum EventType last_event_type = NoneYet;
52   char current_eventdef = '\0';
54   int passed_prefix, prefix_len,
55     beats_per_bar_known, ticks_per_beat_known,
56     passed_primary_content, primary_len, suffix_len;
58   int currtick;
59   unsigned char curreventdef = '\0';
61   /* When lexing a line of events for a pv, we won't know which voice
62      the events are for until the end of the line, so we buffer them
63      until then.  This allows multiple lines to be specified per voice
64      (polyphony, automation, etc) */
65 # define MAX_LINE_EVENTS 1024
66   int nevents = 0;
67   qn_event_t events[MAX_LINE_EVENTS];
68   /* int note_wraps_pv = 0; */
70   static void maybe_note_off() {
71     // Maybe emit a note-off if prior was note-on.
72     // TODO: if last tick is EVENTDEFCHAR, be sure to add the note off there too.
73     if(curreventdef
74        && eventdefs[curreventdef].is_set
75        && eventdefs[curreventdef].type==NoteOn) {
77       nevents++;
78       if(nevents > MAX_LINE_EVENTS) {
79         fprintf(stderr,"Too many events defined in one line (> %d), char '%c' at line %d\n",
80                 MAX_LINE_EVENTS, curreventdef, yylineno);
81         exit(2);
82       }
84       qn_event_t* evtnew = events + nevents - 1;
85       evtnew->start_tick = currtick;
86       evtnew->data[0] = 0x80; // note off
87       evtnew->data[1] = eventdefs[curreventdef].data[0];
89       curreventdef = '\0';
90     }
91   }
93   int current_clock_port_index = -1;
94   static int ensure_port(char* name, char** permname) {
95     int i;
96     for(i=0; i<config->noutports; i++) {
97       char* thisp = config->outports[i].name;
98       if(!strcmp(thisp,name)) {
99         *permname = thisp;
100         return i;
101       }
102     }
104     config->noutports++;
105     config->outports = realloc(config->outports, sizeof(qn_config_port_t) * config->noutports);
106     config->outports[config->noutports-1].name = *permname = strndup(name,strlen(yytext));
107     config->outports[config->noutports-1].clock_numerator = 0;
108     config->outports[config->noutports-1].clock_denominator = 0;
109     return config->noutports-1;
110   }
112   /* Get the MIDI pitch if this was in octave four according to
113      https://en.wikipedia.org/wiki/Scientific_pitch_notation
114   */
115   static char get_octave_four_pitch(char* token) {
116     size_t len = strnlen(token,4);
117     int is_sharp = (len > 1) && token[1]=='#';
118     int is_flat = (len > 1) && token[1]=='b';
120     if(is_sharp) {
121       switch(token[0]) {
122       case 'a':
123       case 'A':
124         return 70;
125       case 'b':
126       case 'B':
127         return 60;
128       case 'c':
129       case 'C':
130         return 61;
131       case 'd':
132       case 'D':
133         return 63;
134       case 'e':
135       case 'E':
136         return 65;
137       case 'f':
138       case 'F':
139         return 66;
140       case 'g':
141       case 'G':
142         return 68;
143       }
144     } else if(is_flat) {
145       switch(token[0]) {
146       case 'a':
147       case 'A':
148         return 68;
149       case 'b':
150       case 'B':
151         return 70;
152       case 'c':
153       case 'C':
154         return 71;
155       case 'd':
156       case 'D':
157         return 61;
158       case 'e':
159       case 'E':
160         return 63;
161       case 'f':
162       case 'F':
163         return 64;
164       case 'g':
165       case 'G':
166         return 66;
167       }
168     } else {
169       switch(token[0]) {
170       case 'a':
171       case 'A':
172         return 69;
173       case 'b':
174       case 'B':
175         return 71;
176       case 'c':
177       case 'C':
178         return 60;
179       case 'd':
180       case 'D':
181         return 62;
182       case 'e':
183       case 'E':
184         return 64;
185       case 'f':
186       case 'F':
187         return 65;
188       case 'g':
189       case 'G':
190         return 67;
191       }
192     }
194     // Shouldn't happen since token regex should only match correct vals
195     return -1;
196   }
198   static char midi_pitch_from_named_absolute(char* token) {
199     // midi note range:  c-1 (0) to g9 (127)
200     size_t len = strnlen(token,4);
201     char octave = token[len-1] - '0';
202     if(token[len-2]=='-') octave = -octave;
203     
204     char o4pitch = get_octave_four_pitch(token);
205     if(o4pitch < 0) return o4pitch;
207     char newpitch = o4pitch + (12 * (octave-4));
209     if(newpitch < 0 || newpitch > 127) return -1;
211     return newpitch;
212   }
214   // Initial value is F4 so that if the first event def
215   // has a relative pitch it will end up in octave 4
216   char last_midi_pitch = 65;
218   static char midi_pitch_from_named_relative(char* token) {
219     // midi note range:  c-1 (0) to g9 (127)
221     char o4pitch = get_octave_four_pitch(token);
222     if(o4pitch < 0) return o4pitch;
224     char test_pitch = o4pitch;
225     if(test_pitch >= last_midi_pitch) {
226       while(test_pitch - last_midi_pitch > 6 && test_pitch >= 12) {
227         test_pitch -= 12;
228       }
229       return test_pitch;
230     } else { // test_pitch < last_midi_pitch
231       while(last_midi_pitch - test_pitch >= 6
232             && CHAR_MAX-12 >= test_pitch) {
233         test_pitch += 12;
234       }
235       return test_pitch;
236     }
237   }
241 %s voicename voiceport voicechannel voicenl
242 %s incfilename
243 %s comment
244 %s patname patbpm patswing
245 %s eventdefchar eventdefcharortickdef eventtypeorval eventval1 eventval1progchange eventval2
246 %s tickdefchar
247 %s pv pvvoicename
248 %s runspec
249 %s clockport clocknum clockden clocknl
251 ID [a-z][a-z0-9]{0,15}
252 CHANNEL 1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16
254 EVENTDEFCHAR [[:print:]]{-}[S0\.\'|\- ]
255 EVENTTYPE note|cc|pc
256 EVENTDEFSHORT [0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-7]
257 RUNSPEC once|loop
259 NAMEDPITCHABSOLUTE [a-gA-G][b#]?(-1|[0-9])
260 NAMEDPITCHRELATIVE [a-gA-G][b#]?
263 <INITIAL,pv>voice BEGIN(voicename);
265 <INITIAL>run BEGIN(runspec);
267 <runspec>{RUNSPEC} {
268    if(!strcmp("once",yytext)) {
269      config->next_pattern_func = once_through;
270    } else if(!strcmp("loop",yytext)) {
271      config->next_pattern_func = loop_all;
272    } else {
273       fprintf(stderr,"Invalid run spec %s at line %d\n", yytext, yylineno);
274       exit(2);
275    }
276    BEGIN(INITIAL);
279 <voicename>{ID} {
280   int i;
281   for(i=0; i<config->nvoices; i++) {
282     qn_voice_t* thisv = config->voices + i;
283     if(!strcmp(thisv->name,yytext)) {
284       fprintf(stderr,"Duplicate declaration of voice %s at line %d\n", yytext, yylineno);
285       exit(2);
286     }
287   }
289   config->nvoices++;
290   config->voices = realloc(config->voices, sizeof(qn_voice_t) * config->nvoices);
291   config->voices[config->nvoices-1].name = strndup(yytext,strlen(yytext));
292   BEGIN(voiceport);
295 <voiceport>{ID} {
296   char* permname;
297   int portindex = ensure_port(yytext, &permname);
299   config->voices[config->nvoices-1].portname = permname;
300   BEGIN(voicechannel);
303 <voicechannel>{CHANNEL} {
304   // -1 since channels in input are 1-based but in raw data are
305   // -0-based
306   config->voices[config->nvoices-1].channel = atoi(yytext)-1;
307   BEGIN(voicenl);
310 <voicenl>\n BEGIN(INITIAL);
312 <INITIAL,pv>clock BEGIN(clockport);
314 <clockport>{ID} {
315   char* permname;
316   current_clock_port_index = ensure_port(yytext, &permname);
317   BEGIN(clocknum);
320 <clocknum>[1-9][0-9]* {
321   config->outports[current_clock_port_index].clock_numerator = atoi(yytext);
322   BEGIN(clockden);
325 <clockden>[1-9][0-9]* {
326   config->outports[current_clock_port_index].clock_denominator = atoi(yytext);
327   current_clock_port_index = -1;
328   BEGIN(clocknl);
331 <clocknl>\n BEGIN(INITIAL);
333 <INITIAL,pv>include BEGIN(incfilename);
335 <incfilename>\".*\" {
336   /* Allow quotes around a filename so that it can have spaces. */
337   start_include(1);
338   BEGIN(INITIAL);
341 <incfilename>[^ \t\n]+ {
342   /* Copy an unquoted filename */
343   start_include(0);
344   BEGIN(INITIAL);
347 # {
348   comment_caller = YY_START;
349   BEGIN(comment);
351 <comment>\n {
352   unput('\n');
353   BEGIN(comment_caller);
355 <comment>.* 
357 <INITIAL,pv>pattern {
358   config->npatterns++;
359   config->patterns = realloc(config->patterns, sizeof(qn_pattern_t) * config->npatterns);
360   qn_pattern_t* newp = config->patterns + config->npatterns - 1;
361   newp->npatternvoices = 0;
362   newp->patternvoices = NULL;
363   newp->swing_value = 0.0;
364   newp->tbm_ticks_per_beat = 0;
365   newp->tbm_beats_per_bar = 0.0;
366   newp->tbm_beat_type = 4;
367   newp->beats_per_minute = 0;
368   newp->bpm_relation = Absolute;
370   passed_prefix = prefix_len = 0;
371   passed_primary_content = primary_len = suffix_len = 0;
372   ticks_per_beat_known = 0;
373   beats_per_bar_known = 0;
375   BEGIN(patbpm);
378 <patbpm>[+-]?[123]?[0-9]{1,2} {
379   qn_pattern_t* newp = config->patterns + config->npatterns - 1;
380   if(*yytext == '+' || *yytext == '-') {
381     newp->bpm_relation = RelativeToBase;
382   }
383   newp->beats_per_minute = atof(yytext);
384   BEGIN(patswing);
387 <patswing>[01]?\.?[0-9]+ {
388   qn_pattern_t* newp = config->patterns + config->npatterns - 1;
389   newp->swing_value = atof(yytext);
391   // initialize note definitions
392   int i;
393   for(i=0; i<MAX_EVENT_DEFS; i++) {
394     eventdefs[i].is_set = 0;
395   }
396   last_event_type = NoneYet;
397   current_eventdef = '\0';
398   BEGIN(eventdefchar);
401 <eventdefchar,eventdefcharortickdef>{EVENTDEFCHAR} {
402   current_eventdef = *yytext;
403   struct eventdef* def = eventdefs + current_eventdef;
405   if(def->is_set) {
406     fprintf(stderr,"Duplicate declaration of note definition %s at line %d\n", yytext, yylineno);
407     exit(2);
408   }
410   def->is_set = 1;
412   BEGIN(eventtypeorval);
415 <eventtypeorval>{EVENTTYPE} {
416   struct eventdef* def = eventdefs + current_eventdef;
417   if(!strcmp("note",yytext)) {
418     def->type = last_event_type = NoteOn;
419     BEGIN(eventval1);
420   } else if(!strcmp("cc",yytext)) {
421     def->type = last_event_type = ControlChange;
422     BEGIN(eventval1);
423   } else if(!strcmp("pc",yytext)) {
424     def->type = last_event_type = ProgramChange;
425     BEGIN(eventval1progchange);
426   }
429 <eventtypeorval,eventval1>{NAMEDPITCHABSOLUTE} {
430   struct eventdef* def = eventdefs + current_eventdef;
432   // Default to note event if none was specified
433   if(last_event_type==NoneYet) {
434     def->type = last_event_type = NoteOn;
435   }
437   char parse_result = midi_pitch_from_named_absolute(yytext);
438   if(parse_result == -1) {
439     fprintf(stderr,"Invalid absolute named pitch token %s at line %d\n", yytext, yylineno);
440     exit(2);
441   }
442   
443   def->data[0] = last_midi_pitch = parse_result;
444   BEGIN(eventval2);
447 <eventtypeorval,eventval1>{NAMEDPITCHRELATIVE} {
448   struct eventdef* def = eventdefs + current_eventdef;
450   // Default to note event if none was specified
451   if(last_event_type==NoneYet) {
452     def->type = last_event_type = NoteOn;
453   }
455   char parse_result = midi_pitch_from_named_relative(yytext);
456   if(parse_result == -1) {
457     fprintf(stderr,"Invalid relative named pitch token %s at line %d\n", yytext, yylineno);
458     exit(2);
459   }
460   
461   def->data[0] = last_midi_pitch = parse_result;
462   BEGIN(eventval2);
465 <eventtypeorval,eventval1>{EVENTDEFSHORT} {
466   struct eventdef* def = eventdefs + current_eventdef;
468   // Default to note event if none was specified
469   if(last_event_type==NoneYet) {
470     def->type = last_event_type = NoteOn;
471   }
473   def->data[0] = last_midi_pitch = (char)atoi(yytext);
474   BEGIN(eventval2);
477 <eventval1progchange>{EVENTDEFSHORT} {
478   struct eventdef* def = eventdefs + current_eventdef;
479   def->data[0] = (char)atoi(yytext);
480   BEGIN(eventdefcharortickdef);
483 <eventval2>{EVENTDEFSHORT} {
484   struct eventdef* def = eventdefs + current_eventdef;
485   def->data[1] = (char)atoi(yytext);
486   BEGIN(eventdefcharortickdef);
489 <eventdefcharortickdef,tickdefchar>\| {
490   if(passed_prefix) {
491     beats_per_bar_known = 1;
492     ticks_per_beat_known = 1;
493     if(passed_primary_content) {
494       suffix_len++;
495     } else {
496       primary_len++;
497     }
498   } else {
499     prefix_len++;
500   }
501   
502   BEGIN(tickdefchar);
505 <eventdefcharortickdef,tickdefchar>0 {
506   qn_pattern_t* newp = config->patterns + config->npatterns - 1;
507   if(newp->tbm_beats_per_bar == 0.0) {
508     newp->tbm_beats_per_bar = 1.0;
509     newp->tbm_ticks_per_beat++;
510   }
511   primary_len = 1;
512   passed_prefix = 1;
513   BEGIN(tickdefchar);
516 <tickdefchar>S {
517   passed_primary_content = 1;
518   suffix_len++;
521 <eventdefcharortickdef,tickdefchar>\x27 { // A single quote '
522   if(passed_prefix) {
523     qn_pattern_t* newp = config->patterns + config->npatterns - 1;
524     if(!beats_per_bar_known) {
525       newp->tbm_beats_per_bar += 1.0;
526     }
527     ticks_per_beat_known = 1;
528     if(passed_primary_content) {
529       suffix_len++;
530     } else {
531       primary_len++;
532     }
533   } else {
534     prefix_len++;
535   }
536   BEGIN(tickdefchar);
539 <eventdefcharortickdef,tickdefchar>\. {
540   if(passed_prefix) {
541     qn_pattern_t* newp = config->patterns + config->npatterns - 1;
542     if(!ticks_per_beat_known) {
543       newp->tbm_ticks_per_beat++;
544     }
545     if(passed_primary_content) {
546       suffix_len++;
547     } else {
548       primary_len++;
549     }
550   } else {
551     prefix_len++;
552   }
553   BEGIN(tickdefchar);
556 <tickdefchar>\n {
557   currtick = -prefix_len;
558   BEGIN(pv);
561 <pv>{EVENTDEFCHAR} {
562   maybe_note_off();
564   curreventdef = *yytext;
565   struct eventdef* def = eventdefs + curreventdef;
566   if(!def->is_set) {
567     fprintf(stderr,"Unknown event def char '%c' at line %d\n", curreventdef, yylineno);
568     exit(2);
569   }
571   nevents++;
572   if(nevents > MAX_LINE_EVENTS) {
573     fprintf(stderr,"Too many events defined in one line (> %d), char '%c' at line %d\n",
574             MAX_LINE_EVENTS, curreventdef, yylineno);
575     exit(2);
576   }
577   qn_event_t* evtnew = events + nevents - 1;
578   evtnew->start_tick = currtick;
579   switch(def->type) {
580   case NoteOn:
581     evtnew->data[0] = 0x90; // note on
582     evtnew->data[1] = def->data[0];
583     evtnew->data[2] = def->data[1];
584     break;
585   case ControlChange:
586     evtnew->data[0] = 0xB0; // control change
587     evtnew->data[1] = def->data[0];
588     evtnew->data[2] = def->data[1];
589     break;
590   case ProgramChange:
591     evtnew->data[0] = 0xC0; // program change
592     evtnew->data[1] = def->data[0] - 1; // program 1 should get 0
593     evtnew->data[2] = 0;
594     break;
595   case NoneYet:
596     break;
597   }
599   currtick++;
602 <pv>" " {
603   maybe_note_off();
604   currtick++;
607 <pv>- {
608   currtick++;
611 <pv>\| {
612   maybe_note_off();
613   BEGIN(pvvoicename);
616 <pvvoicename>{ID} {
617   // match to valid voice
618   qn_voice_t* voice = NULL;
619   int i;
620   for(i=0;i<config->nvoices;i++) {
621     qn_voice_t* onevoice = config->voices+i;
622     if(!strcmp(onevoice->name,yytext)) {
623       voice = onevoice;
624     }
625   }
627   if(!voice) {
628     fprintf(stderr,"Line %d must match to a valid voice.  No voice %s exists.\n",yylineno,yytext);
629     exit(2);
630   }
632   qn_pattern_t* newp = config->patterns + config->npatterns - 1;
633   if(!newp->tbm_ticks_per_beat) {
634     fprintf(stderr,"Unable to deduce ticks per beat.  Does your meter line contain a 0-mark?.\n");
635     exit(2);
636   }
638   newp->npatternvoices++;
639   newp->patternvoices =
640     realloc(newp->patternvoices,sizeof(qn_patternvoice_t) * newp->npatternvoices);
642   qn_patternvoice_t* thispv = newp->patternvoices + (newp->npatternvoices - 1);
643   thispv->events = NULL;
644   thispv->nevents = 0;
645   thispv->voice = voice;
646   thispv->nbeats = ((currtick>primary_len) ? primary_len : currtick) / newp->tbm_ticks_per_beat;
648   // copy event buffer to pv
649   thispv->events =
650     realloc(thispv->events,sizeof(qn_event_t)*(thispv->nevents + nevents));
651   memcpy(thispv->events + thispv->nevents, events, nevents*sizeof(qn_event_t));
652   thispv->nevents += nevents;
654   // TODO: sort events by tick then event type?
656   // reset event buffer
657   nevents = 0;
658   curreventdef = '\0';
659   currtick = -prefix_len;
660   BEGIN(pv);
663 [ \t\n] /* ignore whitespace */
665 . { fprintf(stderr,"Bad input character '%s' at line %d\n", yytext, yylineno);
666   exit(2);
669 <<EOF>> {
670   yypop_buffer_state();
671   if ( !YY_CURRENT_BUFFER ) {
672     yyterminate();
673   }
674   }
677 int yywrap() { return 1; }
679 qn_config_t* parse_config(char* filename) {
680   yyin = fopen(filename,"r");
681   config = malloc(sizeof(qn_config_t));
682   qn_init_config(config);
683   if(yylex()) {
684     qn_free_config(config);
685     free(config);
686     config = NULL;
687   }
688   fclose(yyin);
689   yylex_destroy();
690   return config;