pythonaudiosynchronizationjack

jack audio - sync application to master


I'm wondering about the correct way of keeping my application in sync with a timebase master application using the jack audio api.

Let's say I have Hydrogen drum machine running in master mode and I want to print a message on every 1/4 note Hydrogen is playing.

This is what I would do intuitive (using python):

#!/usr/bin/env python3

import time
import jack

client = jack.Client('klicker')

def print_msg (last_tick):
    state, pos = client.transport_query()
    if state == jack.ROLLING:
        if pos['tick'] < last_tick:
            print ("klick")

    return pos['tick']

with client:
   last_tick = 0
   while True:
      last_tick = print_msg (last_tick)
      time.sleep(0.00002)

So I'm running a loop with very little sleep time and check in every iteration if the current beat is already over.

This seems a little bit dirty and imprecise to me. So what would the right way of solving this problem?


Solution

  • Finally, I found a solution which semms to be more precise:

    First, we want to use the process callback instead of an infinite loop.

    def process (f):
    

    Every time this callback is called, f number of frames are processed. The sample rate tells us how many frames we will process in 1 second. By multiplying this with 60 and dividing by the number of beats per minute we get the number of frames which are processed in one beat.

    #!/usr/bin/env python3
    
    import jack
    client = jack.Client('Klick')
    frame_counter = 0
    
    @client.set_process_callback
    def process (f):
        global frame_counter
        state, pos = client.transport_query()
    
        if state == jack.ROLLING:
            frame_counter += f
    
            # how many frames are get processed in 1 beat?
            frames_per_beat = client.samplerate * 60 / pos['beats_per_minute']
    
            # did we process enough frames for 1 beat?
            if frame_counter >= frames_per_beat:
                print ('klick')
                frame_counter -= frames_per_beat
    
    
    with client:
        client.activate()
        input()