Skip to content

Commit e742603

Browse files
committed
Initial commit
0 parents  commit e742603

File tree

8 files changed

+338
-0
lines changed

8 files changed

+338
-0
lines changed

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[submodule "portaudio"]
2+
path = portaudio
3+
url = https://git.assembla.com/portaudio.git

LICENSE

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
Copyright (c) 2017 Matthias Geier
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy
4+
of this software and associated documentation files (the "Software"), to deal
5+
in the Software without restriction, including without limitation the rights
6+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
copies of the Software, and to permit persons to whom the Software is
8+
furnished to do so, subject to the following conditions:
9+
10+
The above copyright notice and this permission notice shall be included in
11+
all copies or substantial portions of the Software.
12+
13+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19+
THE SOFTWARE.

MANIFEST.in

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
include LICENSE
2+
include *.rst
3+
recursive-include doc *.rst *.py
4+
include portaudio/LICENSE.txt
5+
include portaudio/index.html
6+
include portaudio/src/common/pa_ringbuffer.h
7+
include portaudio/src/common/pa_memorybarrier.h

README.rst

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
Python wrapper for PortAudio's ring buffer
2+
==========================================
3+
4+
The ring buffer functionality is typically not included in binary distributions
5+
of PortAudio_, therefore most Python wrappers don't include it, either.
6+
7+
This module can be compiled and used on any Python version where CFFI_ is
8+
available.
9+
10+
The class `pa_ringbuffer.RingBuffer` is designed to be used together with the
11+
sounddevice_ module for non-blocking transfer of data from the main Python
12+
program to an audio callback function which is implemented in C or some other
13+
compiled language.
14+
15+
.. _PortAudio: http://portaudio.com/
16+
.. _sounddevice: http://python-sounddevice.readthedocs.io/
17+
.. _CFFI: http://cffi.readthedocs.io/
18+
19+
Installation
20+
------------
21+
22+
::
23+
24+
python3 setup.py develop --user
25+
26+
or ::
27+
28+
python3 -m pip install -e . --user

portaudio

Submodule portaudio added at a1f61a9

setup.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
from setuptools import setup
2+
3+
__version__ = 'unknown'
4+
5+
# "import" __version__
6+
for line in open('src/pa_ringbuffer.py'):
7+
if line.startswith('__version__'):
8+
exec(line)
9+
break
10+
11+
setup(
12+
name='pa-ringbuffer',
13+
version=__version__,
14+
package_dir={'': 'src'},
15+
py_modules=['pa_ringbuffer', '_pa_ringbuffer_build'],
16+
setup_requires=['CFFI>=1.4.0'],
17+
cffi_modules=['src/_pa_ringbuffer_build.py:ffibuilder'],
18+
install_requires=['CFFI>=1'], # for _cffi_backend
19+
author='Matthias Geier',
20+
author_email='[email protected]',
21+
description="Python wrapper for PortAudio's ring buffer",
22+
long_description=open('README.rst').read(),
23+
license='MIT',
24+
keywords='sound audio PortAudio ringbuffer lock-free'.split(),
25+
#url='http://python-pa-ringbuffer.readthedocs.io/',
26+
platforms='any',
27+
classifiers=[
28+
'License :: OSI Approved :: MIT License',
29+
'Operating System :: OS Independent',
30+
'Programming Language :: Python',
31+
'Programming Language :: Python :: 2',
32+
'Programming Language :: Python :: 3',
33+
'Topic :: Multimedia :: Sound/Audio',
34+
],
35+
)

