Cythonize some low-level VM stuff.

looper
Ben Niemann 5 years ago
parent 0aed9fd747
commit 5dcb9075cb
  1. 24
      bin/runtests.py
  2. 4
      noisicaa/audioproc/node.py
  3. 4
      noisicaa/audioproc/nodes/track_event_source.py
  4. 18
      noisicaa/audioproc/ports.py
  5. 145
      noisicaa/audioproc/vm/buffer_type.py
  6. 59
      noisicaa/audioproc/vm/buffer_type_test.py
  7. 16
      noisicaa/audioproc/vm/buffers.pxd
  8. 220
      noisicaa/audioproc/vm/buffers.pyx
  9. 148
      noisicaa/audioproc/vm/buffers_test.py
  10. 105
      noisicaa/audioproc/vm/engine.pyx
  11. 108
      noisicaa/audioproc/vm/engine_perftest.py
  12. 32
      noisicaa/audioproc/vm/engine_test.py
  13. 16
      noisicaa/bindings/csound.pyx
  14. 4
      noisicaa/bindings/ladspa.pyx
  15. 4
      noisicaa/bindings/lilv.pyx
  16. 2
      noisicaa/bindings/lv2/atom.pxd
  17. 37
      noisicaa/bindings/lv2/atom.pyx
  18. 2
      noisicaa/bindings/lv2/urid.pxd
  19. 2
      noisicaa/bindings/lv2/urid.pyx
  20. 37
      noisicaa/music/player_integration_test.py
  21. 0
      noisidev/__init__.py
  22. 46
      noisidev/perf_stats.py
  23. 2
      testlogs/engine_perftest.csv
  24. 1
      testlogs/player_integration_test.csv

@ -28,8 +28,9 @@ def main(argv):
default='error',
help="Minimum level for log messages written to STDERR.")
parser.add_argument('--nocoverage', action='store_true', default=False)
parser.add_argument('--write_perf_stats', action='store_true', default=False)
parser.add_argument('--write-perf-stats', action='store_true', default=False)
parser.add_argument('--profile', action='store_true', default=False)
parser.add_argument('--gdb', action='store_true', default=False)
args = parser.parse_args(argv[1:])
constants.TEST_OPTS.WRITE_PERF_STATS = args.write_perf_stats
@ -51,6 +52,10 @@ def main(argv):
directives['profile'] = True
build_dir += '-profile'
if args.profile:
directives['gdb_debug'] = True
build_dir += '-dbg'
pyximport.install(
build_dir=os.path.join(os.getenv('VIRTUAL_ENV'), build_dir),
setup_args={
@ -90,21 +95,24 @@ def main(argv):
modpath = modpath[len(LIBDIR)+1:]
modname = modpath.replace('/', '.')
is_test = modname.endswith('test')
is_unittest = modname.endswith('_test')
if args.selectors:
matched = False
for selector in args.selectors:
if modname.startswith(selector):
matched = True
if modname == selector:
matched = is_test
elif modname.startswith(selector):
matched = is_unittest
else:
matched = True
is_test = modname.endswith('_test')
matched = is_unittest
if (is_test and matched) or not args.nocoverage:
if matched or not args.nocoverage:
logging.info("Loading module %s...", modname)
__import__(modname)
if not is_test or not matched:
if not matched:
continue
modsuite = loader.loadTestsFromName(modname)

@ -9,7 +9,7 @@ from noisicaa import node_db
from .exceptions import Error
from . import ports
from .vm import ast
from .vm import buffer_type
from .vm import buffers
logger = logging.getLogger(__name__)
@ -158,7 +158,7 @@ class Node(object):
for parameter_name, parameter_desc in sorted(self.__parameters.items()):
buf_name = '%s:param:%s' % (self.id, parameter_name)
seq.add(ast.AllocBuffer(buf_name, buffer_type.Float()))
seq.add(ast.AllocBuffer(buf_name, buffers.Float()))
seq.add(ast.FetchParameter(buf_name, buf_name))
for port in itertools.chain(

@ -10,7 +10,7 @@ from noisicaa import node_db
from .. import ports
from .. import node
from ..vm import ast
from ..vm import buffer_type
from ..vm import buffers
logger = logging.getLogger(__name__)
@ -36,7 +36,7 @@ class TrackEventSource(node.BuiltinNode):
'track:' + self.track_id,
self.outputs['out'].buf_name))
seq.add(ast.AllocBuffer(
self.id + ':messages', buffer_type.AtomData(10240)))
self.id + ':messages', buffers.AtomData(10240)))
seq.add(ast.FetchMessages(
core.build_labelset({core.MessageKey.trackId: self.track_id}),
self.id + ':messages'))

