Skip to content

Commit fe97d98

Browse files
committed
Move RingBuffer to separate module
1 parent a5f771e commit fe97d98

5 files changed

Lines changed: 39 additions & 235 deletions

File tree

rtmixer_build.py

Lines changed: 7 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,24 @@
11
# This is used to create the _rtmixer extension module (see setup.py).
22

33
from cffi import FFI
4-
import platform
4+
import pa_ringbuffer
55

6-
if platform.system() == 'Darwin':
7-
ring_buffer_size_t = 'int32_t'
8-
else:
9-
ring_buffer_size_t = 'long'
6+
RINGBUFFER_CDEF = pa_ringbuffer.cdef()
107

118
ffibuilder = FFI()
9+
ffibuilder.cdef(RINGBUFFER_CDEF)
1210
ffibuilder.cdef("""
1311
14-
/* From portaudio.h: */
15-
16-
typedef double PaTime;
17-
typedef struct PaStreamCallbackTimeInfo PaStreamCallbackTimeInfo;
18-
typedef unsigned long PaStreamCallbackFlags;
19-
20-
/* From pa_ringbuffer.h: */
21-
22-
typedef %(ring_buffer_size_t)s ring_buffer_size_t;
23-
typedef struct PaUtilRingBuffer
24-
{
25-
ring_buffer_size_t bufferSize;
26-
volatile ring_buffer_size_t writeIndex;
27-
volatile ring_buffer_size_t readIndex;
28-
ring_buffer_size_t bigMask;
29-
ring_buffer_size_t smallMask;
30-
ring_buffer_size_t elementSizeBytes;
31-
char* buffer;
32-
} PaUtilRingBuffer;
33-
ring_buffer_size_t PaUtil_InitializeRingBuffer(PaUtilRingBuffer* rbuf, ring_buffer_size_t elementSizeBytes, ring_buffer_size_t elementCount, void* dataPtr);
34-
void PaUtil_FlushRingBuffer(PaUtilRingBuffer* rbuf);
35-
ring_buffer_size_t PaUtil_GetRingBufferWriteAvailable(const PaUtilRingBuffer* rbuf);
36-
ring_buffer_size_t PaUtil_GetRingBufferReadAvailable(const PaUtilRingBuffer* rbuf);
37-
ring_buffer_size_t PaUtil_WriteRingBuffer(PaUtilRingBuffer* rbuf, const void* data, ring_buffer_size_t elementCount);
38-
ring_buffer_size_t PaUtil_ReadRingBuffer(PaUtilRingBuffer* rbuf, void* data, ring_buffer_size_t elementCount);
39-
ring_buffer_size_t PaUtil_GetRingBufferWriteRegions(PaUtilRingBuffer* rbuf, ring_buffer_size_t elementCount, void** dataPtr1, ring_buffer_size_t* sizePtr1, void** dataPtr2, ring_buffer_size_t* sizePtr2);
40-
ring_buffer_size_t PaUtil_AdvanceRingBufferWriteIndex(PaUtilRingBuffer* rbuf, ring_buffer_size_t elementCount);
41-
ring_buffer_size_t PaUtil_GetRingBufferReadRegions(PaUtilRingBuffer* rbuf, ring_buffer_size_t elementCount, void** dataPtr1, ring_buffer_size_t* sizePtr1, void** dataPtr2, ring_buffer_size_t* sizePtr2);
42-
ring_buffer_size_t PaUtil_AdvanceRingBufferReadIndex(PaUtilRingBuffer* rbuf, ring_buffer_size_t elementCount);
43-
4412
/* From limits.h: */
4513
4614
#define ULONG_MAX ...
47-
""" % locals())
15+
16+
""")
4817
ffibuilder.cdef(open('src/rtmixer.h').read())
4918
ffibuilder.set_source(
5019
'_rtmixer',
51-
open('src/rtmixer.c').read(),
52-
include_dirs=['src', 'portaudio/include', 'portaudio/src/common'],
20+
RINGBUFFER_CDEF + open('src/rtmixer.c').read(),
21+
include_dirs=['src'],
5322
sources=['portaudio/src/common/pa_ringbuffer.c'],
5423
#extra_compile_args=['-Wconversion'],
5524
# TODO: release mode by default, option for using debug mode

setup.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@
1313
version=__version__,
1414
package_dir={'': 'src'},
1515
py_modules=['rtmixer'],
16-
setup_requires=['CFFI>=1.4.0'],
16+
setup_requires=['CFFI>=1.4.0', 'pa_ringbuffer'],
1717
cffi_modules=['rtmixer_build.py:ffibuilder'],
18-
install_requires=['sounddevice>0.3.6'],
18+
install_requires=['sounddevice>0.3.6', 'pa_ringbuffer'],
1919
author='Matthias Geier',
2020
author_email='Matthias.Geier@gmail.com',
2121
description='Reliable low-latency audio playback and recording',

src/rtmixer.c

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,30 @@
1-
/* See ../rtmixer_build.py */
1+
/* See ../rtmixer_build.py, the ring buffer declarations are included there */
22

33
#include <math.h> // for llround()
44
#include <stdbool.h> // for bool, true, false
55

6-
#include <portaudio.h>
7-
#include <pa_ringbuffer.h>
8-
96
#include "rtmixer.h"
107

8+
/* From portaudio.h: */
9+
typedef struct PaStreamCallbackTimeInfo{
10+
PaTime inputBufferAdcTime;
11+
PaTime currentTime;
12+
PaTime outputBufferDacTime;
13+
} PaStreamCallbackTimeInfo;
14+
typedef unsigned long PaStreamCallbackFlags;
15+
#define paInputUnderflow ((PaStreamCallbackFlags) 0x00000001)
16+
#define paInputOverflow ((PaStreamCallbackFlags) 0x00000002)
17+
#define paOutputUnderflow ((PaStreamCallbackFlags) 0x00000004)
18+
#define paOutputOverflow ((PaStreamCallbackFlags) 0x00000008)
19+
#define paPrimingOutput ((PaStreamCallbackFlags) 0x00000010)
20+
typedef enum PaStreamCallbackResult
21+
{
22+
paContinue=0,
23+
paComplete=1,
24+
paAbort=2
25+
} PaStreamCallbackResult;
26+
/* End of declarations from portaudio.h */
27+
1128
#ifdef NDEBUG
1229
#define CALLBACK_ASSERT(expr) ((void)(0))
1330
#else

src/rtmixer.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
/* From portaudio.h: */
2+
typedef double PaTime;
3+
typedef struct PaStreamCallbackTimeInfo PaStreamCallbackTimeInfo;
4+
typedef unsigned long PaStreamCallbackFlags;
5+
/* End of declarations from portaudio.h */
6+
17
typedef unsigned long frame_t;
28

39
enum actiontype

src/rtmixer.py

Lines changed: 3 additions & 191 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
"""Reliable low-latency audio playback and recording."""
2+
23
__version__ = '0.0.0'
34

45
import sounddevice as _sd
6+
from pa_ringbuffer import init as _init_ringbuffer
57
from _rtmixer import ffi as _ffi, lib as _lib
8+
RingBuffer = _init_ringbuffer(_ffi, _lib)
69

710

811
class _Base(_sd._StreamBase):
@@ -234,194 +237,3 @@ def __init__(self, **kwargs):
234237
_Base.__init__(self, kind='duplex', **kwargs)
235238
self._state.input_channels = self.channels[0]
236239
self._state.output_channels = self.channels[1]
237-
238-
239-
class RingBuffer(object):
240-
"""Wrapper for PortAudio's ring buffer.
241-
242-
See __init__().
243-
244-
"""
245-
246-
def __init__(self, elementsize, size):
247-
"""Create an instance of PortAudio's ring buffer.
248-
249-
Parameters
250-
----------
251-
elementsize : int
252-
The size of a single data element in bytes.
253-
size : int
254-
The number of elements in the buffer (must be a power of 2).
255-
256-
"""
257-
self._ptr = _ffi.new('PaUtilRingBuffer*')
258-
self._data = _ffi.new('unsigned char[]', size * elementsize)
259-
res = _lib.PaUtil_InitializeRingBuffer(
260-
self._ptr, elementsize, size, self._data)
261-
if res != 0:
262-
assert res == -1
263-
raise ValueError('size must be a power of 2')
264-
assert self._ptr.bufferSize == size
265-
assert self._ptr.elementSizeBytes == elementsize
266-
267-
def flush(self):
268-
"""Reset buffer to empty.
269-
270-
Should only be called when buffer is NOT being read or written.
271-
272-
"""
273-
_lib.PaUtil_FlushRingBuffer(self._ptr)
274-
275-
@property
276-
def write_available(self):
277-
"""Number of elements available in the ring buffer for writing."""
278-
return _lib.PaUtil_GetRingBufferWriteAvailable(self._ptr)
279-
280-
@property
281-
def read_available(self):
282-
"""Number of elements available in the ring buffer for reading."""
283-
return _lib.PaUtil_GetRingBufferReadAvailable(self._ptr)
284-
285-
def write(self, data, size=-1):
286-
"""Write data to the ring buffer.
287-
288-
Parameters
289-
----------
290-
data : CData pointer or buffer or bytes
291-
Data to write to the buffer.
292-
size : int, optional
293-
The number of elements to be written.
294-
295-
Returns
296-
-------
297-
int
298-
The number of elements written.
299-
300-
"""
301-
try:
302-
data = _ffi.from_buffer(data)
303-
except TypeError:
304-
pass # input is not a buffer
305-
if size < 0:
306-
size, rest = divmod(_ffi.sizeof(data), self._ptr.elementSizeBytes)
307-
if rest:
308-
raise ValueError('data size must be multiple of elementsize')
309-
return _lib.PaUtil_WriteRingBuffer(self._ptr, data, size)
310-
311-
def read(self, data, size=-1):
312-
"""Read data from the ring buffer.
313-
314-
Parameters
315-
----------
316-
data : CData pointer or buffer
317-
The memory where the data should be stored.
318-
size : int, optional
319-
The number of elements to be read.
320-
321-
Returns
322-
-------
323-
int
324-
The number of elements read.
325-
326-
"""
327-
try:
328-
data = _ffi.from_buffer(data)
329-
except TypeError:
330-
pass # input is not a buffer
331-
if size < 0:
332-
size, rest = divmod(_ffi.sizeof(data), self._ptr.elementSizeBytes)
333-
if rest:
334-
raise ValueError('data size must be multiple of elementsize')
335-
return _lib.PaUtil_ReadRingBuffer(self._ptr, data, size)
336-
337-
def get_write_buffers(self, size):
338-
"""Get buffer(s) to which we can write data.
339-
340-
Parameters
341-
----------
342-
size : int
343-
The number of elements desired.
344-
345-
Returns
346-
-------
347-
int
348-
The room available to be written or the given *size*,
349-
whichever is smaller.
350-
buffer
351-
The first buffer.
352-
buffer
353-
The second buffer.
354-
355-
"""
356-
ptr1 = _ffi.new('void**')
357-
ptr2 = _ffi.new('void**')
358-
size1 = _ffi.new('ring_buffer_size_t*')
359-
size2 = _ffi.new('ring_buffer_size_t*')
360-
return (_lib.PaUtil_GetRingBufferWriteRegions(
361-
self._ptr, size, ptr1, size1, ptr2, size2),
362-
_ffi.buffer(ptr1[0], size1[0] * self.elementsize),
363-
_ffi.buffer(ptr2[0], size2[0] * self.elementsize))
364-
365-
def advance_write_index(self, size):
366-
"""Advance the write index to the next location to be written.
367-
368-
Parameters
369-
----------
370-
size : int
371-
The number of elements to advance.
372-
373-
Returns
374-
-------
375-
int
376-
The new position.
377-
378-
"""
379-
return _lib.PaUtil_AdvanceRingBufferWriteIndex(self._ptr, size)
380-
381-
def get_read_buffers(self, size):
382-
"""Get buffer(s) from which we can read data.
383-
384-
Parameters
385-
----------
386-
size : int
387-
The number of elements desired.
388-
389-
Returns
390-
-------
391-
int
392-
The number of elements available for reading.
393-
buffer
394-
The first buffer.
395-
buffer
396-
The second buffer.
397-
398-
"""
399-
ptr1 = _ffi.new('void**')
400-
ptr2 = _ffi.new('void**')
401-
size1 = _ffi.new('ring_buffer_size_t*')
402-
size2 = _ffi.new('ring_buffer_size_t*')
403-
return (_lib.PaUtil_GetRingBufferReadRegions(
404-
self._ptr, size, ptr1, size1, ptr2, size2),
405-
_ffi.buffer(ptr1[0], size1[0] * self.elementsize),
406-
_ffi.buffer(ptr2[0], size2[0] * self.elementsize))
407-
408-
def advance_read_index(self, size):
409-
"""Advance the read index to the next location to be read.
410-
411-
Parameters
412-
----------
413-
size : int
414-
The number of elements to advance.
415-
416-
Returns
417-
-------
418-
int
419-
The new position.
420-
421-
"""
422-
return _lib.PaUtil_AdvanceRingBufferReadIndex(self._ptr, size)
423-
424-
@property
425-
def elementsize(self):
426-
"""Element size in bytes."""
427-
return self._ptr.elementSizeBytes

0 commit comments

Comments
 (0)