【鼎革‧革鼎】︰ Raspbian Stretch 《六之 J.2D 》

承上文,應用 JACK Client for Python 寫程式,主要重心就在set_process_callback 程序。以及詳實認識輸入、出聲音緩衝區︰

class OwnPort(Port):
    """A JACK audio port owned by a `Client`.
    This class is derived from `Port`. `OwnPort` objects can do
    everything that `Port` objects can, plus a lot more.
    This class cannot be instantiated directly (see `Port`).
    New JACK audio/MIDI ports can be created with the
    :meth:`~Ports.register` method of `Client.inports`,
    `Client.outports`, `Client.midi_inports` and `Client.midi_outports`.
    """

    def get_buffer(self):
        """Get buffer for audio data.
        This returns a buffer holding the memory area associated with
        the specified port. For an output port, it will be a memory
        area that can be written to; for an input port, it will be an
        area containing the data from the port's connection(s), or
        zero-filled. If there are multiple inbound connections, the
        data will be mixed appropriately.
        Caching output ports is DEPRECATED in JACK 2.0, due to some new
        optimization (like "pipelining"). Port buffers have to be
        retrieved in each callback for proper functioning.
        This method shall only be called from within the process
        callback (see `Client.set_process_callback()`).
        """
        blocksize = self._client.blocksize
        return _ffi.buffer(_lib.jack_port_get_buffer(self._ptr, blocksize),
        blocksize * _ffi.sizeof('float'))

    def get_array(self):
        """Get audio buffer as NumPy array.
        Make sure to ``import numpy`` before calling this, otherwise the
        first call might take a long time.
        This method shall only be called from within the process
        callback (see `Client.set_process_callback()`).
        See Also
        --------
        get_buffer
        """
        import numpy as np
        return np.frombuffer(self.get_buffer(), dtype=np.float32)

 

雖說深入了解 CFFI 並非必要︰

cffi 1.11.2

Foreign Function Interface for Python calling C code.

Foreign Function Interface for Python calling C code. Please see the Documentation.

……

Overview

CFFI can be used in one of four modes: “ABI” versus “API” level, each with “in-line” or “out-of-line” preparation (or compilation).

The ABI mode accesses libraries at the binary level, whereas the faster API mode accesses them with a C compiler. This is described in detail below.

In the in-line mode, everything is set up every time you import your Python code. In the out-of-line mode, you have a separate step of preparation (and possibly C compilation) that produces a module which your main program can then import.

(The examples below assume that you have installed CFFI.)

>>> from cffi import FFI
>>> ffi = FFI()
>>> ffi.cdef("""
...     int printf(const char *format, ...);   // copy-pasted from the man page
... """)
>>> C = ffi.dlopen(None)                     # loads the entire C namespace
>>> arg = ffi.new("char[]", "world")         # equivalent to C code: char arg[] = "world";
>>> C.printf("hi there, %s.\n", arg)         # call printf
hi there, world.
17                                           # this is the return value
>>>

 

然而終將發現它的好處與重要性也。

此處且藉派生內建之 memoryview 功能

class memoryview(obj)

Create a memoryview that references obj. obj must support the buffer protocol. Built-in objects that support the buffer protocol include bytes and bytearray.

A memoryview has the notion of an element, which is the atomic memory unit handled by the originating object obj. For many simple types such as bytes and bytearray, an element is a single byte, but other types such as array.array may have bigger elements.

len(view) is equal to the length of tolist. If view.ndim = 0, the length is 1. If view.ndim = 1, the length is equal to the number of elements in the view. For higher dimensions, the length is equal to the length of the nested list representation of the view. The itemsize attribute will give you the number of bytes in a single element.

A memoryview supports slicing and indexing to expose its data. One-dimensional slicing will result in a subview:

>>> v = memoryview(b'abcefg')
>>> v[1]
98
>>> v[-1]
103
>>> v[1:4]
<memory at 0x7f3ddc9f4350>
>>> bytes(v[1:4])
b'bce'

 

來趟簡短的探索之旅︰

pi@raspberrypi:~ python3 Python 3.5.3 (default, Jan 19 2017, 14:11:04)  [GCC 6.3.0 20170124] on linux Type "help", "copyright", "credits" or "license" for more information. >>> from cffi import FFI >>> ffi = FFI() >>> buf = ffi.new("float audio[]",512) >>> buf <cdata 'float[]' owning 2048 bytes> >>> buf[0] 0.0 >>> buf[1] 0.0 >>> buf[511] 0.0 >>> buffer = ffi.buffer(buf) >>> buffer <_cffi_backend.buffer object at 0x767117b0> >>> buffer[0] b'\x00' >>> buffer[1] b'\x00' >>> buffer[511] b'\x00' >>> view = memoryview(buffer).cast('f') >>> view <memory at 0x767341d0> >>> view[0] 0.0 >>> view[1] 0.0 >>> view[511] 0.0 >>> len(view) 512 >>>  </pre>    <span style="color: #666699;">可用以實現能控制音量大小的大聲公乎☆</span> <pre class="lang:default decode:true">pi@raspberrypi:~ python3
Python 3.5.3 (default, Jan 19 2017, 14:11:04) 
[GCC 6.3.0 20170124] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import jack
>>> client = jack.Client("thru_client")
>>> audioVol = 1.0
>>> @client.set_process_callback
... def process(frames):
...     assert len(client.inports) == len(client.outports)
...     assert frames == client.blocksize
...     for i, o in zip(client.inports, client.outports):
...         ibuf = memoryview(i.get_buffer()).cast('f')
...         obuf = memoryview(o.get_buffer()).cast('f')
...         for n in range(len(ibuf)):
...             obuf[n] = ibuf[n] * audioVol
... 
>>> for number in 1, 2:
...     client.inports.register('input_{0}'.format(number))
...     client.outports.register('output_{0}'.format(number))
... 
jack.OwnPort('thru_client:input_1')
jack.OwnPort('thru_client:output_1')
jack.OwnPort('thru_client:input_2')
jack.OwnPort('thru_client:output_2')
>>> client.activate()
>>> audioVol = 0.2
>>> audioVol = 1.2
>>>