@ -3,7 +3,7 @@
import logging
from .exceptions import Error
from .vm import buffer_type
from .vm import buffers
logger = logging.getLogger(__name__)
@ -102,7 +102,7 @@ class AudioInputPort(InputPort):
raise Error("Can only connect to AudioOutputPort")
def get_buf_type(self, compiler):
return buffer_type.FloatArray(compiler.frame_size)
return buffers.FloatArray(compiler.frame_size)
class AudioOutputPort(OutputPort):
@ -147,7 +147,7 @@ class AudioOutputPort(OutputPort):
self.drywet = drywet
def get_buf_type(self, compiler):
return buffer_type.FloatArray(compiler.frame_size)
return buffers.FloatArray(compiler.frame_size)
class ARateControlInputPort(InputPort):
@ -157,12 +157,12 @@ class ARateControlInputPort(InputPort):
raise Error("Can only connect to ARateControlOutputPort")
def get_buf_type(self, compiler):
return buffer_type.FloatArray(compiler.frame_size)
return buffers.FloatArray(compiler.frame_size)
class ARateControlOutputPort(OutputPort):
def get_buf_type(self, compiler):
return buffer_type.FloatArray(compiler.frame_size)
return buffers.FloatArray(compiler.frame_size)
class KRateControlInputPort(InputPort):
@ -172,12 +172,12 @@ class KRateControlInputPort(InputPort):
raise Error("Can only connect to KRateControlOutputPort")
def get_buf_type(self, compiler):
return buffer_type.Float()
return buffers.Float()
class KRateControlOutputPort(OutputPort):
def get_buf_type(self, compiler):
return buffer_type.Float()
return buffers.Float()
class EventInputPort(InputPort):
@ -191,9 +191,9 @@ class EventInputPort(InputPort):
raise Error("Can only connect to EventOutputPort")
def get_buf_type(self, compiler):
return buffer_type.AtomData(10240)
return buffers.AtomData(10240)
class EventOutputPort(OutputPort):
def get_buf_type(self, compiler):
return buffer_type.AtomData(10240)
return buffers.AtomData(10240)

@ -1,145 +0,0 @@
#!/usr/bin/python3
import numpy
from noisicaa.bindings import lv2
class BufferType(object):
def __init__(self):
pass
def __eq__(self, other):
return str(self) == str(other)
def __str__(self):
raise NotImplementedError
def __len__(self):
raise NotImplementedError
def make_view(self, buf):
raise NotImplementedError
def clear_buffer(self, buf):
raise NotImplementedError
def mix_buffers(self, buf1, buf2):
raise NotImplementedError
class Float(BufferType):
def __str__(self):
return 'float'
def __len__(self):
return 4
def make_view(self, buf):
return numpy.frombuffer(buf, dtype=numpy.float32, offset=0, count=1)
def clear_buffer(self, buf):
buf[0:4] = bytes([0,0,0,0])
def mix_buffers(self, buf1, buf2):
view1 = self.make_view(buf1)
view2 = self.make_view(buf2)
view1 += view2
class FloatArray(BufferType):
def __init__(self, size):
super().__init__()
self.__size = size
def __str__(self):
return 'float[%d]' % self.__size
def __len__(self):
return 4 * self.__size
@property
def size(self):
return self.__size
def make_view(self, buf):
return numpy.frombuffer(buf, dtype=numpy.float32, offset=0, count=self.__size)
def clear_buffer(self, buf):
view = self.make_view(buf)
view.fill(0.0)
def mix_buffers(self, buf1, buf2):
view1 = self.make_view(buf1)
view2 = self.make_view(buf2)
view1 += view2
class AtomData(BufferType):
def __init__(self, size):
super().__init__()
self.__size = size
def __str__(self):
return 'atom[%d]' % self.__size
def __len__(self):
return self.__size
@property
def size(self):
return self.__size
def make_view(self, buf):
return numpy.frombuffer(buf, dtype=numpy.uint8, offset=0, count=self.__size)
def clear_buffer(self, buf):
forge = lv2.AtomForge(lv2.static_mapper)
forge.set_buffer(buf, self.__size)
with forge.sequence():
pass
def mix_buffers(self, buf1, buf2):
merged = bytearray(self.__size)
seq1 = lv2.wrap_atom(lv2.static_mapper, buf1)
events1 = seq1.events
seq2 = lv2.wrap_atom(lv2.static_mapper, buf2)
events2 = seq2.events
forge = lv2.AtomForge(lv2.static_mapper)
forge.set_buffer(merged, self.__size)
with forge.sequence():
idx1 = 0
idx2 = 0
while idx1 < len(events1) and idx2 < len(events2):
if events1[idx1].frames <= events2[idx2].frames:
event = events1[idx1]
idx1 += 1
else:
event = events2[idx2]
idx2 += 1
atom = event.atom
forge.write_atom_event(
event.frames,
atom.type_urid, atom.data, atom.size)
for idx in range(idx1, len(events1)):
event = events1[idx]
atom = event.atom
forge.write_atom_event(
event.frames,
atom.type_urid, atom.data, atom.size)
for idx in range(idx2, len(events2)):
event = events2[idx]
atom = event.atom
forge.write_atom_event(
event.frames,
atom.type_urid, atom.data, atom.size)
buf1[:] = merged

