uClock icon indicating copy to clipboard operation
uClock copied to clipboard

Sync to external Eurorack trigger

Open carlosedp opened this issue 1 year ago • 5 comments

Hi, I'm writing an Eurorack module for clock generation and division and wonder if I can sync my module that uses uClock to an external trigger coming from another Eurorack module.

The input trigger is usually a square wave from another oscillator or LFO (not MIDI).

Can I use the uClock.clockMe() for this? Any tips?

Thanks!

carlosedp avatar Jul 22 '24 00:07 carlosedp

Hi @carlosedp,

Yes it is possible to sync to external clock. one of the uclock main features is the phase lock mechanism to keep a good and solid internal clock based on external one.

uClock.clockMe() should be called at each low or high state of you square wave depending of the rise/fall style of your clock signal.

Rigth now the expected clock is 24PPQN, we have a TODO list to include the option to change expected external clock too.

But for now if you have any external clock different from 24PPQN you need to handle it on your code. For example: if you have a 48PPQN input clock, you should trigger uclock.clockMe() each second tick.

A code example

uint8_t clockDiv48Trig = 0;
void ppqInterrupt() {
  if (clockDiv48Trig == 0 || clockDiv48Trig % 2 == 0) {
    uClock.clockMe();
  }
  ++clockDiv48Trig;
}         

and a thread about the subject: https://github.com/midilab/uClock/issues/37

If you external clock is 24PPQN than you only need to call uClock.clockMe() each external signal rise or fall.

midilab avatar Jul 23 '24 11:07 midilab

Got it but I think in my case it's kinda the opposite... I need to generate a 24PPQN from a single pulse (can I call it 1 PPQN?)... right? Is this viable or precise?

carlosedp avatar Jul 23 '24 12:07 carlosedp

i dont think it exist such of 1PPQN resolution. Most eurorack modules are based on 48PPQN or 24PPQN, if it is a simple LFO that you have, there is no resolution at all, it is only a signal that you will try to sync to.

My suggestion is that you try it first uClock.clockMe() at each signal input LOW or HIGH(choose one of those only), and check it how it behaves, for clock generation. After that, just adjust the clock output to sane levels based on your LFO input(example: maybe the middle of your LFO time knob should be something like 140BPM output? then try to find how many signals to count before trigger uclock.clockMe())

Since a LFO is not PPQN sync based, you will need to find the ideal sync count that fits you purpose. To find the ideal clock count, try different values of CLOCK_COUNTER incrementing from 2 to 20 or more.... until you find the ideal value.

#define CLOCK_COUNTER 2
uint8_t clockDiv48Trig = 0;
void ppqInterrupt() {
  if (clockDiv48Trig == 0 || clockDiv48Trig % CLOCK_COUNTER == 0) {
    uClock.clockMe();
  }
  ++clockDiv48Trig;
}         

Mabe you can start with the example: https://github.com/midilab/uClock/blob/main/examples/GenericMasterOrExternalSync/GenericMasterOrExternalSync.ino

your clock output will be done inside onSync24Callback function based on bellow example(wich outputs at 24PPQN resolution)

The example is doing clockMe() inside loop() function, but ideally you should use interruption linked to your clock input PIN instead of doing inside loop().

midilab avatar Jul 23 '24 12:07 midilab

I think the OP is looking for a 'tap tempo' style of input, with a trigger per step/quarter note.

houtson avatar Sep 22 '24 21:09 houtson

But for now if you have any external clock different from 24PPQN you need to handle it on your code. For example: if you have a 48PPQN input clock, you should trigger uclock.clockMe() each second tick.

Thanks for the 48PPQN example, I'll use that for syncing with old Korg DINSYNC synths.

Cheers Paul

houtson avatar Sep 22 '24 21:09 houtson

That's exactly what @houtson mentioned... suppose I want to sync my module that uses uClock with MI Marbles T2 output for example... it would output 120BPM in the middle position... if I connect it to my module's INPUT that triggers the clockMe function, it wouldn't work since it expects 24PPQN which would be 24x Marbles output.

Is there any way to make the clockMe set the input PPQN for the sync signal?

carlosedp avatar Nov 04 '24 20:11 carlosedp

The library needs a new feature to set the resolution of clock sync input signal other than 24ppqn, its on the TODO list.

If the resolution is higher you can div your clock like 48ppqn with div = 2. but in your case where the resolution is lowerw than the uClock base resolution nothing can be done with uClock.clockMe() to solve it on current version.

@doctea starts some code on tap tempo feature wich can leads to another way of doing what you need.

But the ideal feature would be to set PPQN input resolution, just as we have on current version of uClock a set PPQN for output. Also some setMultiplier and setDivider functions would be nice to add on input and output of uClock signal.

midilab avatar Nov 17 '24 10:11 midilab

@carlosedp

