diff --git a/pysoundio/__init__.py b/pysoundio/__init__.py index d1f3929..dbdd80e 100644 --- a/pysoundio/__init__.py +++ b/pysoundio/__init__.py @@ -21,7 +21,7 @@ import sys -__version__ = '0.0.3' +__version__ = '0.0.4' try: diff --git a/pysoundio/_soundiox.c b/pysoundio/_soundiox.c index 4509b8a..dc38d1e 100644 --- a/pysoundio/_soundiox.c +++ b/pysoundio/_soundiox.c @@ -199,8 +199,8 @@ static PyMethodDef soundio_methods[] = { "get bytes per second" }, { - "set_read_callback", - pysoundio__set_read_callback, METH_VARARGS, + "set_read_callbacks", + pysoundio__set_read_callbacks, METH_VARARGS, "set read callback" }, { @@ -234,8 +234,8 @@ static PyMethodDef soundio_methods[] = { "get next input frame length in seconds" }, { - "set_write_callback", - pysoundio__set_write_callback, METH_VARARGS, + "set_write_callbacks", + pysoundio__set_write_callbacks, METH_VARARGS, "set write callback" }, { @@ -348,6 +348,8 @@ struct RecordContext { PyObject *read_callback; PyObject *write_callback; + PyObject *overflow_callback; + PyObject *underflow_callback; }; struct RecordContext rc; @@ -867,23 +869,37 @@ read_callback(struct SoundIoInStream *instream, int frame_count_min, int frame_c static void overflow_callback(struct SoundIoInStream *instream) { - static int count = 0; - count++; + struct RecordContext *rc = instream->userdata; + + if (rc->overflow_callback) { + PyGILState_STATE state = PyGILState_Ensure(); + PyObject *result = PyObject_CallObject(rc->overflow_callback, NULL); + Py_XDECREF(result); + PyGILState_Release(state); + } } static PyObject * -pysoundio__set_read_callback(PyObject *self, PyObject *args) +pysoundio__set_read_callbacks(PyObject *self, PyObject *args) { - PyObject *temp; + PyObject *read; + PyObject *flow; - if (PyArg_ParseTuple(args, "O", &temp)) { - if (!PyCallable_Check(temp)) { + if (PyArg_ParseTuple(args, "OO", &read, &flow)) { + if (!PyCallable_Check(read)) { PyErr_SetString(PyExc_TypeError, "parameter must be callable"); return NULL; } - Py_XINCREF(temp); + if (!PyCallable_Check(flow)) { + PyErr_SetString(PyExc_TypeError, "parameter must be callable"); + return NULL; + } + Py_XINCREF(read); + Py_XINCREF(flow); Py_XDECREF(rc.read_callback); - rc.read_callback = temp; + Py_XDECREF(rc.overflow_callback); + rc.read_callback = read; + rc.overflow_callback = flow; Py_RETURN_NONE; } return NULL; @@ -986,18 +1002,26 @@ pysoundio__instream_get_latency(PyObject *self, PyObject *args) *************************************************************/ static PyObject * -pysoundio__set_write_callback(PyObject *self, PyObject *args) +pysoundio__set_write_callbacks(PyObject *self, PyObject *args) { - PyObject *temp; + PyObject *write; + PyObject *flow; - if (PyArg_ParseTuple(args, "O", &temp)) { - if (!PyCallable_Check(temp)) { + if (PyArg_ParseTuple(args, "OO", &write, &flow)) { + if (!PyCallable_Check(write)) { PyErr_SetString(PyExc_TypeError, "parameter must be callable"); return NULL; } - Py_XINCREF(temp); + if (!PyCallable_Check(flow)) { + PyErr_SetString(PyExc_TypeError, "parameter must be callable"); + return NULL; + } + Py_XINCREF(write); + Py_XINCREF(flow); Py_XDECREF(rc.write_callback); - rc.write_callback = temp; + Py_XDECREF(rc.underflow_callback); + rc.write_callback = write; + rc.underflow_callback = flow; Py_RETURN_NONE; } return NULL; @@ -1081,12 +1105,17 @@ write_callback(struct SoundIoOutStream *outstream, int frame_count_min, int fram } } - static void underflow_callback(struct SoundIoOutStream *outstream) { - static int count = 0; - count++; + struct RecordContext *rc = outstream->userdata; + + if (rc->underflow_callback) { + PyGILState_STATE state = PyGILState_Ensure(); + PyObject *result = PyObject_CallObject(rc->underflow_callback, NULL); + Py_XDECREF(result); + PyGILState_Release(state); + } } static PyObject * diff --git a/pysoundio/_soundiox.h b/pysoundio/_soundiox.h index bd778dc..40bf804 100644 --- a/pysoundio/_soundiox.h +++ b/pysoundio/_soundiox.h @@ -107,7 +107,7 @@ pysoundio__get_bytes_per_second(PyObject *self, PyObject *args); * Input Stream API */ static PyObject * -pysoundio__set_read_callback(PyObject *self, PyObject *args); +pysoundio__set_read_callbacks(PyObject *self, PyObject *args); static PyObject * pysoundio__instream_create(PyObject *self, PyObject *args); static PyObject * @@ -125,7 +125,7 @@ pysoundio__instream_get_latency(PyObject *self, PyObject *args); * Output Stream API */ static PyObject * -pysoundio__set_write_callback(PyObject *self, PyObject *args); +pysoundio__set_write_callbacks(PyObject *self, PyObject *args); static PyObject * pysoundio__outstream_create(PyObject *self, PyObject *args); static PyObject * diff --git a/pysoundio/pysoundio.py b/pysoundio/pysoundio.py index 13838d4..e8adcd0 100644 --- a/pysoundio/pysoundio.py +++ b/pysoundio/pysoundio.py @@ -39,9 +39,9 @@ class PySoundIoError(Exception): class _InputProcessingThread(threading.Thread): def __init__(self, parent, *args, **kwargs): - self.buffer = parent.input_buffer - self.callback = parent.read_callback - self.bytes_per_frame = parent.input_bytes_per_frame + self.buffer = parent.input['buffer'] + self.callback = parent.input['read_callback'] + self.bytes_per_frame = parent.input['bytes_per_frame'] super(_InputProcessingThread, self).__init__(*args, **kwargs) def run(self): @@ -56,9 +56,9 @@ def run(self): class _OutputProcessingThread(threading.Thread): def __init__(self, parent, block_size, *args, **kwargs): - self.buffer = parent.output_buffer - self.callback = parent.write_callback - self.bytes_per_frame = parent.output_bytes_per_frame + self.buffer = parent.output['buffer'] + self.callback = parent.output['write_callback'] + self.bytes_per_frame = parent.output['bytes_per_frame'] self.block_size = block_size super(_OutputProcessingThread, self).__init__(*args, **kwargs) @@ -86,14 +86,8 @@ def __init__(self, backend=None): self.backend = backend self.testing = False - self.input_device = None - self.output_device = None - self.input_stream = None - self.output_stream = None - self.input_buffer = None - self.output_buffer = None - self.read_callback = None - self.write_callback = None + self.input = {'device': None, 'stream': None, 'buffer': None, 'read_callback': None} + self.output = {'device': None, 'stream': None, 'buffer': None, 'write_callback': None} self._soundio = soundio.create() if backend: @@ -107,24 +101,24 @@ def close(self): Clean up allocated memory Close libsoundio connections """ - if self.input_stream: + if self.input['stream']: soundio.instream_destroy() - self.input_stream = None - if self.output_stream: + self.input['stream'] = None + if self.output['stream']: soundio.outstream_destroy() - self.output_stream = None - if self.input_buffer: - soundio.ring_buffer_destroy(self.input_buffer) - self.input_buffer = None - if self.output_buffer: - soundio.ring_buffer_destroy(self.output_buffer) - self.output_buffer = None - if self.input_device: - soundio.device_unref(self.input_device) - self.input_device = None - if self.output_device: - soundio.device_unref(self.output_device) - self.output_device = None + self.output['stream'] = None + if self.input['buffer']: + soundio.ring_buffer_destroy(self.input['buffer']) + self.input['buffer'] = None + if self.output['buffer']: + soundio.ring_buffer_destroy(self.output['buffer']) + self.output['buffer'] = None + if self.input['device']: + soundio.device_unref(self.input['device']) + self.input['device'] = None + if self.output['device']: + soundio.device_unref(self.output['device']) + self.output['device'] = None if self._soundio: soundio.disconnect() soundio.destroy() @@ -176,8 +170,8 @@ def get_input_device(self, device_id): """ if device_id < 0 or device_id > soundio.get_input_device_count(): raise PySoundIoError('Invalid input device id') - self.input_device = soundio.get_input_device(device_id) - return self.input_device + self.input['device'] = soundio.get_input_device(device_id) + return self.input['device'] def get_default_output_device(self): """ @@ -212,8 +206,8 @@ def get_output_device(self, device_id): """ if device_id < 0 or device_id > soundio.get_output_device_count(): raise PySoundIoError('Invalid output device id') - self.output_device = soundio.get_output_device(device_id) - return self.output_device + self.output['device'] = soundio.get_output_device(device_id) + return self.output['device'] def list_devices(self): """ @@ -282,6 +276,7 @@ def get_default_sample_rate(self, device): ------- (int) The best available sample rate """ + sample_rate = None for rate in PRIORITISED_SAMPLE_RATES: if self.supports_sample_rate(device, rate): sample_rate = rate @@ -318,13 +313,13 @@ def get_default_format(self, device): ------ (SoundIoFormat) The best available format """ + dtype = soundio.SoundIoFormatInvalid for fmt in PRIORITISED_FORMATS: if self.supports_format(device, fmt): dtype = fmt break if dtype == soundio.SoundIoFormatInvalid: - pydevice = _ctypes.cast(device, _ctypes.POINTER(SoundIoDevice)) - dtype = pydevice.formats.contents + raise PySoundIoError('Incompatible sample formats') return dtype def sort_channel_layouts(self, device): @@ -397,36 +392,36 @@ def _create_input_ring_buffer(self, capacity): Creates ring buffer with the capacity to hold 30 seconds of data, by default. """ - self.input_buffer = soundio.input_ring_buffer_create(capacity) - return self.input_buffer + self.input['buffer'] = soundio.input_ring_buffer_create(capacity) + return self.input['buffer'] def _create_output_ring_buffer(self, capacity): """ Creates ring buffer with the capacity to hold 30 seconds of data, by default. """ - self.output_buffer = soundio.output_ring_buffer_create(capacity) - return self.output_buffer + self.output['buffer'] = soundio.output_ring_buffer_create(capacity) + return self.output['buffer'] def _create_input_stream(self): """ Allocates memory and sets defaults for input stream """ - self.input_stream = soundio.instream_create(self.input_device) + self.input['stream'] = soundio.instream_create(self.input['device']) - pyinstream = _ctypes.cast(self.input_stream, _ctypes.POINTER(SoundIoInStream)) - soundio.set_read_callback(self._read_callback) + pyinstream = _ctypes.cast(self.input['stream'], _ctypes.POINTER(SoundIoInStream)) + soundio.set_read_callbacks(self._read_callback, self._overflow_callback) - layout = self._get_default_layout(self.channels) + layout = self._get_default_layout(self.input['channels']) pylayout = _ctypes.cast(layout, _ctypes.POINTER(SoundIoChannelLayout)) pyinstream.contents.layout = pylayout.contents - pyinstream.contents.format = self.format - pyinstream.contents.sample_rate = self.sample_rate - if self.block_size: - pyinstream.contents.software_latency = float(self.block_size) / self.sample_rate + pyinstream.contents.format = self.input['format'] + pyinstream.contents.sample_rate = self.input['sample_rate'] + if self.input['block_size']: + pyinstream.contents.software_latency = float(self.input['block_size']) / self.input['sample_rate'] - return self.input_stream + return self.input['stream'] def _open_input_stream(self): """ @@ -468,27 +463,18 @@ def _read_callback(self): """ _InputProcessingThread(parent=self).start() - def _overflow_callback(self, stream): + def _overflow_callback(self): """ Internal overflow callback, which calls the external overflow callback if defined. """ - if self.overflow_callback: - self.overflow_callback(stream) - - def _error_callback(self, stream, err): - """ - Internal error callback, which calls the external - error callback if defined. - """ - if self.error_callback: - self.error_callback(stream, err) + if self.input['overflow_callback']: + self.input['overflow_callback']() def start_input_stream(self, device_id=None, sample_rate=None, dtype=None, block_size=None, channels=None, - read_callback=None, overflow_callback=None, - error_callback=None): + read_callback=None, overflow_callback=None): """ Creates input stream, and sets parameters. Then allocates a ring buffer and starts the stream. @@ -506,6 +492,7 @@ def start_input_stream(self, device_id=None, channels: (int) number of channels [1: mono, 2: stereo] (optional) read_callback: (fn) function to call with data, the function must have the arguments data and length. See record example + overflow_callback: (fn) function to call if data is not being read fast enough Raises ------ @@ -521,40 +508,39 @@ def start_input_stream(self, device_id=None, def read_callback(data, length): wav.write(data) """ - self.sample_rate = sample_rate - self.format = dtype - self.block_size = block_size - self.channels = channels - self.read_callback = read_callback - self.overflow_callback = overflow_callback - self.error_callback = error_callback + self.input['sample_rate'] = sample_rate + self.input['format'] = dtype + self.input['block_size'] = block_size + self.input['channels'] = channels + self.input['read_callback'] = read_callback + self.input['overflow_callback'] = overflow_callback if device_id: - self.input_device = self.get_input_device(device_id) + self.input['device'] = self.get_input_device(device_id) else: - self.input_device = self.get_default_input_device() + self.input['device'] = self.get_default_input_device() - pydevice = _ctypes.cast(self.input_device, _ctypes.POINTER(SoundIoDevice)) + pydevice = _ctypes.cast(self.input['device'], _ctypes.POINTER(SoundIoDevice)) LOGGER.info('Input Device: %s' % pydevice.contents.name.decode()) - self.sort_channel_layouts(self.input_device) + self.sort_channel_layouts(self.input['device']) - if self.sample_rate: - if not self.supports_sample_rate(self.input_device, self.sample_rate): - raise PySoundIoError('Invalid sample rate: %d' % self.sample_rate) + if self.input['sample_rate']: + if not self.supports_sample_rate(self.input['device'], self.input['sample_rate']): + raise PySoundIoError('Invalid sample rate: %d' % self.input['sample_rate']) else: - self.sample_rate = self.get_default_sample_rate(self.input_device) + self.input['sample_rate'] = self.get_default_sample_rate(self.input['device']) - if self.format: - if not self.supports_format(self.input_device, self.format): + if self.input['format']: + if not self.supports_format(self.input['device'], self.input['format']): raise PySoundIoError('Invalid format: %s interleaved' % - (soundio.format_string(self.format))) + (soundio.format_string(self.input['format']))) else: - self.format = self.get_default_format(self.input_device) + self.input['format'] = self.get_default_format(self.input['device']) self._create_input_stream() self._open_input_stream() - pystream = _ctypes.cast(self.input_stream, _ctypes.POINTER(SoundIoInStream)) - self.input_bytes_per_frame = pystream.contents.bytes_per_frame + pystream = _ctypes.cast(self.input['stream'], _ctypes.POINTER(SoundIoInStream)) + self.input['bytes_per_frame'] = pystream.contents.bytes_per_frame capacity = (DEFAULT_RING_BUFFER_DURATION * pystream.contents.sample_rate * pystream.contents.bytes_per_frame) self._create_input_ring_buffer(capacity) @@ -565,30 +551,30 @@ def _create_output_stream(self): """ Allocates memory and sets defaults for output stream """ - self.output_stream = soundio.outstream_create(self.output_device) + self.output['stream'] = soundio.outstream_create(self.output['device']) - pystream = _ctypes.cast(self.output_stream, _ctypes.POINTER(SoundIoOutStream)) + pystream = _ctypes.cast(self.output['stream'], _ctypes.POINTER(SoundIoOutStream)) if not self.testing: - soundio.set_write_callback(self._write_callback) + soundio.set_write_callbacks(self._write_callback, self._underflow_callback) - layout = self._get_default_layout(self.channels) + layout = self._get_default_layout(self.output['channels']) pylayout = _ctypes.cast(layout, _ctypes.POINTER(SoundIoChannelLayout)) pystream.contents.layout = pylayout.contents - pystream.contents.format = self.format - pystream.contents.sample_rate = self.sample_rate - if self.block_size: - pystream.contents.software_latency = float(self.block_size) / self.sample_rate + pystream.contents.format = self.output['format'] + pystream.contents.sample_rate = self.output['sample_rate'] + if self.output['block_size']: + pystream.contents.software_latency = float(self.output['block_size']) / self.output['sample_rate'] - return self.output_stream + return self.output['stream'] def _open_output_stream(self): """ Open an output stream. """ soundio.outstream_open() - pystream = _ctypes.cast(self.output_stream, _ctypes.POINTER(SoundIoOutStream)) - self.block_size = int(pystream.contents.software_latency / self.sample_rate) + pystream = _ctypes.cast(self.output['stream'], _ctypes.POINTER(SoundIoOutStream)) + self.output['block_size'] = int(pystream.contents.software_latency / self.output['sample_rate']) def _start_output_stream(self): """ @@ -612,20 +598,20 @@ def _write_callback(self, size): """ _OutputProcessingThread(parent=self, block_size=size).start() - def _underflow_callback(self, stream): + def _underflow_callback(self): """ Internal underflow callback, which calls the external underflow callback if defined. """ - if self.underflow_callback: - self.underflow_callback(stream) + if self.output['underflow_callback']: + self.output['underflow_callback']() def _clear_output_buffer(self): """ Clear the output buffer """ - if self.output_buffer: - soundio.ring_buffer_clear(self.output_buffer) + if self.output['buffer']: + soundio.ring_buffer_clear(self.output['buffer']) def get_output_latency(self, out_latency): """ @@ -641,7 +627,7 @@ def get_output_latency(self, out_latency): def start_output_stream(self, device_id=None, sample_rate=None, dtype=None, block_size=None, channels=None, - write_callback=None): + write_callback=None, underflow_callback=None): """ Creates output stream, and sets parameters. Then allocates a ring buffer and starts the stream. @@ -659,6 +645,7 @@ def start_output_stream(self, device_id=None, channels: (int) number of channels [1: mono, 2: stereo] (optional) write_callback: (fn) function to call with data, the function must have the arguments data and length. + underflow_callback: (fn) function to call if data is not being written fast enough Raises ------ @@ -677,38 +664,39 @@ def write_callback(data, length): outdata = 1.0 data[:] = outdata.tostring() """ - self.sample_rate = sample_rate - self.format = dtype - self.block_size = block_size - self.channels = channels - self.write_callback = write_callback + self.output['sample_rate'] = sample_rate + self.output['format'] = dtype + self.output['block_size'] = block_size + self.output['channels'] = channels + self.output['write_callback'] = write_callback + self.output['underflow_callback'] = underflow_callback if device_id: - self.output_device = self.get_output_device(device_id) + self.output['device'] = self.get_output_device(device_id) else: - self.output_device = self.get_default_output_device() + self.output['device'] = self.get_default_output_device() - pydevice = _ctypes.cast(self.output_device, _ctypes.POINTER(SoundIoDevice)) + pydevice = _ctypes.cast(self.output['device'], _ctypes.POINTER(SoundIoDevice)) LOGGER.info('Input Device: %s' % pydevice.contents.name.decode()) - self.sort_channel_layouts(self.output_device) + self.sort_channel_layouts(self.output['device']) - if self.sample_rate: - if not self.supports_sample_rate(self.output_device, self.sample_rate): - raise PySoundIoError('Invalid sample rate: %d' % self.sample_rate) + if self.output['sample_rate']: + if not self.supports_sample_rate(self.output['device'], self.output['sample_rate']): + raise PySoundIoError('Invalid sample rate: %d' % self.output['sample_rate']) else: - self.sample_rate = self.get_default_sample_rate(self.output_device) + self.output['sample_rate'] = self.get_default_sample_rate(self.output['device']) - if self.format: - if not self.supports_format(self.output_device, self.format): + if self.output['format']: + if not self.supports_format(self.output['device'], self.output['format']): raise PySoundIoError('Invalid format: %s interleaved' % - (soundio.format_string(self.format))) + (soundio.format_string(self.output['format']))) else: - self.format = self.get_default_format(self.output_device) + self.output['format'] = self.get_default_format(self.output['device']) self._create_output_stream() self._open_output_stream() - pystream = _ctypes.cast(self.output_stream, _ctypes.POINTER(SoundIoOutStream)) - self.output_bytes_per_frame = pystream.contents.bytes_per_frame + pystream = _ctypes.cast(self.output['stream'], _ctypes.POINTER(SoundIoOutStream)) + self.output['bytes_per_frame'] = pystream.contents.bytes_per_frame capacity = (DEFAULT_RING_BUFFER_DURATION * pystream.contents.sample_rate * pystream.contents.bytes_per_frame) self._create_output_ring_buffer(capacity) diff --git a/tests/test_pysoundio.py b/tests/test_pysoundio.py index ca4d1b5..8f2cf3d 100644 --- a/tests/test_pysoundio.py +++ b/tests/test_pysoundio.py @@ -20,14 +20,18 @@ def tearDownClass(cls): cls.sio.close() def setUp(self): - self.sio.channels = 2 - self.sio.sample_rate = 44100 - self.sio.format = pysoundio.SoundIoFormatFloat32LE - self.sio.block_size = None - self.sio.testing = True + self.sio.input['channels'] = 2 + self.sio.input['sample_rate'] = 44100 + self.sio.input['format'] = pysoundio.SoundIoFormatFloat32LE + self.sio.input['block_size'] = None + + self.sio.output['channels'] = 2 + self.sio.output['sample_rate'] = 44100 + self.sio.output['format'] = pysoundio.SoundIoFormatFloat32LE + self.sio.output['block_size'] = None + self.sio.testing = True self.callback_called = False - self.error_callback_called = False # - Device API @@ -77,6 +81,13 @@ def test_get_default_sample_rate(self): device = self.sio.get_input_device(0) self.assertIsInstance(self.sio.get_default_sample_rate(device), int) + def test_get_default_sample_rate_max(self): + sample_rates = pysoundio.pysoundio.PRIORITISED_SAMPLE_RATES + pysoundio.pysoundio.PRIORITISED_SAMPLE_RATES = [0] + device = self.sio.get_input_device(0) + self.assertIsInstance(self.sio.get_default_sample_rate(device), int) + pysoundio.pysoundio.PRIORITISED_SAMPLE_RATES = sample_rates + def test_supports_format(self): device = self.sio.get_input_device(0) self.assertTrue(self.sio.supports_format(device, pysoundio.SoundIoFormatFloat32LE)) @@ -85,6 +96,14 @@ def test_get_default_format(self): device = self.sio.get_input_device(0) self.assertIsInstance(self.sio.get_default_format(device), int) + def test_get_default_format_invalid(self): + formats = pysoundio.pysoundio.PRIORITISED_FORMATS + pysoundio.pysoundio.PRIORITISED_FORMATS = [] + device = self.sio.get_input_device(0) + with self.assertRaises(pysoundio.PySoundIoError): + self.assertIsInstance(self.sio.get_default_format(device), int) + pysoundio.pysoundio.PRIORITISED_FORMATS = formats + def test_get_default_layout(self): self.assertIsNotNone(self.sio._get_default_layout(2)) @@ -104,8 +123,8 @@ def test_bytes_per_second(self): def fill_input_buffer(self): data = bytearray(b'\x00' * 4096 * 8) - _soundiox.ring_buffer_write_ptr(self.sio.input_buffer, data, len(data)) - _soundiox.ring_buffer_advance_write_ptr(self.sio.input_buffer, len(data)) + _soundiox.ring_buffer_write_ptr(self.sio.input['buffer'], data, len(data)) + _soundiox.ring_buffer_advance_write_ptr(self.sio.input['buffer'], len(data)) def test__create_input_ring_buffer(self): capacity = 44100 * 8 @@ -130,29 +149,21 @@ def test_get_input_latency(self): self.fill_input_buffer() self.assertIsInstance(self.sio.get_input_latency(0.2), int) - def overflow_callback(self, stream): + def overflow_callback(self): self.callback_called = True - def error_callback(self, stream, err): - self.error_callback_called = True - def test_overflow_callback(self): - self.sio.overflow_callback = self.overflow_callback - self.sio._overflow_callback(None) + self.sio.input['overflow_callback'] = self.overflow_callback + self.sio._overflow_callback() self.assertTrue(self.callback_called) - def test_error_callback(self): - self.sio.error_callback = self.error_callback - self.sio._error_callback(None, None) - self.assertTrue(self.error_callback_called) - def test_start_input_stream(self): self.sio.start_input_stream( sample_rate=44100, dtype=pysoundio.SoundIoFormatFloat32LE, channels=2) self.fill_input_buffer() - self.assertIsNotNone(self.sio.input_stream) + self.assertIsNotNone(self.sio.input['stream']) def test_pause_input_stream(self): self.sio.start_input_stream( @@ -169,7 +180,7 @@ def test_start_input_stream_device(self): dtype=pysoundio.SoundIoFormatFloat32LE, channels=2) self.fill_input_buffer() - self.assertIsNotNone(self.sio.input_stream) + self.assertIsNotNone(self.sio.input['stream']) def test_start_input_invalid_rate(self): with self.assertRaises(pysoundio.PySoundIoError): @@ -183,8 +194,8 @@ def test_start_input_default_rate(self): dtype=pysoundio.SoundIoFormatFloat32LE, channels=2) self.fill_input_buffer() - self.assertIsNotNone(self.sio.input_stream) - self.assertIsInstance(self.sio.sample_rate, int) + self.assertIsNotNone(self.sio.input['stream']) + self.assertIsInstance(self.sio.input['sample_rate'], int) def test_start_input_invalid_format(self): with self.assertRaises(pysoundio.PySoundIoError): @@ -198,8 +209,8 @@ def test_start_input_default_format(self): sample_rate=44100, channels=2) self.fill_input_buffer() - self.assertIsNotNone(self.sio.input_stream) - self.assertIsInstance(self.sio.format, int) + self.assertIsNotNone(self.sio.input['stream']) + self.assertIsInstance(self.sio.input['format'], int) # -- Output Stream API @@ -229,12 +240,12 @@ def test_clear_output_buffer(self): self.assertIsNotNone(self.sio._create_output_ring_buffer(capacity)) self.sio._clear_output_buffer() - def underflow_callback(self, stream): + def underflow_callback(self): self.callback_called = True def test_underflow_callback(self): - self.sio.underflow_callback = self.underflow_callback - self.sio._underflow_callback(None) + self.sio.output['underflow_callback'] = self.underflow_callback + self.sio._underflow_callback() self.assertTrue(self.callback_called) def test_get_output_latency(self): @@ -256,7 +267,7 @@ def test_start_output_stream(self): sample_rate=44100, dtype=pysoundio.SoundIoFormatFloat32LE, channels=2) - self.assertIsNotNone(self.sio.output_stream) + self.assertIsNotNone(self.sio.output['stream']) def test_start_output_stream_device(self): self.sio.start_output_stream( @@ -264,7 +275,7 @@ def test_start_output_stream_device(self): sample_rate=44100, dtype=pysoundio.SoundIoFormatFloat32LE, channels=2) - self.assertIsNotNone(self.sio.output_stream) + self.assertIsNotNone(self.sio.output['stream']) def test_start_output_invalid_rate(self): with self.assertRaises(pysoundio.PySoundIoError): @@ -277,8 +288,8 @@ def test_start_output_default_rate(self): self.sio.start_output_stream( dtype=pysoundio.SoundIoFormatFloat32LE, channels=2) - self.assertIsNotNone(self.sio.output_stream) - self.assertIsInstance(self.sio.sample_rate, int) + self.assertIsNotNone(self.sio.output['stream']) + self.assertIsInstance(self.sio.output['sample_rate'], int) def test_start_output_invalid_format(self): with self.assertRaises(pysoundio.PySoundIoError): @@ -291,8 +302,8 @@ def test_start_output_default_format(self): self.sio.start_output_stream( sample_rate=44100, channels=2) - self.assertIsNotNone(self.sio.output_stream) - self.assertIsInstance(self.sio.format, int) + self.assertIsNotNone(self.sio.output['stream']) + self.assertIsInstance(self.sio.output['format'], int) class TestInputProcessing(unittest.TestCase): @@ -315,11 +326,11 @@ def test_read_callback(self): channels=2, block_size=4096, read_callback=self.callback) - self.assertIsNotNone(self.sio.input_stream) + self.assertIsNotNone(self.sio.input['stream']) data = bytearray(b'\x00' * 4096 * 8) - _soundiox.ring_buffer_write_ptr(self.sio.input_buffer, data, len(data)) - _soundiox.ring_buffer_advance_write_ptr(self.sio.input_buffer, len(data)) + _soundiox.ring_buffer_write_ptr(self.sio.input['buffer'], data, len(data)) + _soundiox.ring_buffer_advance_write_ptr(self.sio.input['buffer'], len(data)) thread = pysoundio.pysoundio._InputProcessingThread(parent=self.sio) thread.run() @@ -347,7 +358,7 @@ def test_write_callback(self): channels=2, block_size=4096, write_callback=self.callback) - self.assertIsNotNone(self.sio.output_stream) + self.assertIsNotNone(self.sio.output['stream']) thread = pysoundio.pysoundio._OutputProcessingThread(parent=self.sio, block_size=4096) thread.run() self.assertTrue(self.callback_called) diff --git a/tests/test_soundiox.py b/tests/test_soundiox.py index cf6a899..b594b15 100644 --- a/tests/test_soundiox.py +++ b/tests/test_soundiox.py @@ -141,7 +141,7 @@ def callback(self): pass def setup_stream(self): - soundio.set_read_callback(self.callback) + soundio.set_read_callbacks(self.callback, self.callback) self.instream = soundio.instream_create(self.device) instream = ctypes.cast(self.instream, ctypes.POINTER(pysoundio.SoundIoInStream)) instream.contents.format = soundio.SoundIoFormatFloat32LE