@ -1,59 +0,0 @@
#!/usr/bin/python3
import unittest
from noisicaa.bindings import lv2
from . import buffer_type
class BufferTypeTest(unittest.TestCase):
def test_float(self):
bt = buffer_type.Float()
self.assertEqual(len(bt), 4)
def test_float_array(self):
bt = buffer_type.FloatArray(5)
self.assertEqual(len(bt), 20)
class AtomDataTest(unittest.TestCase):
def _fill_buffer(self, buf, data):
forge = lv2.AtomForge(lv2.static_mapper)
forge.set_buffer(buf, len(buf))
urid = lv2.static_mapper.map(b'http://lv2plug.in/ns/ext/atom#String')
with forge.sequence():
for frames, item in data:
forge.write_atom_event(frames, urid, item, len(item))
def _read_buffer(self, buf):
result = []
seq = lv2.wrap_atom(lv2.static_mapper, buf)
for event in seq.events:
self.assertEqual(
event.atom.type_uri, b'http://lv2plug.in/ns/ext/atom#String')
result.append((event.frames, event.atom.data))
return result
def test_mix_buffers(self):
bt = buffer_type.AtomData(1024)
buf1 = bytearray(1024)
self._fill_buffer(buf1, [(0, b'0'), (10, b'10'), (20, b'20'), (30, b'30')])
buf2 = bytearray(1024)
self._fill_buffer(buf2, [(1, b'1'), (9, b'9'), (11, b'11'), (15, b'15')])
bt.mix_buffers(buf1, buf2)
self.assertEqual(
self._read_buffer(buf1),
[(0, b'0'), (1, b'1'), (9, b'9'), (10, b'10'),
(11, b'11'), (15, b'15'), (20, b'20'), (30, b'30')])
if __name__ == '__main__':
unittest.main()

@ -0,0 +1,16 @@
cdef class BufferType:
cpdef int clear_buffer(self, char* buf) except -1
cpdef int mix_buffers(self, const char* buf1, char* buf2) except -1
cpdef int mul_buffer(self, char* buf, float factor) except -1
cdef class Buffer(object):
cdef readonly BufferType type
cdef char* data
cpdef bytes to_bytes(self)
cpdef int set_bytes(self, bytes data) except -1
cpdef int clear(self) except -1
cpdef int mix(self, Buffer other) except -1
cpdef int mul(self, float factor) except -1

@ -0,0 +1,220 @@
#!/usr/bin/python3
from libc.stdint cimport uint32_t, uint8_t
from libc cimport string
from libc cimport stdlib
cimport cpython
cimport cpython.mem
from noisicaa.bindings.lv2 cimport atom
from noisicaa.bindings.lv2 cimport urid
from noisicaa.bindings.lv2 import urid
cdef class BufferType(object):
def __init__(self):
pass
def __richcmp__(self, other, int op):
a = str(self)
b = str(other)
if op == 0:
return a < b
elif op == 1:
return a <= b
elif op == 2:
return a == b
elif op == 3:
return a != b
elif op == 4:
return a > b
elif op == 5:
return a >= b
def __str__(self):
raise NotImplementedError
def __len__(self):
raise NotImplementedError
cpdef int clear_buffer(self, char* buf) except -1:
raise NotImplementedError
cpdef int mix_buffers(self, const char* buf1, char* buf2) except -1:
raise NotImplementedError
cpdef int mul_buffer(self, char* buf, float factor) except -1:
raise NotImplementedError
cdef class Float(BufferType):
def __str__(self):
return 'float'
def __len__(self):
return 4
cpdef int clear_buffer(self, char* buf) except -1:
(<float*>buf)[0] = 0.0
return 0
cpdef int mix_buffers(self, const char* buf1, char* buf2) except -1:
(<float*>buf2)[0] += (<float*>buf1)[0]
return 0
cpdef int mul_buffer(self, char* buf, float factor) except -1:
(<float*>buf)[0] *= factor
return 0
cdef class FloatArray(BufferType):
cdef uint32_t __size
def __init__(self, size):
super().__init__()
self.__size = size
def __str__(self):
return 'float[%d]' % self.__size
def __len__(self):
return 4 * self.__size
@property
def size(self):
return self.__size
cpdef int clear_buffer(self, char* buf) except -1:
for i in range(self.__size):
(<float*>buf)[i] = 0.0
return 0
cpdef int mix_buffers(self, const char* buf1, char* buf2) except -1:
for i in range(self.__size):
(<float*>buf2)[i] += (<float*>buf1)[i]
return 0
cpdef int mul_buffer(self, char* buf, float factor) except -1:
for i in range(self.__size):
(<float*>buf)[i] *= factor
return 0
cdef class AtomData(BufferType):
cdef uint32_t __size
def __init__(self, size):
super().__init__()
self.__size = size
def __str__(self):
return 'atom[%d]' % self.__size
def __len__(self):
return self.__size
cpdef int clear_buffer(self, char* buf) except -1:
forge = atom.AtomForge(urid.static_mapper)
temp = bytearray(self.__size)
forge.set_buffer(temp, self.__size)
with forge.sequence():
pass
string.memmove(buf, <char*>temp, self.__size)
return 0
cpdef int mix_buffers(self, const char* buf1, char* buf2) except -1:
seq1 = atom.Atom.wrap(urid.static_mapper, <uint8_t *>buf1)
events1 = seq1.events
seq2 = atom.Atom.wrap(urid.static_mapper, <uint8_t *>buf2)
events2 = seq2.events
forge = atom.AtomForge(urid.static_mapper)
merged = bytearray(self.__size)
forge.set_buffer(merged, self.__size)
with forge.sequence():
idx1 = 0
idx2 = 0
while idx1 < len(events1) and idx2 < len(events2):
if events1[idx1].frames <= events2[idx2].frames:
event = events1[idx1]
idx1 += 1
else:
event = events2[idx2]
idx2 += 1
forge.write_atom_event(
event.frames,
event.atom.type_urid, event.atom.data, event.atom.size)
for idx in range(idx1, len(events1)):
event = events1[idx]
forge.write_atom_event(
event.frames,
event.atom.type_urid, event.atom.data, event.atom.size)
for idx in range(idx2, len(events2)):
event = events2[idx]
forge.write_atom_event(
event.frames,
event.atom.type_urid, event.atom.data, event.atom.size)
string.memmove(buf2, <char*>merged, self.__size)
return 0
cpdef int mul_buffer(self, char* buf, float factor) except -1:
raise TypeError("Operation not supported for AtomData")
cdef class Buffer(object):
def __init__(self, buf_type):
self.type = buf_type
self.data = <char*>stdlib.malloc(len(self.type))
self.type.clear_buffer(self.data)
def __dealloc__(self):
if self.data != NULL:
stdlib.free(self.data)
self.data = NULL
def __getbuffer__(self, cpython.Py_buffer *buffer, int flags):
buffer.obj = self
buffer.buf = self.data
buffer.itemsize = 1
buffer.len = len(self.type)
buffer.ndim = 1
buffer.internal = NULL
buffer.readonly = 0
buffer.suboffsets = NULL
buffer.format = 'B'
buffer.shape = NULL
buffer.strides = NULL
def __releasebuffer__(self, cpython.Py_buffer *buffer):
pass
cpdef bytes to_bytes(self):
return bytes(self.data[:len(self.type)])
cpdef int set_bytes(self, bytes data) except -1:
cdef uint32_t len_self = len(self.type)
cdef uint32_t len_data = len(data)
assert len_data <= len_self, '%s > %s' % (len_data, len_self)
if len_data < len_self:
self.type.clear_buffer(self.data)
string.memmove(self.data, <char*>data, len_data)
return 0
cpdef int clear(self) except -1:
self.type.clear_buffer(self.data)
return 0
cpdef int mix(self, Buffer other) except -1:
assert self.type == other.type, '%s != %s' % (self.type, other.type)
self.type.mix_buffers(other.data, self.data)
return 0
cpdef int mul(self, float factor) except -1:
self.type.mul_buffer(self.data, factor)
return 0

