From f67efe95645ddad96deaaabc67b5a89fbe4cbcf3 Mon Sep 17 00:00:00 2001 From: Erik Mackdanz Date: Fri, 22 Sep 2017 01:37:20 +0000 Subject: [PATCH] Implement MIDI clock Squashed commit of the following: commit 68f45a23cef15b544dc1559b72c8e28f7bd4aa3e Author: Erik Mackdanz Date: Fri Sep 22 01:33:50 2017 +0000 Update README with clock usage commit 49f20cfae37f0ad70a18809443b88ffe054d0da0 Author: Erik Mackdanz Date: Mon Sep 18 23:37:23 2017 +0000 Support sending MIDI start before next MIDI clock (was concurrent) commit 82023cb5f6052153084c7b894f20ac99f6a488a5 Author: Erik Mackdanz Date: Wed Sep 13 03:09:01 2017 +0000 Fix segfaults at end-of-song commit 0d0125c8492d915a11221a061481a432bc8a7b78 Author: Erik Mackdanz Date: Wed Sep 13 02:50:26 2017 +0000 Fixes for clock drift commit fe891f24ce27026e09c4b03dc3beb9ed5236260d Author: Erik Mackdanz Date: Sat Sep 9 18:21:20 2017 +0000 Sync first pattern note to next clock beat, remove slow-sync client commit 0ccbc88d19863f75c603439e20c1fc216000fa6a Author: Erik Mackdanz Date: Sat Sep 9 01:31:41 2017 +0000 Implement as slow-sync client commit 47e5f925f50724863bec82a2b3cebd6145a8fa25 Author: Erik Mackdanz Date: Fri Sep 8 02:56:37 2017 +0000 Remove some spurious printf's commit f9b8f01d4f572ca919a06dfb965565d3a24ef0ed Author: Erik Mackdanz Date: Thu Sep 7 22:17:16 2017 +0000 On play message, wait to start until next tick commit de4d8236a47263714b9f9f3cd86fba00ec32c649 Author: Erik Mackdanz Date: Thu Sep 7 02:26:22 2017 +0000 Fix clock tick interval, also ensure midi messages declare their size commit ca5e9a59eafe6c9d9b4ee99d82082ec9544f0fcd Author: Erik Mackdanz Date: Wed Sep 6 03:42:41 2017 +0000 Clock emitted before transport starts Getting closer, but clock tempo exceeds song tempo for some reason (song tempo may be too slow) commit 3e74495c4f2dade7b039234b4e82340910f4584f Author: Erik Mackdanz Date: Mon Sep 4 03:48:20 2017 +0000 Fix a timing shift by filtering end-of-pattern-really-next-pattern events commit f06a5238ec2894c084748d5ee7287ee560f8e1c1 Author: Erik Mackdanz Date: Sun Sep 3 23:40:05 2017 +0000 Add parsing of clock definition, wrap config port commit 18bf009c1774a08a683f850c6bc92e079499af78 Author: Erik Mackdanz Date: Sun Sep 3 20:52:19 2017 +0000 Wrap jack port in a type that contains clock numerator/denominator commit 091369ca14f75e9c32c259206086d1c162b993b8 Author: Erik Mackdanz Date: Sun Sep 3 17:46:06 2017 +0000 Fix for clock signal, it kind of works! commit 2a60e79b31f4c86dc6bb0499ba6d368cf24ae2ef Author: Erik Mackdanz Date: Sun Sep 3 17:32:51 2017 +0000 Draft of sending clock signal Unverified, hard-coded, seems not to work --- README | 52 +++++++++-- debug.gdb | 38 ++++++++ src/context.c | 40 ++++++--- src/context.h | 23 ++++- src/lex.l | 58 +++++++++---- src/realtime.c | 270 +++++++++++++++++++++++++++++++++++++++------------------ 6 files changed, 364 insertions(+), 117 deletions(-) create mode 100644 debug.gdb diff --git a/README b/README index 73c62fd..da1d318 100644 --- a/README +++ b/README @@ -1,6 +1,6 @@ quincer -quincer is a sequencer for live performance of composed works. +quincer will be a sequencer for live performance of composed works. Features ======== @@ -13,15 +13,18 @@ Currently implemented: - Input files can include other input files - Ticks-per-beat and beats-per-bar are not constrained (though they must be constant within a pattern) +- can emit MIDI clock to drive external sequencers/effects +- MIDI clock specified at flexible multiples/divisions + of the JACK clock +- MIDI clock multiples/divisions are unique per output +- run looped or once through - pure JACK (and is a timebase master) Planned features, more imminent items first: -- Song mode - Per-pattern swing setting - Base tempo can be altered at runtime via MIDI CC - Jump controls - use MIDI input to skip around (song mode) - prefix/suffix events mask events from main pattern -- optional MIDI clock output for driving hardware synths Rationale ========= @@ -161,11 +164,24 @@ This allows you as many output ports as you need, without requiring a separate view of the note events for each port like in many sequencers. +This is also good for a device like the Korg Volca Sample. To play +samples 1-10 on that device, notes must be sent to channels 1-10. +This isn't difficult in MIDI but almost no sequencers support that +kind of multi-channel output, to the point that a custom +channel-splitting cable was devised just for the Sample! No need +custom cables if you use quincer. + Multiple patterns ----------------- A new pattern declaration can follow the voice lines, as many patterns -as you need. Until a song mode is implemented, the behavior will be -to play from top to bottom and start again with the first pattern. +as you need. + +A directive at the top 'run once' will cause the sequencer to play +top-to-bottom then stop. + +You could otherwise specify 'run loop' at the top to cause the +sequencer to start over after it has reached the bottom. This is the +default behavior if no 'run ...' directive was specified. Comments -------- @@ -219,6 +235,32 @@ should be omitted. Today, all the events just play over each other. When masking is implemented, this will allow for affixes that represent breaks (silence). +MIDI clock +---------- +If a line is specified at the beginning like: + +clock vsamp 1 1 + +...then MIDI clock messages (including start & stop) will be emitted +to the output 'vsamp'. This allows you to use external sequencers, +whose clocks will be synced to quincer's tempo. In addition to +sequencers, some other kinds of devices can use this information +(synths can set LFO, or delay effects like Eventide Timefactor can set +tap tempo). + +The '1 1' is the numerator and denominator, which allow you to stretch +or compress the emitted clock signals in a flexible way. Specifying +'2 1' with quincer's BPM set at 120 will cause clock to be emitted at +60 BPM. '4 1' would emit clock at 30 BPM, and '1 2' would emit clock +at 240 BPM. It is supported to specify strange (integer) values such +as '3 2', which would emit clock at 80 BPM against quincer's internal +120 BPM. + +Notice that clock is specified per output. I could emit clock to a +Volca Sample device at '1 1' (matching quincer's tempo) but also emit +at '2 1' to a Volca Keys device which would run its sequence at half +the tempo. + License ======= Copyright (C) 2016 Erik Mackdanz diff --git a/debug.gdb b/debug.gdb new file mode 100644 index 0000000..2dece36 --- /dev/null +++ b/debug.gdb @@ -0,0 +1,38 @@ +set pagination off + +dprintf accumulate_pattern_events:increment_nout," event %d at %d (%d - %d + (%d - %d)), pitch %d, playhead at %d, didnt break because %d + %d < %d\n",k,outev->at,evtime,pv->playhead_tmp[evtset],fill_last_nframes_orig,fill_last_nframes_in_pv,outev->data[1],pv->playhead[CURRENT],range_start,evtime,pv_len +cond 1 ((ev.data[0] & 0xf0) == 0x90) && (evtset==CURRENT) + +dprintf accumulate_clock_events:clock_ticked," clock beat at %d\n",next_tick +cond 2 is_beat_start + +# dprintf process:pre_accum_events,"process, awaiting %d\n",run->awaiting_common_start_beat +# cond 3 (i==0) + +# dprintf accumulate_pattern_events:looping_event_candidates," event %d, if %d < %d < %d\n",k,range_start,evtime,range_end +# cond 3 (evtset==CURRENT) && (k==6) + +# dprintf accumulate_pattern_events:maybe_break_event," skip event if %d + %d >= %d\n",range_start,evtime,pv_len +# cond 4 ((ev.start_tick % 4) == 0) && ((ev.data[0] & 0xf0) == 0x90) + +# dprintf set_playheads:set_playheads_zero," set_playheads_zero\n" +# cond 4 (evtset == CURRENT) && (k == 0) + +# dprintf set_playheads:set_playheads_value," set_playheads_value %d\n",pv->playhead_tmp[evtset] +# cond 5 (evtset == CURRENT) && (k == 0) + +# dprintf accumulate_pattern_events:accum_one_pv," %d < range < %d, pv_end %d\n",pv->playhead_tmp[CURRENT],pv->playhead_tmp[CURRENT]+fill_last_nframes, pv_end +# cond 4 (evtset==CURRENT) + +# dprintf accumulate_pattern_events:save_out_nframes," fill_last_nframes_in_pv %d\n",fill_last_nframes_in_pv +# cond 7 (evtset==CURRENT) + +# dprintf accumulate_pattern_events:inner_loop_end," fill_last_nframes %d til_curr_pattern_end %d\n",fill_last_nframes,til_curr_pattern_end +# cond 7 (evtset==CURRENT) + +# b accumulate_pattern_events:inner_loop_end if (evtset==CURRENT) && (fill_last_nframes > til_curr_pattern_end) + +# dprintf accumulate_pattern_events:pv_no_wrap," no_wrap\n" +# cond 8 (evtset==CURRENT) + +run work.qn \ No newline at end of file diff --git a/src/context.c b/src/context.c index ef9f62a..0808d90 100644 --- a/src/context.c +++ b/src/context.c @@ -41,8 +41,10 @@ qn_config_t* qn_get_fake_config() { config->noutports = 1; char* portname = "fluidsynth"; - config->outports = malloc(sizeof(char*) * config->noutports); - config->outports[0] = strndup(portname,strlen(portname)); + config->outports = malloc(sizeof(qn_config_port_t) * config->noutports); + config->outports[0].name = strndup(portname,strlen(portname)); + config->outports[0].clock_numerator = 0; + config->outports[0].clock_denominator = 0; config->nvoices = 1; config->voices = malloc(sizeof(qn_voice_t) * config->nvoices); @@ -51,8 +53,8 @@ qn_config_t* qn_get_fake_config() { int i; for(i=0; inoutports; i++) { /* fprintf(stderr,"First, does %s equal %s\n","fluidsynth",config->outports[i]); */ - if(!strcmp("fluidsynth",config->outports[i])) { - config->voices[0].portname = config->outports[i]; + if(!strcmp("fluidsynth",config->outports[i].name)) { + config->voices[0].portname = config->outports[i].name; break; } } @@ -184,12 +186,19 @@ qn_context_t* qn_init_context(qn_config_t* config, jack_client_t* client) { ctx->run = malloc(sizeof(qn_runstate_t)); ctx->run->client = client; - ctx->run->ports = malloc(sizeof(jack_port_t*) * config->noutports); + ctx->run->ports = malloc(sizeof(qn_port_t*) * config->noutports); + ctx->run->awaiting_common_start_beat = 0; int i; for(i=0; inoutports; i++) { - ctx->run->ports[i] = - jack_port_register(client, config->outports[i], + ctx->run->ports[i] = malloc(sizeof(qn_port_t)); + ctx->run->ports[i]->jackport = + jack_port_register(client, config->outports[i].name, JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0); + ctx->run->ports[i]->clock_numerator = config->outports[i].clock_numerator; + /* printf("Set num to %d\n",ctx->run->ports[i]->clock_numerator); */ + ctx->run->ports[i]->clock_denominator = config->outports[i].clock_denominator; + ctx->run->ports[i]->clock_next_tick = 0; + ctx->run->ports[i]->clock_tick_index = 0; } // Set the port on each voice @@ -198,7 +207,7 @@ qn_context_t* qn_init_context(qn_config_t* config, jack_client_t* client) { int j; for(j=0; jnoutports; j++) { /* fprintf(stderr,"Does %s equal %s\n",config->outports[j],config->voices[i].portname); */ - if(!strcmp(config->outports[j],config->voices[i].portname)) { + if(!strcmp(config->outports[j].name,config->voices[i].portname)) { config->voices[i].port = ctx->run->ports[j]; found = 1; break; @@ -295,7 +304,7 @@ void qn_free_config(qn_config_t* cfg) { free(cfg->voices); for(i=0; inoutports; i++) { - free(cfg->outports[i]); + free(cfg->outports[i].name); } free(cfg->outports); @@ -323,7 +332,8 @@ void qn_free_context(qn_context_t* ctx) { int i; for(i=0; inoutports; i++) { - jack_port_unregister(ctx->run->client, ctx->run->ports[i]); + jack_port_unregister(ctx->run->client, ctx->run->ports[i]->jackport); + free(ctx->run->ports[i]); } free(ctx->run->ports); free(ctx->run); @@ -338,11 +348,21 @@ int cmp_outevent(const void* a, const void* b) { const qn_outevent_t* ea = a; const qn_outevent_t* eb = b; + // early message before late ones if(ea->at < eb->at) { return -1; } else if(ea->at > eb->at) { return 1; } + // real-time (clock) messages over channel messages + int as = ((ea->data[0] & 0xf8) == 0xf8); + int bs = ((eb->data[0] & 0xf8) == 0xf8); + if(as && !bs) { + return -1; + } else if(!as && bs) { + return 1; + } + return 0; } diff --git a/src/context.h b/src/context.h index c56c024..9aa0778 100644 --- a/src/context.h +++ b/src/context.h @@ -4,17 +4,32 @@ #include typedef struct { + jack_port_t* jackport; + int clock_numerator; + int clock_denominator; + jack_nframes_t clock_next_tick; + int clock_tick_index; +} qn_port_t; + +typedef struct { + char* name; + int clock_numerator; + int clock_denominator; +} qn_config_port_t; + +typedef struct { char* name; char* portname; int channel; - jack_port_t* port; // NULL at parse time, set at init time + qn_port_t* port; // NULL at parse time, set at init time } qn_voice_t; #define NBYTES_MIDI 3 // Used to sort all the output events for a port typedef struct { jack_nframes_t at; + int szdata; char data[NBYTES_MIDI]; } qn_outevent_t; @@ -80,7 +95,7 @@ qn_pattern_t* loop_all(void* ctx); typedef struct { int noutports; - char** outports; + qn_config_port_t* outports; int nvoices; qn_voice_t* voices; @@ -94,7 +109,7 @@ typedef struct { typedef struct { jack_client_t* client; - jack_port_t** ports; + qn_port_t** ports; int base_beats_per_minute; // current, user can change real-time qn_pattern_t* prev; @@ -110,6 +125,8 @@ typedef struct { int tbm_tick; double tbm_bar_start_tick; jack_nframes_t frame_in_tick; + + int awaiting_common_start_beat; } qn_runstate_t; typedef struct { diff --git a/src/lex.l b/src/lex.l index 864b130..7e93b89 100644 --- a/src/lex.l +++ b/src/lex.l @@ -88,6 +88,25 @@ } } + int current_clock_port_index = -1; + static int ensure_port(char* name, char** permname) { + int i; + for(i=0; inoutports; i++) { + char* thisp = config->outports[i].name; + if(!strcmp(thisp,name)) { + *permname = thisp; + return i; + } + } + + config->noutports++; + config->outports = realloc(config->outports, sizeof(qn_config_port_t) * config->noutports); + config->outports[config->noutports-1].name = *permname = strndup(name,strlen(yytext)); + config->outports[config->noutports-1].clock_numerator = 0; + config->outports[config->noutports-1].clock_denominator = 0; + return config->noutports-1; + } + %} %s voicename voiceport voicechannel voicenl @@ -98,6 +117,7 @@ %s tickdefchar %s pv pvvoicename %s runspec +%s clockport clocknum clockden clocknl ID [a-z][a-z0-9]{0,15} CHANNEL 1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16 @@ -141,22 +161,10 @@ RUNSPEC once|loop } {ID} { - int i; - char* existing = NULL; - for(i=0; inoutports; i++) { - char* thisp = config->outports[i]; - if(!strcmp(thisp,yytext)) { - existing = thisp; - } - } + char* permname; + int portindex = ensure_port(yytext, &permname); - if(!existing) { - config->noutports++; - config->outports = realloc(config->outports, sizeof(char*) * config->noutports); - config->outports[config->noutports-1] = existing = strndup(yytext,strlen(yytext)); - } - - config->voices[config->nvoices-1].portname = existing; + config->voices[config->nvoices-1].portname = permname; BEGIN(voicechannel); } @@ -169,6 +177,26 @@ RUNSPEC once|loop \n BEGIN(INITIAL); +clock BEGIN(clockport); + +{ID} { + char* permname; + current_clock_port_index = ensure_port(yytext, &permname); + BEGIN(clocknum); + } + +[1-9][0-9]* { + config->outports[current_clock_port_index].clock_numerator = atoi(yytext); + BEGIN(clockden); + } + +[1-9][0-9]* { + config->outports[current_clock_port_index].clock_denominator = atoi(yytext); + current_clock_port_index = -1; + BEGIN(clocknl); + } + +\n BEGIN(INITIAL); include BEGIN(incfilename); diff --git a/src/realtime.c b/src/realtime.c index 786292a..b0fd243 100644 --- a/src/realtime.c +++ b/src/realtime.c @@ -43,6 +43,7 @@ static void set_playheads(qn_context_t* ctx, int pattern_advanced, enum eventset // block will overwrite the 0 with playhead_tmp for(k=0;knpatternvoices;k++) { qn_patternvoice_t* pv = start_pattern->patternvoices + k; + set_playheads_zero: pv->playhead[evtset] = 0; } } @@ -51,16 +52,89 @@ static void set_playheads(qn_context_t* ctx, int pattern_advanced, enum eventset if(newest_pattern) { for(k=0;knpatternvoices;k++) { qn_patternvoice_t* pv = newest_pattern->patternvoices + k; + set_playheads_value: pv->playhead[evtset] = pv->playhead_tmp[evtset]; } } + +} + +static void accumulate_clock_events(qn_context_t* ctx, qn_port_t* port, + int* nout, qn_outevent_t* out, + jack_nframes_t fill_last_nframes, + int* start_message_sent) { + + if(!port->clock_numerator) return; + + jack_nframes_t next_tick = port->clock_next_tick; + if(next_tick >= fill_last_nframes) { + port->clock_next_tick = next_tick - fill_last_nframes; + return; + } + + // We have a clock event to emit + qn_outevent_t* outev = out + *nout; + outev->at = next_tick; + outev->data[0] = 0xf8; // clock message + outev->szdata = 1; + *nout = *nout+1; + + qn_runstate_t* run = ctx->run; + jack_transport_state_t transport + = jack_transport_query(run->client, NULL); + + // Re-calculate clock_next_tick + int curr_bpm = get_effective_bpm(run->curr,run->base_beats_per_minute); + int curr_beat_len = (run->sample_rate * 60) / curr_bpm; + int is_beat_start = (port->clock_tick_index == 0); + + // Compensate for rounding errors + jack_nframes_t tick_interval = + (curr_beat_len * port->clock_numerator) / + (24 * port->clock_denominator); + + port->clock_tick_index++; + if(port->clock_tick_index == 24) { + port->clock_tick_index = 0; + jack_nframes_t round_error = (curr_beat_len * port->clock_numerator) - (tick_interval * 24 * port->clock_denominator); + next_tick += round_error; + } + + if(transport == JackTransportStopped) { + run->awaiting_common_start_beat = 1; + } else if(transport == JackTransportRolling && + is_beat_start && + run->awaiting_common_start_beat) { + run->awaiting_common_start_beat = 0; + + // send start message + outev = out + *nout; + outev->at = 0; //next_tick; + outev->data[0] = 0xfa; // start message + outev->szdata = 1; + *nout = *nout+1; + + // inform caller of the event + *start_message_sent = 1; + + // set all playheads to -next_tick + int i; + for(i=0; icurr->npatternvoices; i++) { + qn_patternvoice_t* pv = run->curr->patternvoices + i; + pv->playhead_tmp[CURRENT] = pv->playhead[CURRENT] = -next_tick; + } + } + + clock_ticked: + port->clock_next_tick = tick_interval - (fill_last_nframes - next_tick); } -static void accumulate_pattern_events(qn_context_t* ctx, jack_port_t* port, +static void accumulate_pattern_events(qn_context_t* ctx, qn_port_t* port, qn_pattern_t* pat, int* nout, qn_outevent_t* out, jack_nframes_t fill_last_nframes, int* pattern_advanced, enum eventset evtset) { - if(!pat) return; + + jack_nframes_t fill_last_nframes_orig = fill_last_nframes; qn_runstate_t* run = ctx->run; // If we straddle two patterns, the latter one will only @@ -70,6 +144,10 @@ static void accumulate_pattern_events(qn_context_t* ctx, jack_port_t* port, // (pat). If we advance to a new pattern, we advance this loop and // reduce fill_last_nframes. + int pattern_advanced_now = 0; + + if(!pat) return; + int k; for(k=0;knpatternvoices;k++) { qn_patternvoice_t* pv = pat->patternvoices + k; @@ -126,11 +204,6 @@ static void accumulate_pattern_events(qn_context_t* ctx, jack_port_t* port, int range_start = pv->playhead_tmp[evtset]; int range_end = range_start + fill_last_nframes; - // Don't collect suffix events in current pattern - if(fill_last_nframes > til_curr_pattern_end) { - range_end = range_start + til_curr_pattern_end; - } - int maybe_more_loops = 1; while(maybe_more_loops) { if(pv->voice->port == port) { @@ -144,6 +217,8 @@ static void accumulate_pattern_events(qn_context_t* ctx, jack_port_t* port, int evtime = (this_beat_len * ev.start_tick) / pat->tbm_ticks_per_beat; + looping_event_candidates: + // Enforce some space between note off and subsequent // note on, otherwise the later note may not sound // articulated @@ -154,8 +229,8 @@ static void accumulate_pattern_events(qn_context_t* ctx, jack_port_t* port, evtime += pv_len; } } -# define MAX_OUT_EVENTS 1024 +# define MAX_OUT_EVENTS 1024 if(evtime >= range_start && evtime < range_end && *nout < MAX_OUT_EVENTS) { @@ -164,16 +239,18 @@ static void accumulate_pattern_events(qn_context_t* ctx, jack_port_t* port, // time in the out event must be indexed // with 0 being the start of nframes - outev->at = evtime - pv->playhead_tmp[evtset]; + outev->at = evtime - pv->playhead_tmp[evtset] + + (fill_last_nframes_orig - fill_last_nframes_in_pv); outev->data[0] = ev.data[0]; outev->data[1] = ev.data[1]; outev->data[2] = ev.data[2]; switch(ev.data[0] & 0xf0) { - case 0x90: // note on case 0x80: // note off + case 0x90: // note on case 0xb0: // control change + outev->szdata = 3; outev->data[0] |= pv->voice->channel; break; } @@ -181,7 +258,7 @@ static void accumulate_pattern_events(qn_context_t* ctx, jack_port_t* port, increment_nout: *nout = *nout+1; } - } + } // loop events } // if(pv->voice->port == port) inner_loop_end: @@ -192,7 +269,7 @@ static void accumulate_pattern_events(qn_context_t* ctx, jack_port_t* port, // This is the end of the pattern, and // We're straddling two patterns maybe_more_loops = 0; - *pattern_advanced = 1; + *pattern_advanced = pattern_advanced_now = 1; } else { // Loop this patternvoice because there's // still a longer one in progress @@ -206,6 +283,7 @@ static void accumulate_pattern_events(qn_context_t* ctx, jack_port_t* port, } else { // Simple case, this patternvoice didn't wrap maybe_more_loops = 0; + pv_no_wrap: fill_last_nframes_in_pv = 0; if(evtset==CURRENT) { pv->playhead_tmp[evtset] = range_end; @@ -214,19 +292,26 @@ static void accumulate_pattern_events(qn_context_t* ctx, jack_port_t* port, } // while(maybe_more_loops) } // loop patternvoices - save_out_nframes: - fill_last_nframes = fill_last_nframes_in_pv; - - if(*pattern_advanced && evtset==CURRENT) { - pat = ctx->next_pattern_func(ctx); - + if(*pattern_advanced && evtset == CURRENT) { + int playhead_target; + if(pattern_advanced_now) { + pat = ctx->next_pattern_func(ctx); + playhead_target = 0; + } else { + playhead_target = fill_last_nframes; + } + if(pat) { for(k=0;knpatternvoices;k++) { qn_patternvoice_t* pv = pat->patternvoices + k; - pv->playhead_tmp[evtset] = 0; + pv->playhead_tmp[evtset] = playhead_target; } } } + + save_out_nframes: + fill_last_nframes = fill_last_nframes_in_pv; + } // while(fill_last_nframes) } @@ -238,19 +323,21 @@ int process(jack_nframes_t nframes, void *arg) { // between calls int i; for(i=0; iconfig->noutports; i++) { - jack_port_t* port = run->ports[i]; - void* port_buf = jack_port_get_buffer( port, nframes); + qn_port_t* port = run->ports[i]; + void* port_buf = jack_port_get_buffer( port->jackport, nframes); jack_midi_clear_buffer(port_buf); } jack_transport_state_t transport = jack_transport_query(run->client, NULL); - // If stopping, send "All Notes Off" MIDI message + // If a change in transport state if(transport != run->transtate) { run->transtate = transport; + + // If stopping, send "All Notes Off" MIDI message if(transport == JackTransportStopped) { for(i=0; iconfig->noutports; i++) { - jack_port_t* port = run->ports[i]; + qn_port_t* port = run->ports[i]; int channels_encountered[16]; int ce; @@ -258,10 +345,18 @@ int process(jack_nframes_t nframes, void *arg) { channels_encountered[ce] = 0; } - void* port_buf = jack_port_get_buffer( port, nframes); + void* port_buf = jack_port_get_buffer( port->jackport, nframes); jack_midi_clear_buffer(port_buf); int j; + unsigned char val[NBYTES_MIDI]; + if(port->clock_numerator) { // if port is receiving clock messages + val[0] = 0xfc; // clock stop + if(jack_midi_event_write(port_buf,0,val,1)) { + printf("Couldn't reserve space for a clock stop event.\n"); + } + } + for(j=0; jconfig->nvoices; j++) { qn_voice_t* voice = ctx->config->voices + j; if(voice->port == port) { @@ -271,7 +366,6 @@ int process(jack_nframes_t nframes, void *arg) { channels_encountered[voice->channel] = 1; - unsigned char val[NBYTES_MIDI]; val[0] = (176 | voice->channel); val[1] = 123; val[2] = 0; @@ -280,19 +374,19 @@ int process(jack_nframes_t nframes, void *arg) { } } } + } } } - if(transport != JackTransportRolling) { - return 0; - } + if(!run->curr) return 0; int pattern_advanced = 0; + int start_message_sent = 0; // Write a list of output events, grouped by output port for(i=0; iconfig->noutports; i++) { - jack_port_t* port = run->ports[i]; + qn_port_t* port = run->ports[i]; // We can't write events to the actual port_buf yet since they // would be out of order. Use a fixed buffer for our intermediate @@ -300,28 +394,34 @@ int process(jack_nframes_t nframes, void *arg) { int nout = 0; qn_outevent_t out[MAX_OUT_EVENTS]; - accumulate_pattern_events(ctx,port,ctx->next_pattern_func(ctx), - &nout,out,nframes,&pattern_advanced, PREFIX); - accumulate_pattern_events(ctx,port,run->prev, - &nout,out,nframes,&pattern_advanced, SUFFIX); - accumulate_pattern_events(ctx,port,run->curr, - &nout,out,nframes,&pattern_advanced, CURRENT); + pre_accum_events: + accumulate_clock_events(ctx,port,&nout,out,nframes,&start_message_sent); + + if(transport == JackTransportRolling && !run->awaiting_common_start_beat) { + accumulate_pattern_events(ctx,port,ctx->next_pattern_func(ctx), + &nout,out,nframes,&pattern_advanced, PREFIX); + accumulate_pattern_events(ctx,port,run->prev, + &nout,out,nframes,&pattern_advanced, SUFFIX); + accumulate_pattern_events(ctx,port,run->curr, + &nout,out,nframes,&pattern_advanced, CURRENT); + } if(nout) { qsort(out,nout,sizeof(qn_outevent_t),cmp_outevent); - void* port_buf = jack_port_get_buffer( port, nframes); + void* port_buf = jack_port_get_buffer( port->jackport, nframes); jack_midi_clear_buffer(port_buf); jack_midi_data_t* buffer; int j; for(j=0; jat, NBYTES_MIDI); + buffer = jack_midi_event_reserve(port_buf, evt->at, evt->szdata); if(buffer) { - memcpy(buffer,evt->data,NBYTES_MIDI); + memcpy(buffer,evt->data,evt->szdata); } else { printf("Couldn't send event, probably the time %d is wrong.\n", evt->at); @@ -330,59 +430,61 @@ int process(jack_nframes_t nframes, void *arg) { } } // loop noutports - set_playheads(ctx,pattern_advanced,PREFIX); - set_playheads(ctx,pattern_advanced,SUFFIX); - set_playheads(ctx,pattern_advanced,CURRENT); - - // Calculate bar/beat/tick since we are a jack timebase master. - // It's easier to do this before we advance the pattern than - // to do it in timebase() where we have to look backwards. - jack_nframes_t frames_left = nframes; - qn_pattern_t* pat = run->curr; - while(frames_left && pat) { - int bpm = get_effective_bpm(pat,run->base_beats_per_minute); - jack_nframes_t tick_len = (run->sample_rate * 60) / (bpm * pat->tbm_ticks_per_beat); - - run->frame_in_tick += frames_left; - if(run->frame_in_tick > tick_len) { - // We advanced a tick - run->tbm_bar_start_tick++; - run->tbm_tick++; - if(run->tbm_tick > pat->tbm_ticks_per_beat) { - // We advanced a beat - run->tbm_tick = 1; - run->tbm_beat++; - - if(run->tbm_beat > pat->tbm_beats_per_bar) { - // We advanced a bar - run->tbm_bar_start_tick = 1; - run->tbm_beat = 1; - run->tbm_bar++; - - if(run->tbm_bar*pat->tbm_beats_per_bar > pat->longest_pv->nbeats) { - // We advanced to the next pattern - pat = ctx->next_pattern_func(ctx); + if(transport == JackTransportRolling && !run->awaiting_common_start_beat) { + set_playheads(ctx,pattern_advanced,PREFIX); + set_playheads(ctx,pattern_advanced,SUFFIX); + set_playheads(ctx,pattern_advanced,CURRENT); + + // Calculate bar/beat/tick since we are a jack timebase master. + // It's easier to do this before we advance the pattern than + // to do it in timebase() where we have to look backwards. + jack_nframes_t frames_left = nframes; + qn_pattern_t* pat = run->curr; + while(frames_left && pat) { + int bpm = get_effective_bpm(pat,run->base_beats_per_minute); + jack_nframes_t tick_len = (run->sample_rate * 60) / (bpm * pat->tbm_ticks_per_beat); + + run->frame_in_tick += frames_left; + if(run->frame_in_tick > tick_len) { + // We advanced a tick + run->tbm_bar_start_tick++; + run->tbm_tick++; + if(run->tbm_tick > pat->tbm_ticks_per_beat) { + // We advanced a beat + run->tbm_tick = 1; + run->tbm_beat++; + + if(run->tbm_beat > pat->tbm_beats_per_bar) { + // We advanced a bar + run->tbm_bar_start_tick = 1; + run->tbm_beat = 1; + run->tbm_bar++; + + if(run->tbm_bar*pat->tbm_beats_per_bar > pat->longest_pv->nbeats) { + // We advanced to the next pattern + pat = ctx->next_pattern_func(ctx); + } } } - } - frames_left -= (run->frame_in_tick - tick_len); - run->frame_in_tick = 0; - } else { - // still in same tick - frames_left = 0; + frames_left -= (run->frame_in_tick - tick_len); + run->frame_in_tick = 0; + } else { + // still in same tick + frames_left = 0; + } } - } - // End BBT calculations + // End BBT calculations - if(pattern_advanced) { - run->prev = run->curr; - run->curr = ctx->next_pattern_func(ctx); + if(pattern_advanced) { + run->prev = run->curr; + run->curr = ctx->next_pattern_func(ctx); - if(run->curr && run->curr->bpm_relation == Absolute) { - run->base_beats_per_minute = run->curr->beats_per_minute; + if(run->curr && run->curr->bpm_relation == Absolute) { + run->base_beats_per_minute = run->curr->beats_per_minute; + } } - } + } // if(transport == JackTransportRolling) return 0; } -- 2.11.4.GIT