src/_pa_ringbuffer_build.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
"""Build script for the _pa_ringbuffer extension module."""
2+
3+
from cffi import FFI
4+
import platform
5+
6+
if platform.system() == 'Darwin':
7+
ring_buffer_size_t = 'int32_t'
8+
else:
9+
ring_buffer_size_t = 'long'
10+
11+
RINGBUFFER_DECLARATIONS = """
12+
/* Excerpt from PortAudio's pa_ringbuffer.h: */
13+
14+
typedef %(ring_buffer_size_t)s ring_buffer_size_t;
15+
typedef struct PaUtilRingBuffer
16+
{
17+
ring_buffer_size_t bufferSize;
18+
volatile ring_buffer_size_t writeIndex;
19+
volatile ring_buffer_size_t readIndex;
20+
ring_buffer_size_t bigMask;
21+
ring_buffer_size_t smallMask;
22+
ring_buffer_size_t elementSizeBytes;
23+
char* buffer;
24+
} PaUtilRingBuffer;
25+
ring_buffer_size_t PaUtil_InitializeRingBuffer(PaUtilRingBuffer* rbuf, ring_buffer_size_t elementSizeBytes, ring_buffer_size_t elementCount, void* dataPtr);
26+
void PaUtil_FlushRingBuffer(PaUtilRingBuffer* rbuf);
27+
ring_buffer_size_t PaUtil_GetRingBufferWriteAvailable(const PaUtilRingBuffer* rbuf);
28+
ring_buffer_size_t PaUtil_GetRingBufferReadAvailable(const PaUtilRingBuffer* rbuf);
29+
ring_buffer_size_t PaUtil_WriteRingBuffer(PaUtilRingBuffer* rbuf, const void* data, ring_buffer_size_t elementCount);
30+
ring_buffer_size_t PaUtil_ReadRingBuffer(PaUtilRingBuffer* rbuf, void* data, ring_buffer_size_t elementCount);
31+
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);
32+
ring_buffer_size_t PaUtil_AdvanceRingBufferWriteIndex(PaUtilRingBuffer* rbuf, ring_buffer_size_t elementCount);
33+
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);
34+
ring_buffer_size_t PaUtil_AdvanceRingBufferReadIndex(PaUtilRingBuffer* rbuf, ring_buffer_size_t elementCount);
35+
36+
/* End of pa_ringbuffer.h declarations. */
37+
""" % locals()
38+
39+
ffibuilder = FFI()
40+
ffibuilder.cdef(RINGBUFFER_DECLARATIONS)
41+
ffibuilder.set_source(
42+
'_pa_ringbuffer',
43+
RINGBUFFER_DECLARATIONS,
44+
sources=['portaudio/src/common/pa_ringbuffer.c'],
45+
)
46+
47+
if __name__ == '__main__':
48+
ffibuilder.compile(verbose=True)