Marbles is a confusing example so my answer below assumes you are asking about generating a MIDI clock from a typical eurorack module.
image

I need to generate a 24PPQN from a single pulse (can I call it 1 PPQN?)... right?

24 PPQN describes timing resolution used for MIDI clock.

I think of PPQN like Frames Per Second (FPS)

FPS is the number of individual images (frames) displayed each second. PPQN is the number of pulses used to represent one beat (specifically, a quarter note) in music

Higher PPQN means the musical timing is divided into more parts, allowing for finer placement of notes and rhythms.

For example Ableton uses 960PPQN which is a high enough resolution where you can come close to any swing imaginable.

IC-Alchemy avatar Dec 10 '24 05:12 IC-Alchemy

Yea, I know... :) When I mean 1PPQN I mean imagine syncing the library to an LFO with a square wave outputting pulses every second (60BPM) ... the idea is that while pretty unstable, this pulse train would drive the clock at 60BPM too.

I think Pamela's workout has a setting where you can set the input PPQN for the output clock where the default is 24PPQN but it can go as low as 1PPQN.

Check this part of the video: https://youtu.be/mkoVfQq7m8Y?t=285

carlosedp avatar Dec 10 '24 21:12 carlosedp

I've made tons of devices that move forward one step on each rising edge trigger. This can be done with out any clock at all, and without keeping track of time.

if (risingEdge)
    step++; 

For this to be synced with uClock, so that you could output a midi clock that is synced with your LFO for example, you'd have to decide what note value you want to assign to each step.

This won't work if you think of an LFO as being 1PPQN

The standard sequencer defaults to 16th notes, like on a TB303.

Each 16th note consists of 6PPQN, assuming your clock is MIDI at 24PPQN

If you don't plan on outputting multiple synd'd signals there are a ton of advantages of not using any clock at all and just moving forward one step like you mention.

I call this "time agnostic" but I think I just made that up.

A time agnostic sequencer automatically locks perfectly to what ever you send it, including swing, tempo changes, randomness etc...

IC-Alchemy avatar Dec 14 '24 20:12 IC-Alchemy

Hi @carlosedp, @houtson, @IC-Alchemy, and @doctea,

I've added an experimental feature that allows setting the input sync clock for different PPQN resolutions. This is currently untested, so I'm reaching out to gather feedback from you all.

The feature is implemented using setClockPPQN(PPQNResolution resolution) method.

uClock.setClockPPQN(uClock.PPQN_4);

This sets the resolution to 4 PPQN (Pulses Per Quarter Note), meaning each pulse corresponds to one step. For reference, here are the available PPQN resolutions in an extended list:

enum PPQNResolution {
    PPQN_4 = 4,
    PPQN_8 = 8,
    PPQN_12 = 12,
    PPQN_24 = 24,
    PPQN_48 = 48,
    PPQN_96 = 96,
    PPQN_384 = 384,
    PPQN_480 = 480,
    PPQN_960 = 960
};

So why 4PPQN? Most analog sequencers send out a pulse every 16th note which equals 4PPQN. So probrably is what you'll need for eurorack modules @carlosedp

Please note that while this code compiles and the logic appears sound on paper, it hasn't been thoroughly tested yet. I'm especially interested in hearing from anyone who has tried using this feature or encountered any issues with it.

This way we can have higher resolution clock running internally(good for groove/shuffle stuff) and and keep on sync with external lower resolution clocks.

There is a nice read about some different machines and their clock setups: https://djjondent.blogspot.com/2020/06/modular-clocks-analog-midi.html

If it happens that your sequencer is 2ppqn or 1ppqn i will need to rethink about the code on develop branch... since for now only 4ppqn minimun supported. Anyway, i will try to review and implement 1ppqn and 2ppqn too asap.

Some know machines that operates with sync at 1ppqn and 2ppqn according to the link reference: DOEPFER DARK TIME, Korg SQ-10, Euro - Zularic Repetitor, (Noise Engineering), Erica Pico Seq & Pico trigger , Delptronics Triggerman, Disting Mk3 & 4, Modcan Touch Sequencer (ext sync), KORG (SQ-1), Volcas, Teenage Engineering Pocket Operators (POs sync on audio pulses, essentially a click track).

Thank you in advance for your feedback and insights.

Best regards,

midilab avatar Feb 16 '25 23:02 midilab

That's awesome! I'm currently on vacation and will return next week, then I will take a look and play with it! Thanks! Seems promising!

carlosedp avatar Feb 17 '25 17:02 carlosedp

TE OP-Z also works at 2PPQN and for example if there's a need to sync to a square LFO or Marbles T2 output, 1PPQN would be needed...

carlosedp avatar Feb 17 '25 17:02 carlosedp

