Big change to new stream configuration. Possibility to include output channels back to input
This commit is contained in:
parent
b17178c4a7
commit
287b0cfe83
@ -76,7 +76,8 @@ else()
|
|||||||
set(win32 false)
|
set(win32 false)
|
||||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC -std=c11 \
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC -std=c11 \
|
||||||
-Werror=incompatible-pointer-types")
|
-Werror=incompatible-pointer-types")
|
||||||
|
include_directories(/usr/local/include/rtaudio)
|
||||||
|
link_directories(/usr/local/lib)
|
||||||
|
|
||||||
|
|
||||||
endif(CMAKE_SYSTEM_NAME STREQUAL "Windows")
|
endif(CMAKE_SYSTEM_NAME STREQUAL "Windows")
|
||||||
|
@ -6,3 +6,4 @@ from .lasp_octavefilter import *
|
|||||||
from .lasp_slm import *
|
from .lasp_slm import *
|
||||||
from .lasp_weighcal import *
|
from .lasp_weighcal import *
|
||||||
from .wrappers import *
|
from .wrappers import *
|
||||||
|
from .device import AvType
|
||||||
|
76
lasp/c/lasp_pyarray.h
Normal file
76
lasp/c/lasp_pyarray.h
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
// ascee_python.h
|
||||||
|
//
|
||||||
|
// Author: J.A. de Jong - ASCEE
|
||||||
|
//
|
||||||
|
// Description:
|
||||||
|
// Routine to generate a Numpy array from an arbitrary buffer. Careful, this
|
||||||
|
// code should both be C and C++ compatible!!
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
#pragma once
|
||||||
|
#ifndef LASP_PYARRAY_H
|
||||||
|
#define LASP_PYARRAY_H
|
||||||
|
|
||||||
|
#define TRACERPLUS (-10)
|
||||||
|
#include <numpy/ndarrayobject.h>
|
||||||
|
#ifdef LASP_DOUBLE_PRECISION
|
||||||
|
#define LASP_NUMPY_FLOAT_TYPE NPY_FLOAT64
|
||||||
|
#define LASP_NUMPY_COMPLEX_TYPE NPY_COMPLEX128
|
||||||
|
#else
|
||||||
|
#define LASP_NUMPY_FLOAT_TYPE NPY_FLOAT32
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef MS_WIN64
|
||||||
|
/**
|
||||||
|
* Function passed to Python to use for cleanup of
|
||||||
|
* foreignly obtained data.
|
||||||
|
**/
|
||||||
|
static inline void capsule_cleanup(void *capsule) {
|
||||||
|
void *memory = PyCapsule_GetPointer(capsule, NULL);
|
||||||
|
free(memory);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static inline PyObject *data_to_ndarray(void *data, int n_rows, int n_cols,
|
||||||
|
int typenum, bool transfer_ownership,
|
||||||
|
bool F_contiguous) {
|
||||||
|
|
||||||
|
/* fprintf(stderr, "Enter data_to_ndarray\n"); */
|
||||||
|
assert(data);
|
||||||
|
import_array();
|
||||||
|
|
||||||
|
npy_intp dims[2] = {n_rows, n_cols};
|
||||||
|
assert(n_rows > 0);
|
||||||
|
assert(n_cols > 0);
|
||||||
|
|
||||||
|
PyArrayObject *arr =
|
||||||
|
(PyArrayObject *)PyArray_SimpleNewFromData(2, dims, typenum, data);
|
||||||
|
|
||||||
|
if (!arr) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (F_contiguous) {
|
||||||
|
PyArray_ENABLEFLAGS(arr, NPY_ARRAY_F_CONTIGUOUS);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (transfer_ownership == true) {
|
||||||
|
#ifdef MS_WIN64
|
||||||
|
// The default destructor of Python cannot free the data, as it is allocated
|
||||||
|
// with malloc. Therefore, with this code, we tell Numpy/Python to use
|
||||||
|
// the capsule_cleanup constructor. See:
|
||||||
|
// https://stackoverflow.com/questions/54269956/crash-of-jupyter-due-to-the-use-of-pyarray-enableflags/54278170#54278170
|
||||||
|
// Note that in general it was disadvised to build all C code with MinGW on
|
||||||
|
// Windows. We do it anyway, see if we find any problems on the way.
|
||||||
|
void *capsule = PyCapsule_New(mat->_data, NULL, capsule_cleanup);
|
||||||
|
PyArray_SetBaseObject(arr, capsule);
|
||||||
|
#endif
|
||||||
|
/* fprintf(stderr, "============Ownership transfer================\n"); */
|
||||||
|
PyArray_ENABLEFLAGS(arr, NPY_OWNDATA);
|
||||||
|
}
|
||||||
|
/* fprintf(stderr, "Exit data_to_ndarray\n"); */
|
||||||
|
|
||||||
|
return (PyObject *) arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // LASP_PYARRAY_H
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
@ -9,25 +9,8 @@
|
|||||||
#ifndef LASP_PYTHON_H
|
#ifndef LASP_PYTHON_H
|
||||||
#define LASP_PYTHON_H
|
#define LASP_PYTHON_H
|
||||||
#define TRACERPLUS (-10)
|
#define TRACERPLUS (-10)
|
||||||
#include <numpy/ndarrayobject.h>
|
#include "lasp_pyarray.h"
|
||||||
#ifdef LASP_DOUBLE_PRECISION
|
|
||||||
#define LASP_NUMPY_FLOAT_TYPE NPY_FLOAT64
|
|
||||||
#define LASP_NUMPY_COMPLEX_TYPE NPY_COMPLEX128
|
|
||||||
#else
|
|
||||||
#define LASP_NUMPY_FLOAT_TYPE NPY_FLOAT32
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef MS_WIN64
|
|
||||||
/**
|
|
||||||
* Function passed to Python to use for cleanup of
|
|
||||||
* foreignly obtained data.
|
|
||||||
**/
|
|
||||||
static inline void capsule_cleanup(void* capsule) {
|
|
||||||
void *memory = PyCapsule_GetPointer(capsule, NULL);
|
|
||||||
free(memory);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
/**
|
/**
|
||||||
* Create a numpy array from an existing dmat.
|
* Create a numpy array from an existing dmat.
|
||||||
*
|
*
|
||||||
@ -38,50 +21,31 @@ static inline void capsule_cleanup(void* capsule) {
|
|||||||
* @return Numpy array
|
* @return Numpy array
|
||||||
*/
|
*/
|
||||||
static inline PyObject* dmat_to_ndarray(dmat* mat,bool transfer_ownership) {
|
static inline PyObject* dmat_to_ndarray(dmat* mat,bool transfer_ownership) {
|
||||||
fsTRACE(15);
|
|
||||||
dbgassert(mat,NULLPTRDEREF);
|
dbgassert(mat,NULLPTRDEREF);
|
||||||
|
dbgassert(mat->_data,NULLPTRDEREF);
|
||||||
|
/* fprintf(stderr, "Enter dmat_to_ndarray\n"); */
|
||||||
|
|
||||||
import_array();
|
|
||||||
|
|
||||||
// Dimensions given in wrong order, as mat is
|
// Dimensions given in wrong order, as mat is
|
||||||
// Fortran-contiguous. Later on we transpose the result. This is
|
// Fortran-contiguous. Later on we transpose the result. This is
|
||||||
// more easy than using the PyArray_New syntax.
|
// more easy than using the PyArray_New syntax.
|
||||||
npy_intp dims[] = {mat->n_cols,mat->n_rows};
|
PyObject* arr = data_to_ndarray(mat->_data,
|
||||||
PyObject* arr_t = PyArray_SimpleNewFromData(2,dims,
|
mat->n_rows,
|
||||||
LASP_NUMPY_FLOAT_TYPE,
|
mat->n_cols,
|
||||||
mat->_data);
|
LASP_NUMPY_FLOAT_TYPE,
|
||||||
if(!arr_t) {
|
transfer_ownership,
|
||||||
|
true); // Fortran-contiguous
|
||||||
|
|
||||||
|
if(transfer_ownership) {
|
||||||
|
mat->_foreign_data = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!arr) {
|
||||||
WARN("Array creation failure");
|
WARN("Array creation failure");
|
||||||
feTRACE(15);
|
feTRACE(15);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
/* fprintf(stderr, "Exit dmat_to_ndarray\n"); */
|
||||||
|
|
||||||
if(transfer_ownership) {
|
|
||||||
mat->_foreign_data = true;
|
|
||||||
#ifdef MS_WIN64
|
|
||||||
// The default destructor of Python cannot free the data, as it is allocated
|
|
||||||
// with malloc. Therefore, with this code, we tell Numpy/Python to use
|
|
||||||
// the capsule_cleanup constructor. See:
|
|
||||||
// https://stackoverflow.com/questions/54269956/crash-of-jupyter-due-to-the-use-of-pyarray-enableflags/54278170#54278170
|
|
||||||
// Note that in general it was disadvised to build all C code with MinGW on Windows.
|
|
||||||
// We do it anyway, see if we find any problems on the way.
|
|
||||||
void* capsule = PyCapsule_New(mat->_data, NULL, capsule_cleanup);
|
|
||||||
PyArray_SetBaseObject( arr_t, capsule);
|
|
||||||
#else
|
|
||||||
PyArray_ENABLEFLAGS(arr_t, NPY_OWNDATA);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transpose the array
|
|
||||||
PyObject* arr = PyArray_Transpose((PyArrayObject*) arr_t,NULL);
|
|
||||||
if(!arr) {
|
|
||||||
WARN("Array transpose failure");
|
|
||||||
feTRACE(15);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
Py_DECREF(arr_t);
|
|
||||||
|
|
||||||
feTRACE(15);
|
|
||||||
return arr;
|
return arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,3 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/python3
|
||||||
__all__ = ['DAQConfiguration']
|
from .lasp_rtaudio import *
|
||||||
from .lasp_daqconfig import DAQConfiguration, DAQInputChannel, DeviceInfo
|
from .lasp_daqconfig import *
|
||||||
from .lasp_rtaudio import (RtAudio,
|
|
||||||
get_numpy_dtype_from_format_string,
|
|
||||||
get_sampwidth_from_format_string)
|
|
||||||
|
@ -9,11 +9,14 @@ Data Acquistiion (DAQ) device descriptors, and the DAQ devices themselves
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
|
from dataclasses_json import dataclass_json
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
from ..lasp_common import lasp_shelve, Qty
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class DeviceInfo:
|
class DeviceInfo:
|
||||||
|
api: int
|
||||||
index: int
|
index: int
|
||||||
probed: bool
|
probed: bool
|
||||||
name: str
|
name: str
|
||||||
@ -25,17 +28,17 @@ class DeviceInfo:
|
|||||||
prefsamplerate: int
|
prefsamplerate: int
|
||||||
|
|
||||||
|
|
||||||
from ..lasp_common import lasp_shelve
|
|
||||||
|
|
||||||
|
@dataclass_json
|
||||||
@dataclass
|
@dataclass
|
||||||
class DAQInputChannel:
|
class DAQChannel:
|
||||||
channel_enabled: bool
|
channel_enabled: bool
|
||||||
channel_name: str
|
channel_name: str
|
||||||
sensitivity: float
|
sensitivity: float
|
||||||
|
unit: Qty
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass_json
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class DAQConfiguration:
|
class DAQConfiguration:
|
||||||
"""
|
"""
|
||||||
@ -44,6 +47,9 @@ class DAQConfiguration:
|
|||||||
Args:
|
Args:
|
||||||
duplex_mode: Set device to duplex mode, if possible
|
duplex_mode: Set device to duplex mode, if possible
|
||||||
monitor_gen: If set to true, add monitor channel to recording.
|
monitor_gen: If set to true, add monitor channel to recording.
|
||||||
|
outputDelayBlocks: number of blocks to delay output stream when added
|
||||||
|
to input for monitoring the output synchronously with input.
|
||||||
|
|
||||||
input_device_name: ASCII name with which to open the device when connected
|
input_device_name: ASCII name with which to open the device when connected
|
||||||
outut_device_name: ASCII name with which to open the device when connected
|
outut_device_name: ASCII name with which to open the device when connected
|
||||||
|
|
||||||
@ -66,6 +72,7 @@ class DAQConfiguration:
|
|||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
api: int
|
||||||
duplex_mode: bool
|
duplex_mode: bool
|
||||||
|
|
||||||
input_device_name: str
|
input_device_name: str
|
||||||
@ -76,28 +83,12 @@ class DAQConfiguration:
|
|||||||
en_input_rate: int
|
en_input_rate: int
|
||||||
en_output_rate: int
|
en_output_rate: int
|
||||||
|
|
||||||
input_channel_configs: list = None
|
input_channel_configs: list
|
||||||
monitor_gen: bool = False
|
output_channel_configs: list
|
||||||
|
monitor_gen: bool
|
||||||
|
|
||||||
|
outputDelayBlocks: int
|
||||||
def __post_init__(self):
|
nFramesPerBlock: int
|
||||||
"""
|
|
||||||
We do a check here to see whether the list of enabled channels is
|
|
||||||
contiguous. Non-contiguous is not yet implemented in RtAudio backend.
|
|
||||||
"""
|
|
||||||
en_input = self.input_channel_configs
|
|
||||||
first_ch_enabled_found = False
|
|
||||||
ch_disabled_found_after = False
|
|
||||||
for ch in en_input:
|
|
||||||
if ch.channel_enabled:
|
|
||||||
first_ch_enabled_found = True
|
|
||||||
if ch_disabled_found_after:
|
|
||||||
raise ValueError('Error: non-contiguous array of channels'
|
|
||||||
' found. This is not yet implemented in'
|
|
||||||
' backend')
|
|
||||||
else:
|
|
||||||
if first_ch_enabled_found:
|
|
||||||
ch_disabled_found_after = True
|
|
||||||
|
|
||||||
def firstEnabledInputChannelNumber(self):
|
def firstEnabledInputChannelNumber(self):
|
||||||
"""
|
"""
|
||||||
@ -109,18 +100,55 @@ class DAQConfiguration:
|
|||||||
return i
|
return i
|
||||||
return -1
|
return -1
|
||||||
|
|
||||||
|
def firstEnabledOutputChannelNumber(self):
|
||||||
|
"""
|
||||||
|
Returns the channel number of the first enabled output channel. Returns -1 if
|
||||||
|
no channels are enabled.
|
||||||
|
"""
|
||||||
|
for i, ch in enumerate(self.output_channel_configs):
|
||||||
|
if ch.channel_enabled:
|
||||||
|
return i
|
||||||
|
return -1
|
||||||
|
|
||||||
def getEnabledChannels(self):
|
def lastEnabledInputChannelNumber(self):
|
||||||
|
last = -1
|
||||||
|
for i, ch in enumerate(self.input_channel_configs):
|
||||||
|
if ch.channel_enabled:
|
||||||
|
last = i
|
||||||
|
return last
|
||||||
|
|
||||||
|
def lastEnabledOutputChannelNumber(self):
|
||||||
|
last = -1
|
||||||
|
for i, ch in enumerate(self.output_channel_configs):
|
||||||
|
print(ch)
|
||||||
|
if ch.channel_enabled:
|
||||||
|
last = i
|
||||||
|
return last
|
||||||
|
|
||||||
|
def getEnabledInputChannels(self):
|
||||||
en_channels = []
|
en_channels = []
|
||||||
for chan in self.input_channel_configs:
|
for chan in self.input_channel_configs:
|
||||||
if chan.channel_enabled:
|
if chan.channel_enabled:
|
||||||
en_channels.append(chan)
|
en_channels.append(chan)
|
||||||
return en_channels
|
return en_channels
|
||||||
|
|
||||||
def getEnabledChannelSensitivities(self):
|
def getEnabledInputChannelNames(self):
|
||||||
return np.array(
|
return [ch.channel_name for ch in self.getEnabledInputChannels()]
|
||||||
[float(channel.sensitivity) for channel in
|
|
||||||
self.getEnabledChannels()])
|
def getEnabledInputChannelSensitivities(self):
|
||||||
|
return [float(channel.sensitivity) for channel in
|
||||||
|
self.getEnabledInputChannels()]
|
||||||
|
|
||||||
|
def getEnabledOutputChannels(self):
|
||||||
|
en_channels = []
|
||||||
|
for chan in self.output_channel_configs:
|
||||||
|
if chan.channel_enabled:
|
||||||
|
en_channels.append(chan)
|
||||||
|
return en_channels
|
||||||
|
|
||||||
|
def getEnabledOutputChannelSensitivities(self):
|
||||||
|
return [float(channel.sensitivity) for channel in
|
||||||
|
self.getEnabledOutputChannels()]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def loadConfigs():
|
def loadConfigs():
|
||||||
|
@ -9,6 +9,15 @@ from libc.stdio cimport printf, fprintf, stderr
|
|||||||
from libc.string cimport memcpy, memset
|
from libc.string cimport memcpy, memset
|
||||||
from cpython.ref cimport PyObject,Py_INCREF, Py_DECREF
|
from cpython.ref cimport PyObject,Py_INCREF, Py_DECREF
|
||||||
|
|
||||||
|
__all__ = ['AvType', 'RtAudio', 'get_numpy_dtype_from_format_string',
|
||||||
|
'get_sampwidth_from_format_string']
|
||||||
|
|
||||||
|
class AvType:
|
||||||
|
"""Specificying the type of data, for adding and removing callbacks from
|
||||||
|
the stream."""
|
||||||
|
audio_input = 1
|
||||||
|
audio_output = 2
|
||||||
|
video = 4
|
||||||
|
|
||||||
cdef extern from "RtAudio.h" nogil:
|
cdef extern from "RtAudio.h" nogil:
|
||||||
ctypedef unsigned long RtAudioStreamStatus
|
ctypedef unsigned long RtAudioStreamStatus
|
||||||
@ -47,7 +56,7 @@ cdef extern from "RtAudio.h" nogil:
|
|||||||
|
|
||||||
ctypedef int (*RtAudioCallback)(void* outputBuffer,
|
ctypedef int (*RtAudioCallback)(void* outputBuffer,
|
||||||
void* inputBuffer,
|
void* inputBuffer,
|
||||||
unsigned int nFrames,
|
unsigned int nFramesPerBlock,
|
||||||
double streamTime,
|
double streamTime,
|
||||||
RtAudioStreamStatus status,
|
RtAudioStreamStatus status,
|
||||||
void* userData)
|
void* userData)
|
||||||
@ -56,6 +65,15 @@ cdef extern from "RtAudio.h" nogil:
|
|||||||
const string& errortxt)
|
const string& errortxt)
|
||||||
|
|
||||||
cdef cppclass cppRtAudio "RtAudio":
|
cdef cppclass cppRtAudio "RtAudio":
|
||||||
|
enum Api:
|
||||||
|
UNSPECIFIED
|
||||||
|
LINUX_ALSA
|
||||||
|
LINUX_PULSE
|
||||||
|
MACOSX_CORE
|
||||||
|
WINDOWS_WASAPI
|
||||||
|
WINDOWS_ASIO
|
||||||
|
WINDOWS_DS
|
||||||
|
|
||||||
cppclass DeviceInfo:
|
cppclass DeviceInfo:
|
||||||
bool probed
|
bool probed
|
||||||
string name
|
string name
|
||||||
@ -69,17 +87,35 @@ cdef extern from "RtAudio.h" nogil:
|
|||||||
RtAudioFormat nativeFormats
|
RtAudioFormat nativeFormats
|
||||||
|
|
||||||
cppclass StreamOptions:
|
cppclass StreamOptions:
|
||||||
|
StreamOptions()
|
||||||
RtAudioStreamFlags flags
|
RtAudioStreamFlags flags
|
||||||
unsigned int numberOfBuffers
|
unsigned int numberOfBuffers
|
||||||
string streamName
|
string streamName
|
||||||
int priority
|
int priority
|
||||||
cppclass StreamParameters:
|
cppclass StreamParameters:
|
||||||
|
StreamParameters()
|
||||||
unsigned int deviceId
|
unsigned int deviceId
|
||||||
unsigned int nChannels
|
unsigned int nChannels
|
||||||
unsigned int firstChannel
|
unsigned int firstChannel
|
||||||
|
|
||||||
RtAudio() except +
|
@staticmethod
|
||||||
|
void getCompiledApi(vector[cppRtAudio.Api]& apis)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
cppRtAudio.Api getCompiledApiByName(string& name)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
string getApiDisplayName(cppRtAudio.Api api)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
string getApiName(cppRtAudio.Api api)
|
||||||
|
|
||||||
|
# RtAudio() except +
|
||||||
|
cppRtAudio() except +
|
||||||
|
cppRtAudio(cppRtAudio.Api api) except +
|
||||||
|
|
||||||
# ~RtAudio() Destructors should not be listed
|
# ~RtAudio() Destructors should not be listed
|
||||||
|
|
||||||
unsigned int getDeviceCount()
|
unsigned int getDeviceCount()
|
||||||
DeviceInfo getDeviceInfo(unsigned int device)
|
DeviceInfo getDeviceInfo(unsigned int device)
|
||||||
unsigned int getDefaultOutputDevice()
|
unsigned int getDefaultOutputDevice()
|
||||||
@ -126,6 +162,13 @@ cdef extern from "atomic" namespace "std" nogil:
|
|||||||
T load()
|
T load()
|
||||||
void store(T)
|
void store(T)
|
||||||
|
|
||||||
|
cdef extern from "lasp_pyarray.h":
|
||||||
|
PyObject* data_to_ndarray(void* data,
|
||||||
|
int n_rows,int n_cols,
|
||||||
|
int typenum,
|
||||||
|
bool transfer_ownership,
|
||||||
|
bool F_contiguous)
|
||||||
|
|
||||||
_formats_strkey = {
|
_formats_strkey = {
|
||||||
'8-bit integers': (RTAUDIO_SINT8, 1, np.int8),
|
'8-bit integers': (RTAUDIO_SINT8, 1, np.int8),
|
||||||
'16-bit integers': (RTAUDIO_SINT16, 2, np.int16),
|
'16-bit integers': (RTAUDIO_SINT16, 2, np.int16),
|
||||||
@ -135,10 +178,10 @@ _formats_strkey = {
|
|||||||
'64-bit floats': (RTAUDIO_FLOAT64, 8, np.float64),
|
'64-bit floats': (RTAUDIO_FLOAT64, 8, np.float64),
|
||||||
}
|
}
|
||||||
_formats_rtkey = {
|
_formats_rtkey = {
|
||||||
RTAUDIO_SINT8: ('8-bit integers', 1, cnp.NPY_INT8),
|
RTAUDIO_SINT8: ('8-bit integers', 1, cnp.NPY_INT8),
|
||||||
RTAUDIO_SINT16: ('16-bit integers',2, cnp.NPY_INT16),
|
RTAUDIO_SINT16: ('16-bit integers', 2, cnp.NPY_INT16),
|
||||||
RTAUDIO_SINT24: ('24-bit integers',3),
|
RTAUDIO_SINT24: ('24-bit integers', 3),
|
||||||
RTAUDIO_SINT32: ('32-bit integers',4, cnp.NPY_INT32),
|
RTAUDIO_SINT32: ('32-bit integers', 4, cnp.NPY_INT32),
|
||||||
RTAUDIO_FLOAT32: ('32-bit floats', 4, cnp.NPY_FLOAT32),
|
RTAUDIO_FLOAT32: ('32-bit floats', 4, cnp.NPY_FLOAT32),
|
||||||
RTAUDIO_FLOAT64: ('64-bit floats', 8, cnp.NPY_FLOAT64),
|
RTAUDIO_FLOAT64: ('64-bit floats', 8, cnp.NPY_FLOAT64),
|
||||||
}
|
}
|
||||||
@ -153,52 +196,60 @@ def get_sampwidth_from_format_string(format_string):
|
|||||||
# It took me quite a long time to fully understand Cython's idiosyncrasies
|
# It took me quite a long time to fully understand Cython's idiosyncrasies
|
||||||
# concerning C(++) callbacks, the GIL and passing Python objects as pointers
|
# concerning C(++) callbacks, the GIL and passing Python objects as pointers
|
||||||
# into C(++) functions. But finally, here it is!
|
# into C(++) functions. But finally, here it is!
|
||||||
cdef object fromBufferToNPYNoCopy(
|
|
||||||
cnp.NPY_TYPES buffer_format_type,
|
|
||||||
void* buf,
|
|
||||||
size_t nchannels,
|
|
||||||
size_t nframes):
|
|
||||||
cdef cnp.npy_intp[2] dims = [nframes, nchannels]
|
|
||||||
|
|
||||||
# Interleaved data is C-style contiguous. Therefore, we can directly use
|
|
||||||
# SimpleNewFromData()
|
|
||||||
array = cnp.PyArray_SimpleNewFromData(2, &dims[0], buffer_format_type,
|
|
||||||
buf)
|
|
||||||
|
|
||||||
return array
|
# cdef void fromNPYToBuffer(cnp.ndarray arr,
|
||||||
|
# void* buf):
|
||||||
|
# """
|
||||||
|
# Copy a Python numpy array over to a buffer
|
||||||
|
# No checks, just memcpy! Careful!
|
||||||
|
# """
|
||||||
|
# memcpy(buf, arr.data, arr.size*arr.itemsize)
|
||||||
|
|
||||||
|
cdef void copyChannel(void* to, void* from_,
|
||||||
|
unsigned bytesperchan,
|
||||||
|
unsigned toindex,
|
||||||
|
unsigned fromindex) nogil:
|
||||||
|
memcpy(<void*> &((<char*> to)[bytesperchan*toindex]),
|
||||||
|
<void*> &((<char*> from_)[bytesperchan*fromindex]),
|
||||||
|
bytesperchan)
|
||||||
|
|
||||||
cdef void fromNPYToBuffer(cnp.ndarray arr,
|
|
||||||
void* buf):
|
|
||||||
"""
|
|
||||||
Copy a Python numpy array over to a buffer
|
|
||||||
No checks, just memcpy! Careful!
|
|
||||||
"""
|
|
||||||
memcpy(buf, arr.data, arr.size*arr.itemsize)
|
|
||||||
|
|
||||||
ctypedef struct _Stream:
|
ctypedef struct _Stream:
|
||||||
PyObject* pyCallback
|
PyObject* pyCallback
|
||||||
RtAudioFormat sampleformat
|
RtAudioFormat sampleformat
|
||||||
|
|
||||||
|
# Flag used to pass the stopThread.
|
||||||
atomic[bool] stopThread
|
atomic[bool] stopThread
|
||||||
|
|
||||||
unsigned int nFrames
|
# Number of frames per block
|
||||||
|
unsigned nFramesPerBlock
|
||||||
|
# Number of bytes per channel
|
||||||
|
unsigned int nBytesPerChan
|
||||||
|
|
||||||
|
# Number of blocks to delay the output before adding to the input
|
||||||
|
unsigned int outputDelayBlocks
|
||||||
|
|
||||||
|
# The structures as used by RtAudio
|
||||||
cppRtAudio.StreamParameters inputParams
|
cppRtAudio.StreamParameters inputParams
|
||||||
cppRtAudio.StreamParameters outputParams
|
cppRtAudio.StreamParameters outputParams
|
||||||
|
|
||||||
|
bool* inputChannelsEnabled
|
||||||
|
bool* outputChannelsEnabled
|
||||||
|
|
||||||
|
unsigned ninputchannels_forwarded
|
||||||
|
unsigned noutputchannels_forwarded
|
||||||
|
|
||||||
# If these queue pointers are NULL, it means the stream does not have an
|
# If these queue pointers are NULL, it means the stream does not have an
|
||||||
# input, or output.
|
# input, or output.
|
||||||
|
SafeQueue[void*] *outputDelayQueue
|
||||||
SafeQueue[void*] *inputQueue
|
SafeQueue[void*] *inputQueue
|
||||||
SafeQueue[void*] *outputQueue
|
SafeQueue[void*] *outputQueue
|
||||||
size_t inputbuffersize # Full size of the output buffer, in BYTES
|
|
||||||
size_t outputbuffersize # Full size of the output buffer, in BYTES
|
|
||||||
CPPThread[void*, void (*)(void*)] *thread
|
CPPThread[void*, void (*)(void*)] *thread
|
||||||
|
|
||||||
|
|
||||||
cdef int audioCallback(void* outputbuffer,
|
cdef int audioCallback(void* outputbuffer,
|
||||||
void* inputbuffer,
|
void* inputbuffer,
|
||||||
unsigned int nFrames,
|
unsigned int nFramesPerBlock,
|
||||||
double streamTime,
|
double streamTime,
|
||||||
RtAudioStreamStatus status,
|
RtAudioStreamStatus status,
|
||||||
void* userData) nogil:
|
void* userData) nogil:
|
||||||
@ -211,8 +262,22 @@ cdef int audioCallback(void* outputbuffer,
|
|||||||
_Stream* stream
|
_Stream* stream
|
||||||
void* outputbuffercpy = NULL
|
void* outputbuffercpy = NULL
|
||||||
void* inputbuffercpy = NULL
|
void* inputbuffercpy = NULL
|
||||||
|
unsigned j, i
|
||||||
|
unsigned bytesperchan
|
||||||
|
unsigned noutputchannels_forwarded, ninputchannels_forwarded
|
||||||
|
bint ch_en
|
||||||
|
|
||||||
stream = <_Stream*>(userData)
|
stream = <_Stream*>(userData)
|
||||||
|
bytesperchan = stream.nBytesPerChan
|
||||||
|
ninputchannels_forwarded = stream.ninputchannels_forwarded
|
||||||
|
noutputchannels_forwarded = stream.noutputchannels_forwarded
|
||||||
|
|
||||||
|
# with gil:
|
||||||
|
# print(f'bytesperchan: {bytesperchan}')
|
||||||
|
# print(f'ninputchannels_forwarded:: {ninputchannels_forwarded}')
|
||||||
|
# print(f'noutputchannels_forwarded:: {noutputchannels_forwarded}')
|
||||||
|
# fprintf(stderr, "Stream heartbeat...\n")
|
||||||
|
|
||||||
|
|
||||||
# Returning 2 means aborting the stream immediately
|
# Returning 2 means aborting the stream immediately
|
||||||
if status == RTAUDIO_INPUT_OVERFLOW:
|
if status == RTAUDIO_INPUT_OVERFLOW:
|
||||||
@ -224,35 +289,78 @@ cdef int audioCallback(void* outputbuffer,
|
|||||||
# stream.stopThread.store(True)
|
# stream.stopThread.store(True)
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
if nFrames != stream.nFrames:
|
if nFramesPerBlock != stream.nFramesPerBlock:
|
||||||
printf('Number of frames mismath in callback data!\n')
|
printf('Number of frames mismath in callback data!\n')
|
||||||
stream.stopThread.store(True)
|
stream.stopThread.store(True)
|
||||||
return 2
|
return 2
|
||||||
|
|
||||||
if inputbuffer:
|
if inputbuffer:
|
||||||
# assert stream.inputQueue is not NULL
|
# fprintf(stderr, "enter copying input buffer code\n")
|
||||||
inputbuffercpy = malloc(stream.inputbuffersize)
|
# with gil:
|
||||||
memcpy(inputbuffercpy, inputbuffer,
|
# assert stream.inputQueue is not NULL
|
||||||
stream.inputbuffersize)
|
inputbuffercpy = malloc(bytesperchan*ninputchannels_forwarded)
|
||||||
|
if not inputbuffercpy:
|
||||||
|
fprintf(stderr, "Error allocating buffer\n")
|
||||||
|
return 2
|
||||||
|
|
||||||
|
if stream.outputDelayQueue:
|
||||||
|
if stream.outputDelayQueue.size() > stream.outputDelayBlocks:
|
||||||
|
outputbuffercpy = stream.outputDelayQueue.dequeue()
|
||||||
|
memcpy(inputbuffercpy, outputbuffercpy,
|
||||||
|
bytesperchan*noutputchannels_forwarded)
|
||||||
|
|
||||||
|
# Cleanup buffer
|
||||||
|
free(outputbuffercpy)
|
||||||
|
outputbuffercpy = NULL
|
||||||
|
else:
|
||||||
|
memset(inputbuffercpy, 0,
|
||||||
|
bytesperchan*noutputchannels_forwarded)
|
||||||
|
|
||||||
|
# Starting channel for copying input channels according to the channel
|
||||||
|
# map
|
||||||
|
j = stream.noutputchannels_forwarded
|
||||||
|
else:
|
||||||
|
j = 0
|
||||||
|
i = 0
|
||||||
|
for i in range(stream.inputParams.nChannels):
|
||||||
|
ch_en = stream.inputChannelsEnabled[i]
|
||||||
|
if ch_en:
|
||||||
|
copyChannel(inputbuffercpy, inputbuffer, bytesperchan, j, i)
|
||||||
|
j+=1
|
||||||
|
i+=1
|
||||||
|
|
||||||
stream.inputQueue.enqueue(inputbuffercpy)
|
stream.inputQueue.enqueue(inputbuffercpy)
|
||||||
|
|
||||||
if outputbuffer:
|
if outputbuffer:
|
||||||
# assert stream.outputQueue is not NULL
|
# with gil:
|
||||||
|
# assert stream.outputQueue
|
||||||
|
# fprintf(stderr, "enter copying output buffer code\n")
|
||||||
if stream.outputQueue.empty():
|
if stream.outputQueue.empty():
|
||||||
fprintf(stderr, 'Stream output buffer underflow, zero-ing buffer...\n')
|
fprintf(stderr, 'Stream output buffer underflow, zero-ing buffer...\n')
|
||||||
# Pre-stack three empty output buffers
|
memset(outputbuffer, 0, stream.outputParams.nChannels*bytesperchan)
|
||||||
# printf('Pre-stacking\n')
|
else:
|
||||||
# outputbuffer = malloc(stream.outputbuffersize)
|
outputbuffercpy = stream.outputQueue.dequeue()
|
||||||
memset(outputbuffer, 0, stream.outputbuffersize)
|
# fprintf(stderr, 'Copying data to stream output buffer...\n')
|
||||||
if stream.inputQueue:
|
j = 0
|
||||||
stream.inputQueue.enqueue(NULL)
|
i = 0
|
||||||
return 0
|
for i in range(stream.outputParams.nChannels):
|
||||||
|
ch_en = stream.outputChannelsEnabled[i]
|
||||||
outputbuffercpy = stream.outputQueue.dequeue()
|
if ch_en:
|
||||||
memcpy(outputbuffer, outputbuffercpy,
|
copyChannel(outputbuffer, outputbuffercpy, bytesperchan, i, j)
|
||||||
stream.outputbuffersize)
|
j+=1
|
||||||
free(outputbuffercpy)
|
else:
|
||||||
|
# If channel is not enabled, we set the data to zero
|
||||||
|
memset(<void*> &((<char*> outputbuffer)[bytesperchan*i]), 0, bytesperchan)
|
||||||
|
pass
|
||||||
|
i+=1
|
||||||
|
|
||||||
|
if stream.outputDelayQueue:
|
||||||
|
# fprintf(stderr, "Adding to delay queue\n")
|
||||||
|
stream.outputDelayQueue.enqueue(outputbuffercpy)
|
||||||
|
else:
|
||||||
|
free(outputbuffercpy)
|
||||||
|
|
||||||
|
outputbuffercpy = NULL
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
@ -263,69 +371,104 @@ cdef void audioCallbackPythonThreadFunction(void* voidstream) nogil:
|
|||||||
cnp.NPY_TYPES npy_format
|
cnp.NPY_TYPES npy_format
|
||||||
void* inputbuffer = NULL
|
void* inputbuffer = NULL
|
||||||
void* outputbuffer = NULL
|
void* outputbuffer = NULL
|
||||||
|
unsigned noutputchannels_forwarded
|
||||||
|
unsigned ninputchannels_forwarded
|
||||||
|
unsigned nBytesPerChan
|
||||||
|
unsigned nFramesPerBlock
|
||||||
|
|
||||||
|
void* _TESTDATA
|
||||||
|
|
||||||
stream = <_Stream*> voidstream
|
stream = <_Stream*> voidstream
|
||||||
printf('Thread started\n')
|
ninputchannels_forwarded = stream.ninputchannels_forwarded
|
||||||
|
noutputchannels_forwarded = stream.noutputchannels_forwarded
|
||||||
|
nBytesPerChan = stream.nBytesPerChan
|
||||||
|
nFramesPerBlock = stream.nFramesPerBlock
|
||||||
|
|
||||||
with gil:
|
with gil:
|
||||||
npy_format = _formats_rtkey[stream.sampleformat][2]
|
npy_format = _formats_rtkey[stream.sampleformat][2]
|
||||||
callback = <object> stream.pyCallback
|
callback = <object> stream.pyCallback
|
||||||
|
print(f'noutputchannels_forwarded: {noutputchannels_forwarded}')
|
||||||
|
print(f'ninputchannels_forwarded: {ninputchannels_forwarded}')
|
||||||
|
print(f'nBytesPerChan: {nBytesPerChan}')
|
||||||
|
print(f'nFramesPerBlock: {nFramesPerBlock}')
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
if stream.stopThread.load() == True:
|
if stream.stopThread.load() == True:
|
||||||
printf('Stopping thread...\n')
|
printf('Stopping thread...\n')
|
||||||
return
|
return
|
||||||
|
|
||||||
if stream.inputQueue:
|
if stream.inputQueue:
|
||||||
|
# fprintf(stderr, "Waiting on input queue\n")
|
||||||
inputbuffer = stream.inputQueue.dequeue()
|
inputbuffer = stream.inputQueue.dequeue()
|
||||||
# if inputbuffer == NULL:
|
if not inputbuffer:
|
||||||
# continue
|
printf('Stopping thread...\n')
|
||||||
|
return
|
||||||
|
|
||||||
if stream.outputQueue:
|
if stream.outputQueue:
|
||||||
outputbuffer = malloc(stream.outputbuffersize)
|
# fprintf(stderr, 'Allocating output buffer...\n')
|
||||||
|
outputbuffer = malloc(nBytesPerChan*noutputchannels_forwarded)
|
||||||
|
# memset(outputbuffer, 0, nBytesPerChan*noutputchannels_forwarded)
|
||||||
|
|
||||||
with gil:
|
with gil:
|
||||||
|
|
||||||
# Obtain stream information
|
# Obtain stream information
|
||||||
npy_input = None
|
npy_input = None
|
||||||
npy_output = None
|
npy_output = None
|
||||||
|
|
||||||
if stream.inputQueue and inputbuffer:
|
if stream.inputQueue:
|
||||||
|
# assert(inputbuffer)
|
||||||
try:
|
try:
|
||||||
npy_input = fromBufferToNPYNoCopy(
|
# print(f'========ninputchannels_forwarded: {ninputchannels_forwarded}')
|
||||||
npy_format,
|
# print(f'========nFramesPerBlock: {nFramesPerBlock}')
|
||||||
inputbuffer,
|
# print(f'========npy_format: {npy_format}')
|
||||||
stream.inputParams.nChannels,
|
|
||||||
stream.nFrames)
|
npy_input = <object> data_to_ndarray(
|
||||||
|
inputbuffer,
|
||||||
|
nFramesPerBlock,
|
||||||
|
ninputchannels_forwarded,
|
||||||
|
npy_format,
|
||||||
|
True, # Do transfer ownership
|
||||||
|
True) # F-contiguous is True: data is Fortran-cont.
|
||||||
|
# fprintf(stderr, "Copying array...\n")
|
||||||
|
# fprintf(stderr, "End Copying array...\n")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print('exception in cython callback for audio input: ', str(e))
|
print('exception in cython callback for audio input: ', str(e))
|
||||||
return
|
return
|
||||||
|
|
||||||
if stream.outputQueue:
|
if stream.outputQueue:
|
||||||
|
# fprintf(stderr, 'Copying output buffer to Numpy...\n')
|
||||||
try:
|
try:
|
||||||
assert outputbuffer != NULL
|
npy_output = <object> data_to_ndarray(
|
||||||
npy_output = fromBufferToNPYNoCopy(
|
outputbuffer,
|
||||||
npy_format,
|
nFramesPerBlock,
|
||||||
outputbuffer,
|
noutputchannels_forwarded,
|
||||||
stream.outputParams.nChannels,
|
npy_format,
|
||||||
stream.nFrames)
|
False, # Do not transfer ownership
|
||||||
|
True) # F-contiguous
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print('exception in Cython callback for audio output: ', str(e))
|
print('exception in Cython callback for audio output: ', str(e))
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
# fprintf(stderr, "Python callback...\n")
|
||||||
rval = callback(npy_input,
|
rval = callback(npy_input,
|
||||||
npy_output,
|
npy_output,
|
||||||
stream.nFrames,
|
nFramesPerBlock,
|
||||||
)
|
)
|
||||||
|
# fprintf(stderr, "Return from Python callback...\n")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print('Exception in Cython callback: ', str(e))
|
print('Exception in Cython callback: ', str(e))
|
||||||
return
|
return
|
||||||
|
|
||||||
if stream.outputQueue:
|
if stream.outputQueue:
|
||||||
|
# fprintf(stderr, 'Enqueuing output buffer...\n')
|
||||||
|
|
||||||
stream.outputQueue.enqueue(outputbuffer)
|
stream.outputQueue.enqueue(outputbuffer)
|
||||||
if not stream.inputQueue:
|
if not stream.inputQueue:
|
||||||
|
# fprintf(stderr, 'No input queue!\n')
|
||||||
while stream.outputQueue.size() > 10 and not stream.stopThread.load():
|
while stream.outputQueue.size() > 10 and not stream.stopThread.load():
|
||||||
# printf('Sleeping...\n')
|
# printf('Sleeping...\n')
|
||||||
# No input queue to wait on, so we relax a bit here.
|
# No input queue to wait on, so we relax a bit here.
|
||||||
@ -334,30 +477,50 @@ cdef void audioCallbackPythonThreadFunction(void* voidstream) nogil:
|
|||||||
# Outputbuffer is free'ed by the audiothread, so should not be touched
|
# Outputbuffer is free'ed by the audiothread, so should not be touched
|
||||||
# here.
|
# here.
|
||||||
outputbuffer = NULL
|
outputbuffer = NULL
|
||||||
|
|
||||||
# Inputbuffer memory is owned by Numpy, so should not be free'ed
|
# Inputbuffer memory is owned by Numpy, so should not be free'ed
|
||||||
inputbuffer = NULL
|
inputbuffer = NULL
|
||||||
|
|
||||||
|
|
||||||
cdef void errorCallback(RtAudioError.Type _type,const string& errortxt) nogil:
|
cdef void errorCallback(RtAudioError.Type _type,const string& errortxt) nogil:
|
||||||
printf('RtAudio error callback called: ')
|
fprintf(stderr, 'RtAudio error callback called: ')
|
||||||
printf(errortxt.c_str())
|
fprintf(stderr, errortxt.c_str())
|
||||||
printf('\n')
|
fprintf(stderr, '\n')
|
||||||
|
|
||||||
|
|
||||||
cdef class RtAudio:
|
cdef class RtAudio:
|
||||||
cdef:
|
cdef:
|
||||||
cppRtAudio _rtaudio
|
cppRtAudio* _rtaudio
|
||||||
_Stream* _stream
|
_Stream* _stream
|
||||||
|
int api
|
||||||
|
|
||||||
def __cinit__(self):
|
def __cinit__(self, unsigned int iapi):
|
||||||
|
cdef:
|
||||||
|
cppRtAudio.Api api = <cppRtAudio.Api> iapi
|
||||||
|
self._rtaudio = new cppRtAudio(api)
|
||||||
self._stream = NULL
|
self._stream = NULL
|
||||||
self._rtaudio.showWarnings(True)
|
self._rtaudio.showWarnings(True)
|
||||||
|
self.api = api
|
||||||
|
|
||||||
def __dealloc__(self):
|
def __dealloc__(self):
|
||||||
if self._stream is not NULL:
|
if self._stream is not NULL:
|
||||||
fprintf(stderr, 'Force closing stream...')
|
# fprintf(stderr, 'Force closing stream...')
|
||||||
self._rtaudio.closeStream()
|
self._rtaudio.closeStream()
|
||||||
self.cleanupStream(self._stream)
|
self.cleanupStream(self._stream)
|
||||||
|
del self._rtaudio
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def getApi():
|
||||||
|
cdef:
|
||||||
|
vector[cppRtAudio.Api] apis
|
||||||
|
cppRtAudio.getCompiledApi(apis)
|
||||||
|
apidict = {}
|
||||||
|
for api in apis:
|
||||||
|
apidict[<int> api] = {
|
||||||
|
'displayname': cppRtAudio.getApiDisplayName(api).decode('utf-8'),
|
||||||
|
'name': cppRtAudio.getApiName(api).decode('utf-8')
|
||||||
|
}
|
||||||
|
return apidict
|
||||||
|
|
||||||
cpdef unsigned int getDeviceCount(self):
|
cpdef unsigned int getDeviceCount(self):
|
||||||
return self._rtaudio.getDeviceCount()
|
return self._rtaudio.getDeviceCount()
|
||||||
@ -372,14 +535,17 @@ cdef class RtAudio:
|
|||||||
"""
|
"""
|
||||||
Return device information of the current device
|
Return device information of the current device
|
||||||
"""
|
"""
|
||||||
cdef cppRtAudio.DeviceInfo devinfo = self._rtaudio.getDeviceInfo(device)
|
|
||||||
sampleformats = []
|
sampleformats = []
|
||||||
|
cdef:
|
||||||
|
cppRtAudio.DeviceInfo devinfo
|
||||||
|
devinfo = self._rtaudio.getDeviceInfo(device)
|
||||||
nf = devinfo.nativeFormats
|
nf = devinfo.nativeFormats
|
||||||
for format_ in [ RTAUDIO_SINT8, RTAUDIO_SINT16, RTAUDIO_SINT24,
|
for format_ in [ RTAUDIO_SINT8, RTAUDIO_SINT16, RTAUDIO_SINT24,
|
||||||
RTAUDIO_SINT32, RTAUDIO_FLOAT32, RTAUDIO_FLOAT64]:
|
RTAUDIO_SINT32, RTAUDIO_FLOAT32, RTAUDIO_FLOAT64]:
|
||||||
if nf & format_:
|
if nf & format_:
|
||||||
sampleformats.append(_formats_rtkey[format_][0])
|
sampleformats.append(_formats_rtkey[format_][0])
|
||||||
return DeviceInfo(
|
return DeviceInfo(
|
||||||
|
api = self.api,
|
||||||
index = device,
|
index = device,
|
||||||
probed = devinfo.probed,
|
probed = devinfo.probed,
|
||||||
name = devinfo.name.decode('utf-8'),
|
name = devinfo.name.decode('utf-8'),
|
||||||
@ -391,109 +557,204 @@ cdef class RtAudio:
|
|||||||
prefsamplerate = devinfo.preferredSampleRate)
|
prefsamplerate = devinfo.preferredSampleRate)
|
||||||
|
|
||||||
@cython.nonecheck(True)
|
@cython.nonecheck(True)
|
||||||
def openStream(self,object outputParams,
|
def openStream(self,
|
||||||
object inputParams,
|
avstream
|
||||||
str sampleformat,
|
):
|
||||||
unsigned int sampleRate,
|
|
||||||
unsigned int bufferFrames,
|
|
||||||
object pyCallback,
|
|
||||||
object options = None,
|
|
||||||
object pyErrorCallback = None):
|
|
||||||
"""
|
"""
|
||||||
Opening a stream with specified parameters
|
Opening a stream with specified parameters
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
outputParams: dictionary of stream outputParameters, set to None
|
avstream: AvStream instance
|
||||||
if no outputPararms are specified
|
|
||||||
inputParams: dictionary of stream inputParameters, set to None
|
|
||||||
if no inputPararms are specified
|
|
||||||
sampleRate: desired sample rate.
|
|
||||||
bufferFrames: the amount of frames in a callback buffer
|
|
||||||
callback: callback to call. Note: this callback is called on a
|
|
||||||
different thread!
|
|
||||||
options: A dictionary of optional additional stream options
|
|
||||||
errorCallback: client-defined function that will be invoked when an
|
|
||||||
error has occured.
|
|
||||||
|
|
||||||
Returns: None
|
Returns: None
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self._stream is not NULL:
|
if self._stream is not NULL:
|
||||||
raise RuntimeError('Stream is already opened.')
|
raise RuntimeError('Stream is already opened.')
|
||||||
|
|
||||||
|
daqconfig = avstream.daqconfig
|
||||||
|
avtype = avstream.avtype
|
||||||
|
device = avstream.device
|
||||||
|
|
||||||
cdef:
|
cdef:
|
||||||
|
bint duplex_mode = daqconfig.duplex_mode
|
||||||
|
bint monitorOutput = daqconfig.monitor_gen
|
||||||
|
unsigned int outputDelayBlocks = daqconfig.outputDelayBlocks
|
||||||
cppRtAudio.StreamParameters *rtOutputParams_ptr = NULL
|
cppRtAudio.StreamParameters *rtOutputParams_ptr = NULL
|
||||||
cppRtAudio.StreamParameters *rtInputParams_ptr = NULL
|
cppRtAudio.StreamParameters *rtInputParams_ptr = NULL
|
||||||
cppRtAudio.StreamOptions streamoptions
|
cppRtAudio.StreamOptions streamoptions
|
||||||
size_t bytespersample
|
size_t sw
|
||||||
unsigned int bufferFrames_local
|
unsigned int nFramesPerBlock = int(daqconfig.nFramesPerBlock)
|
||||||
|
int firstinputchannel, firstoutputchannel
|
||||||
|
int lastinputchannel, lastoutputchannel
|
||||||
|
unsigned int ninputchannels_forwarded=0
|
||||||
|
unsigned int ninputchannels_rtaudio=0
|
||||||
|
unsigned int noutputchannels_rtaudio=0
|
||||||
|
unsigned int noutputchannels_forwarded=0
|
||||||
|
unsigned int samplerate
|
||||||
|
int i
|
||||||
|
bint input_stream=False, output_stream=False
|
||||||
|
bool* inputChannelsEnabled
|
||||||
|
bool* outputChannelsEnabled
|
||||||
|
|
||||||
streamoptions.flags = RTAUDIO_HOG_DEVICE
|
if daqconfig.nFramesPerBlock > 8192 or daqconfig.nFramesPerBlock < 512:
|
||||||
streamoptions.numberOfBuffers = 4
|
raise ValueError('Invalid number of nFramesPerBlock')
|
||||||
bufferFrames_local = bufferFrames
|
|
||||||
|
|
||||||
self._stream = <_Stream*> malloc(sizeof(_Stream))
|
if daqconfig.outputDelayBlocks < 0 or daqconfig.outputDelayBlocks > 10:
|
||||||
if self._stream == NULL:
|
raise ValueError('Invalid number of outputDelayBlocks')
|
||||||
raise MemoryError()
|
|
||||||
|
|
||||||
self._stream.pyCallback = <PyObject*> pyCallback
|
|
||||||
Py_INCREF(pyCallback)
|
|
||||||
self._stream.sampleformat = _formats_strkey[sampleformat][0]
|
|
||||||
self._stream.inputQueue = NULL
|
|
||||||
self._stream.outputQueue = NULL
|
|
||||||
self._stream.outputbuffersize = 0
|
|
||||||
self._stream.inputbuffersize = 0
|
|
||||||
self._stream.stopThread.store(False)
|
|
||||||
self._stream.thread = NULL
|
|
||||||
|
|
||||||
bytespersample = get_sampwidth_from_format_string(sampleformat)
|
|
||||||
|
|
||||||
if outputParams is not None:
|
|
||||||
rtOutputParams_ptr = &self._stream.outputParams
|
|
||||||
rtOutputParams_ptr.deviceId = outputParams['deviceid']
|
|
||||||
rtOutputParams_ptr.nChannels = outputParams['nchannels']
|
|
||||||
rtOutputParams_ptr.firstChannel = outputParams['firstchannel']
|
|
||||||
self._stream.outputQueue = new SafeQueue[void*]()
|
|
||||||
|
|
||||||
if inputParams is not None:
|
|
||||||
rtInputParams_ptr = &self._stream.inputParams
|
|
||||||
rtInputParams_ptr.deviceId = inputParams['deviceid']
|
|
||||||
rtInputParams_ptr.nChannels = inputParams['nchannels']
|
|
||||||
rtInputParams_ptr.firstChannel = inputParams['firstchannel']
|
|
||||||
self._stream.inputQueue = new SafeQueue[void*]()
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
||||||
|
# Determine sample rate and sample format, determine whether we are an
|
||||||
|
# input or an output stream, or both
|
||||||
|
if avtype == AvType.audio_input or duplex_mode:
|
||||||
|
# Here, we override the sample format in case of duplex mode.
|
||||||
|
sampleformat = daqconfig.en_input_sample_format
|
||||||
|
samplerate = int(daqconfig.en_input_rate)
|
||||||
|
input_stream = True
|
||||||
|
if duplex_mode:
|
||||||
|
output_stream = True
|
||||||
|
else:
|
||||||
|
sampleformat = daqconfig.en_output_sample_format
|
||||||
|
samplerate = int(daqconfig.en_output_rate)
|
||||||
|
output_stream = True
|
||||||
|
|
||||||
|
print(f'Is input stream: {input_stream}')
|
||||||
|
print(f'Is output stream: {output_stream}')
|
||||||
|
|
||||||
|
sw = get_sampwidth_from_format_string(sampleformat)
|
||||||
|
print(f'samplewidth: {sw}')
|
||||||
|
|
||||||
|
# All set, allocate the stream!
|
||||||
|
self._stream = <_Stream*> malloc(sizeof(_Stream))
|
||||||
|
if self._stream == NULL:
|
||||||
|
raise MemoryError('Could not allocate stream: memory error.')
|
||||||
|
|
||||||
|
self._stream.pyCallback = <PyObject*> avstream._audioCallback
|
||||||
|
# Increase reference count to the callback
|
||||||
|
Py_INCREF(<object> avstream._audioCallback)
|
||||||
|
|
||||||
|
self._stream.sampleformat = _formats_strkey[sampleformat][0]
|
||||||
|
self._stream.stopThread.store(False)
|
||||||
|
self._stream.inputQueue = NULL
|
||||||
|
self._stream.outputQueue = NULL
|
||||||
|
self._stream.outputDelayQueue = NULL
|
||||||
|
|
||||||
|
self._stream.thread = NULL
|
||||||
|
|
||||||
|
self._stream.outputDelayBlocks = outputDelayBlocks
|
||||||
|
self._stream.ninputchannels_forwarded = 0
|
||||||
|
self._stream.noutputchannels_forwarded = 0
|
||||||
|
self._stream.inputChannelsEnabled = NULL
|
||||||
|
self._stream.outputChannelsEnabled = NULL
|
||||||
|
|
||||||
|
# Create channel maps for input channels, set RtAudio input stream
|
||||||
|
# parameters
|
||||||
|
if input_stream:
|
||||||
|
firstinputchannel = daqconfig.firstEnabledInputChannelNumber()
|
||||||
|
lastinputchannel = daqconfig.lastEnabledInputChannelNumber()
|
||||||
|
ninputchannels_rtaudio = lastinputchannel-firstinputchannel+1
|
||||||
|
|
||||||
|
# print(firstinputchannel)
|
||||||
|
# print(lastinputchannel)
|
||||||
|
# print(ninputchannels_rtaudio)
|
||||||
|
|
||||||
|
if lastinputchannel < 0 or ninputchannels_rtaudio < 1:
|
||||||
|
raise ValueError('Not enough input channels selected')
|
||||||
|
input_ch = daqconfig.input_channel_configs
|
||||||
|
|
||||||
|
inputChannelsEnabled = <bool*> malloc(sizeof(bool)*ninputchannels_rtaudio)
|
||||||
|
self._stream.inputChannelsEnabled = inputChannelsEnabled
|
||||||
|
|
||||||
|
for i in range(firstinputchannel, lastinputchannel+1):
|
||||||
|
ch_en = input_ch[i].channel_enabled
|
||||||
|
if ch_en:
|
||||||
|
ninputchannels_forwarded += 1
|
||||||
|
inputChannelsEnabled[i] = ch_en
|
||||||
|
|
||||||
|
rtInputParams_ptr = &self._stream.inputParams
|
||||||
|
rtInputParams_ptr.deviceId = device.index
|
||||||
|
rtInputParams_ptr.nChannels = ninputchannels_rtaudio
|
||||||
|
rtInputParams_ptr.firstChannel = firstinputchannel
|
||||||
|
|
||||||
|
self._stream.inputQueue = new SafeQueue[void*]()
|
||||||
|
self._stream.ninputchannels_forwarded = ninputchannels_forwarded
|
||||||
|
|
||||||
|
|
||||||
|
# Create channel maps for output channels
|
||||||
|
if output_stream:
|
||||||
|
firstoutputchannel = daqconfig.firstEnabledOutputChannelNumber()
|
||||||
|
lastoutputchannel = daqconfig.lastEnabledOutputChannelNumber()
|
||||||
|
noutputchannels_rtaudio = lastoutputchannel-firstoutputchannel+1
|
||||||
|
|
||||||
|
# print(firstoutputchannel)
|
||||||
|
# print(lastoutputchannel)
|
||||||
|
# print(noutputchannels_rtaudio)
|
||||||
|
|
||||||
|
if lastoutputchannel < 0 or noutputchannels_rtaudio < 1:
|
||||||
|
raise ValueError('Not enough output channels selected')
|
||||||
|
output_ch = daqconfig.output_channel_configs
|
||||||
|
|
||||||
|
outputChannelsEnabled = <bool*> malloc(sizeof(bool)*noutputchannels_rtaudio)
|
||||||
|
self._stream.outputChannelsEnabled = outputChannelsEnabled
|
||||||
|
for i in range(firstoutputchannel, lastoutputchannel+1):
|
||||||
|
ch_en = output_ch[i].channel_enabled
|
||||||
|
if ch_en:
|
||||||
|
noutputchannels_forwarded += 1
|
||||||
|
outputChannelsEnabled[i] = ch_en
|
||||||
|
|
||||||
|
rtOutputParams_ptr = &self._stream.outputParams
|
||||||
|
rtOutputParams_ptr.deviceId = device.index
|
||||||
|
rtOutputParams_ptr.nChannels = noutputchannels_rtaudio
|
||||||
|
rtOutputParams_ptr.firstChannel = firstoutputchannel
|
||||||
|
|
||||||
|
self._stream.outputQueue = new SafeQueue[void*]()
|
||||||
|
self._stream.noutputchannels_forwarded = noutputchannels_forwarded
|
||||||
|
|
||||||
|
if monitorOutput and duplex_mode:
|
||||||
|
self._stream.outputDelayQueue = new SafeQueue[void*]()
|
||||||
|
self._stream.ninputchannels_forwarded += noutputchannels_forwarded
|
||||||
|
|
||||||
|
|
||||||
|
streamoptions.flags = RTAUDIO_HOG_DEVICE
|
||||||
|
streamoptions.flags |= RTAUDIO_NONINTERLEAVED
|
||||||
|
streamoptions.numberOfBuffers = 4
|
||||||
|
streamoptions.streamName = "LASP Audio stream".encode('utf-8')
|
||||||
|
streamoptions.priority = 1
|
||||||
|
|
||||||
self._rtaudio.openStream(rtOutputParams_ptr,
|
self._rtaudio.openStream(rtOutputParams_ptr,
|
||||||
rtInputParams_ptr,
|
rtInputParams_ptr,
|
||||||
_formats_strkey[sampleformat][0],
|
_formats_strkey[sampleformat][0],
|
||||||
sampleRate,
|
samplerate,
|
||||||
&bufferFrames_local,
|
&nFramesPerBlock,
|
||||||
audioCallback,
|
audioCallback,
|
||||||
<void*> self._stream,
|
<void*> self._stream,
|
||||||
&streamoptions, # Stream options
|
&streamoptions, # Stream options
|
||||||
errorCallback # Error callback
|
errorCallback # Error callback
|
||||||
)
|
)
|
||||||
self._stream.nFrames = bufferFrames_local
|
|
||||||
|
self._stream.nBytesPerChan = nFramesPerBlock*sw
|
||||||
|
self._stream.nFramesPerBlock = nFramesPerBlock
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print('Exception occured in stream opening: ', str(e))
|
print('Exception occured in stream opening: ', e)
|
||||||
self.cleanupStream(self._stream)
|
self.cleanupStream(self._stream)
|
||||||
self._stream = NULL
|
self._stream = NULL
|
||||||
raise
|
raise e
|
||||||
|
|
||||||
if inputParams is not None:
|
|
||||||
self._stream.inputbuffersize = bufferFrames_local*bytespersample*inputParams['nchannels']
|
|
||||||
if outputParams is not None:
|
|
||||||
self._stream.outputbuffersize = bufferFrames_local*bytespersample*outputParams['nchannels']
|
|
||||||
|
|
||||||
with nogil:
|
with nogil:
|
||||||
self._stream.thread = new CPPThread[void*, void (*)(void*)](audioCallbackPythonThreadFunction,
|
self._stream.thread = new CPPThread[void*, void (*)(void*)](audioCallbackPythonThreadFunction,
|
||||||
<void*> self._stream)
|
<void*> self._stream)
|
||||||
|
# Allow it to start
|
||||||
CPPsleep(500)
|
CPPsleep(500)
|
||||||
|
pass
|
||||||
|
|
||||||
return bufferFrames_local
|
return nFramesPerBlock
|
||||||
|
|
||||||
cdef cleanupStream(self, _Stream* stream):
|
cdef cleanupStream(self, _Stream* stream):
|
||||||
# printf('Entrance function cleanupStream...\n')
|
# printf('Entrance function cleanupStream...\n')
|
||||||
|
cdef:
|
||||||
|
void* ptr
|
||||||
if stream == NULL:
|
if stream == NULL:
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -509,17 +770,32 @@ cdef class RtAudio:
|
|||||||
stream.thread.join()
|
stream.thread.join()
|
||||||
# printf('Thread joined!\n')
|
# printf('Thread joined!\n')
|
||||||
del stream.thread
|
del stream.thread
|
||||||
|
stream.thread = NULL
|
||||||
|
|
||||||
|
if stream.inputChannelsEnabled:
|
||||||
|
free(stream.inputChannelsEnabled)
|
||||||
|
if stream.outputChannelsEnabled:
|
||||||
|
free(stream.outputChannelsEnabled)
|
||||||
|
|
||||||
if stream.outputQueue:
|
if stream.outputQueue:
|
||||||
|
while not stream.outputQueue.empty():
|
||||||
|
free(stream.outputQueue.dequeue())
|
||||||
del stream.outputQueue
|
del stream.outputQueue
|
||||||
if stream.inputQueue:
|
if stream.inputQueue:
|
||||||
|
while not stream.inputQueue.empty():
|
||||||
|
free(stream.inputQueue.dequeue())
|
||||||
del stream.inputQueue
|
del stream.inputQueue
|
||||||
|
if stream.outputDelayQueue:
|
||||||
|
while not stream.outputDelayQueue.empty():
|
||||||
|
free(stream.outputDelayQueue.dequeue())
|
||||||
|
del stream.outputDelayQueue
|
||||||
|
fprintf(stderr, "End cleanup stream queues...\n")
|
||||||
|
|
||||||
if stream.pyCallback:
|
if stream.pyCallback:
|
||||||
Py_DECREF(<object> stream.pyCallback)
|
Py_DECREF(<object> stream.pyCallback)
|
||||||
|
stream.pyCallback = NULL
|
||||||
|
# fprintf(stderr, "End cleanup callback...\n")
|
||||||
free(stream)
|
free(stream)
|
||||||
# printf('Cleanup of stream is done\n')
|
|
||||||
|
|
||||||
def startStream(self):
|
def startStream(self):
|
||||||
self._rtaudio.startStream()
|
self._rtaudio.startStream()
|
||||||
|
@ -11,20 +11,13 @@ import numpy as np
|
|||||||
import time
|
import time
|
||||||
from .device import (RtAudio, DeviceInfo, DAQConfiguration,
|
from .device import (RtAudio, DeviceInfo, DAQConfiguration,
|
||||||
get_numpy_dtype_from_format_string,
|
get_numpy_dtype_from_format_string,
|
||||||
get_sampwidth_from_format_string)
|
get_sampwidth_from_format_string, AvType)
|
||||||
|
|
||||||
__all__ = ['AvType', 'AvStream']
|
__all__ = ['AvStream']
|
||||||
|
|
||||||
video_x, video_y = 640, 480
|
video_x, video_y = 640, 480
|
||||||
|
|
||||||
|
|
||||||
class AvType:
|
|
||||||
"""Specificying the type of data, for adding and removing callbacks from
|
|
||||||
the stream."""
|
|
||||||
audio_input = 1
|
|
||||||
audio_output = 2
|
|
||||||
video = 4
|
|
||||||
|
|
||||||
|
|
||||||
class AvStream:
|
class AvStream:
|
||||||
"""Audio and video data stream, to which callbacks can be added for
|
"""Audio and video data stream, to which callbacks can be added for
|
||||||
@ -49,59 +42,28 @@ class AvStream:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
self.daqconfig = daqconfig
|
self.daqconfig = daqconfig
|
||||||
self._device = device
|
self.device = device
|
||||||
self.avtype = avtype
|
self.avtype = avtype
|
||||||
self.duplex_mode = daqconfig.duplex_mode
|
self.duplex_mode = daqconfig.duplex_mode
|
||||||
self.monitor_gen = daqconfig.monitor_gen
|
|
||||||
|
|
||||||
# Determine highest input channel number
|
self.output_channel_names = [ch.channel_name for ch in daqconfig.getEnabledOutputChannels()]
|
||||||
channelconfigs = daqconfig.input_channel_configs
|
|
||||||
firstchannel = daqconfig.firstEnabledInputChannelNumber()
|
|
||||||
if firstchannel < 0:
|
|
||||||
raise ValueError('No input channels enabled')
|
|
||||||
|
|
||||||
self.channel_names = []
|
|
||||||
self.sensitivity = self.daqconfig.getEnabledChannelSensitivities()
|
|
||||||
|
|
||||||
if daqconfig.monitor_gen:
|
if daqconfig.monitor_gen:
|
||||||
assert self.duplex_mode
|
self.input_channel_names = self.output_channel_names
|
||||||
self.channel_names.append('Generated signal')
|
self.input_sensitivity = daqconfig.getEnabledOutputChannelSensitivities()
|
||||||
self.sensitivity = np.concatenate([np.array([1.]),
|
else:
|
||||||
self.sensitivity])
|
self.input_channel_names = []
|
||||||
|
self.input_sensitivity = []
|
||||||
|
|
||||||
|
self.input_sensitivity += daqconfig.getEnabledInputChannelSensitivities()
|
||||||
|
self.input_sensitivity = np.asarray(self.input_sensitivity)
|
||||||
|
|
||||||
|
|
||||||
rtaudio_inputparams = None
|
self.input_channel_names += [
|
||||||
rtaudio_outputparams = None
|
ch.channel_name for ch in daqconfig.getEnabledInputChannels()]
|
||||||
|
|
||||||
self.nframes_per_block = 1024
|
self.input_samplerate = float(daqconfig.en_input_rate)
|
||||||
|
self.output_samplerate = float(daqconfig.en_output_rate)
|
||||||
if self.duplex_mode or avtype == AvType.audio_output:
|
|
||||||
rtaudio_outputparams = {'deviceid': device.index,
|
|
||||||
# TODO: Add option to specify the number of output channels to use
|
|
||||||
'nchannels': 1, # device.outputchannels,
|
|
||||||
'firstchannel': 0}
|
|
||||||
self.sampleformat = daqconfig.en_output_sample_format
|
|
||||||
self.samplerate = int(daqconfig.en_output_rate)
|
|
||||||
|
|
||||||
if avtype == AvType.audio_input or self.duplex_mode:
|
|
||||||
for i, channelconfig in enumerate(channelconfigs):
|
|
||||||
if channelconfig.channel_enabled:
|
|
||||||
self.nchannels = i+1
|
|
||||||
self.channel_names.append(channelconfig.channel_name)
|
|
||||||
rtaudio_inputparams = {'deviceid': device.index,
|
|
||||||
'nchannels': self.nchannels,
|
|
||||||
'firstchannel': firstchannel}
|
|
||||||
|
|
||||||
# Here, we override the sample format in case of duplex mode.
|
|
||||||
self.sampleformat = daqconfig.en_input_sample_format
|
|
||||||
self.samplerate = int(daqconfig.en_input_rate)
|
|
||||||
|
|
||||||
|
|
||||||
# Fill in numpy data type, and sample width
|
|
||||||
self.numpy_dtype = get_numpy_dtype_from_format_string(
|
|
||||||
self.sampleformat)
|
|
||||||
self.sampwidth = get_sampwidth_from_format_string(
|
|
||||||
self.sampleformat)
|
|
||||||
|
|
||||||
# Counters for the number of frames that have been coming in
|
# Counters for the number of frames that have been coming in
|
||||||
self._aframectr = Atomic(0)
|
self._aframectr = Atomic(0)
|
||||||
@ -125,19 +87,19 @@ class AvStream:
|
|||||||
|
|
||||||
# Possible, but long not tested: store video
|
# Possible, but long not tested: store video
|
||||||
self._videothread = None
|
self._videothread = None
|
||||||
|
self._rtaudio = RtAudio(daqconfig.api)
|
||||||
|
self.blocksize = self._rtaudio.openStream(self)
|
||||||
|
|
||||||
try:
|
# Fill in numpy data type, and sample width
|
||||||
self._rtaudio = RtAudio()
|
self.input_numpy_dtype = get_numpy_dtype_from_format_string(
|
||||||
self.blocksize = self._rtaudio.openStream(
|
daqconfig.en_input_sample_format)
|
||||||
rtaudio_outputparams, # Outputparams
|
self.output_numpy_dtype = get_numpy_dtype_from_format_string(
|
||||||
rtaudio_inputparams, # Inputparams
|
daqconfig.en_output_sample_format)
|
||||||
self.sampleformat, # Sampleformat
|
|
||||||
self.samplerate,
|
|
||||||
self.nframes_per_block, # Buffer size in frames
|
|
||||||
self._audioCallback)
|
|
||||||
|
|
||||||
except Exception as e:
|
self.input_sampwidth = get_sampwidth_from_format_string(
|
||||||
raise RuntimeError(f'Could not initialize DAQ device: {str(e)}')
|
daqconfig.en_input_sample_format)
|
||||||
|
self.output_sampwidth = get_sampwidth_from_format_string(
|
||||||
|
daqconfig.en_output_sample_format)
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
self._rtaudio.closeStream()
|
self._rtaudio.closeStream()
|
||||||
@ -212,29 +174,30 @@ class AvStream:
|
|||||||
"""This is called (from a separate thread) for each audio block."""
|
"""This is called (from a separate thread) for each audio block."""
|
||||||
self._aframectr += nframes
|
self._aframectr += nframes
|
||||||
with self._callbacklock:
|
with self._callbacklock:
|
||||||
|
|
||||||
# Count the number of output callbacks. If no output callbacks are
|
# Count the number of output callbacks. If no output callbacks are
|
||||||
# present, and there should be output callbacks, we explicitly set
|
# present, and there should be output callbacks, we explicitly set
|
||||||
# the output buffer to zero
|
# the output buffer to zero
|
||||||
noutput_cb = len(self._callbacks[AvType.audio_output])
|
noutput_cb = len(self._callbacks[AvType.audio_output])
|
||||||
shouldhaveoutput = (self.avtype == AvType.audio_output or
|
shouldhaveoutput = (self.avtype == AvType.audio_output or
|
||||||
self.duplex_mode)
|
self.daqconfig.duplex_mode)
|
||||||
if noutput_cb == 0 and shouldhaveoutput:
|
if noutput_cb == 0 and shouldhaveoutput and outdata is not None:
|
||||||
outdata[:, :] = 0
|
outdata[:, :] = 0
|
||||||
|
|
||||||
# Loop over callbacks
|
# Loop over callbacks
|
||||||
for cb in self._callbacks[AvType.audio_output]:
|
if outdata is not None:
|
||||||
try:
|
for cb in self._callbacks[AvType.audio_output]:
|
||||||
cb(indata, outdata, self._aframectr())
|
try:
|
||||||
except Exception as e:
|
cb(indata, outdata, self._aframectr())
|
||||||
print(e)
|
except Exception as e:
|
||||||
return 1
|
print(e)
|
||||||
for cb in self._callbacks[AvType.audio_input]:
|
return 2
|
||||||
try:
|
if indata is not None:
|
||||||
cb(indata, outdata, self._aframectr())
|
for cb in self._callbacks[AvType.audio_input]:
|
||||||
except Exception as e:
|
try:
|
||||||
print(e)
|
cb(indata, outdata, self._aframectr())
|
||||||
return 1
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
return 1
|
||||||
|
|
||||||
return 0 if self._running else 1
|
return 0 if self._running else 1
|
||||||
|
|
||||||
|
@ -9,6 +9,8 @@ import numpy as np
|
|||||||
|
|
||||||
from .wrappers import Window as wWindow
|
from .wrappers import Window as wWindow
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from dataclasses_json import dataclass_json
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Common definitions used throughout the code.
|
Common definitions used throughout the code.
|
||||||
@ -39,9 +41,24 @@ U_REF = 5e-8 # 50 nano meter / s
|
|||||||
# hence this is the reference level as specified below.
|
# hence this is the reference level as specified below.
|
||||||
dBFS_REF = 0.5*2**0.5 # Which level would be -3.01 dBFS
|
dBFS_REF = 0.5*2**0.5 # Which level would be -3.01 dBFS
|
||||||
|
|
||||||
Qty = namedtuple('Qty', 'name unit_name unit_symb level_unit level_ref_name level_ref_value')
|
@dataclass_json
|
||||||
|
@dataclass
|
||||||
|
class Qty:
|
||||||
|
name: str
|
||||||
|
unit_name: str
|
||||||
|
unit_symb: str
|
||||||
|
level_unit: str
|
||||||
|
level_ref_name: str
|
||||||
|
level_ref_value: str
|
||||||
|
|
||||||
class SIQtys:
|
class SIQtys:
|
||||||
|
N = Qty(name='Number',
|
||||||
|
unit_name='No unit / full scale',
|
||||||
|
unit_symb=('-'),
|
||||||
|
level_unit=('dBFS',),
|
||||||
|
level_ref_name=('Full scale sine wave',),
|
||||||
|
level_ref_value=(dBFS_REF,)
|
||||||
|
)
|
||||||
AP = Qty(name='Acoustic Pressure',
|
AP = Qty(name='Acoustic Pressure',
|
||||||
unit_name='Pascal',
|
unit_name='Pascal',
|
||||||
unit_symb=('Pa', 'muPa'),
|
unit_symb=('Pa', 'muPa'),
|
||||||
@ -51,21 +68,14 @@ class SIQtys:
|
|||||||
)
|
)
|
||||||
|
|
||||||
V = Qty(name='Voltage',
|
V = Qty(name='Voltage',
|
||||||
unit_name='volt',
|
unit_name='Volt',
|
||||||
unit_symb='V',
|
unit_symb='V',
|
||||||
level_unit=('dBV',), # dBV
|
level_unit=('dBV',), # dBV
|
||||||
level_ref_name=('1V',),
|
level_ref_name=('1V',),
|
||||||
level_ref_value=(1.0,),
|
level_ref_value=(1.0,),
|
||||||
)
|
)
|
||||||
N = Qty(name='No unit',
|
|
||||||
unit_name='',
|
|
||||||
unit_symb='[-]',
|
|
||||||
level_unit=('dBFS',),
|
|
||||||
level_ref_name=('Full scale sine wave',),
|
|
||||||
level_ref_value=(dBFS_REF,)
|
|
||||||
)
|
|
||||||
types = (AP, V, N)
|
types = (AP, V, N)
|
||||||
default = AP
|
default = N
|
||||||
default_index = 0
|
default_index = 0
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -78,7 +88,7 @@ class SIQtys:
|
|||||||
"""
|
"""
|
||||||
cb.clear()
|
cb.clear()
|
||||||
for ty in SIQtys.types:
|
for ty in SIQtys.types:
|
||||||
cb.addItem(f'{ty.name} [{ty.unit_symb[0]}')
|
cb.addItem(f'{ty.unit_name}')
|
||||||
cb.setCurrentIndex(SIQtys.default_index)
|
cb.setCurrentIndex(SIQtys.default_index)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -9,6 +9,8 @@ The ASCEE hdf5 measurement file format contains the following fields:
|
|||||||
|
|
||||||
- Attributes:
|
- Attributes:
|
||||||
|
|
||||||
|
'version': If not given, version 1 is assumed. For version 1, measurement data
|
||||||
|
is assumed to be acoustic data.
|
||||||
'samplerate': The audio data sample rate in Hz.
|
'samplerate': The audio data sample rate in Hz.
|
||||||
'nchannels': The number of audio channels in the file
|
'nchannels': The number of audio channels in the file
|
||||||
'sensitivity': (Optionally) the stored sensitivity of the record channels.
|
'sensitivity': (Optionally) the stored sensitivity of the record channels.
|
||||||
@ -42,6 +44,7 @@ from .lasp_config import LASP_NUMPY_FLOAT_TYPE
|
|||||||
from scipy.io import wavfile
|
from scipy.io import wavfile
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
|
import wave
|
||||||
|
|
||||||
|
|
||||||
class BlockIter:
|
class BlockIter:
|
||||||
|
@ -78,10 +78,6 @@ class Recording:
|
|||||||
stream = self._stream
|
stream = self._stream
|
||||||
f = self._f
|
f = self._f
|
||||||
nchannels = stream.nchannels
|
nchannels = stream.nchannels
|
||||||
if stream.monitor_gen:
|
|
||||||
nchannels += 1
|
|
||||||
|
|
||||||
self.monitor_gen = stream.monitor_gen
|
|
||||||
|
|
||||||
self._ad = f.create_dataset('audio',
|
self._ad = f.create_dataset('audio',
|
||||||
(1, stream.blocksize, nchannels),
|
(1, stream.blocksize, nchannels),
|
||||||
@ -173,11 +169,7 @@ class Recording:
|
|||||||
return
|
return
|
||||||
|
|
||||||
self._ad.resize(self._ablockno()+1, axis=0)
|
self._ad.resize(self._ablockno()+1, axis=0)
|
||||||
if self.monitor_gen:
|
self._ad[self._ablockno(), :, :] = indata
|
||||||
self._ad[self._ablockno(), :, 0] = outdata[:, 0]
|
|
||||||
self._ad[self._ablockno(), :, 1:] = indata
|
|
||||||
else:
|
|
||||||
self._ad[self._ablockno(), :, :] = indata
|
|
||||||
self._ablockno += 1
|
self._ablockno += 1
|
||||||
|
|
||||||
def _vCallback(self, frame, framectr):
|
def _vCallback(self, frame, framectr):
|
||||||
|
1
setup.py
1
setup.py
@ -32,6 +32,7 @@ setup(
|
|||||||
author_email="j.a.dejong@ascee.nl",
|
author_email="j.a.dejong@ascee.nl",
|
||||||
install_requires=['matplotlib>=1.0',
|
install_requires=['matplotlib>=1.0',
|
||||||
'scipy>=1.0', 'numpy>=1.0', 'h5py',
|
'scipy>=1.0', 'numpy>=1.0', 'h5py',
|
||||||
|
'dataclasses_json',
|
||||||
],
|
],
|
||||||
license='MIT',
|
license='MIT',
|
||||||
description="Library for Acoustic Signal Processing",
|
description="Library for Acoustic Signal Processing",
|
||||||
|
Loading…
Reference in New Issue
Block a user