On Wed, Dec 11, 2024 at 11:42:57AM +0100, Jeanette C. wrote:

> Is there a better, hopefully simple, way to play these clicks?

If you're building a sequencer anyway, just output the metronome
clicks as one additional track. i.e. just a repeating sequence
of note on/off events to be rendered by a synth.

Or you could use the JackSignal class from zita-jacktools
to play the ticks. Giant overkill but it will work.

JackSignal allows to play and/or capture Jack audio from/to numpy
buffers. It's meant to do automated audio measurements with all
signals generated and/or analysed in Python.

The example below receives MIDI clock (using the Aseqmidi class)
and outputs a tick every quarter note. In this case the two tick
sounds are generated in Python, but you could as well input them
from files (using zita-audiotools).

When S.process () is called, output will start at the next Jack
period boundary, so with -p 256 you'd have +/- 2.7 ms jitter.

Note that if the metronome is a separate program (like the one
below), it also needs to handle song position messages to be in
sync with the sequencer.


#!/usr/bin/python

from zita_alsamidi.aseqmidi import *
from zita_jacktools.jacksignal import *
from time import sleep
from math import pi, sin
import numpy as np


# Play sound
#
def playsound ():
    global K  # Beat counter
    if S.get_state () != JackSignal.SILENCE:
        return  # Still playing previous one...
    # Enable one of the two waveforms.  
    if K == 0:
        S.set_output_gain (0, 1.0)
        S.set_output_gain (1, 0.0)
    else:    
        S.set_output_gain (0, 0.0)
        S.set_output_gain (1, 1.0)
    # Start playback.   
    S.process ()    
    # Handle 4/4 signature.     
    K += 1
    if K == 4: K = 0

    
# Midi message handler
#
def handler (args):
    global C  # Clock counter
    if args [1][0] == 36: # 36 = SND_SEQ_EVENT_CLOCK
        C += 1
        if C == 24:
            playsound ()
            C = 0       


# Generate a 'ping' waveform.
#
def genping (freq, rate, size):
    D = np.zeros ((size,), dtype = np.float32)
    w = 2 * pi * freq / rate
    a = 0.3 
    m = pow (0.01, 1.0 / size) 
    for i in range (size):
        D [i] = a * sin (i * w)
        a *= m
    return D

            
S = JackSignal ("Metronome")
if S.get_state () < 0:
    print ("Can't create JackSignal")
    exit (1)

name, rate, frag = S.get_jack_info ()
# Should be short enough so they don't overlap.
size = int (0.15 * rate)             
D1 = genping (880, rate, size)
D2 = genping (440, rate, size)

S.create_output (0, 'out-1')
S.create_output (1, 'out-2')
S.silence ()
# Connect Jack outputs...
# S.connect_output (0, '...')
# S.connect_output (1, '...')
S.set_output_data (0, D1)
S.set_output_data (1, D2)

          
C = 0 # Clock message counter.
K = 0 # Beat counter
M = Aseqmidi ("Metronome", handler)
sleep (10000)


-- 
FA


_______________________________________________
Linux-audio-dev mailing list -- linux-audio-dev@lists.linuxaudio.org
To unsubscribe send an email to linux-audio-dev-le...@lists.linuxaudio.org

Reply via email to