src/pa_ringbuffer.py

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
"""Python wrapper for PortAudio's ring buffer."""
2+
3+
__version__ = '0.0.0'
4+
5+
import _cffi_backend
6+
from _pa_ringbuffer import ffi as _ffi, lib as _lib
7+
8+
9+
class RingBuffer(object):
10+
"""Wrapper for PortAudio's ring buffer.
11+
12+
See __init__().
13+
14+
"""
15+
16+
def __init__(self, elementsize, size):
17+
"""Create an instance of PortAudio's ring buffer.
18+
19+
Parameters
20+
----------
21+
elementsize : int
22+
The size of a single data element in bytes.
23+
size : int
24+
The number of elements in the buffer (must be a power of 2).
25+
26+
"""
27+
self._ptr = _ffi.new('PaUtilRingBuffer*')
28+
self._data = _ffi.new('unsigned char[]', size * elementsize)
29+
res = _lib.PaUtil_InitializeRingBuffer(
30+
self._ptr, elementsize, size, self._data)
31+
if res != 0:
32+
assert res == -1
33+
raise ValueError('size must be a power of 2')
34+
assert self._ptr.bufferSize == size
35+
assert self._ptr.elementSizeBytes == elementsize
36+
37+
def flush(self):
38+
"""Reset buffer to empty.
39+
40+
Should only be called when buffer is NOT being read or written.
41+
42+
"""
43+
_lib.PaUtil_FlushRingBuffer(self._ptr)
44+
45+
@property
46+
def write_available(self):
47+
"""Number of elements available in the ring buffer for writing."""
48+
return _lib.PaUtil_GetRingBufferWriteAvailable(self._ptr)
49+
50+
@property
51+
def read_available(self):
52+
"""Number of elements available in the ring buffer for reading."""
53+
return _lib.PaUtil_GetRingBufferReadAvailable(self._ptr)
54+
55+
def write(self, data, size=-1):
56+
"""Write data to the ring buffer.
57+
58+
Parameters
59+
----------
60+
data : CData pointer or buffer or bytes
61+
Data to write to the buffer.
62+
size : int, optional
63+
The number of elements to be written.
64+
65+
Returns
66+
-------
67+
int
68+
The number of elements written.
69+
70+
"""
71+
try:
72+
data = _ffi.from_buffer(data)
73+
except TypeError:
74+
pass # input is not a buffer
75+
if size < 0:
76+
size, rest = divmod(_ffi.sizeof(data), self._ptr.elementSizeBytes)
77+
if rest:
78+
raise ValueError('data size must be multiple of elementsize')
79+
return _lib.PaUtil_WriteRingBuffer(self._ptr, data, size)
80+
81+
def read(self, data, size=-1):
82+
"""Read data from the ring buffer.
83+
84+
Parameters
85+
----------
86+
data : CData pointer or buffer
87+
The memory where the data should be stored.
88+
size : int, optional
89+
The number of elements to be read.
90+
91+
Returns
92+
-------
93+
int
94+
The number of elements read.
95+
96+
"""
97+
try:
98+
data = _ffi.from_buffer(data)
99+
except TypeError:
100+
pass # input is not a buffer
101+
if size < 0:
102+
size, rest = divmod(_ffi.sizeof(data), self._ptr.elementSizeBytes)
103+
if rest:
104+
raise ValueError('data size must be multiple of elementsize')
105+
return _lib.PaUtil_ReadRingBuffer(self._ptr, data, size)
106+
107+
def get_write_buffers(self, size):
108+
"""Get buffer(s) to which we can write data.
109+
110+
Parameters
111+
----------
112+
size : int
113+
The number of elements desired.
114+
115+
Returns
116+
-------
117+
int
118+
The room available to be written or the given *size*,
119+
whichever is smaller.
120+
buffer
121+
The first buffer.
122+
buffer
123+
The second buffer.
124+
125+
"""
126+
ptr1 = _ffi.new('void**')
127+
ptr2 = _ffi.new('void**')
128+
size1 = _ffi.new('ring_buffer_size_t*')
129+
size2 = _ffi.new('ring_buffer_size_t*')
130+
return (_lib.PaUtil_GetRingBufferWriteRegions(
131+
self._ptr, size, ptr1, size1, ptr2, size2),
132+
_ffi.buffer(ptr1[0], size1[0] * self.elementsize),
133+
_ffi.buffer(ptr2[0], size2[0] * self.elementsize))
134+
135+
def advance_write_index(self, size):
136+
"""Advance the write index to the next location to be written.
137+
138+
Parameters
139+
----------
140+
size : int
141+
The number of elements to advance.
142+
143+
Returns
144+
-------
145+
int
146+
The new position.
147+
148+
"""
149+
return _lib.PaUtil_AdvanceRingBufferWriteIndex(self._ptr, size)
150+
151+
def get_read_buffers(self, size):
152+
"""Get buffer(s) from which we can read data.
153+
154+
Parameters
155+
----------
156+
size : int
157+
The number of elements desired.
158+
159+
Returns
160+
-------
161+
int
162+
The number of elements available for reading.
163+
buffer
164+
The first buffer.
165+
buffer
166+
The second buffer.
167+
168+
"""
169+
ptr1 = _ffi.new('void**')
170+
ptr2 = _ffi.new('void**')
171+
size1 = _ffi.new('ring_buffer_size_t*')
172+
size2 = _ffi.new('ring_buffer_size_t*')
173+
return (_lib.PaUtil_GetRingBufferReadRegions(
174+
self._ptr, size, ptr1, size1, ptr2, size2),
175+
_ffi.buffer(ptr1[0], size1[0] * self.elementsize),
176+
_ffi.buffer(ptr2[0], size2[0] * self.elementsize))
177+
178+
def advance_read_index(self, size):
179+
"""Advance the read index to the next location to be read.
180+
181+
Parameters
182+
----------
183+
size : int
184+
The number of elements to advance.
185+
186+
Returns
187+
-------
188+
int
189+
The new position.
190+
191+
"""
192+
return _lib.PaUtil_AdvanceRingBufferReadIndex(self._ptr, size)
193+
194+
@property
195+
def elementsize(self):
196+
"""Element size in bytes."""
197+
return self._ptr.elementSizeBytes

0 commit comments

Comments
 (0)