1 %option nodefault warn yylineno
6 #define MAX_FILENAME_LEN 64
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);
16 int len = strlen(start);
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");
27 fprintf(stderr,"Couldn't open include file %s\n",filename);
30 yypush_buffer_state(yy_create_buffer(yyin,YY_BUF_SIZE));
33 // TODO: other channel mode messages:
34 // aftertouch (both kinds), program change, pitch bend
45 char data[NBYTES_MIDI-1];
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;
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
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.
74 && eventdefs[curreventdef].is_set
75 && eventdefs[curreventdef].type==NoteOn) {
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);
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];
93 int current_clock_port_index = -1;
94 static int ensure_port(char* name, char** permname) {
96 for(i=0; i<config->noutports; i++) {
97 char* thisp = config->outports[i].name;
98 if(!strcmp(thisp,name)) {
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;
112 /* Get the MIDI pitch if this was in octave four according to
113 https://en.wikipedia.org/wiki/Scientific_pitch_notation
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';
194 // Shouldn't happen since token regex should only match correct vals
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;
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;
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) {
230 } else { // test_pitch < last_midi_pitch
231 while(last_midi_pitch - test_pitch >= 6
232 && CHAR_MAX-12 >= test_pitch) {
241 %s voicename voiceport voicechannel voicenl
244 %s patname patbpm patswing
245 %s eventdefchar eventdefcharortickdef eventtypeorval eventval1 eventval1progchange eventval2
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\.\'|\- ]
256 EVENTDEFSHORT [0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-7]
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);
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;
273 fprintf(stderr,"Invalid run spec %s at line %d\n", yytext, yylineno);
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);
290 config->voices = realloc(config->voices, sizeof(qn_voice_t) * config->nvoices);
291 config->voices[config->nvoices-1].name = strndup(yytext,strlen(yytext));
297 int portindex = ensure_port(yytext, &permname);
299 config->voices[config->nvoices-1].portname = permname;
303 <voicechannel>{CHANNEL} {
304 // -1 since channels in input are 1-based but in raw data are
306 config->voices[config->nvoices-1].channel = atoi(yytext)-1;
310 <voicenl>\n BEGIN(INITIAL);
312 <INITIAL,pv>clock BEGIN(clockport);
316 current_clock_port_index = ensure_port(yytext, &permname);
320 <clocknum>[1-9][0-9]* {
321 config->outports[current_clock_port_index].clock_numerator = atoi(yytext);
325 <clockden>[1-9][0-9]* {
326 config->outports[current_clock_port_index].clock_denominator = atoi(yytext);
327 current_clock_port_index = -1;
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. */
341 <incfilename>[^ \t\n]+ {
342 /* Copy an unquoted filename */
348 comment_caller = YY_START;
353 BEGIN(comment_caller);
357 <INITIAL,pv>pattern {
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;
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;
383 newp->beats_per_minute = atof(yytext);
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
393 for(i=0; i<MAX_EVENT_DEFS; i++) {
394 eventdefs[i].is_set = 0;
396 last_event_type = NoneYet;
397 current_eventdef = '\0';
401 <eventdefchar,eventdefcharortickdef>{EVENTDEFCHAR} {
402 current_eventdef = *yytext;
403 struct eventdef* def = eventdefs + current_eventdef;
406 fprintf(stderr,"Duplicate declaration of note definition %s at line %d\n", yytext, yylineno);
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;
420 } else if(!strcmp("cc",yytext)) {
421 def->type = last_event_type = ControlChange;
423 } else if(!strcmp("pc",yytext)) {
424 def->type = last_event_type = ProgramChange;
425 BEGIN(eventval1progchange);
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;
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);
443 def->data[0] = last_midi_pitch = parse_result;
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;
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);
461 def->data[0] = last_midi_pitch = parse_result;
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;
473 def->data[0] = last_midi_pitch = (char)atoi(yytext);
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>\| {
491 beats_per_bar_known = 1;
492 ticks_per_beat_known = 1;
493 if(passed_primary_content) {
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++;
517 passed_primary_content = 1;
521 <eventdefcharortickdef,tickdefchar>\x27 { // A single quote '
523 qn_pattern_t* newp = config->patterns + config->npatterns - 1;
524 if(!beats_per_bar_known) {
525 newp->tbm_beats_per_bar += 1.0;
527 ticks_per_beat_known = 1;
528 if(passed_primary_content) {
539 <eventdefcharortickdef,tickdefchar>\. {
541 qn_pattern_t* newp = config->patterns + config->npatterns - 1;
542 if(!ticks_per_beat_known) {
543 newp->tbm_ticks_per_beat++;
545 if(passed_primary_content) {
557 currtick = -prefix_len;
564 curreventdef = *yytext;
565 struct eventdef* def = eventdefs + curreventdef;
567 fprintf(stderr,"Unknown event def char '%c' at line %d\n", curreventdef, yylineno);
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);
577 qn_event_t* evtnew = events + nevents - 1;
578 evtnew->start_tick = currtick;
581 evtnew->data[0] = 0x90; // note on
582 evtnew->data[1] = def->data[0];
583 evtnew->data[2] = def->data[1];
586 evtnew->data[0] = 0xB0; // control change
587 evtnew->data[1] = def->data[0];
588 evtnew->data[2] = def->data[1];
591 evtnew->data[0] = 0xC0; // program change
592 evtnew->data[1] = def->data[0] - 1; // program 1 should get 0
617 // match to valid voice
618 qn_voice_t* voice = NULL;
620 for(i=0;i<config->nvoices;i++) {
621 qn_voice_t* onevoice = config->voices+i;
622 if(!strcmp(onevoice->name,yytext)) {
628 fprintf(stderr,"Line %d must match to a valid voice. No voice %s exists.\n",yylineno,yytext);
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");
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;
645 thispv->voice = voice;
646 thispv->nbeats = ((currtick>primary_len) ? primary_len : currtick) / newp->tbm_ticks_per_beat;
648 // copy event buffer to pv
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
659 currtick = -prefix_len;
663 [ \t\n] /* ignore whitespace */
665 . { fprintf(stderr,"Bad input character '%s' at line %d\n", yytext, yylineno);
670 yypop_buffer_state();
671 if ( !YY_CURRENT_BUFFER ) {
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);
684 qn_free_config(config);