Fix so that comments can go elsewhere in input file
[quincer.git] / src / lex.l
blob864b130432fe17b7ed349d25bab9cf14d2fc0d02
1 %option nodefault warn yylineno
3 %{
4 #include "lex.h"
5 #define MAX_FILENAME_LEN 64
7   int comment_caller;
9   qn_config_t* config = NULL;
10   char filename[MAX_FILENAME_LEN+1];
12   static void start_include(int quoted) {
13     memset(filename,0,MAX_FILENAME_LEN);
14     char* start = yytext;
15     int len = strlen(start);
16     if(quoted) {
17       start += 1;
18       len -= 2;
19     }
20     strncpy(filename,start,
21             (len > MAX_FILENAME_LEN) ? MAX_FILENAME_LEN : len);
22     // fprintf(stderr,"Just lexed a filename: %s\n",filename);
24     yyin = fopen(filename,"r");
25     if(!yyin) {
26       fprintf(stderr,"Couldn't open include file %s\n",filename);
27       exit(2);
28     }
29     yypush_buffer_state(yy_create_buffer(yyin,YY_BUF_SIZE));
30   }
32   // TODO: other channel mode messages:
33   // aftertouch (both kinds), program change, pitch bend
34   enum EventType {
35     NoneYet = -1,
36     NoteOn,
37     Cc
38   };
40   struct eventdef {
41     int is_set;
42     enum EventType type;
43     char data[NBYTES_MIDI-1];
44   };
46 # define MAX_EVENT_DEFS 128
47   struct eventdef eventdefs[MAX_EVENT_DEFS];
49   enum EventType last_event_type = NoneYet;
50   char current_eventdef = '\0';
52   int passed_prefix, prefix_len,
53     beats_per_bar_known, ticks_per_beat_known,
54     passed_primary_content, primary_len, suffix_len;
56   int currtick;
57   unsigned char curreventdef = '\0';
59   /* When lexing a line of events for a pv, we won't know which voice
60      the events are for until the end of the line, so we buffer them
61      until then.  This allows multiple lines to be specified per voice
62      (polyphony, automation, etc) */
63 # define MAX_LINE_EVENTS 1024
64   int nevents = 0;
65   qn_event_t events[MAX_LINE_EVENTS];
66   /* int note_wraps_pv = 0; */
68   static void maybe_note_off() {
69     // Maybe emit a note-off if prior was note-on.
70     // TODO: if last tick is EVENTDEFCHAR, be sure to add the note off there too.
71     if(curreventdef
72        && eventdefs[curreventdef].is_set
73        && eventdefs[curreventdef].type==NoteOn) {
75       nevents++;
76       if(nevents > MAX_LINE_EVENTS) {
77         fprintf(stderr,"Too many events defined in one line (> %d), char '%c' at line %d\n",
78                 MAX_LINE_EVENTS, curreventdef, yylineno);
79         exit(2);
80       }
82       qn_event_t* evtnew = events + nevents - 1;
83       evtnew->start_tick = currtick;
84       evtnew->data[0] = 0x80; // note off
85       evtnew->data[1] = eventdefs[curreventdef].data[0];
87       curreventdef = '\0';
88     }
89   }
93 %s voicename voiceport voicechannel voicenl
94 %s incfilename
95 %s comment
96 %s patname patbpm patswing
97 %s eventdefchar eventdefcharortickdef eventtypeorval eventval1 eventval2
98 %s tickdefchar
99 %s pv pvvoicename
100 %s runspec
102 ID [a-z][a-z0-9]{0,15}
103 CHANNEL 1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16
105 EVENTDEFCHAR [[:print:]]{-}[S0\.\'|\- ]
106 EVENTTYPE note|cc
107 EVENTDEFSHORT [0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-7]
108 RUNSPEC once|loop
111 <INITIAL,pv>voice BEGIN(voicename);
113 <INITIAL>run BEGIN(runspec);
115 <runspec>{RUNSPEC} {
116    if(!strcmp("once",yytext)) {
117      config->next_pattern_func = once_through;
118    } else if(!strcmp("loop",yytext)) {
119      config->next_pattern_func = loop_all;
120    } else {
121       fprintf(stderr,"Invalid run spec %s at line %d\n", yytext, yylineno);
122       exit(2);
123    }
124    BEGIN(INITIAL);
127 <voicename>{ID} {
128   int i;
129   for(i=0; i<config->nvoices; i++) {
130     qn_voice_t* thisv = config->voices + i;
131     if(!strcmp(thisv->name,yytext)) {
132       fprintf(stderr,"Duplicate declaration of voice %s at line %d\n", yytext, yylineno);
133       exit(2);
134     }
135   }
137   config->nvoices++;
138   config->voices = realloc(config->voices, sizeof(qn_voice_t) * config->nvoices);
139   config->voices[config->nvoices-1].name = strndup(yytext,strlen(yytext));
140   BEGIN(voiceport);
143 <voiceport>{ID} {
144   int i;
145   char* existing = NULL;
146   for(i=0; i<config->noutports; i++) {
147     char* thisp = config->outports[i];
148     if(!strcmp(thisp,yytext)) {
149       existing = thisp;
150     }
151   }
153   if(!existing) {
154     config->noutports++;
155     config->outports = realloc(config->outports, sizeof(char*) * config->noutports);
156     config->outports[config->noutports-1] = existing = strndup(yytext,strlen(yytext));
157   }
159   config->voices[config->nvoices-1].portname = existing;
160   BEGIN(voicechannel);
163 <voicechannel>{CHANNEL} {
164   // -1 since channels in input are 1-based but in raw data are
165   // -0-based
166   config->voices[config->nvoices-1].channel = atoi(yytext)-1;
167   BEGIN(voicenl);
170 <voicenl>\n BEGIN(INITIAL);
173 <INITIAL,pv>include BEGIN(incfilename);
175 <incfilename>\".*\" {
176   /* Allow quotes around a filename so that it can have spaces. */
177   start_include(1);
178   BEGIN(INITIAL);
181 <incfilename>[^ \t\n]+ {
182   /* Copy an unquoted filename */
183   start_include(0);
184   BEGIN(INITIAL);
187 # {
188   comment_caller = YY_START;
189   BEGIN(comment);
191 <comment>\n {
192   BEGIN(comment_caller);
194 <comment>.* 
196 <INITIAL,pv>pattern {
197   config->npatterns++;
198   config->patterns = realloc(config->patterns, sizeof(qn_pattern_t) * config->npatterns);
199   qn_pattern_t* newp = config->patterns + config->npatterns - 1;
200   newp->npatternvoices = 0;
201   newp->patternvoices = NULL;
202   newp->swing_value = 0.0;
203   newp->tbm_ticks_per_beat = 0;
204   newp->tbm_beats_per_bar = 0.0;
205   newp->tbm_beat_type = 4;
206   newp->beats_per_minute = 0;
207   newp->bpm_relation = Absolute;
209   passed_prefix = prefix_len = 0;
210   passed_primary_content = primary_len = suffix_len = 0;
211   ticks_per_beat_known = 0;
212   beats_per_bar_known = 0;
214   BEGIN(patbpm);
217 <patbpm>[+-]?[123]?[0-9]{1,2} {
218   qn_pattern_t* newp = config->patterns + config->npatterns - 1;
219   if(*yytext == '+' || *yytext == '-') {
220     newp->bpm_relation = RelativeToBase;
221   }
222   newp->beats_per_minute = atof(yytext);
223   BEGIN(patswing);
226 <patswing>[01]?\.?[0-9]+ {
227   qn_pattern_t* newp = config->patterns + config->npatterns - 1;
228   newp->swing_value = atof(yytext);
230   // initialize note definitions
231   int i;
232   for(i=0; i<MAX_EVENT_DEFS; i++) {
233     eventdefs[i].is_set = 0;
234   }
235   last_event_type = NoneYet;
236   current_eventdef = '\0';
237   BEGIN(eventdefchar);
240 <eventdefchar,eventdefcharortickdef>{EVENTDEFCHAR} {
241   current_eventdef = *yytext;
242   struct eventdef* def = eventdefs + current_eventdef;
244   if(def->is_set) {
245     fprintf(stderr,"Duplicate declaration of note definition %s at line %d\n", yytext, yylineno);
246     exit(2);
247   }
249   def->is_set = 1;
251   BEGIN(eventtypeorval);
254 <eventtypeorval>{EVENTTYPE} {
255   struct eventdef* def = eventdefs + current_eventdef;
256   if(!strcmp("note",yytext)) {
257     def->type = last_event_type = NoteOn;
258   } else if(!strcmp("cc",yytext)) {
259     def->type = last_event_type = Cc;
260   }
261   BEGIN(eventval1);
264 <eventtypeorval,eventval1>{EVENTDEFSHORT} {
265   struct eventdef* def = eventdefs + current_eventdef;
267   // Default to note event if none was specified
268   if(last_event_type==NoneYet) {
269     def->type = last_event_type = NoteOn;
270   }
272   def->data[0] = (char)atoi(yytext);
273   BEGIN(eventval2);
276 <eventval2>{EVENTDEFSHORT} {
277   struct eventdef* def = eventdefs + current_eventdef;
278   def->data[1] = (char)atoi(yytext);
279   BEGIN(eventdefcharortickdef);
282 <eventdefcharortickdef,tickdefchar>\| {
283   if(passed_prefix) {
284     beats_per_bar_known = 1;
285     ticks_per_beat_known = 1;
286     if(passed_primary_content) {
287       suffix_len++;
288     } else {
289       primary_len++;
290     }
291   } else {
292     prefix_len++;
293   }
294   
295   BEGIN(tickdefchar);
298 <eventdefcharortickdef,tickdefchar>0 {
299   qn_pattern_t* newp = config->patterns + config->npatterns - 1;
300   if(newp->tbm_beats_per_bar == 0.0) {
301     newp->tbm_beats_per_bar = 1.0;
302     newp->tbm_ticks_per_beat++;
303   }
304   primary_len = 1;
305   passed_prefix = 1;
306   BEGIN(tickdefchar);
309 <tickdefchar>S {
310   passed_primary_content = 1;
311   suffix_len++;
314 <eventdefcharortickdef,tickdefchar>\x27 { // A single quote '
315   if(passed_prefix) {
316     qn_pattern_t* newp = config->patterns + config->npatterns - 1;
317     if(!beats_per_bar_known) {
318       newp->tbm_beats_per_bar += 1.0;
319     }
320     ticks_per_beat_known = 1;
321     if(passed_primary_content) {
322       suffix_len++;
323     } else {
324       primary_len++;
325     }
326   } else {
327     prefix_len++;
328   }
329   BEGIN(tickdefchar);
332 <eventdefcharortickdef,tickdefchar>\. {
333   if(passed_prefix) {
334     qn_pattern_t* newp = config->patterns + config->npatterns - 1;
335     if(!ticks_per_beat_known) {
336       newp->tbm_ticks_per_beat++;
337     }
338     if(passed_primary_content) {
339       suffix_len++;
340     } else {
341       primary_len++;
342     }
343   } else {
344     prefix_len++;
345   }
346   BEGIN(tickdefchar);
349 <tickdefchar>\n {
350   currtick = -prefix_len;
351   BEGIN(pv);
354 <pv>{EVENTDEFCHAR} {
355   maybe_note_off();
357   curreventdef = *yytext;
358   struct eventdef* def = eventdefs + curreventdef;
359   if(!def->is_set) {
360     fprintf(stderr,"Unknown event def char '%c' at line %d\n", curreventdef, yylineno);
361     exit(2);
362   }
364   nevents++;
365   if(nevents > MAX_LINE_EVENTS) {
366     fprintf(stderr,"Too many events defined in one line (> %d), char '%c' at line %d\n",
367             MAX_LINE_EVENTS, curreventdef, yylineno);
368     exit(2);
369   }
370   qn_event_t* evtnew = events + nevents - 1;
371   evtnew->start_tick = currtick;
372   switch(def->type) {
373   case NoteOn:
374     evtnew->data[0] = 0x90; // note on
375     evtnew->data[1] = def->data[0];
376     evtnew->data[2] = def->data[1];
377     break;
378   case Cc:
379     evtnew->data[0] = 0xB0; // control change
380     evtnew->data[1] = def->data[0];
381     evtnew->data[2] = def->data[1];
382     break;
383   case NoneYet:
384     break;
385   }
387   currtick++;
390 <pv>" " {
391   maybe_note_off();
392   currtick++;
395 <pv>- {
396   currtick++;
399 <pv>\| {
400   maybe_note_off();
401   BEGIN(pvvoicename);
404 <pvvoicename>{ID} {
405   // match to valid voice
406   qn_voice_t* voice = NULL;
407   int i;
408   for(i=0;i<config->nvoices;i++) {
409     qn_voice_t* onevoice = config->voices+i;
410     if(!strcmp(onevoice->name,yytext)) {
411       voice = onevoice;
412     }
413   }
415   if(!voice) {
416     fprintf(stderr,"Line %d must match to a valid voice.  No voice %s exists.\n",yylineno,yytext);
417     exit(2);
418   }
420   qn_pattern_t* newp = config->patterns + config->npatterns - 1;
421   if(!newp->tbm_ticks_per_beat) {
422     fprintf(stderr,"Unable to deduce ticks per beat.  Does your meter line contain a 0-mark?.\n");
423     exit(2);
424   }
426   newp->npatternvoices++;
427   newp->patternvoices =
428     realloc(newp->patternvoices,sizeof(qn_patternvoice_t) * newp->npatternvoices);
430   qn_patternvoice_t* thispv = newp->patternvoices + (newp->npatternvoices - 1);
431   thispv->events = NULL;
432   thispv->nevents = 0;
433   thispv->voice = voice;
434   thispv->nbeats = ((currtick>primary_len) ? primary_len : currtick) / newp->tbm_ticks_per_beat;
436   // copy event buffer to pv
437   thispv->events =
438     realloc(thispv->events,sizeof(qn_event_t)*(thispv->nevents + nevents));
439   memcpy(thispv->events + thispv->nevents, events, nevents*sizeof(qn_event_t));
440   thispv->nevents += nevents;
442   // TODO: sort events by tick then event type?
444   // reset event buffer
445   nevents = 0;
446   curreventdef = '\0';
447   currtick = -prefix_len;
448   BEGIN(pv);
451 [ \t\n] /* ignore whitespace */
453 . { fprintf(stderr,"Bad input character '%s' at line %d\n", yytext, yylineno);
454   exit(2);
457 <<EOF>> {
458   yypop_buffer_state();
459   if ( !YY_CURRENT_BUFFER ) {
460     yyterminate();
461   }
462   }
465 int yywrap() { return 1; }
467 qn_config_t* parse_config(char* filename) {
468   yyin = fopen(filename,"r");
469   config = malloc(sizeof(qn_config_t));
470   qn_init_config(config);
471   if(yylex()) {
472     qn_free_config(config);
473     free(config);
474     config = NULL;
475   }
476   fclose(yyin);
477   yylex_destroy();
478   return config;