jackclient-python icon indicating copy to clipboard operation
jackclient-python copied to clipboard

Example program for playing audio data from memory?

Open mrzapp opened this issue 1 year ago • 2 comments

It would be nice with an example program that demonstrates in the simplest way possible to record and play audio. Like the thru_client.py, but with record/playback implemented.

This is how far I got with my limited understanding of both numpy and the python JACK client.

EDIT: This updated example actually seems to work. Is this in your opinion how this should be done? If so, it could be very helpful for newcomers to have it on the project page along with the other examples.

#!/usr/bin/env python3

"""
Create a JACK client that records input audio and plays it back through the outputs.
"""
import sys
import os
import jack
import numpy
import threading

argv = iter(sys.argv)
# By default, use script name without extension as client name:
defaultclientname = os.path.splitext(os.path.basename(next(argv)))[0]
clientname = next(argv, defaultclientname)
servername = next(argv, None)

client = jack.Client(clientname, servername=servername)

if client.status.server_started:
    print('JACK server started')
if client.status.name_not_unique:
    print(f'unique name {client.name!r} assigned')

input_data = numpy.empty(client.blocksize, dtype = 'float32')   # For storing the recorded audio
recording_duration = 5.0                                        # Record audio for 5 seconds
playback_position = 0                                           # For tracking the current playback position in frames

event = threading.Event()

@client.set_process_callback
def process(frames):
    global input_data
    global playback_position

    assert len(client.inports) == len(client.outports)
    assert frames == client.blocksize

    # If the recorded audio data is less that 5 seconds,
    # keep adding the input buffer data to it
    if len(input_data) < recording_duration * client.samplerate:
        mono_sample = numpy.zeros(frames, dtype = 'float32')

        for i in client.inports:
            mono_sample = numpy.add(mono_sample, i.get_array())

        input_data = numpy.append(input_data, mono_sample)
       
    # If the recording is complete and the playback position isn't yet at the end,
    # keep adding the recorded data to the output buffer
    elif playback_position + frames < len(input_data):
        for o in client.outports:
            o.get_buffer()[:] = input_data[playback_position:playback_position + frames]

        playback_position += frames


@client.set_shutdown_callback
def shutdown(status, reason):
    print('JACK shutdown!')
    print('status:', status)
    print('reason:', reason)
    event.set()

# create two port pairs
for number in 1, 2:
    client.inports.register(f'input_{number}')
    client.outports.register(f'output_{number}')

with client:
    # When entering this with-statement, client.activate() is called.
    # This tells the JACK server that we are ready to roll.
    # Our process() callback will start running now.

    # Connect the ports.  You can't do this before the client is activated,
    # because we can't make connections to clients that aren't running.
    # Note the confusing (but necessary) orientation of the driver backend
    # ports: playback ports are "input" to the backend, and capture ports
    # are "output" from it.

    capture = client.get_ports(is_physical=True, is_output=True)
    if not capture:
        raise RuntimeError('No physical capture ports')

    for src, dest in zip(capture, client.inports):
        client.connect(src, dest)

    playback = client.get_ports(is_physical=True, is_input=True)
    if not playback:
        raise RuntimeError('No physical playback ports')

    for src, dest in zip(client.outports, playback):
        client.connect(src, dest)

    print('Press Ctrl+C to stop')
    try:
        event.wait()
    except KeyboardInterrupt:
        print('\nInterrupted by user')

# When the above with-statement is left (either because the end of the
# code block is reached, or because an exception was raised inside),
# client.deactivate() and client.close() are called automatically.

mrzapp avatar Apr 24 '23 11:04 mrzapp