Awesome I'm gonna try it later today most likely.
The type of sequencers that increment one step for each trigger don't even think about PPQN, it is fairly easy to design a sequencer that simple steps forward on each trigger. No need to even consider time at all, so I call them "time agnostic" but I think "analog clock input" would be the language that the most people understand.

I'm gonna try and use the analog clock input (4PPQN) along with a faster clock + swing at the same time.

IC-Alchemy avatar Feb 22 '25 21:02 IC-Alchemy

I have time to test and fix a little error on clock input new schema. now should be working as expected.

i also have added Sync callbacks set other than 24PPQN: 1PPQN, 2PPQN, 4PPQN, 8PPQN, 12PPQN and 48PPQN and can be set via setOnSyncXX() function, that way will be easier to setup more complex clocking outputs(one or more clock outputs at a time) to control your setup with multiple clock time signatures when uClock is the master clock.

still no clock input sync for 1PPQN and 2PPQN... i will investigate the implementation of it soon...

midilab avatar Mar 25 '25 11:03 midilab

@carlosedp

The latest version on the develop branch is now ready for external sync with 1 PPQN and 2 PPQN. I’ve done some basic tests, but I don’t have a modular to test it with. If someone could please test the code to validate it for a new release, that would be great.

midilab avatar Mar 26 '25 20:03 midilab

@doctea

This lower PPQN sync support, 1 PPQN in special, opens the door to implementing the tap function. I will commit a version with tap() support soon.

midilab avatar Mar 26 '25 20:03 midilab

@houtson

Now you can do SYNC48 without aditional code, just set the clock input to 48PPQN and you are ready to go.

uClock.setClockPPQN(uClock.PPQN_48);

Then at each clock signal comming from your korg you just call uClock.clockMe();

midilab avatar Mar 26 '25 21:03 midilab

I was also thinking about changing the names of the methods to avoid confusion:

  • uClock.setClockPPQN() to uClock.setInputPPQN()
  • uClock.setPPQN() to uClock.setOutputPPQN()

I'll probably make these changes before the official release. Right now, we'll keep it as it is.

midilab avatar Mar 26 '25 21:03 midilab

Hacked in CV clock input support (in an ugly fashion) into my project and got this roughly working using the develop branch and PPQN_4. Not sure if it is working like intended though -- getting very variable tempo even when fed a stable clock, and also getting the clock running at erratic speeds when the CV input is disconnected entirely. That could be my hardware or surrounding project code at fault, tho -- I'll have to do some more experiments.

Question: is this intended to drive output ticks at 24 PPQN in a smooth and steady fashion, ie interpolating between input pulses?

doctea avatar Apr 09 '25 00:04 doctea

Question: is this intended to drive output ticks at 24 PPQN in a smooth and steady fashion, ie interpolating between input pulses?

Yes, in that case you now have new multiple Sync callbacks to make use. Wich will be independent from Input and Output PPQN.

example:

uClock.setInputPPQN(uClock.PPQN_4); // the resolution you are expecting to receive in case uClock set to slave mode using uClock.clockMe();
uClock.setOutputPPQN(uClock.PPQN_96); // your sequencer internal/output clock could be any greater or equal to InputPPQN.
uClock.setOnSync48(yourCallbackAt48Ppqn); // send Korg DIN Sync48 to LINN LM-2, ARTURIA, ELEKTRON...
uClock.setOnSync24(yourCallbackAt24Ppqn); // send MIDI CLOCK
uClock.setOnSync4(yourCallbackAt4Ppqn); // send clock to ARTURIA , DOEPFER MCV 24 – MSY2 or 1/16th , Korg SQ-1 analog sync , most analog sequencers, Orthogonal Devices ER-101, TipTop Trigger Riot & Circadian Rhythm. Erica Synths Drum Sequencer , 010 Black Box (clock in)  Quite common to find as the default for Eurorack modules.
uClock.setOnSync2(yourCallbackAt2Ppqn); // send clock to KORG (SQ-1), Volcas, Teenage Engineering Pocket Operators (POs sync on audio pulses, essentially a click track).
uClock.setOnSync1(yourCallbackAt1Ppqn); // send clock to DOEPFER DARK TIME, Korg SQ-10, Zularic Repetitor, (Noise Engineering), Erica Pico Seq & Pico trigger , Delptronics Triggerman, Disting Mk3 & 4, Modcan Touch Sequencer.

For the other strange behaviours you mentioned, can you please open a new issue since this one is already closed as enhancment done. You can use same description you've done here.

midilab avatar Apr 09 '25 11:04 midilab

Thanks @midilab ... since I wrote a bunch of the logic myself, I'll try to test and start porting the clocking core for my module to uClock.

I also agree on renaming the functions to set the input/output ppqn.

I'll report soon... thanks a lot, this looks neat!

carlosedp avatar Apr 09 '25 12:04 carlosedp

Implemented at Release 2.2.1

midilab avatar May 22 '25 09:05 midilab