diff --git a/muxlicer/functions.ino b/muxlicer/functions.ino index 4e908c7..0cc95d3 100644 --- a/muxlicer/functions.ino +++ b/muxlicer/functions.ino @@ -4,7 +4,8 @@ void read_clock_detect_input () { if (first_no_clock_detect) { first_no_clock_detect = false; clk_in_mult = 0; - write_clock_in_mult_to_EEPROM (); + EEPROM_modified = true; /// defer the write; see service_pending_EEPROM_writes() + EEPROM_counter = current_micros; } } else { @@ -199,14 +200,6 @@ void read_encoder () { } } } - if (EEPROM_modified) { - if (current_micros > EEPROM_counter + 3000000) { - EEPROM_modified = false; - write_tempo_to_EEPROM (); - write_clock_in_mult_to_EEPROM (); - } - } - } @@ -252,11 +245,11 @@ void read_internal_clock_tap () { bitWrite(encoder_button_state, 1, 0); if (range_changed) { range_changed = false; - write_range_to_EEPROM(); + range_write_pending = true; /// defer the write; see service_pending_EEPROM_writes() } if (clk_out_mul_changed) { clk_out_mul_changed = false; - write_clock_out_mult_to_EEPROM(); + clock_out_mult_write_pending = true; } } } @@ -317,6 +310,28 @@ void write_no_clock_when_stop_to_EEPROM () { EEPROM.write(15, no_clock_out_when_stop); } +void service_pending_EEPROM_writes () { + /// EEPROM.write blocks ~3.3 ms per byte; a stall that long delays step, + /// gate and clock out timing, so pending writes are only performed while + /// the sequencer is stopped. + if (start_on) return; + if (EEPROM_modified) { + if (current_micros > EEPROM_counter + 3000000) { + EEPROM_modified = false; + write_tempo_to_EEPROM (); + write_clock_in_mult_to_EEPROM (); + } + } + if (range_write_pending) { + range_write_pending = false; + write_range_to_EEPROM (); + } + if (clock_out_mult_write_pending) { + clock_out_mult_write_pending = false; + write_clock_out_mult_to_EEPROM (); + } +} + void read_start_toggle () { //// START STOP TOGGLE CONTROL @@ -380,12 +395,18 @@ void read_start_toggle () { void read_one_shot_reset_input () { /// RESET CONTROL - reset_state = digitalRead(reset_input); - if ((reset_state == false) && (reset_first == false) ) { /// reset_input jack - - reset_first = true; + bool do_reset = false; + noInterrupts(); + if (reset_edge) { /// edge captured by the pin change ISR + reset_edge = false; + do_reset = true; + } + interrupts(); + if (do_reset) { /// reset_input jack if (start_on) { address_counter = 7; + division_counter = -clk_in_mult; /// re-arm the clock divider so the next clock advances to step 1 right away + gate_out_window = 0; /// hold multiplied steps until the next clock edge re-opens the window if (one_shot_state) one_shot_start = true; } else { @@ -401,9 +422,6 @@ void read_one_shot_reset_input () { division_counter = -clk_in_mult; } } - if ((reset_state == true) && (reset_first == true)) { - reset_first = false; - } } void read_one_shot_reset_toggle () { @@ -413,6 +431,8 @@ void read_one_shot_reset_toggle () { one_shot_first = true; if (start_on) { address_counter = 7; + division_counter = -clk_in_mult; /// re-arm the clock divider so the next clock advances to step 1 right away + gate_out_window = 0; /// hold multiplied steps until the next clock edge re-opens the window if (one_shot_state) one_shot_start = true; } else { @@ -426,8 +446,6 @@ void read_one_shot_reset_toggle () { } division_counter = -clk_in_mult; } - - //division_counter = -clk_in_mult; } if ((digitalRead(one_shot_switch)) && (one_shot_first == true)) { one_shot_first = false; @@ -437,6 +455,12 @@ void read_one_shot_reset_toggle () { void read_address () { ///// ADDRESS CV/pot + /// analogRead blocks for ~112 us; reading once per loop pass dominated + /// the loop time and with it the external clock processing latency. + /// Reading every 2 ms (500 Hz) is still plenty for the address CV. + static unsigned long last_address_read = 0; + if (current_micros - last_address_read < 2000) return; + last_address_read = current_micros; address_value = analogRead(address_input); switch (address_value) { case 0 ... 112: @@ -682,10 +706,32 @@ void read_clock () { current_micros = micros(); if (clock_detect) { - if ((!digitalRead(clock_input)) && (external_clock_first == false)) { /// IF THE CLOCK IS NOT HIGH AND IT IS THE FIRST TIME IT IS, - ext_clock = current_micros - old_external_clock; - old_external_clock = current_micros; - external_clock_first = true; + bool clock_edge = false; + unsigned long clock_edge_stamp = 0; + noInterrupts(); + if (ext_clock_edge) { /// edge captured by the pin change ISR + ext_clock_edge = false; + clock_edge_stamp = ext_clock_edge_stamp; + clock_edge = true; + } + interrupts(); + if (clock_edge) { + unsigned long clock_interval = clock_edge_stamp - old_external_clock; + old_external_clock = clock_edge_stamp; + /// The first edge after the external clock was stopped and restarted + /// (e.g. sequencer stop/play) measures the whole pause instead of the + /// tempo. Adopting that value stalls multiplied steps for a full clock + /// interval and leaves the playhead offset afterwards. Accept an + /// interval as the new period only if it is plausible (shorter than + /// 3x the current period) or confirmed by a second similar interval + /// (a real tempo change); otherwise keep the previous period. + if ((ext_clock == 0) + || (clock_interval < ext_clock * 3) + || ((clock_interval < last_clock_interval + (last_clock_interval >> 2)) + && (last_clock_interval < clock_interval + (last_clock_interval >> 2)))) { + ext_clock = clock_interval; + } + last_clock_interval = clock_interval; if (one_shot_start) { one_shot_start = false; @@ -704,7 +750,7 @@ void read_clock () { } calculate_clock_out (); /// count the tics to create the clock out bearing in mind the clock out multiplier, triggering if (clock_out_mult > 0) { /// CLOCK OUT MULTIPLIER - old_clock_out = current_micros; + old_clock_out = clock_edge_stamp; if( clock_running ){//andyB ADDED CONDITION next_clock_flag = true; //digitalWrite(clock_out, LOW); @@ -722,7 +768,7 @@ void read_clock () { // clock_out_state = HIGH; // - old_clock_out = current_micros; + old_clock_out = clock_edge_stamp; if( clock_running ){//andyB ADDED CONDITION next_clock_flag = true; //digitalWrite(clock_out, LOW); @@ -733,7 +779,7 @@ void read_clock () { } else { //// CLOCK OUT NEUTRAL - old_clock_out = current_micros; + old_clock_out = clock_edge_stamp; if( clock_running ){//andyB ADDED CONDITION next_clock_flag = true; //digitalWrite(clock_out, LOW); @@ -752,9 +798,9 @@ void read_clock () { gate_out_window = ext_clock; next_address_flag = true; next_step_flag = true; - old_micros_mult = current_micros; - gate_counter_old = current_micros; - next_address_stamp = current_micros; + old_micros_mult = clock_edge_stamp; + gate_counter_old = clock_edge_stamp; + next_address_stamp = clock_edge_stamp; repetitions_counter = 0; } else if (clk_in_mult < 0) { @@ -765,9 +811,9 @@ void read_clock () { division_counter = 0; next_address_flag = true; next_step_flag = true; - old_micros_mult = current_micros; - gate_counter_old = current_micros; - next_address_stamp = current_micros; + old_micros_mult = clock_edge_stamp; + gate_counter_old = clock_edge_stamp; + next_address_stamp = clock_edge_stamp; } } else { @@ -775,9 +821,9 @@ void read_clock () { gate_out_window = ext_clock_mult; next_address_flag = true; next_step_flag = true; - old_micros_mult = current_micros; - gate_counter_old = current_micros; - next_address_stamp = current_micros; + old_micros_mult = clock_edge_stamp; + gate_counter_old = clock_edge_stamp; + next_address_stamp = clock_edge_stamp; } } } @@ -847,8 +893,6 @@ void read_clock () { } } } - if ((digitalRead(clock_input)) && (external_clock_first)) external_clock_first = 0; - } void event_control () { diff --git a/muxlicer/muxlicer.ino b/muxlicer/muxlicer.ino index 1279d2d..33fb9a6 100644 --- a/muxlicer/muxlicer.ino +++ b/muxlicer/muxlicer.ino @@ -15,6 +15,32 @@ void timerIsr() { timer1_interrupt_flag = true;// so just flag it needs to be done } +/// External clock edge capture via pin change interrupt (PD4 / PCINT20). +/// The clock input used to be polled in loop(), so edge detection jittered +/// by one loop pass and pulses could be missed entirely during blocking +/// calls (e.g. EEPROM writes). The ISR only stores a timestamp; all +/// processing stays in read_clock(). +volatile bool ext_clock_edge = false; +volatile unsigned long ext_clock_edge_stamp = 0; + +ISR(PCINT2_vect) { + if (!(PIND & (1 << PIND4))) { /// falling edge of the incoming clock (non-inverting input) - same edge the polled code used + ext_clock_edge_stamp = micros(); + ext_clock_edge = true; + } +} + +/// Reset input edge capture via pin change interrupt (PC3 / PCINT11), +/// for the same reason as the external clock above: a polled reset pulse +/// could be missed during blocking calls. +volatile bool reset_edge = false; + +ISR(PCINT1_vect) { + if (!(PINC & (1 << PINC3))) { /// rising edge of the incoming reset (input stage inverts) - same edge the polled code used + reset_edge = true; + } +} + //// PORTS DEFINITION @@ -114,6 +140,7 @@ bool start_stop_down_first = true; unsigned long current_external_clock = 0; unsigned long ext_clock = 0; +unsigned long last_clock_interval = 0; unsigned long ext_clock_mult = 0; unsigned long old_micros_address = 0; unsigned long old_external_clock = 0; @@ -145,9 +172,6 @@ bool second_gate = false; bool no_gate_or_full = false; -bool external_clock_first = false; -bool reset_first = false; -bool reset_state = false; bool one_shot_state = false; bool one_shot_first = false; bool one_shot_start = false; @@ -183,6 +207,8 @@ int no_odd_clock_in_index = 0; bool EEPROM_modified = false; unsigned long EEPROM_counter = 0; +bool range_write_pending = false; +bool clock_out_mult_write_pending = false; bool new_code = false; @@ -232,6 +258,12 @@ void setup() { pinMode (one_shot_switch, INPUT_PULLUP); pinMode (clock_input, INPUT_PULLUP); + /// pin change interrupts for the external clock and reset inputs + PCICR |= (1 << PCIE2); /// enable pin change interrupt group 2 (PORTD) + PCMSK2 |= (1 << PCINT20); /// PD4 = clock_input + PCICR |= (1 << PCIE1); /// enable pin change interrupt group 1 (PORTC) + PCMSK1 |= (1 << PCINT11); /// PC3 (A3) = reset_input + pinMode (encoder_button, INPUT_PULLUP); pinMode (encoder_A, INPUT_PULLUP); @@ -396,7 +428,8 @@ void loop() { gate_delay(); control_clock_out(); gate_to_low_control (); - if(timer1_interrupt_flag){//andyB + service_pending_EEPROM_writes (); + if(timer1_interrupt_flag){//andyB encoder->service();//andyB, not essential to do this, but should help timing timer1_interrupt_flag = false; }