@ -0,0 +1,148 @@
#!/usr/bin/python3
import struct
import unittest
from noisicaa.bindings import lv2
from . import buffers
class BufferTypeTest(unittest.TestCase):
def test_float(self):
bt = buffers.Float()
self.assertEqual(len(bt), 4)
def test_floatarray(self):
bt = buffers.FloatArray(5)
self.assertEqual(bt.size, 5)
self.assertEqual(len(bt), 20)
def test_atomdata(self):
bt = buffers.AtomData(128)
self.assertEqual(len(bt), 128)
class BufferTest(unittest.TestCase):
def test_byte_access(self):
buf = buffers.Buffer(buffers.FloatArray(4))
self.assertEqual(
struct.unpack('=ffff', buf.to_bytes()),
(0.0, 0.0, 0.0, 0.0))
buf.set_bytes(struct.pack('=ff', 1.0, 2.0))
self.assertEqual(
struct.unpack('=ffff', buf.to_bytes()),
(1.0, 2.0, 0.0, 0.0))
with self.assertRaises(AssertionError):
buf.set_bytes(struct.pack('=fffff', 1.0, 2.0, 3.0, 4.0, 5.0))
def test_buffer_protocol(self):
buf = buffers.Buffer(buffers.Float())
buf.set_bytes(struct.pack('=f', 1.0))
v = memoryview(buf)
self.assertEqual(v.itemsize, 1)
self.assertEqual(v.format, 'B')
self.assertEqual(v.nbytes, 4)
self.assertEqual(v.ndim, 1)
self.assertEqual(v.shape, (4,))
self.assertEqual(v.strides, (1,))
self.assertEqual(struct.unpack('=f', v[:]), (1.0,))
v[:] = b'abcd'
self.assertEqual(buf.to_bytes(), b'abcd')
def test_clear_float(self):
buf = buffers.Buffer(buffers.Float())
buf.set_bytes(struct.pack('=f', 1.0))
buf.clear()
self.assertEqual(struct.unpack('=f', buf.to_bytes()), (0.0,))
def test_mix_float(self):
buf1 = buffers.Buffer(buffers.Float())
buf1.set_bytes(struct.pack('=f', 1.0))
buf2 = buffers.Buffer(buffers.Float())
buf2.set_bytes(struct.pack('=f', 2.0))
buf1.mix(buf2)
self.assertEqual(struct.unpack('=f', buf1.to_bytes()), (3.0,))
self.assertEqual(struct.unpack('=f', buf2.to_bytes()), (2.0,))
def test_mul_float(self):
buf = buffers.Buffer(buffers.Float())
buf.set_bytes(struct.pack('=f', 2.0))
buf.mul(3.0)
self.assertEqual(struct.unpack('=f', buf.to_bytes()), (6.0,))
def test_clear_floatarray(self):
buf = buffers.Buffer(buffers.FloatArray(4))
buf.set_bytes(struct.pack('=ffff', 1.0, 2.0, 3.0, 4.0))
buf.clear()
self.assertEqual(struct.unpack('=ffff', buf.to_bytes()), (0.0, 0.0, 0.0, 0.0))
def test_mix_float(self):
buf1 = buffers.Buffer(buffers.FloatArray(4))
buf1.set_bytes(struct.pack('=ffff', 1.0, 2.0, 3.0, 4.0))
buf2 = buffers.Buffer(buffers.FloatArray(4))
buf2.set_bytes(struct.pack('=ffff', 2.0, 3.0, 4.0, 1.0))
buf1.mix(buf2)
self.assertEqual(struct.unpack('=ffff', buf1.to_bytes()), (3.0, 5.0, 7.0, 5.0))
self.assertEqual(struct.unpack('=ffff', buf2.to_bytes()), (2.0, 3.0, 4.0, 1.0))
def test_mul_float(self):
buf = buffers.Buffer(buffers.FloatArray(4))
buf.set_bytes(struct.pack('=ffff', 1.0, 2.0, 3.0, 4.0))
buf.mul(3.0)
self.assertEqual(struct.unpack('=ffff', buf.to_bytes()), (3.0, 6.0, 9.0, 12.0))
def _fill_atom_buffer(self, buf, data):
temp = bytearray(len(buf.type))
forge = lv2.AtomForge(lv2.static_mapper)
forge.set_buffer(temp, len(temp))
urid = lv2.static_mapper.map(b'http://lv2plug.in/ns/ext/atom#String')
with forge.sequence():
for frames, item in data:
forge.write_atom_event(frames, urid, item, len(item))
buf.set_bytes(bytes(temp))
def _read_atom_buffer(self, buf):
result = []
seq = lv2.wrap_atom(lv2.static_mapper, buf.to_bytes())
for event in seq.events:
self.assertEqual(
event.atom.type_uri, b'http://lv2plug.in/ns/ext/atom#String')
result.append((event.frames, event.atom.data))
return result
def test_clear_atomdata(self):
buf = buffers.Buffer(buffers.AtomData(1024))
self._fill_atom_buffer(buf, [(0, b'0'), (10, b'10'), (20, b'20'), (30, b'30')])
buf.clear()
self.assertEqual(self._read_atom_buffer(buf), [])
def test_mix_atomdata(self):
buf1 = buffers.Buffer(buffers.AtomData(1024))
self._fill_atom_buffer(buf1, [(0, b'0'), (10, b'10'), (20, b'20'), (30, b'30')])
buf2 = buffers.Buffer(buffers.AtomData(1024))
self._fill_atom_buffer(buf2, [(1, b'1'), (9, b'9'), (11, b'11'), (15, b'15')])
buf1.mix(buf2)
self.assertEqual(
self._read_atom_buffer(buf1),
[(0, b'0'), (1, b'1'), (9, b'9'), (10, b'10'),
(11, b'11'), (15, b'15'), (20, b'20'), (30, b'30')])
self.assertEqual(
self._read_atom_buffer(buf2),
[(1, b'1'), (9, b'9'), (11, b'11'), (15, b'15')])
def test_mul_atomdata(self):
buf = buffers.Buffer(buffers.AtomData(1024))
with self.assertRaises(TypeError):
buf.mul(3.0)
if __name__ == '__main__':
unittest.main()

