SwampFlux / tri-plus-ger

Alternative firmware for Qu-Bit Tri-ger
GNU General Public License v3.0
3 stars 0 forks source link

Synchronize to Clock Input #5

Closed wraybowling closed 4 years ago

wraybowling commented 4 years ago

Synchronize to Clock Input and allow clock multiply/divide via clock division knob

wraybowling commented 4 years ago

wow. so clock input is hard to do. brain dumping what i think has to happen...

So there's a clock in jack that is triggered when it goes high (above 900/1023). We sort of assume that the space between triggers is the period. (A very nice rolling average function from Mutable Instruments Peaks helps with this). The period that comes back from the smoothing function is the master clock.

In the event that the master clock becomes too slow then the internal clock will kick in. This is due to the face that I cannot use the Tempo CV input jack, which means there are technically two different tempo CV inputs and somehow I have to negotiate between them..

Next, there's an internal clock. if external clock is fast enough, it will have a period of its own. If the external clock is too slow, then we will obey the value of both the Tempo CV input AND tempo knob from the module itself or from a master module if the jumper is set to slave mode. Because all of this ishard wired together, there's literally no way around this.

Okay. So. Next, there is the value that comes from clock division knob. For now, let's assume it controls the clock input division or multiplication. The internal period (which, again, may or may not be external) is either multiplied or divided. Each new pulse advances a counter which will either skip a beat in cases where the period must be multiplied or will be used to choose which fractional step towards the next beat will be when the period must be divided. Each internal pulse will recalculate when the next external pulse should occur based on that simple arithmetic.. i think..

I also keep thinking about counting up how many milliseconds have passed each frame and readjusting expectations for the next pulse because there may be control voltage senanigans going on. which is a good segue to...

Then there's the phase of this externalized clock. when there's an external clock being used, it may be useful to use the tempo knob as a way to slip the beats away from zero either forwards or backwards by adding or subtracting to those counters. Since tempo inputs are all connected together, that would also enable the tempo cv input to be a swing input when given a tempo-synced LFO

wraybowling commented 4 years ago

Brain dump number 2:

More than one clock

External (Clock division CV)

Reads the control voltage inputs. Each frame, if the value rises from below 900 to above 900, record a new timestamp from millis(). The difference between the previous timestamp and the new timestamp is a duration. This duration is passed along to Mutable Instruments Pattern Detection for averaging wigglies and un-swinging swings. The result is a duration.

Automatic Transmission

if the duration is longer than 10 seconds, begin internal clock? Maybe there's a way the user could manually reset instead...

Duration Decision

The clock division knob does one of three things

  1. In the middle the external beat passes through unprocessed
  2. Turning left "multiplies" Faster beats = shorter duration = divide duration by 2,4,8,16,24 as the knob turns anti-clockwise
  3. Turning right "divides" Slower beats = longer duration = multiply duration by 2,4,8,16,24 as the knob turns clockwise

Internal 24ppqn

simply an expected duration. each frame, use millis() % duration + offset to find ramp wave. If the ramp has dropped lower than previous value, set pulse end time to millis() + 25. if millis() is below pulse end time, output HIGH.

Retrigger

Either this will set the offset so that the internal clock stays in sync or it will snap to the nearest quarter note somehow

wraybowling commented 4 years ago

The clock code has been refactored a number of times as I've struggled to understand time multiplication. I made many mistakes along the way. But now that the basics are working, it's a good time to recap how it works; if future me is curious, or if you are, dear reader.

DebouncedBoolean sync_debounce

The Debounced boolean class catches the rising action of the clock. This runs inside the clock's code instead of main because the clock needs to be aware of every time loop whether the clock is rising or not.

16ppqn to 24ppqn sucks

This is not a clean and easy thing to do. For each pulse on the 16 scale, 1.5 pulses advance on the 24 scale. I'm pretty sure that I failed to make 16->24 conversion work properly but I'm well aware of how much it sucks. I had been accumulating time deltas and trying to count pulses but i kept getting false returned.

beats are occasionally skipped

again, i threw out the parts of the code that count steps. I want elegant solutions to these problems. My mind races with all sorts of ideas that wouldn't be efficient or even work like storing predicted timestamps in an array and removing them when they're used or invalidated always checking to make sure that a step didn't get left behind. But I think there's a simpler solution. Time moves forward. I know when the sync signal is coming in and I'm the one deciding to reset previous time stamp. I just need a counter I think.

this issue is long and has lots of checkboxes

I feel pretty accomplished getting the basics working. I think I'd like to turn all those little remaining checkboxes into issues of their own and put them all into a milestone. Clock detection is, apparently, hard!