1 %option nodefault warn yylineno
5 #define MAX_FILENAME_LEN 64
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);
15 int len = strlen(start);
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");
26 fprintf(stderr,"Couldn't open include file %s\n",filename);
29 yypush_buffer_state(yy_create_buffer(yyin,YY_BUF_SIZE));
32 // TODO: other channel mode messages:
33 // aftertouch (both kinds), program change, pitch bend
43 char data[NBYTES_MIDI-1];
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;
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
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.
72 && eventdefs[curreventdef].is_set
73 && eventdefs[curreventdef].type==NoteOn) {
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);
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];
93 %s voicename voiceport voicechannel voicenl
96 %s patname patbpm patswing
97 %s eventdefchar eventdefcharortickdef eventtypeorval eventval1 eventval2
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\.\'|\- ]
107 EVENTDEFSHORT [0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-7]
111 <INITIAL,pv>voice BEGIN(voicename);
113 <INITIAL>run BEGIN(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;
121 fprintf(stderr,"Invalid run spec %s at line %d\n", yytext, yylineno);
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);
138 config->voices = realloc(config->voices, sizeof(qn_voice_t) * config->nvoices);
139 config->voices[config->nvoices-1].name = strndup(yytext,strlen(yytext));
145 char* existing = NULL;
146 for(i=0; i<config->noutports; i++) {
147 char* thisp = config->outports[i];
148 if(!strcmp(thisp,yytext)) {
155 config->outports = realloc(config->outports, sizeof(char*) * config->noutports);
156 config->outports[config->noutports-1] = existing = strndup(yytext,strlen(yytext));
159 config->voices[config->nvoices-1].portname = existing;
163 <voicechannel>{CHANNEL} {
164 // -1 since channels in input are 1-based but in raw data are
166 config->voices[config->nvoices-1].channel = atoi(yytext)-1;
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. */
181 <incfilename>[^ \t\n]+ {
182 /* Copy an unquoted filename */
188 comment_caller = YY_START;
192 BEGIN(comment_caller);
196 <INITIAL,pv>pattern {
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;
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;
222 newp->beats_per_minute = atof(yytext);
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
232 for(i=0; i<MAX_EVENT_DEFS; i++) {
233 eventdefs[i].is_set = 0;
235 last_event_type = NoneYet;
236 current_eventdef = '\0';
240 <eventdefchar,eventdefcharortickdef>{EVENTDEFCHAR} {
241 current_eventdef = *yytext;
242 struct eventdef* def = eventdefs + current_eventdef;
245 fprintf(stderr,"Duplicate declaration of note definition %s at line %d\n", yytext, yylineno);
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;
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;
272 def->data[0] = (char)atoi(yytext);
276 <eventval2>{EVENTDEFSHORT} {
277 struct eventdef* def = eventdefs + current_eventdef;
278 def->data[1] = (char)atoi(yytext);
279 BEGIN(eventdefcharortickdef);
282 <eventdefcharortickdef,tickdefchar>\| {
284 beats_per_bar_known = 1;
285 ticks_per_beat_known = 1;
286 if(passed_primary_content) {
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++;
310 passed_primary_content = 1;
314 <eventdefcharortickdef,tickdefchar>\x27 { // A single quote '
316 qn_pattern_t* newp = config->patterns + config->npatterns - 1;
317 if(!beats_per_bar_known) {
318 newp->tbm_beats_per_bar += 1.0;
320 ticks_per_beat_known = 1;
321 if(passed_primary_content) {
332 <eventdefcharortickdef,tickdefchar>\. {
334 qn_pattern_t* newp = config->patterns + config->npatterns - 1;
335 if(!ticks_per_beat_known) {
336 newp->tbm_ticks_per_beat++;
338 if(passed_primary_content) {
350 currtick = -prefix_len;
357 curreventdef = *yytext;
358 struct eventdef* def = eventdefs + curreventdef;
360 fprintf(stderr,"Unknown event def char '%c' at line %d\n", curreventdef, yylineno);
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);
370 qn_event_t* evtnew = events + nevents - 1;
371 evtnew->start_tick = currtick;
374 evtnew->data[0] = 0x90; // note on
375 evtnew->data[1] = def->data[0];
376 evtnew->data[2] = def->data[1];
379 evtnew->data[0] = 0xB0; // control change
380 evtnew->data[1] = def->data[0];
381 evtnew->data[2] = def->data[1];
405 // match to valid voice
406 qn_voice_t* voice = NULL;
408 for(i=0;i<config->nvoices;i++) {
409 qn_voice_t* onevoice = config->voices+i;
410 if(!strcmp(onevoice->name,yytext)) {
416 fprintf(stderr,"Line %d must match to a valid voice. No voice %s exists.\n",yylineno,yytext);
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");
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;
433 thispv->voice = voice;
434 thispv->nbeats = ((currtick>primary_len) ? primary_len : currtick) / newp->tbm_ticks_per_beat;
436 // copy event buffer to pv
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
447 currtick = -prefix_len;
451 [ \t\n] /* ignore whitespace */
453 . { fprintf(stderr,"Bad input character '%s' at line %d\n", yytext, yylineno);
458 yypop_buffer_state();
459 if ( !YY_CURRENT_BUFFER ) {
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);
472 qn_free_config(config);