@ -1,5 +1,8 @@
#!/usr/bin/python3
from libc cimport stdlib
from libc cimport string
import enum
import logging
import math
@ -15,7 +18,8 @@ from noisicaa import rwlock
from noisicaa import audioproc
from noisicaa.bindings import lv2
from .. import resample
from . import buffer_type
from . cimport buffers
from . import buffers
from . import graph
from . import compiler
@ -40,39 +44,6 @@ def at_init(func):
return func
class Buffer(object):
def __init__(self, buf_type):
self.__type = buf_type
self.__data = bytearray(len(self.__type))
self.view = self.__type.make_view(self.__data)
@property
def type(self):
return self.__type
@property
def data(self):
return self.__data
def to_bytes(self):
return bytes(self.__data)
def set_bytes(self, data):
assert len(data) <= len(self.__data), \
'%s > %s' % (len(data), len(self.__data))
if len(data) < len(self.__data):
self.__type.clear_buffer(self.__data)
self.__data[:len(data)] = data
def clear(self):
self.__type.clear_buffer(self.__data)
def mix(self, other):
assert self.type == other.type, \
'%s != %s' % (self.type, other.type)
self.__type.mix_buffers(self.__data, other.__data)
class PipelineVM(object):
def __init__(
self, *,
@ -110,12 +81,13 @@ class PipelineVM(object):
# TODO: reimplement
pass
def setup(self):
def setup(self, *, start_thread=True):
self.__vm_started = threading.Event()
self.__vm_exit = threading.Event()
self.__vm_thread = threading.Thread(target=self.vm_main)
self.__vm_thread.start()
self.__vm_started.wait()
if start_thread:
self.__vm_thread = threading.Thread(target=self.vm_main)
self.__vm_thread.start()
self.__vm_started.wait()
logger.info("VM up and running.")
def cleanup(self):
@ -138,7 +110,7 @@ class PipelineVM(object):
self.cleanup_spec()
def setup_spec(self, spec):
self.__buffers = [Buffer(btype) for btype in spec.buffers]
self.__buffers = [buffers.Buffer(btype) for btype in spec.buffers]
self.__opcode_states = [{} for _ in spec.opcodes]
self.__spec = spec
self.__spec_initialized = False
@ -333,33 +305,33 @@ class PipelineVM(object):
@at_performance
def op_COPY_BUFFER(self, ctxt, state, *, src_idx, dest_idx):
src = self.__buffers[src_idx].view
dest = self.__buffers[dest_idx].view
dest[:] = src
cdef buffers.Buffer src = self.__buffers[src_idx]
cdef buffers.Buffer dest = self.__buffers[dest_idx]
assert len(src.type) == len(dest.type)
string.memmove(dest.data, src.data, len(dest.type))
@at_performance
def op_CLEAR_BUFFER(self, ctxt, state, *, buf_idx):
self.__buffers[buf_idx].clear()
cdef buffers.Buffer buf = self.__buffers[buf_idx]
buf.clear()
@at_performance
def op_SET_FLOAT(self, ctxt, state, *, buf_idx, value):
buf = self.__buffers[buf_idx]
assert isinstance(buf.type, buffer_type.Float), str(buf.type)
buf.view[0] = value
cdef buffers.Buffer buf = self.__buffers[buf_idx]
assert isinstance(buf.type, buffers.Float), str(buf.type)
cdef float* data = <float*>buf.data
data[0] = value
@at_performance
def op_OUTPUT(self, ctxt, state, *, buf_idx, channel):
buf = self.__buffers[buf_idx]
assert isinstance(buf.type, buffer_type.FloatArray), str(buf.type)
cdef buffers.Buffer buf = self.__buffers[buf_idx]
assert isinstance(buf.type, buffers.FloatArray), str(buf.type)
assert buf.type.size == ctxt.duration
data = self.__buffers[buf_idx].to_bytes()
self.__backend.output(channel, data)
self.__backend.output(channel, buf.to_bytes())
@at_performance
def op_FETCH_ENTITY(self, ctxt, state, *, entity_id, buf_idx):
buf = self.__buffers[buf_idx]
cdef buffers.Buffer buf = self.__buffers[buf_idx]
try:
entity = ctxt.entities[entity_id]
except KeyError:
@ -370,12 +342,12 @@ class PipelineVM(object):
@at_performance
def op_FETCH_PARAMETER(self, ctxt, state, *, parameter_idx, buf_idx):
#parameter_name = self.__parameters[parameter_idx]
buf = self.__buffers[buf_idx]
cdef buffers.Buffer buf = self.__buffers[buf_idx]
buf.clear()
@at_performance
def op_FETCH_MESSAGES(self, ctxt, state, *, labelset, buf_idx):
buf = self.__buffers[buf_idx]
cdef buffers.Buffer buf = self.__buffers[buf_idx]
forge = lv2.AtomForge(lv2.static_mapper)
forge.set_buffer(buf.data, len(buf.type))
@ -399,18 +371,18 @@ class PipelineVM(object):
@at_performance
def op_NOISE(self, ctxt, state, *, buf_idx):
buf = self.__buffers[buf_idx]
assert isinstance(buf.type, buffer_type.FloatArray), str(buf.type)
view = buf.view
cdef buffers.Buffer buf = self.__buffers[buf_idx]
assert isinstance(buf.type, buffers.FloatArray), str(buf.type)
cdef float* view = <float*>buf.data
for i in range(buf.type.size):
view[i] = 2 * random.random() - 1.0
@at_performance
def op_SINE(self, ctxt, state, *, buf_idx, freq):
buf = self.__buffers[buf_idx]
assert isinstance(buf.type, buffer_type.FloatArray), str(buf.type)
view = buf.view
cdef buffers.Buffer buf = self.__buffers[buf_idx]
assert isinstance(buf.type, buffers.FloatArray), str(buf.type)
cdef float* view = <float*>buf.data
p = state.get('p', 0.0)
for i in range(buf.type.size):
@ -422,8 +394,11 @@ class PipelineVM(object):
@at_performance
def op_MUL(self, ctxt, state, *, buf_idx, factor):
buf = self.__buffers[buf_idx].view
buf *= factor
cdef buffers.Buffer buf = self.__buffers[buf_idx]
assert isinstance(buf.type, buffers.FloatArray), str(buf.type)
cdef float* view = <float*>buf.data
for i in range(buf.type.size):
view[i] *= factor
@at_performance
def op_MIX(self, ctxt, state, *, src_idx, dest_idx):
@ -433,8 +408,8 @@ class PipelineVM(object):
def op_CONNECT_PORT(self, ctxt, state, *, node_idx, port_name, buf_idx):
node_id = self.__spec.nodes[node_idx]
node = self.__graph.find_node(node_id)
buf = self.__buffers[buf_idx].data
node.connect_port(port_name, buf)
cdef buffers.Buffer buf = self.__buffers[buf_idx]
node.connect_port(port_name, memoryview(buf))
@at_performance
def op_CALL(self, ctxt, state, *, node_idx):

@ -0,0 +1,108 @@
#!/usr/bin/python3
import logging
import struct
import threading
import unittest
import os.path
from noisicaa import node_db
from noisicaa import audioproc
from noisidev import perf_stats
from .. import backend
from .. import resample
from .. import nodes
from . import engine
from . import spec
from . import compiler
from . import graph
logger = logging.getLogger(__name__)
class TestBackend(backend.Backend):
def __init__(self, *, num_frames=1000, skip_first=10):
super().__init__()
self.__frame_num = 0
self.__num_frames = num_frames
self.__skip_first = skip_first
self.frame_times = []
def begin_frame(self, ctxt):
super().begin_frame(ctxt)
ctxt.duration = 128
ctxt.perf.start_span('frame')
if self.__frame_num >= self.__num_frames:
self.stop()
return
self.__frame_num += 1
def end_frame(self):
self.ctxt.perf.end_span()
if not self.stopped:
topspan = self.ctxt.perf.serialize().spans[0]
assert topspan.parentId == 0
assert topspan.name == 'frame'
duration = (topspan.endTimeNSec - topspan.startTimeNSec) / 1000.0
if self.__frame_num > self.__skip_first:
self.frame_times.append(duration)
super().end_frame()
def output(self, channel, samples):
pass
class PipelineVMPerfTest(unittest.TestCase):
def test_fluidsynth(self):
vm = engine.PipelineVM()
try:
vm.setup(start_thread=False)
node1 = nodes.FluidSynthSource(
id='node1',
soundfont_path='/usr/share/sounds/sf2/TimGM6mb.sf2', bank=0, preset=0)
node1.setup()
vm.add_node(node1)
node2 = nodes.FluidSynthSource(
id='node2',
soundfont_path='/usr/share/sounds/sf2/TimGM6mb.sf2', bank=0, preset=1)
node2.setup()
vm.add_node(node2)
node3 = nodes.FluidSynthSource(
id='node3',
soundfont_path='/usr/share/sounds/sf2/TimGM6mb.sf2', bank=0, preset=2)
node3.setup()
vm.add_node(node3)
sink = nodes.Sink()
sink.setup()
vm.add_node(sink)
for src in (node1, node2, node3):
sink.inputs['in:left'].connect(src.outputs['out:left'])
sink.inputs['in:right'].connect(src.outputs['out:right'])
vm.update_spec()
be = TestBackend()
vm.setup_backend(be)
vm.vm_loop()
perf_stats.write_frame_stats(
os.path.splitext(os.path.basename(__file__))[0],
'.'.join(self.id().split('.')[-2:]),
be.frame_times)
finally:
vm.cleanup()
if __name__ == '__main__':
unittest.main()

@ -10,7 +10,7 @@ from noisicaa import audioproc
from .. import backend
from .. import resample
from .. import nodes
from . import buffer_type
from . import buffers
from . import engine
from . import spec
@ -59,7 +59,7 @@ class PipelineVMTest(unittest.TestCase):
vm = engine.PipelineVM()
vm_spec = spec.PipelineVMSpec()
vm_spec.buffers.append(buffer_type.FloatArray(4))
vm_spec.buffers.append(buffers.FloatArray(4))
vm.setup_spec(vm_spec)
vm.set_buffer_bytes(0, struct.pack('=ffff', 1, 2, 3, 4))
@ -79,14 +79,14 @@ class PipelineVMTest(unittest.TestCase):
# run with a spec
vm_spec = spec.PipelineVMSpec()
vm_spec.buffers.append(buffer_type.Float())
vm_spec.buffers.append(buffers.Float())
vm_spec.opcodes.append(spec.OpCode('SET_FLOAT', buf_idx=0, value=12))
vm.set_spec(vm_spec)
be.next_step()
# replace spec
vm_spec = spec.PipelineVMSpec()
vm_spec.buffers.append(buffer_type.Float())
vm_spec.buffers.append(buffers.Float())
vm_spec.opcodes.append(spec.OpCode('SET_FLOAT', buf_idx=0, value=14))
vm.set_spec(vm_spec)
be.next_step()
@ -100,9 +100,9 @@ class PipelineVMTest(unittest.TestCase):
def test_run_vm(self):
vm_spec = spec.PipelineVMSpec()
vm_spec.buffers.append(buffer_type.Float())
vm_spec.buffers.append(buffer_type.FloatArray(256))
vm_spec.buffers.append(buffer_type.FloatArray(256))
vm_spec.buffers.append(buffers.Float())
vm_spec.buffers.append(buffers.FloatArray(256))
vm_spec.buffers.append(buffers.FloatArray(256))
vm_spec.opcodes.append(spec.OpCode('SET_FLOAT', buf_idx=0, value=12))
vm_spec.opcodes.append(spec.OpCode('COPY_BUFFER', src_idx=1, dest_idx=2))
@ -117,7 +117,7 @@ class PipelineVMTest(unittest.TestCase):
def test_OUTPUT(self):
vm_spec = spec.PipelineVMSpec()
vm_spec.buffers.append(buffer_type.FloatArray(4))
vm_spec.buffers.append(buffers.FloatArray(4))
vm_spec.opcodes.append(spec.OpCode('OUTPUT', buf_idx=0, channel='center'))
be = TestBackend()
@ -141,7 +141,7 @@ class PipelineVMTest(unittest.TestCase):
def test_NOISE(self):
vm_spec = spec.PipelineVMSpec()
vm_spec.buffers.append(buffer_type.FloatArray(4))
vm_spec.buffers.append(buffers.FloatArray(4))
vm_spec.opcodes.append(spec.OpCode('NOISE', buf_idx=0))
be = TestBackend()
@ -162,7 +162,7 @@ class PipelineVMTest(unittest.TestCase):
def test_MUL(self):
vm_spec = spec.PipelineVMSpec()
vm_spec.buffers.append(buffer_type.FloatArray(4))
vm_spec.buffers.append(buffers.FloatArray(4))
vm_spec.opcodes.append(spec.OpCode('MUL', buf_idx=0, factor=2))
be = TestBackend()
@ -185,8 +185,8 @@ class PipelineVMTest(unittest.TestCase):
def test_MIX(self):
vm_spec = spec.PipelineVMSpec()
vm_spec.buffers.append(buffer_type.FloatArray(4))
vm_spec.buffers.append(buffer_type.FloatArray(4))
vm_spec.buffers.append(buffers.FloatArray(4))
vm_spec.buffers.append(buffers.FloatArray(4))
vm_spec.opcodes.append(spec.OpCode('MIX', src_idx=1, dest_idx=0))
be = TestBackend()
@ -239,8 +239,8 @@ class PipelineVMTest(unittest.TestCase):
vm.add_node(node)
vm_spec = spec.PipelineVMSpec()
vm_spec.buffers.append(buffer_type.FloatArray(4))
vm_spec.buffers.append(buffer_type.FloatArray(4))
vm_spec.buffers.append(buffers.FloatArray(4))
vm_spec.buffers.append(buffers.FloatArray(4))
vm_spec.nodes.append('node')
vm_spec.opcodes.append(spec.OpCode(
'CONNECT_PORT', node_idx=0, port_name='in', buf_idx=0))
@ -276,8 +276,8 @@ class PipelineVMTest(unittest.TestCase):
# vm.set_backend(backend.PyAudioBackend())
# vm_spec = spec.PipelineVMSpec()
# vm_spec.buffers.append(buffer_type.FloatArray(128))
# vm_spec.buffers.append(buffer_type.FloatArray(128))
# vm_spec.buffers.append(buffers.FloatArray(128))
# vm_spec.buffers.append(buffers.FloatArray(128))
# vm_spec.opcodes.append(spec.OpCode(
# 'SINE', buf_idx=0, freq=440))
# vm_spec.opcodes.append(spec.OpCode(

@ -316,11 +316,19 @@ cdef class CSound(object):
self.csnd, &channel_dat,
channel.name.encode('utf-8'), channel.cs_type))
cdef int i = 0
cdef float* inp = <float*><char*>samples
while i < self.ksmps:
cdef float* inp
cdef char[:] view
if isinstance(samples, memoryview):
view = samples
inp = <float*>(&view[0])
elif isinstance(samples, (bytes, bytearray)):
inp = <float*><char*>samples
else:
raise TypeError(type(samples))
cdef int i
for i in range(self.ksmps):
channel_dat[i] = inp[i]
i += 1
def set_control_channel_value(self, name, value):
assert name in self.channels, name

@ -276,11 +276,15 @@ cdef class Instance(object):
def connect_port(self, port, data):
cdef void* ptr
cdef numpy.ndarray[float, ndim=1, mode="c"] arr
cdef char[:] view
if data is None:
ptr = NULL
elif isinstance(data, numpy.ndarray):
arr = data
ptr = &arr[0]
elif isinstance(data, memoryview):
view = data
ptr = &view[0]
elif isinstance(data, (bytes, bytearray)):
ptr = <uint8_t*>data
else:

@ -1522,6 +1522,7 @@ cdef class Instance(object):
cdef void* ptr
cdef numpy.ndarray[float, ndim=1, mode="c"] arr
cdef char[:] view
if data is None:
ptr = NULL
elif isinstance(data, numpy.ndarray):
@ -1529,6 +1530,9 @@ cdef class Instance(object):
ptr = &arr[0]
elif isinstance(data, (bytes, bytearray)):
ptr = <uint8_t*>data
elif isinstance(data, memoryview):
view = data
ptr = &view[0]
else:
raise TypeError(type(data))

@ -473,6 +473,8 @@ cdef class Atom(object):
cdef LV2_Atom* atom
cdef URID_Mapper mapper
cdef init(self, LV2_Atom* atom)