1123 lines
35 KiB
C
1123 lines
35 KiB
C
#include "recording.h"
|
|
#include <math.h>
|
|
#include <string.h>
|
|
#include "Python.h"
|
|
#include "channel.h"
|
|
#include "structmember.h"
|
|
|
|
// class RecordingData
|
|
|
|
typedef struct {
|
|
PyObject_HEAD;
|
|
|
|
/**
|
|
* the time stamp for the first data in this record. unit mesc
|
|
*/
|
|
double time_stamp;
|
|
|
|
/**
|
|
* sample rate, unit 1/s
|
|
*/
|
|
unsigned int sample_rate;
|
|
unsigned int sample_rate_channel_upper_256;
|
|
|
|
/**
|
|
* data encode format:
|
|
* 0 : single channel data
|
|
* 1 : multiple channel data
|
|
*/
|
|
unsigned int format;
|
|
|
|
/**
|
|
* device ID.
|
|
*/
|
|
unsigned int device;
|
|
|
|
/**
|
|
* channel ID.
|
|
*
|
|
* actual meaning according to the *format*
|
|
* 0 : channel ID
|
|
* 1 : has invalid channel
|
|
*/
|
|
unsigned int channel;
|
|
|
|
/*
|
|
* data width in bytes.
|
|
*/
|
|
unsigned int data_width;
|
|
|
|
unsigned int data_capacity;
|
|
unsigned int data_size;
|
|
|
|
/**
|
|
* data.
|
|
*
|
|
* data layout according to the *format*
|
|
* 0 : [int]
|
|
* 1 : [channel, int], which negative channel value represent invalid value.
|
|
* channel = -value - 1
|
|
*/
|
|
unsigned int batch_number;
|
|
|
|
int *cycle;
|
|
|
|
int *data;
|
|
|
|
int *sample_rates;
|
|
|
|
} RecordingData;
|
|
|
|
static PyMemberDef RecordingDataMembers[] = { //
|
|
// device:int
|
|
{"device", T_UINT, offsetof(RecordingData, device), 0, "device ID"},
|
|
|
|
// time_stamp:int
|
|
{"time_stamp", T_DOUBLE, offsetof(RecordingData, time_stamp), 0, "time stamp relative to beginning"},
|
|
|
|
// sample_rate:int
|
|
{"sample_rate", T_UINT, offsetof(RecordingData, sample_rate), 0, "sampling rate"},
|
|
|
|
// sample_rate_channel_upper_256:int
|
|
{"sample_rate_channel_upper_256", T_UINT, offsetof(RecordingData, sample_rate), 0, "sampling rate_2"},
|
|
|
|
// data_width:int
|
|
{"data_width", T_UINT, offsetof(RecordingData, data_width), READONLY, "data width in bytes"},
|
|
|
|
// cycle:object
|
|
{"cycle", T_OBJECT_EX, offsetof(RecordingData, cycle), 0, "cycle start time list"},
|
|
|
|
// data_capacity:int
|
|
{"data_capacity", T_UINT, offsetof(RecordingData, data_capacity), READONLY, "data storage capacity"},
|
|
|
|
// data_size:int
|
|
{"data_size", T_UINT, offsetof(RecordingData, data_size), READONLY, "data storage size"},
|
|
|
|
// data:int*
|
|
{"data", T_UINT, offsetof(RecordingData, data), READONLY, "your data"},
|
|
|
|
{"batch_number", T_UINT, offsetof(RecordingData, batch_number), 0, "batch number"},
|
|
|
|
{"sample_rates", T_OBJECT_EX, offsetof(RecordingData, sample_rates), 0, "cycle start time list"},
|
|
|
|
{NULL}};
|
|
|
|
// RecordingData __init__
|
|
static PyObject *RecordingData_new(PyTypeObject *type, PyObject *args, PyObject *keywords);
|
|
static int RecordingData_init(RecordingData *self, PyObject *args, PyObject *kwargs);
|
|
static void RecordingData_dealloc(RecordingData *self);
|
|
|
|
// RecordingData property
|
|
static PyObject *RecordingData_to_time(RecordingData *self, void *ignore);
|
|
|
|
static PyGetSetDef RecordingDataGetSet[] = { //
|
|
// to_time: int [G]
|
|
{"to_time", (getter)RecordingData_to_time, NULL, "", NULL},
|
|
|
|
{NULL}};
|
|
|
|
// RecordingData sequence
|
|
static Py_ssize_t RecordingData_len(RecordingData *self);
|
|
static PyObject * RecordingData_get(RecordingData *self, Py_ssize_t index);
|
|
|
|
static PySequenceMethods RecordingDataSeq = {
|
|
// __len__(self) -> int
|
|
.sq_length = (lenfunc)RecordingData_len,
|
|
|
|
// __getitem__(self, item:int) -> Tuple[int, int, Union[None, int, Tuple[int, int, int]]]
|
|
.sq_item = (ssizeargfunc)RecordingData_get,
|
|
};
|
|
|
|
// RecordingData methods
|
|
static PyObject *RecordingData_clear(RecordingData *self);
|
|
static PyObject *RecordingData_channels(RecordingData *self);
|
|
static PyObject *RecordingData_append_data(RecordingData *self, PyObject *args, PyObject *kwargs);
|
|
static PyObject *RecordingData_in_time_range(RecordingData *self, PyObject *args, PyObject *kwargs);
|
|
static PyObject *RecordingData_encode(RecordingData *self);
|
|
static PyObject *RecordingData_decode(RecordingData *self, PyObject *args, PyObject *kwargs);
|
|
static PyObject *RecordingData_entry_iter(RecordingData *self, PyObject *args, PyObject *kwargs);
|
|
static PyObject *RecordingData_read_block(PyObject *self, PyObject *args, PyObject *kwargs);
|
|
|
|
static PyMethodDef RecordingDataMethods[] = { //
|
|
// clear(self) -> None
|
|
{"clear", (PyCFunction)RecordingData_clear, METH_NOARGS, "RecordingData.clear"},
|
|
|
|
// channel_bits: List[int]
|
|
{"channels", (PyCFunction)RecordingData_channels, METH_NOARGS, "RecordingData.channels"},
|
|
|
|
// in_time_range(self, time_start:int, time_stop:int) -> bool
|
|
{"in_time_range", (PyCFunction)RecordingData_in_time_range, METH_VARARGS | METH_KEYWORDS, "RecordingData.in_time_range"},
|
|
|
|
// append_data(self, channel:int, value:Optional[int]) -> None
|
|
{"append_data", (PyCFunction)RecordingData_append_data, METH_VARARGS | METH_KEYWORDS, "RecordingData.append_data"},
|
|
|
|
// encode(self) -> bytes
|
|
{"encode", (PyCFunction)RecordingData_encode, METH_NOARGS, "RecordingData.encode"},
|
|
|
|
// decode(data:bytes) -> RecordingData
|
|
{"decode", (PyCFunction)RecordingData_decode, METH_VARARGS | METH_KEYWORDS | METH_STATIC, "RecordingData.decode"},
|
|
|
|
// entry_iter(self,
|
|
// time_start: float = 0.0,
|
|
// time_stop: float = inf) -> Iterable[Tuple[float, int, int]]
|
|
{"entry_iter", (PyCFunction)RecordingData_entry_iter, METH_VARARGS | METH_KEYWORDS, "RecordingData.entry_iter"},
|
|
|
|
// read_block(file: IO) -> Optional[Union[None, int, Tuple[int, int, int]]]
|
|
{"read_block", (PyCFunction)RecordingData_read_block, METH_VARARGS | METH_KEYWORDS | METH_STATIC, "RecordingData.read_block"},
|
|
{NULL}};
|
|
|
|
// RecordingData iterator
|
|
static PyObject *RecordingData_iter(RecordingData *self);
|
|
|
|
static PyTypeObject RecordingDataType = {
|
|
PyVarObject_HEAD_INIT(NULL, 0) //,
|
|
.tp_name = "RecordingData",
|
|
.tp_doc = "Recording Data Base",
|
|
.tp_basicsize = sizeof(RecordingData),
|
|
.tp_itemsize = 0,
|
|
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
|
|
.tp_new = RecordingData_new,
|
|
// __init__(time_stamp:int = 0, sample_rate:int = 1)
|
|
.tp_init = (initproc)RecordingData_init,
|
|
.tp_dealloc = (destructor)RecordingData_dealloc,
|
|
.tp_members = RecordingDataMembers,
|
|
.tp_getset = RecordingDataGetSet,
|
|
.tp_methods = RecordingDataMethods,
|
|
.tp_as_sequence = &RecordingDataSeq,
|
|
// __iter__
|
|
.tp_iter = (getiterfunc)RecordingData_iter,
|
|
};
|
|
|
|
// class RecordingDataIter
|
|
typedef struct {
|
|
PyObject_HEAD;
|
|
RecordingData *data;
|
|
unsigned int limit;
|
|
unsigned int offset;
|
|
unsigned int offset_d;
|
|
unsigned int offset_e;
|
|
double time_start;
|
|
double time_stop;
|
|
} RecordingDataIter;
|
|
|
|
// RecordingDataIter __init__
|
|
static PyObject *RecordingDataIter_new(PyTypeObject *type, PyObject *args, PyObject *keywords);
|
|
static int RecordingDataIter_init(RecordingDataIter *self, PyObject *args, PyObject *kwargs);
|
|
static void RecordingDataIter_dealloc(RecordingDataIter *self);
|
|
|
|
// RecordingDataIter iterator
|
|
static PyObject *RecordingDataIter_next(RecordingDataIter *self);
|
|
static PyObject *RecordingData_iter(RecordingData *self);
|
|
|
|
static PyTypeObject RecordingDataIterType = {
|
|
PyVarObject_HEAD_INIT(NULL, 0) //,
|
|
.tp_name = "RecordingDataIter",
|
|
.tp_doc = "Recording Data Iterator",
|
|
.tp_basicsize = sizeof(RecordingDataIter),
|
|
.tp_itemsize = 0,
|
|
.tp_flags = Py_TPFLAGS_DEFAULT,
|
|
.tp_new = RecordingDataIter_new,
|
|
// __init__(data:RecordingData)
|
|
.tp_init = (initproc)RecordingDataIter_init,
|
|
.tp_dealloc = (destructor)RecordingDataIter_dealloc,
|
|
// __iter__
|
|
.tp_iter = PyObject_SelfIter,
|
|
// __next__() -> Tuple[float, int, int, Union[None, int, Tuple[int, int, int]]]
|
|
.tp_iternext = (iternextfunc)RecordingDataIter_next,
|
|
};
|
|
|
|
// function implement
|
|
|
|
static PyObject *RecordingData_new(PyTypeObject *type, PyObject *args, PyObject *keywords) {
|
|
RecordingData *self = (RecordingData *)type->tp_alloc(type, 0);
|
|
|
|
if (self != NULL) {
|
|
self->time_stamp = 0;
|
|
self->sample_rate = 0;
|
|
self->sample_rate_channel_upper_256 = 0;
|
|
self->format = 0;
|
|
self->device = 0;
|
|
self->channel = 0;
|
|
self->data_width = 0;
|
|
self->data_capacity = 0;
|
|
self->data_size = 0;
|
|
self->batch_number = 0;
|
|
self->cycle = NULL;
|
|
self->data = NULL;
|
|
self->sample_rates = NULL;
|
|
}
|
|
|
|
return (PyObject *)self;
|
|
}
|
|
|
|
static int RecordingData_init(RecordingData *self, PyObject *args, PyObject *kwargs) {
|
|
static char *keywords[] = {"device", "time_stamp", "sample_rate", "sample_rate_channel_upper_256", "batch_number", NULL};
|
|
|
|
unsigned int device = 0;
|
|
double time_stamp = 0;
|
|
unsigned int sample_rate = 1;
|
|
unsigned int data_width = 2;
|
|
unsigned int sample_rate_channel_upper_256 = 1;
|
|
unsigned int batch_number = 0;
|
|
|
|
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|IdIII", keywords, &device, &time_stamp, &sample_rate, &sample_rate_channel_upper_256, &batch_number)) return -1;
|
|
|
|
self->device = device;
|
|
self->time_stamp = time_stamp;
|
|
self->sample_rate = sample_rate;
|
|
self->sample_rate_channel_upper_256 = sample_rate_channel_upper_256;
|
|
self->data_width = data_width;
|
|
// self->data_capacity = data_capacity;
|
|
self->batch_number = batch_number;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void RecordingData_dealloc(RecordingData *self) {
|
|
if (self->data != NULL) {
|
|
PyMem_Free(self->data);
|
|
}
|
|
self->data = NULL;
|
|
Py_TYPE(self)->tp_free((PyObject *)self);
|
|
}
|
|
|
|
/**
|
|
* data count in this record.
|
|
*
|
|
*/
|
|
static Py_ssize_t RecordingData_len(RecordingData *self) {
|
|
return self->data_size / (self->format == 0 ? 1 : 2);
|
|
}
|
|
|
|
static int RecordingData_get0(RecordingData *self, unsigned int index, unsigned int *channel, int *value) {
|
|
if (self->format == 0) {
|
|
if (index >= self->data_size) goto RecordingData_get0_index_error;
|
|
|
|
*channel = self->channel;
|
|
*value = self->data[index];
|
|
|
|
return 1;
|
|
} else {
|
|
unsigned int offset = index * 2;
|
|
|
|
if (index + 1 >= self->data_size) goto RecordingData_get0_index_error;
|
|
|
|
int value_tmp = self->data[offset];
|
|
int channel_tmp = self->data[offset + 1];
|
|
|
|
if (channel_tmp < 0) {
|
|
// invalid data
|
|
*channel = (unsigned int)(-channel_tmp - 1);
|
|
*value = 0;
|
|
return 0;
|
|
} else {
|
|
// valid data
|
|
*channel = (unsigned int)channel_tmp;
|
|
*value = value_tmp;
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
RecordingData_get0_index_error:
|
|
PyErr_SetString(PyExc_IndexError, "index out of boundary");
|
|
return 0;
|
|
}
|
|
|
|
static PyObject *RecordingData_get(RecordingData *self, Py_ssize_t index) {
|
|
unsigned int channel = 0;
|
|
int value;
|
|
|
|
if (RecordingData_get0(self, index, &channel, &value)) {
|
|
return Py_BuildValue("(Ii)", channel, value);
|
|
} else if (!PyErr_Occurred()) {
|
|
// invalid data
|
|
return Py_BuildValue("(IO)", channel, Py_None);
|
|
} else {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
static double RecordingData_at_time0(RecordingData *self, unsigned int index) {
|
|
if (self->sample_rate == 0) {
|
|
return self->time_stamp;
|
|
} else {
|
|
return self->time_stamp + 1000000.0 * index / self->sample_rate;
|
|
}
|
|
}
|
|
|
|
static double RecordingData_at_time1(RecordingData *self, unsigned int index) {
|
|
if (self->sample_rate_channel_upper_256 == 0) {
|
|
return self->time_stamp;
|
|
} else {
|
|
if (self->batch_number == 0) {
|
|
return self->time_stamp + 1000000.0 * index / self->sample_rate_channel_upper_256;
|
|
} else {
|
|
return self->time_stamp + 1000000.0 * (int)(index / self->batch_number) / self->sample_rate_channel_upper_256;
|
|
}
|
|
}
|
|
}
|
|
|
|
static double RecordingData_to_time0(RecordingData *self) {
|
|
unsigned int size = RecordingData_len(self);
|
|
|
|
if (size <= 1 || self->sample_rate == 0) {
|
|
return self->time_stamp;
|
|
} else {
|
|
return self->time_stamp + 1000000 * (size - 1) / self->sample_rate;
|
|
}
|
|
}
|
|
|
|
static PyObject *RecordingData_to_time(RecordingData *self, void *ignore) {
|
|
return PyLong_FromLong(RecordingData_to_time0(self));
|
|
}
|
|
|
|
static int RecordingData_clear0(RecordingData *self) {
|
|
self->format = 0;
|
|
self->device = 0;
|
|
self->channel = 0;
|
|
self->data_width = 0;
|
|
self->data_capacity = 0;
|
|
self->data_size = 0;
|
|
self->batch_number = 0;
|
|
|
|
if (self->cycle != NULL) {
|
|
PyMem_Free(self->cycle);
|
|
}
|
|
|
|
if (self->data != NULL) {
|
|
PyMem_Free(self->data);
|
|
}
|
|
|
|
self->data = NULL;
|
|
|
|
return 1;
|
|
}
|
|
static PyObject *RecordingData_clear(RecordingData *self) {
|
|
if (!RecordingData_clear0(self)) return NULL;
|
|
Py_RETURN_NONE;
|
|
}
|
|
|
|
static PyObject *RecordingData_channels(RecordingData *self) {
|
|
IntSet mask;
|
|
int_set_init(&mask);
|
|
|
|
//
|
|
|
|
if (self->format == 0) {
|
|
if (self->data_size > 0) {
|
|
int_set_add(&mask, self->channel);
|
|
}
|
|
} else {
|
|
for (unsigned int i = 0; i < self->data_size; i += 2) {
|
|
int_set_add(&mask, self->data[i + 1]);
|
|
}
|
|
}
|
|
|
|
// create channel list
|
|
PyObject *list = int_set_list(&mask);
|
|
|
|
int_set_clear(&mask);
|
|
|
|
return list;
|
|
}
|
|
|
|
static int RecordingData_append_data0(RecordingData *self, unsigned int channel, int *value) {
|
|
|
|
// initialize
|
|
if (self->data == NULL) {
|
|
unsigned int SIZE = 10;
|
|
int * data = (int *)PyMem_Malloc(SIZE * sizeof(int));
|
|
if (data == NULL) goto RecordingData_append_data0_malloc_error;
|
|
|
|
self->data_capacity = SIZE;
|
|
self->data_size = 0;
|
|
self->data = data;
|
|
}
|
|
|
|
// check data width
|
|
if (value != NULL) {
|
|
int v = *value;
|
|
if (-0x7FFF <= v && v < 0x7FFF) {
|
|
} else if (-0x7FFFFFFF <= v && v <= 0x7FFFFFFF) {
|
|
// for any value over range, change data_width to 4
|
|
self->data_width = 4;
|
|
} else {
|
|
PyErr_SetString(PyExc_ValueError, "value overflow");
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
// first data
|
|
if (self->data_size == 0 && value != NULL && self->data_size + 1 < self->data_capacity) {
|
|
self->format = 0;
|
|
self->channel = channel;
|
|
self->data[0] = *value;
|
|
self->data_size = 1;
|
|
return 0;
|
|
}
|
|
|
|
// expend data array
|
|
if (self->format == 0 && (self->channel != channel || value == NULL)) {
|
|
unsigned int new_size;
|
|
|
|
if (self->data_size * 2 + 2 < self->data_capacity) {
|
|
new_size = self->data_capacity;
|
|
} else {
|
|
new_size = 2 * self->data_capacity;
|
|
}
|
|
|
|
int *new_data = (int *)PyMem_Malloc(new_size * sizeof(int));
|
|
if (new_data == NULL) goto RecordingData_append_data0_malloc_error;
|
|
|
|
for (unsigned int i = 0, j = 0; i < self->data_size; i++) {
|
|
new_data[j++] = self->data[i];
|
|
new_data[j++] = self->channel;
|
|
}
|
|
|
|
PyMem_Free(self->data);
|
|
self->data = NULL;
|
|
|
|
self->format = 1;
|
|
self->channel = 0;
|
|
self->data_capacity = new_size;
|
|
self->data_size = 2 * self->data_size;
|
|
self->data = new_data;
|
|
|
|
} else if (self->data_size + (self->format == 0 ? 1 : 2) >= self->data_capacity) {
|
|
unsigned int new_size = 10 + self->data_capacity;
|
|
|
|
int *new_data = (int *)PyMem_Realloc(self->data, new_size * sizeof(int));
|
|
if (new_data == NULL) goto RecordingData_append_data0_malloc_error;
|
|
|
|
self->data = new_data;
|
|
self->data_capacity = new_size;
|
|
}
|
|
|
|
if (self->format == 0 && value != NULL) {
|
|
self->data[self->data_size++] = *value;
|
|
|
|
} else if (value != NULL) {
|
|
self->data[self->data_size++] = *value;
|
|
self->data[self->data_size++] = channel;
|
|
|
|
} else {
|
|
// mark has invalid data
|
|
self->channel = 1;
|
|
self->data[self->data_size++] = 0;
|
|
self->data[self->data_size++] = (-channel - 1);
|
|
}
|
|
|
|
return 0;
|
|
|
|
RecordingData_append_data0_malloc_error:
|
|
PyErr_SetString(PyExc_RuntimeError, "PyMem_Malloc fail");
|
|
return 1;
|
|
}
|
|
|
|
static PyObject *RecordingData_append_data(RecordingData *self, PyObject *args, PyObject *kwargs) {
|
|
static char *keywords[] = {"channel", "value", NULL};
|
|
|
|
unsigned int channel = 0;
|
|
PyObject * value_ref = NULL;
|
|
|
|
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "IO", keywords, &channel, &value_ref)) {
|
|
return NULL;
|
|
}
|
|
|
|
int len = RecordingData_len(self);
|
|
if (len >= 0xFFFF) {
|
|
PyErr_Format(PyExc_RuntimeError, "data size over flow : %d", len);
|
|
return NULL;
|
|
}
|
|
|
|
if (value_ref == NULL) {
|
|
return NULL;
|
|
|
|
} else if (value_ref == Py_None) {
|
|
if (RecordingData_append_data0(self, channel, NULL)) return NULL;
|
|
|
|
} else if (!PyLong_Check(value_ref)) {
|
|
PyErr_SetString(PyExc_TypeError, "value not int");
|
|
return NULL;
|
|
|
|
} else {
|
|
long value = PyLong_AsLong(value_ref);
|
|
|
|
if (value == -1 && PyErr_Occurred()) return NULL;
|
|
|
|
if (value < -0x7FFFFFFF || 0x7FFFFFFF < value) {
|
|
PyErr_SetString(PyExc_ValueError, "value overflow");
|
|
return NULL;
|
|
}
|
|
|
|
int int_value = (int)value;
|
|
|
|
if (RecordingData_append_data0(self, channel, &int_value)) return NULL;
|
|
}
|
|
|
|
Py_RETURN_NONE;
|
|
}
|
|
|
|
static PyObject *RecordingData_in_time_range(RecordingData *self, PyObject *args, PyObject *kwargs) {
|
|
static char *keywords[] = {"time_start", "time_stop", NULL};
|
|
double time_start;
|
|
double time_stop;
|
|
|
|
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "dd", keywords, &time_start, &time_stop)) {
|
|
return NULL;
|
|
}
|
|
|
|
unsigned int size = RecordingData_len(self);
|
|
|
|
if (size <= 1) {
|
|
if (time_start <= self->time_stamp && self->time_stamp <= time_stop) {
|
|
Py_RETURN_TRUE;
|
|
}
|
|
} else {
|
|
if (time_start <= RecordingData_to_time0(self) && self->time_stamp <= time_stop) {
|
|
Py_RETURN_TRUE;
|
|
}
|
|
}
|
|
|
|
Py_RETURN_FALSE;
|
|
}
|
|
|
|
#define _init(size) \
|
|
unsigned int offset = 0; \
|
|
char buf[size];
|
|
|
|
#define _append(list) \
|
|
do { \
|
|
if (offset > 0) { \
|
|
PyObject *x = PyBytes_FromStringAndSize(buf, offset); \
|
|
if (x == NULL) return 0; \
|
|
PyList_Append(list, x); \
|
|
offset = 0; \
|
|
} \
|
|
} while (0)
|
|
|
|
#define _u8(a) \
|
|
do { \
|
|
buf[offset++] = (char)(a); \
|
|
} while (0)
|
|
|
|
#define _u16(a) \
|
|
do { \
|
|
unsigned int __a = (unsigned int)(a); \
|
|
buf[offset++] = (char)(__a & 0xFF); \
|
|
buf[offset++] = (char)((__a >> 8) & 0xFF); \
|
|
} while (0)
|
|
|
|
#define _u32(a) \
|
|
do { \
|
|
unsigned int __a = (unsigned int)(a); \
|
|
buf[offset++] = (char)(__a & 0xFF); \
|
|
buf[offset++] = (char)((__a >> 8) & 0xFF); \
|
|
buf[offset++] = (char)((__a >> 16) & 0xFF); \
|
|
buf[offset++] = (char)((__a >> 24) & 0xFF); \
|
|
} while (0)
|
|
|
|
#define _d64(a) \
|
|
do { \
|
|
union{ \
|
|
double d; \
|
|
unsigned char b[8]; \
|
|
}DBC; \
|
|
DBC.d = a; \
|
|
buf[offset++] = DBC.b[0]; \
|
|
buf[offset++] = DBC.b[1]; \
|
|
buf[offset++] = DBC.b[2]; \
|
|
buf[offset++] = DBC.b[3]; \
|
|
buf[offset++] = DBC.b[4]; \
|
|
buf[offset++] = DBC.b[5]; \
|
|
buf[offset++] = DBC.b[6]; \
|
|
buf[offset++] = DBC.b[7]; \
|
|
} while (0)
|
|
|
|
#define _value(w, a) \
|
|
do { \
|
|
switch (w) { \
|
|
case 1: \
|
|
buf[offset++] = (char)(a + 0x80); \
|
|
break; \
|
|
case 2: { \
|
|
unsigned int __a = (unsigned int)(a + 0x8000); \
|
|
buf[offset++] = (char)(__a & 0xFF); \
|
|
buf[offset++] = (char)((__a >> 8) & 0xFF); \
|
|
break; \
|
|
} \
|
|
case 3: { \
|
|
unsigned int __a = (unsigned int)(a + 0x800000); \
|
|
buf[offset++] = (char)(__a & 0xFF); \
|
|
buf[offset++] = (char)((__a >> 8) & 0xFF); \
|
|
buf[offset++] = (char)((__a >> 16) & 0xFF); \
|
|
break; \
|
|
} \
|
|
case 4: { \
|
|
unsigned int __a = (unsigned int)(a + 0x80000000); \
|
|
buf[offset++] = (char)(__a & 0xFF); \
|
|
buf[offset++] = (char)((__a >> 8) & 0xFF); \
|
|
buf[offset++] = (char)((__a >> 16) & 0xFF); \
|
|
buf[offset++] = (char)((__a >> 24) & 0xFF); \
|
|
break; \
|
|
} \
|
|
} \
|
|
} while (0)
|
|
|
|
static int RecordingData_encode_format0(RecordingData *self, PyObject *list) {
|
|
_init(16);
|
|
|
|
_u8(0); // +1
|
|
_u8(self->device); // +1
|
|
_u8(self->channel); // +1
|
|
_u8(self->data_width); // +1
|
|
_d64(self->time_stamp); // +4
|
|
_value(self->data_width, self->data[0]); // +4
|
|
_append(list);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int RecordingData_encode_format1(RecordingData *self, PyObject *list) {
|
|
_init(128);
|
|
|
|
_u8(1); // +1
|
|
_u8(self->device); // +1
|
|
_u8(self->channel); // +1
|
|
_u8(self->data_width); // +1
|
|
_d64(self->time_stamp); // +4
|
|
_u16(RecordingData_len(self)); // +2
|
|
_u16(self->sample_rate); // +2
|
|
|
|
for (unsigned int i = 0; i < self->data_size; i++) {
|
|
_value(self->data_width, self->data[i]); // +4
|
|
|
|
if (offset > 120) _append(list);
|
|
}
|
|
|
|
_append(list);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int RecordingData_encode_format2(RecordingData *self, PyObject *list) {
|
|
_init(128);
|
|
|
|
_u8(2); // +1
|
|
_u8(self->device); // +1
|
|
_u8(0); // +1 channel
|
|
_u8(self->data_width); // +1
|
|
_d64(self->time_stamp); // +4
|
|
_u16(RecordingData_len(self)); // +2
|
|
_u16(self->sample_rate); // +2
|
|
|
|
for (unsigned int i = 0; i < self->data_size; i += 2) {
|
|
_u8(self->data[i + 1]); // +1
|
|
_value(self->data_width, self->data[i]); // +4
|
|
|
|
if (offset > 120) _append(list);
|
|
}
|
|
|
|
_append(list);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int RecordingData_encode_format3(RecordingData *self, PyObject *list) {
|
|
_init(128);
|
|
|
|
_u8(3); // +1
|
|
_u8(self->device); // +1
|
|
_u8(0); // +1 channel
|
|
_u8(self->data_width); // +1
|
|
_d64(self->time_stamp); // +4
|
|
_u16(RecordingData_len(self)); // +2
|
|
_u16(self->sample_rate); // +2
|
|
|
|
for (unsigned int i = 0; i < self->data_size; i += 16) {
|
|
unsigned int bit_map = 0;
|
|
for (unsigned int j = 0; j < 8 && i + 2 * j < self->data_size; j++) {
|
|
if (self->data[i + 2 * j + 1] < 0) {
|
|
bit_map |= (1 << j);
|
|
}
|
|
}
|
|
|
|
if (offset > 120) _append(list);
|
|
|
|
_u8(bit_map);
|
|
|
|
for (unsigned int j = i; j < i + 16 && j < self->data_size; j += 2) {
|
|
int channel = self->data[j + 1];
|
|
if (channel < 0) {
|
|
_u8(-channel - 1);
|
|
_value(self->data_width, 0); // +4
|
|
} else {
|
|
_u8(channel);
|
|
_value(self->data_width, self->data[j]); // +4
|
|
}
|
|
|
|
if (offset > 120) _append(list);
|
|
}
|
|
}
|
|
|
|
_append(list);
|
|
|
|
return 1;
|
|
}
|
|
|
|
#undef _u8
|
|
#undef _u16
|
|
#undef _u32
|
|
#undef _d64
|
|
#undef _value
|
|
#undef _append
|
|
#undef _init
|
|
|
|
static PyObject *RecordingData_encode(RecordingData *self) {
|
|
if (self->data_size == 0) {
|
|
return PyBytes_FromString("");
|
|
}
|
|
|
|
// check data length
|
|
{
|
|
int len = RecordingData_len(self);
|
|
if (len > 0xFFFF) {
|
|
PyErr_Format(PyExc_RuntimeError, "data length over flow : %d", len);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
PyObject *list = PyList_New(0);
|
|
if (list == NULL) return NULL;
|
|
|
|
if (self->data_size == 1) {
|
|
if (!RecordingData_encode_format0(self, list)) return NULL;
|
|
} else if (self->format == 0) {
|
|
if (!RecordingData_encode_format1(self, list)) return NULL;
|
|
} else if (self->channel == 0) {
|
|
// all data are valid
|
|
if (!RecordingData_encode_format2(self, list)) return NULL;
|
|
} else {
|
|
// some invalid data
|
|
if (!RecordingData_encode_format3(self, list)) return NULL;
|
|
}
|
|
|
|
PyObject *empty = PyBytes_FromString("");
|
|
PyObject *ret = PyObject_CallMethod(empty, "join", "(O)", list);
|
|
Py_CLEAR(list);
|
|
Py_CLEAR(empty);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int RecordingData_decode_init(RecordingDataDecodeState *state, //
|
|
void * _data) {
|
|
if (_data != NULL) {
|
|
PyErr_SetString(PyExc_RuntimeError, "RecordingData_decode_append(_data!=NULL)");
|
|
return 1;
|
|
}
|
|
|
|
RecordingData *self = (RecordingData *)PyObject_CallFunctionObjArgs((PyObject *)&RecordingDataType, NULL);
|
|
self->device = state->device;
|
|
self->channel = state->channel;
|
|
self->data_width = state->data_width;
|
|
self->time_stamp = state->time_stamp;
|
|
self->sample_rate = state->sample_rate;
|
|
state->append_data = self;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int RecordingData_decode_append(RecordingDataDecodeState *state, //
|
|
unsigned int data_index,
|
|
unsigned int device,
|
|
unsigned int channel,
|
|
int * value,
|
|
void * _data) {
|
|
if (_data == NULL) {
|
|
PyErr_SetString(PyExc_RuntimeError, "RecordingData_decode_append(_data=NULL)");
|
|
return 1;
|
|
}
|
|
|
|
RecordingData *self = (RecordingData *)_data;
|
|
|
|
return RecordingData_append_data0(self, channel, value);
|
|
}
|
|
|
|
static int RecordingDataDecodeState_init(RecordingDataDecodeState *state) {
|
|
if (state == NULL) {
|
|
PyErr_SetString(PyExc_RuntimeError, "RecordingDataDecodeState_init(state=NULL)");
|
|
return 1;
|
|
}
|
|
|
|
state->state = 0;
|
|
state->init_func = &RecordingData_decode_init;
|
|
state->append_func = &RecordingData_decode_append;
|
|
state->append_data = NULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static RecordingData *RecordingData_decode_bytes(unsigned int size, char *buf) {
|
|
// init state
|
|
RecordingDataDecodeState state;
|
|
RecordingDataDecodeState_init(&state);
|
|
|
|
// decode
|
|
unsigned int offset = 0;
|
|
int read;
|
|
|
|
while (state.state >= 0) {
|
|
// decode data
|
|
read = decode_recording_data(&state, size - offset, buf + offset);
|
|
|
|
// error
|
|
if (read < 0) goto RecordingData_decode_bytes_error;
|
|
|
|
offset += read;
|
|
|
|
// EOF?
|
|
if (size - offset < state.remind) {
|
|
PyErr_SetString(PyExc_IndexError, "data length over bytes length");
|
|
goto RecordingData_decode_bytes_error;
|
|
}
|
|
}
|
|
|
|
if (state.append_data == NULL) {
|
|
PyErr_SetString(PyExc_RuntimeError, "RecordingDataDecodeState->append_data = NULL");
|
|
return NULL;
|
|
}
|
|
|
|
return (RecordingData *)state.append_data;
|
|
|
|
RecordingData_decode_bytes_error:
|
|
if (state.append_data != NULL) {
|
|
Py_CLEAR(state.append_data);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* decode RecordingData from raw bytes.
|
|
*/
|
|
static PyObject *RecordingData_decode(RecordingData *ignore, PyObject *args, PyObject *kwargs) {
|
|
static char *keywords[] = {"data", NULL};
|
|
|
|
PyObject *data_ref = NULL;
|
|
|
|
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O", keywords, &data_ref)) {
|
|
return NULL;
|
|
}
|
|
|
|
// as C array
|
|
char *buf = PyBytes_AsString(data_ref);
|
|
if (buf == NULL) return NULL;
|
|
|
|
unsigned int size = PyBytes_Size(data_ref);
|
|
|
|
return (PyObject *)RecordingData_decode_bytes(size, buf);
|
|
}
|
|
|
|
/**
|
|
* decode RecordingData from file object.
|
|
*/
|
|
static PyObject *RecordingData_read_block(PyObject *ignore, PyObject *args, PyObject *kwargs) {
|
|
static char *keywords[] = {"file", NULL};
|
|
|
|
PyObject *file = NULL;
|
|
|
|
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O", keywords, &file)) {
|
|
return NULL;
|
|
}
|
|
|
|
// init state
|
|
RecordingDataDecodeState state;
|
|
RecordingDataDecodeState_init(&state);
|
|
|
|
int read = decode_recording_data(&state, 0, NULL);
|
|
if (read < 0) return NULL;
|
|
|
|
// read header
|
|
PyObject *data = PyObject_CallMethod(file, "read", "(I)", state.remind);
|
|
if (data == NULL) return NULL;
|
|
|
|
unsigned int size = PyBytes_Size(data);
|
|
|
|
if (size == 0) {
|
|
Py_CLEAR(data);
|
|
Py_RETURN_NONE;
|
|
}
|
|
|
|
// as C array
|
|
char *buf = PyBytes_AsString(data);
|
|
if (buf == NULL) goto RecordingData_read_block_error;
|
|
|
|
// decode header
|
|
read = decode_recording_data(&state, size, buf);
|
|
if (read < 0) goto RecordingData_read_block_error;
|
|
|
|
// decode remind
|
|
while (state.state >= 0) {
|
|
if (state.remind > 0) {
|
|
// read more
|
|
|
|
Py_CLEAR(data);
|
|
data = PyObject_CallMethod(file, "read", "(I)", state.remind);
|
|
if (data == NULL) goto RecordingData_read_block_error;
|
|
|
|
size = PyBytes_Size(data);
|
|
|
|
// as C array
|
|
buf = PyBytes_AsString(data);
|
|
if (buf == NULL) goto RecordingData_read_block_error;
|
|
}
|
|
|
|
// decode data
|
|
read = decode_recording_data(&state, size, buf);
|
|
|
|
// error
|
|
if (read < 0) goto RecordingData_read_block_error;
|
|
}
|
|
|
|
if (state.append_data == NULL) goto RecordingData_read_block_error;
|
|
|
|
Py_CLEAR(data);
|
|
return (PyObject *)state.append_data;
|
|
|
|
RecordingData_read_block_error:
|
|
if (state.append_data != NULL) Py_CLEAR(state.append_data);
|
|
Py_CLEAR(data);
|
|
return NULL;
|
|
}
|
|
|
|
static PyObject *RecordingData_iter(RecordingData *self) {
|
|
return PyObject_CallFunctionObjArgs((PyObject *)&RecordingDataIterType, self, NULL);
|
|
}
|
|
|
|
static PyObject *RecordingData_entry_iter(RecordingData *self, PyObject *args, PyObject *kwargs) {
|
|
static char *keywords[] = {"time_start", "time_stop", NULL};
|
|
double time_start = 0.0;
|
|
double time_stop = INFINITY;
|
|
|
|
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|dd", keywords, &time_start, &time_stop)) {
|
|
return NULL;
|
|
}
|
|
|
|
PyObject *ret = RecordingData_iter(self);
|
|
|
|
if (ret != NULL) {
|
|
RecordingDataIter *iter = (RecordingDataIter *)ret;
|
|
iter->time_start = time_start;
|
|
iter->time_stop = time_stop;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static PyObject *RecordingDataIter_new(PyTypeObject *type, PyObject *args, PyObject *keywords) {
|
|
RecordingDataIter *self = (RecordingDataIter *)type->tp_alloc(type, 0);
|
|
|
|
self->data = NULL;
|
|
self->limit = 0;
|
|
self->offset = 0;
|
|
self->offset_d = 0;
|
|
self->offset_e = 0;
|
|
self->time_start = 0.0;
|
|
self->time_stop = INFINITY;
|
|
|
|
return (PyObject *)self;
|
|
}
|
|
|
|
static int RecordingDataIter_init(RecordingDataIter *self, PyObject *args, PyObject *kwargs) {
|
|
static char *keywords[] = {"data", NULL};
|
|
|
|
PyObject *data;
|
|
|
|
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!", keywords, &RecordingDataType, &data)) return -1;
|
|
|
|
RecordingData *ref = (RecordingData *)data;
|
|
|
|
self->data = ref;
|
|
self->limit = RecordingData_len(ref);
|
|
|
|
Py_INCREF(self->data);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void RecordingDataIter_dealloc(RecordingDataIter *self) {
|
|
Py_CLEAR(self->data);
|
|
Py_TYPE(self)->tp_free((PyObject *)self);
|
|
}
|
|
|
|
static PyObject *RecordingDataIter_next(RecordingDataIter *self) {
|
|
if (self->offset >= self->limit) {
|
|
// out of range
|
|
return NULL;
|
|
}
|
|
|
|
RecordingData *ref = (RecordingData *)self->data;
|
|
unsigned int offset = self->offset;
|
|
unsigned int offset_d = self->offset_d;
|
|
unsigned int offset_e = self->offset_e;
|
|
double time_stamp_d = RecordingData_at_time0(ref, offset_d);
|
|
double time_stamp_e = RecordingData_at_time1(ref, offset_e);
|
|
double time_stamp = RecordingData_at_time0(ref, offset);
|
|
|
|
self->offset = offset + 1;
|
|
|
|
unsigned int channel = 0;
|
|
int value;
|
|
|
|
if (RecordingData_get0(ref, offset, &channel, &value)) {
|
|
if (channel > 255) {
|
|
self->offset_e = self->offset_e + 1;
|
|
return Py_BuildValue("(dIi)", time_stamp_e, channel, value);
|
|
} else {
|
|
self->offset_d = self->offset_d + 1;
|
|
return Py_BuildValue("(dIi)", time_stamp_d, channel, value);
|
|
}
|
|
} else if (!PyErr_Occurred()) {
|
|
// invalid data
|
|
return Py_BuildValue("(dIO)", time_stamp, channel, Py_None);
|
|
} else {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
// module init
|
|
|
|
static PyMethodDef moduleMethods[] = { //
|
|
{NULL, NULL, 0, NULL}};
|
|
|
|
static struct PyModuleDef RecordingModule = { //
|
|
PyModuleDef_HEAD_INIT,
|
|
// module name
|
|
"recording",
|
|
// module doc
|
|
NULL,
|
|
-1,
|
|
moduleMethods};
|
|
|
|
// module export
|
|
|
|
PyMODINIT_FUNC PyInit_recording(void) {
|
|
PyObject *module;
|
|
|
|
module = PyModule_Create(&RecordingModule);
|
|
if (module == NULL) return NULL;
|
|
|
|
if (PyType_Ready(&RecordingDataType) < 0) return NULL;
|
|
|
|
Py_INCREF(&RecordingDataType);
|
|
PyModule_AddObject(module, "RecordingData", (PyObject *)&RecordingDataType);
|
|
|
|
if (PyType_Ready(&RecordingDataIterType) < 0) return NULL;
|
|
|
|
Py_INCREF(&RecordingDataIterType);
|
|
PyModule_AddObject(module, "RecordingDataIter", (PyObject *)&RecordingDataIterType);
|
|
|
|
return module;
|
|
}
|