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