Browse Source

A bunch of cythonizing.

looper
Ben Niemann 5 years ago
parent
commit
8833ba3158
  1. 7
      NOTES.org
  2. 20
      bin/runtests.py
  3. 14
      noisicaa/audioproc/node.pxd
  4. 14
      noisicaa/audioproc/node.pyx
  5. 0
      noisicaa/audioproc/nodes/builtin.pyx
  6. 14
      noisicaa/audioproc/nodes/channels.pyx
  7. 19
      noisicaa/audioproc/nodes/csound.pxd
  8. 40
      noisicaa/audioproc/nodes/csound.pyx
  9. 0
      noisicaa/audioproc/nodes/encode.pyx
  10. 0
      noisicaa/audioproc/nodes/encode_test.pyx
  11. 24
      noisicaa/audioproc/nodes/fluidsynth.pxd
  12. 134
      noisicaa/audioproc/nodes/fluidsynth.py
  13. 179
      noisicaa/audioproc/nodes/fluidsynth.pyx
  14. 19
      noisicaa/audioproc/nodes/fluidsynth_test.pyx
  15. 8
      noisicaa/audioproc/nodes/ipc.pxd
  16. 19
      noisicaa/audioproc/nodes/ipc.pyx
  17. 11
      noisicaa/audioproc/nodes/ladspa.pxd
  18. 21
      noisicaa/audioproc/nodes/ladspa.pyx
  19. 18
      noisicaa/audioproc/nodes/ladspa_test.pyx
  20. 10
      noisicaa/audioproc/nodes/lv2.pxd
  21. 36
      noisicaa/audioproc/nodes/lv2.pyx
  22. 14
      noisicaa/audioproc/nodes/lv2.pyxbld
  23. 12
      noisicaa/audioproc/nodes/lv2_test.pyx
  24. 8
      noisicaa/audioproc/nodes/passthru.pxd
  25. 21
      noisicaa/audioproc/nodes/passthru.pyx
  26. 8
      noisicaa/audioproc/nodes/pipeline_crasher.pyx
  27. 4
      noisicaa/audioproc/nodes/sample_player.pxd
  28. 8
      noisicaa/audioproc/nodes/sample_player.pyx
  29. 18
      noisicaa/audioproc/nodes/sample_player_test.pyx
  30. 0
      noisicaa/audioproc/nodes/track_audio_source.pyx
  31. 0
      noisicaa/audioproc/nodes/track_control_source.pyx
  32. 0
      noisicaa/audioproc/nodes/track_event_source.pyx
  33. 22
      noisicaa/audioproc/nodes/wavfile.pxd
  34. 52
      noisicaa/audioproc/nodes/wavfile.pyx
  35. 16
      noisicaa/audioproc/nodes/wavfile_test.pyx
  36. 23
      noisicaa/audioproc/vm/buffers.pxd
  37. 164
      noisicaa/audioproc/vm/buffers.pyx
  38. 9
      noisicaa/audioproc/vm/engine.pyx
  39. 11
      noisicaa/audioproc/vm/engine_perftest.py
  40. 101
      noisicaa/bindings/csound.pxd
  41. 114
      noisicaa/bindings/csound.pyx
  42. 101
      noisicaa/bindings/fluidsynth.pxd
  43. 134
      noisicaa/bindings/fluidsynth.pyx
  44. 131
      noisicaa/bindings/ladspa.pxd
  45. 47
      noisicaa/bindings/ladspa.pyx
  46. 21
      noisicaa/bindings/ladspa_test.pyx
  47. 470
      noisicaa/bindings/lilv.pxd
  48. 413
      noisicaa/bindings/lilv.pyx
  49. 72
      noisicaa/bindings/lilv_test.pyx
  50. 14
      noisicaa/bindings/lilv_test.pyxbld
  51. 1
      noisicaa/constants.py
  52. 11
      noisicaa/music/score_track_test.py
  53. 6
      noisicaa/ui/pipeline_graph_view.py
  54. 28
      noisidev/profutil.py
  55. 1
      testlogs/engine_perftest.csv
  56. 1
      testlogs/player_integration_test.csv

7
NOTES.org

@ -1,6 +1,13 @@
# -*- org-tags-column: -98 -*-
* VM-based pipeline engine :FR:
- add more nogils to methods in bindings
- can pyximport handle distutils pragmas?
- StaticMapper should have common URIDs as attributes, so client code doesn't have to call map
itself.
- use consistent ctypedef for "pointer to block of bytes"
- use this pattern for C-only classes
https://github.com/cython/cython/wiki/FAQ#can-cython-create-objects-or-apply-operators-to-locally-created-objects-as-pure-c-code
- player_integration_test with null backend
vm thread seems to saturate CPU, doesn't let main thread handle pipeline_status messages.
When turning pipeline down, queued messages cause lots of errors.

20
bin/runtests.py

@ -5,6 +5,7 @@ import fnmatch
import logging
import os
import os.path
import shutil
import sys
import unittest
@ -31,6 +32,7 @@ def main(argv):
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)
parser.add_argument('--rebuild', action='store_true', default=False)
args = parser.parse_args(argv[1:])
constants.TEST_OPTS.WRITE_PERF_STATS = args.write_perf_stats
@ -45,19 +47,27 @@ def main(argv):
'critical': logging.CRITICAL,
}[args.log_level if not args.debug else 'debug'])
build_dir = 'pyxbuild'
directives = {}
modifiers = set()
if args.profile:
directives['profile'] = True
build_dir += '-profile'
modifiers.add('prof')
if args.profile:
if args.gdb:
directives['gdb_debug'] = True
build_dir += '-dbg'
modifiers.add('dbg')
build_dir = os.path.join(
os.getenv('VIRTUAL_ENV'),
'pyxbuild',
'-'.join(sorted(modifiers)) or 'vanilla')
if args.rebuild:
shutil.rmtree(build_dir)
pyximport.install(
build_dir=os.path.join(os.getenv('VIRTUAL_ENV'), build_dir),
build_dir=build_dir,
setup_args={
'script_args': ['--verbose'],
'options': {

14
noisicaa/audioproc/node.pxd

@ -0,0 +1,14 @@
cdef class Node(object):
cdef public object description
cdef readonly str name
cdef readonly str id
cdef public object pipeline
cdef public int broken
cdef public dict inputs
cdef public dict outputs
cdef dict __parameters
cdef class CustomNode(Node):
cdef int connect_port(self, port_name, buf) except -1
cdef int run(self, ctxt) except -1

14
noisicaa/audioproc/node.py → noisicaa/audioproc/node.pyx

@ -14,7 +14,7 @@ from .vm import buffers
logger = logging.getLogger(__name__)
class Node(object):
cdef class Node(object):
class_name = None
init_ports_from_description = True
@ -24,7 +24,7 @@ class Node(object):
assert isinstance(description, node_db.NodeDescription), description
self.description = description
self._name = name or type(self).__name__
self.name = name or type(self).__name__
self.id = id
self.pipeline = None
@ -39,10 +39,6 @@ class Node(object):
if self.init_parameters_from_description:
self.init_parameters()
@property
def name(self):
return self._name
def init_ports(self):
port_cls_map = {
(node_db.PortType.Audio,
@ -175,7 +171,7 @@ class Node(object):
return seq
class CustomNode(Node):
cdef class CustomNode(Node):
def get_ast(self, compiler):
seq = super().get_ast(compiler)
for port in itertools.chain(
@ -185,10 +181,10 @@ class CustomNode(Node):
seq.add(ast.CallNode(self.id))
return seq
def connect_port(self, port_name, buf):
cdef int connect_port(self, port_name, buf) except -1:
raise NotImplementedError(type(self))
def run(self, ctxt):
cdef int run(self, ctxt) except -1:
raise NotImplementedError(type(self))

0
noisicaa/audioproc/nodes/builtin.py → noisicaa/audioproc/nodes/builtin.pyx

14
noisicaa/audioproc/nodes/channels.py → noisicaa/audioproc/nodes/channels.pyx

@ -3,13 +3,13 @@
import logging
from .. import ports
from .. import node
from .. cimport node
from .. import audio_format
logger = logging.getLogger(__name__)
class SplitChannels(node.CustomNode):
cdef class SplitChannels(node.CustomNode):
class_name = 'split_channels'
def __init__(self, **kwargs):
@ -24,15 +24,17 @@ class SplitChannels(node.CustomNode):
self._right = ports.AudioOutputPort('right', audio_format.CHANNELS_MONO)
self.add_output(self._right)
def run(self, ctxt):
cdef int run(self, ctxt) except -1:
self._left.frame.resize(ctxt.duration)
self._right.frame.resize(ctxt.duration)
self._left.frame.samples[0] = self._input.frame.samples[0]
self._right.frame.samples[0] = self._input.frame.samples[1]
return 0
class JoinChannels(node.CustomNode):
cdef class JoinChannels(node.CustomNode):
class_name = 'join_channels'
def __init__(self, **kwargs):
@ -47,7 +49,7 @@ class JoinChannels(node.CustomNode):
self._output = ports.AudioOutputPort('out', audio_format.CHANNELS_STEREO)
self.add_output(self._output)
def run(self, ctxt):
cdef int run(self, ctxt) except -1:
assert len(self._left.frame) == ctxt.duration
assert len(self._right.frame) == ctxt.duration
@ -55,3 +57,5 @@ class JoinChannels(node.CustomNode):
self._output.frame.samples[0] = self._left.frame.samples[0]
self._output.frame.samples[1] = self._right.frame.samples[0]
return 0

19
noisicaa/audioproc/nodes/csound.pxd

@ -0,0 +1,19 @@
from noisicaa.bindings cimport csound
from .. cimport node
cdef class CSoundBase(node.CustomNode):
cdef csound.CSound __csnd
cdef object __next_csnd
cdef dict __buffers
cdef int connect_port(self, port_name, buf) except -1
cdef int run(self, ctxt) except -1
cdef class CSoundFilter(CSoundBase):
cdef str __orchestra
cdef str __score
cdef class CustomCSound(CSoundBase):
cdef str __orchestra_preamble
cdef str __orchestra
cdef str __score

40
noisicaa/audioproc/nodes/csound.py → noisicaa/audioproc/nodes/csound.pyx

@ -1,5 +1,7 @@
#!/usr/bin/python3
from libc.stdint cimport uint8_t
import logging
import textwrap
import time
@ -8,17 +10,21 @@ import queue
import numpy
from noisicaa import node_db
from noisicaa.bindings cimport csound
from noisicaa.bindings import csound
from noisicaa.bindings import lv2
from noisicaa.bindings.lv2 cimport atom
from noisicaa.bindings.lv2 cimport urid
from .. import ports
from .. import node
from .. cimport node
from .. import audio_format
from ..vm cimport buffers
logger = logging.getLogger(__name__)
class CSoundBase(node.CustomNode):
cdef class CSoundBase(node.CustomNode):
def __init__(self, **kwargs):
super().__init__(**kwargs)
@ -55,13 +61,15 @@ class CSoundBase(node.CustomNode):
super().cleanup()
def connect_port(self, port_name, buf):
cdef int connect_port(self, port_name, buf) except -1:
if port_name not in self.outputs and port_name not in self.inputs:
raise ValueError(port_name)
self.__buffers[port_name] = buf
def run(self, ctxt):
return 0
cdef int run(self, ctxt) except -1:
try:
next_csnd = self.__next_csnd.get_nowait()
except queue.Empty:
@ -76,27 +84,25 @@ class CSoundBase(node.CustomNode):
if self.__csnd is None:
for port in self.outputs.values():
if isinstance(port, (ports.AudioOutputPort, ports.ARateControlOutputPort)):
self.__buffers[port.name] = [0] * ctxt.duration
self.__buffers[port.name].clear()
else:
raise ValueError(port)
return
return 0
in_events = {}
for port in self.inputs.values():
if isinstance(port, ports.EventInputPort):
in_events[port.name] = (
port.csound_instr,
list(lv2.wrap_atom(
lv2.static_mapper, self.__buffers[port.name]).events))
l = list(atom.Atom.wrap(urid.get_static_mapper(), <uint8_t*>(<buffers.Buffer>self.__buffers[port.name]).data).events)
in_events[port.name] = (port.csound_instr, l)
pos = 0
cdef int pos = 0
while pos < ctxt.duration:
for port in self.inputs.values():
if isinstance(port, (ports.AudioInputPort, ports.ARateControlInputPort)):
self.__csnd.set_audio_channel_data(
port.name,
self.__buffers[port.name][4*pos:4*(pos+self.__csnd.ksmps)])
(<buffers.Buffer>self.__buffers[port.name]).data[4*pos:4*(pos+self.__csnd.ksmps)])
elif isinstance(port, ports.EventInputPort):
instr, pending_events = in_events[port.name]
@ -128,17 +134,19 @@ class CSoundBase(node.CustomNode):
for port in self.outputs.values():
if isinstance(port, (ports.AudioOutputPort, ports.ARateControlOutputPort)):
self.__buffers[port.name][4*pos:4*(pos+self.__csnd.ksmps)] = (
self.__csnd.get_audio_channel_data(port.name))
self.__csnd.get_audio_channel_data_into(
port.name,
(<float*>(<buffers.Buffer>self.__buffers[port.name]).data) + pos)
else:
raise ValueError(port)
pos += self.__csnd.ksmps
assert pos == ctxt.duration
return 0
class CSoundFilter(CSoundBase):
cdef class CSoundFilter(CSoundBase):
class_name = 'csound_filter'
def __init__(self, **kwargs):
@ -153,7 +161,7 @@ class CSoundFilter(CSoundBase):
self.set_code(self.__orchestra, self.__score)
class CustomCSound(CSoundBase):
cdef class CustomCSound(CSoundBase):
class_name = 'custom_csound'
def __init__(self, **kwargs):

0
noisicaa/audioproc/nodes/encode.py → noisicaa/audioproc/nodes/encode.pyx

0
noisicaa/audioproc/nodes/encode_test.py → noisicaa/audioproc/nodes/encode_test.pyx

24
noisicaa/audioproc/nodes/fluidsynth.pxd

@ -0,0 +1,24 @@
from noisicaa.bindings.lv2 cimport urid
from noisicaa.bindings cimport fluidsynth
from .. cimport node
from ..vm cimport buffers
cdef class FluidSynthSource(node.CustomNode):
cdef str __soundfont_path
cdef int __bank
cdef int __preset
cdef fluidsynth.Settings __settings
cdef fluidsynth.Synth __synth
cdef fluidsynth.Soundfont __sfont
cdef buffers.Buffer __in
cdef buffers.Buffer __out_left
cdef buffers.Buffer __out_right
cdef urid.URID_Mapper __mapper
cdef urid.LV2_URID __sequence_urid
cdef urid.LV2_URID __midi_event_urid
cdef int connect_port(self, port_name, buf) except -1
cdef int run(self, ctxt) except -1

134
noisicaa/audioproc/nodes/fluidsynth.py

@ -1,134 +0,0 @@
#!/usr/bin/python3
import logging
import queue
import time
from noisicaa import node_db
from noisicaa.bindings import fluidsynth
from noisicaa.bindings import lv2
from .. import node
logger = logging.getLogger(__name__)
class FluidSynthSource(node.CustomNode):
class_name = 'fluidsynth'
master_synth = fluidsynth.Synth()
master_sfonts = {}
def __init__(self, *, soundfont_path, bank, preset, **kwargs):
description = node_db.SystemNodeDescription(
ports=[
node_db.EventPortDescription(
name='in',
direction=node_db.PortDirection.Input),
node_db.AudioPortDescription(
name='out:left',
direction=node_db.PortDirection.Output),
node_db.AudioPortDescription(
name='out:right',
direction=node_db.PortDirection.Output),
])
super().__init__(description=description, **kwargs)
self.__soundfont_path = soundfont_path
self.__bank = bank
self.__preset = preset
self.__synth = None
self.__sfont = None
self.__sfid = None
self.__in = None
self.__out_left = None
self.__out_right = None
def setup(self):
super().setup()
assert self.__synth is None
self.__settings = fluidsynth.Settings()
self.__settings.synth_gain = 0.5
self.__settings.synth_sample_rate = 44100 # TODO: get from pipeline
self.__synth = fluidsynth.Synth(self.__settings)
try:
sfont = self.master_sfonts[self.__soundfont_path]
except KeyError:
logger.info(
"Adding new soundfont %s to master synth.",
self.__soundfont_path)
master_sfid = self.master_synth.sfload(self.__soundfont_path)
sfont = self.master_synth.get_sfont(master_sfid)
self.master_sfonts[self.__soundfont_path] = sfont
logger.debug("Using soundfont %s", sfont.id)
self.__sfid = self.__synth.add_sfont(sfont)
logger.debug("Soundfont id=%s", self.__sfid)
self.__sfont = sfont
self.__synth.system_reset()
self.__synth.program_select(
0, self.__sfid, self.__bank, self.__preset)
def cleanup(self):
super().cleanup()
if self.__synth is not None:
self.__synth.system_reset()
if self.__sfont is not None:
# TODO: This call spits out a ton of "CRITICAL **:
# fluid_synth_sfont_unref: assertion 'sfont_info != NULL' failed"
# messages on STDERR
self.__synth.remove_sfont(self.__sfont)
self.__sfont = None
self.__synth = None
def connect_port(self, port_name, buf):
if port_name == 'in':
self.__in = buf
elif port_name == 'out:left':
self.__out_left = buf
elif port_name == 'out:right':
self.__out_right = buf
else:
raise ValueError(port_name)
def run(self, ctxt):
seq = lv2.wrap_atom(lv2.static_mapper, self.__in)
segment_start = 0
bytes_written = 0
for event in seq.events:
if event.frames != -1:
assert 0 <= event.frames < ctxt.duration, (
event.frames, ctxt.duration)
esample_pos = event.frames
else:
esample_pos = 0
if esample_pos >= segment_start:
segmentl, segmentr = self.__synth.get_samples(
esample_pos - segment_start)
self.__out_left[bytes_written:bytes_written+len(segmentl)] = segmentl
self.__out_right[bytes_written:bytes_written+len(segmentl)] = segmentr
segment_start = esample_pos
bytes_written += len(segmentl)
midi = event.atom.data
if midi[0] & 0xf0 == 0x90:
self.__synth.noteon(0, midi[1], midi[2])
elif midi[0] & 0xf0 == 0x80:
self.__synth.noteoff(0, midi[1])
else:
raise NotImplementedError(
"Event class %s not supported" % type(event).__name__)
if segment_start < ctxt.duration:
segmentl, segmentr = self.__synth.get_samples(
ctxt.duration - segment_start)
self.__out_left[bytes_written:bytes_written+len(segmentl)] = segmentl
self.__out_right[bytes_written:bytes_written+len(segmentl)] = segmentr

179
noisicaa/audioproc/nodes/fluidsynth.pyx

@ -0,0 +1,179 @@
#!/usr/bin/python3
from libc.stdint cimport uint8_t, uint32_t
from libc cimport string
import logging
import queue
import time
from noisicaa import node_db
from noisicaa.bindings cimport fluidsynth
from noisicaa.bindings.lv2 cimport atom
from .. cimport node
logger = logging.getLogger(__name__)
cdef class FluidSynthSource(node.CustomNode):
class_name = 'fluidsynth'
master_synth = fluidsynth.Synth()
master_sfonts = {}
def __init__(self, *, soundfont_path, bank, preset, **kwargs):
description = node_db.SystemNodeDescription(
ports=[
node_db.EventPortDescription(
name='in',
direction=node_db.PortDirection.Input),
node_db.AudioPortDescription(
name='out:left',
direction=node_db.PortDirection.Output),
node_db.AudioPortDescription(
name='out:right',
direction=node_db.PortDirection.Output),
])
super().__init__(description=description, **kwargs)
self.__soundfont_path = soundfont_path
self.__bank = bank
self.__preset = preset
self.__synth = None
self.__sfont = None
self.__in = None
self.__out_left = None
self.__out_right = None
self.__mapper = urid.get_static_mapper()
self.__sequence_urid = self.__mapper.map(
b'http://lv2plug.in/ns/ext/atom#Sequence')
self.__midi_event_urid = self.__mapper.map(
b'http://lv2plug.in/ns/ext/midi#MidiEvent')
def setup(self):
super().setup()
assert self.__synth is None
self.__settings = fluidsynth.Settings()
self.__settings.synth_gain = 0.5
self.__settings.synth_sample_rate = 44100 # TODO: get from pipeline
self.__synth = fluidsynth.Synth(self.__settings)
try:
sfont = self.master_sfonts[self.__soundfont_path]
except KeyError:
logger.info(
"Adding new soundfont %s to master synth.",
self.__soundfont_path)
master_sfid = self.master_synth.sfload(self.__soundfont_path)
sfont = self.master_synth.get_sfont(master_sfid)
self.master_sfonts[self.__soundfont_path] = sfont
logger.debug("Using soundfont %s", sfont.id)
sfid = self.__synth.add_sfont(sfont)
logger.debug("Soundfont id=%s", sfid)
self.__sfont = sfont
self.__synth.system_reset()
self.__synth.program_select(0, sfid, self.__bank, self.__preset)
def cleanup(self):
super().cleanup()
if self.__synth is not None:
self.__synth.system_reset()
if self.__sfont is not None:
# TODO: This call spits out a ton of "CRITICAL **:
# fluid_synth_sfont_unref: assertion 'sfont_info != NULL' failed"
# messages on STDERR
self.__synth.remove_sfont(self.__sfont)
self.__sfont = None
self.__synth = None
cdef int connect_port(self, port_name, buf) except -1:
if port_name == 'in':
self.__in = buf
elif port_name == 'out:left':
self.__out_left = buf
elif port_name == 'out:right':
self.__out_right = buf
else:
raise ValueError(port_name)
return 0
cdef int run(self, ctxt) except -1:
cdef:
atom.LV2_Atom_Sequence* seq
atom.LV2_Atom_Event* event
uint32_t segment_start
uint32_t bytes_written
uint32_t esample_pos
uint8_t* midi
float* out_left
float* out_right
uint32_t num_samples
uint8_t event_class
uint32_t frame_size
frame_size = ctxt.duration
with nogil:
seq = <atom.LV2_Atom_Sequence*>self.__in.data
if seq.atom.type != self.__sequence_urid:
with gil:
raise TypeError(
"Excepted sequence, got %s"
% self.__mapper.unmap(seq.atom.type))
event = atom.lv2_atom_sequence_begin(&seq.body)
segment_start = 0
out_left = <float*>self.__out_left.data
out_right = <float*>self.__out_right.data
while not atom.lv2_atom_sequence_is_end(&seq.body, seq.atom.size, event):
if event.body.type != self.__midi_event_urid:
with gil:
raise TypeError(
"Excepted MidiEvent, got %s"
% self.__mapper.unmap(event.body.type))
if event.time.frames != -1:
if not (0 <= event.time.frames < frame_size):
with gil:
raise ValueError(
"Event timestamp %d out of bounds [0,%d]"
% (event.time.frames, frame_size))
esample_pos = event.time.frames
else:
esample_pos = 0
if esample_pos >= segment_start:
num_samples = esample_pos - segment_start
self.__synth.get_samples_into(num_samples, out_left, out_right)
segment_start = esample_pos
out_left += num_samples
out_right += num_samples
midi = (<uint8_t*>&event.body) + sizeof(atom.LV2_Atom)
event_class = (midi[0] & 0xf0) >> 4
if event_class == 0x9:
self.__synth.noteon(0, midi[1], midi[2])
elif event_class == 0x8:
self.__synth.noteoff(0, midi[1])
else:
with gil:
raise NotImplementedError(
"Event class %x not supported" % event_class)
event = atom.lv2_atom_sequence_next(event)
if segment_start < frame_size:
num_samples = frame_size - segment_start
self.__synth.get_samples_into(num_samples, out_left, out_right)
return 0

19
noisicaa/audioproc/nodes/fluidsynth_test.py → noisicaa/audioproc/nodes/fluidsynth_test.pyx

@ -4,25 +4,32 @@ import unittest
from noisicaa.audioproc import data
from noisicaa.bindings import lv2
from . import fluidsynth
from . cimport fluidsynth
from ..vm cimport buffers
class WavFileSourceTest(unittest.TestCase):
class FluidSynthSourceTest(unittest.TestCase):
def test_basic(self):
cdef:
fluidsynth.FluidSynthSource node
buffers.Buffer buf_in
buffers.Buffer buf_out_l
buffers.Buffer buf_out_r
node = fluidsynth.FluidSynthSource(
id='test',
soundfont_path='/usr/share/sounds/sf2/TimGM6mb.sf2', bank=0, preset=0)
node.setup()
try:
buf_in = bytearray(10240)
buf_out_l = bytearray(1024)
buf_out_r = bytearray(1024)
buf_in = buffers.Buffer(buffers.AtomData(10240))
buf_out_l = buffers.Buffer(buffers.FloatArray(256))
buf_out_r = buffers.Buffer(buffers.FloatArray(256))
node.connect_port('in', buf_in)
node.connect_port('out:left', buf_out_l)
node.connect_port('out:right', buf_out_r)
forge = lv2.AtomForge(lv2.static_mapper)
forge.set_buffer(buf_in, 10240)
forge.set_buffer(buf_in.data, 10240)
with forge.sequence():
forge.write_midi_event(0, bytes([0x90, 60, 100]), 3)
forge.write_midi_event(128, bytes([0x80, 60, 0]), 3)

8
noisicaa/audioproc/nodes/ipc.pxd

@ -0,0 +1,8 @@
from .. cimport node
from ..vm cimport buffers
cdef class IPCNode(node.CustomNode):
cdef object __stream
cdef buffers.Buffer __out_l
cdef buffers.Buffer __out_r

19
noisicaa/audioproc/nodes/ipc.py → noisicaa/audioproc/nodes/ipc.pyx

@ -1,5 +1,7 @@
#!/usr/bin/python3
from libc cimport string
import logging
import os
import pickle
@ -13,7 +15,7 @@ from noisicaa import music
from noisicaa import node_db
from .. import ports
from .. import node
from .. cimport node
from .. import audio_stream
from .. import entity_capnp
from .. import frame_data_capnp
@ -22,7 +24,7 @@ from .. import audio_format
logger = logging.getLogger(__name__)
class IPCNode(node.CustomNode):
cdef class IPCNode(node.CustomNode):
class_name = 'ipc'
def __init__(self, *, address, **kwargs):
@ -51,7 +53,7 @@ class IPCNode(node.CustomNode):
self.__stream.cleanup()
super().cleanup()
def connect_port(self, port_name, buf):
cdef int connect_port(self, port_name, buf) except -1:
if port_name == 'out:left':
self.__out_l = buf
elif port_name == 'out:right':
@ -59,7 +61,9 @@ class IPCNode(node.CustomNode):
else:
raise ValueError(port_name)
def run(self, ctxt):
return 0
cdef int run(self, ctxt) except -1:
with ctxt.perf.track('ipc'):
request = frame_data_capnp.FrameData.new_message()
request.samplePos = ctxt.sample_pos
@ -79,8 +83,11 @@ class IPCNode(node.CustomNode):
if entity.id == 'output:left':
assert entity.type == entity_capnp.Entity.Type.audio
assert entity.size == 4 * response.frameSize
self.__out_l[0:entity.size] = entity.data
string.memmove(self.__out_l.data, <char*>entity.data, entity.size)
elif entity.id == 'output:right':
assert entity.type == entity_capnp.Entity.Type.audio
assert entity.size == 4 * response.frameSize
self.__out_r[0:entity.size] = entity.data
string.memmove(self.__out_r.data, <char*>entity.data, entity.size)
return 0

11
noisicaa/audioproc/nodes/ladspa.pxd

@ -0,0 +1,11 @@
from noisicaa.bindings cimport ladspa
from .. cimport node
cdef class Ladspa(node.CustomNode):
cdef object __library
cdef object __descriptor
cdef ladspa.Instance __instance
cdef object __buffers
cdef int connect_port(self, port_name, buf) except -1
cdef int run(self, ctxt) except -1

21
noisicaa/audioproc/nodes/ladspa.py → noisicaa/audioproc/nodes/ladspa.pyx

@ -1,6 +1,7 @@
#!/usr/bin/python3
import logging
import struct
import numpy
@ -8,13 +9,14 @@ from noisicaa.bindings import ladspa
from noisicaa import node_db
from .. import ports
from .. import node
from .. cimport node
from .. import audio_format
from ..vm cimport buffers
logger = logging.getLogger(__name__)
class Ladspa(node.CustomNode):
cdef class Ladspa(node.CustomNode):
class_name = 'ladspa'
def __init__(self, **kwargs):
@ -41,12 +43,12 @@ class Ladspa(node.CustomNode):
if parameter.param_type == node_db.ParameterType.Float:
logger.info("Creating parameter buffer %s...", parameter.name)
buf = numpy.zeros(shape=(1,), dtype=numpy.float32)
buf = bytearray(4)
self.__buffers[parameter.name] = buf
for port in self.__descriptor.ports:
if port.name == parameter.name:
self.__instance.connect_port(port, buf)
self.__instance.connect_port(port, <char*>buf)
def cleanup(self):
if self.__instance is not None:
@ -59,14 +61,17 @@ class Ladspa(node.CustomNode):
super().cleanup()
def connect_port(self, port_name, buf):
cdef int connect_port(self, port_name, buf) except -1:
for port in self.__descriptor.ports:
if port.name == port_name:
self.__instance.connect_port(port, buf)
self.__instance.connect_port(port, (<buffers.Buffer>buf).data)
return 0
def run(self, ctxt):
cdef int run(self, ctxt) except -1:
for parameter in self.description.parameters:
if parameter.param_type == node_db.ParameterType.Float:
self.__buffers[parameter.name][0] = self.get_param(parameter.name)
self.__buffers[parameter.name][:] = struct.pack('=f', self.get_param(parameter.name))
self.__instance.run(ctxt.duration)
return 0

18
noisicaa/audioproc/nodes/ladspa_test.py → noisicaa/audioproc/nodes/ladspa_test.pyx

@ -8,10 +8,18 @@ import struct
from noisicaa import constants
from noisicaa import node_db
from noisicaa.audioproc import data
from . import ladspa
from . cimport ladspa
from ..vm cimport buffers
class LadspaTest(unittest.TestCase):
def test_foo(self):
cdef:
ladspa.Ladspa node
buffers.Buffer buf_cutoff
buffers.Buffer buf_in
buffers.Buffer buf_out
description = node_db.NodeDescription(
ports=[
node_db.AudioPortDescription(
@ -37,11 +45,11 @@ class LadspaTest(unittest.TestCase):
ctxt.sample_pos = 0
ctxt.duration = 256
buf_cutoff = bytearray(4)
buf_in = bytearray(1024)
buf_out = bytearray(1024)
buf_cutoff = buffers.Buffer(buffers.Float())
buf_in = buffers.Buffer(buffers.FloatArray(256))
buf_out = buffers.Buffer(buffers.FloatArray(256))
struct.pack_into('=f', buf_cutoff, 0, 400.0)
buf_cutoff.set_bytes(struct.pack('=f', 400.0))
node = ladspa.Ladspa(id='test', description=description)
node.setup()

10
noisicaa/audioproc/nodes/lv2.pxd

@ -0,0 +1,10 @@
from noisicaa.bindings cimport lilv
from .. cimport node
cdef class LV2(node.CustomNode):
cdef lilv.Plugin __plugin
cdef lilv.Instance __instance
cdef dict __buffers
cdef int connect_port(self, port_name, buf) except -1
cdef int run(self, ctxt) except -1

36
noisicaa/audioproc/nodes/lv2.py → noisicaa/audioproc/nodes/lv2.pyx

@ -1,31 +1,36 @@
#!/usr/bin/python3
import logging
import struct
import threading
import numpy
from noisicaa.bindings import lilv
from noisicaa.bindings cimport lilv
from noisicaa.bindings import lv2
from noisicaa import node_db
from .. import ports
from .. import node
from .. cimport node
from .. import audio_format
from ..vm cimport buffers
logger = logging.getLogger(__name__)
class LV2(node.CustomNode):
world_initialized = False
world_lock = threading.Lock()
cdef class LV2(node.CustomNode):
class_name = 'lv2'
__world = None
__world_lock = threading.Lock()
__world = lilv.World()
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.__plugin = None
self.__instance = None
self.__buffers = None
def setup(self):
@ -34,11 +39,12 @@ class LV2(node.CustomNode):
uri = self.description.get_parameter('uri').value
logger.info("Setting up LV2 plugin %s...", uri)
with self.__world_lock:
if self.__world is None:
with world_lock:
global world_initialized
if not world_initialized:
logger.info("Creating new LV2 world...")
self.__world = lilv.World()
self.__world.load_all()
world_initialized = True
logger.info("Loading plugin...")
plugins = self.__world.get_all_plugins()
@ -55,12 +61,12 @@ class LV2(node.CustomNode):
if parameter.param_type == node_db.ParameterType.Float:
logger.info("Creating parameter buffer %s...", parameter.name)
buf = numpy.zeros(shape=(1,), dtype=numpy.float32)
buf = bytearray(4)
self.__buffers[parameter.name] = buf
lv2_port = self.__plugin.get_port_by_symbol(self.__world.new_string(parameter.name))
assert lv2_port is not None, parameter.name
self.__instance.connect_port(lv2_port.get_index(), buf)
self.__instance.connect_port(lv2_port.get_index(), <char*>buf)
def cleanup(self):
if self.__instance is not None:
@ -73,15 +79,17 @@ class LV2(node.CustomNode):
super().cleanup()
def connect_port(self, port_name, buf):
cdef int connect_port(self, port_name, buf) except -1:
lv2_port = self.__plugin.get_port_by_symbol(
self.__world.new_string(port_name))
assert lv2_port is not None, port_name
self.__instance.connect_port(lv2_port.get_index(), buf)
self.__instance.connect_port(lv2_port.get_index(), (<buffers.Buffer>buf).data)
def run(self, ctxt):
cdef int run(self, ctxt) except -1:
for parameter in self.description.parameters:
if parameter.param_type == node_db.ParameterType.Float:
self.__buffers[parameter.name][0] = self.get_param(parameter.name)
self.__buffers[parameter.name][:] = struct.pack('=f', self.get_param(parameter.name))
self.__instance.run(ctxt.duration)
return 0

14
noisicaa/audioproc/nodes/lv2.pyxbld

@ -0,0 +1,14 @@
# -*- mode: python -*-
import os
import os.path
def make_ext(modname, pyxfilename):
from distutils.extension import Extension
return Extension(
name = modname,
sources=[pyxfilename],
include_dirs=[os.path.join(os.getenv('VIRTUAL_ENV'), 'include', 'lilv-0')],
library_dirs=[os.path.join(os.getenv('VIRTUAL_ENV'), 'lib')],
libraries=['lilv-0'],
)

12
noisicaa/audioproc/nodes/lv2_test.py → noisicaa/audioproc/nodes/lv2_test.pyx

@ -7,10 +7,16 @@ import os.path
from noisicaa import constants
from noisicaa import node_db
from noisicaa.audioproc import data
from . import lv2
from . cimport lv2
from ..vm cimport buffers
class LV2Test(unittest.TestCase):
def test_foo(self):
cdef:
lv2.LV2 node
buffers.Buffer buf_in
buffers.Buffer buf_out
description = node_db.NodeDescription(
ports=[
node_db.AudioPortDescription(
@ -34,8 +40,8 @@ class LV2Test(unittest.TestCase):
ctxt.sample_pos = 0
ctxt.duration = 256
buf_in = bytearray(1024)
buf_out = bytearray(1024)
buf_in = buffers.Buffer(buffers.FloatArray(256))
buf_out = buffers.Buffer(buffers.FloatArray(256))
node = lv2.LV2(id='test', description=description)
node.setup()

8
noisicaa/audioproc/nodes/passthru.pxd

@ -0,0 +1,8 @@
from .. cimport node
from ..vm cimport buffers
cdef class PassThru(node.CustomNode):
cdef buffers.Buffer __in_left
cdef buffers.Buffer __in_right
cdef buffers.Buffer __out_left
cdef buffers.Buffer __out_right

21
noisicaa/audioproc/nodes/passthru.py → noisicaa/audioproc/nodes/passthru.pyx

@ -1,17 +1,20 @@
#!/usr/bin/python3
from libc.stdint cimport uint32_t
from libc cimport string
import logging
from noisicaa import node_db
from .. import ports
from .. import node
from .. cimport node
from .. import audio_format
logger = logging.getLogger(__name__)
class PassThru(node.CustomNode):
cdef class PassThru(node.CustomNode):
class_name = 'passthru'
def __init__(self, **kwargs):
@ -38,7 +41,7 @@ class PassThru(node.CustomNode):
self.__out_left = None
self.__out_right = None
def connect_port(self, port_name, buf):
cdef int connect_port(self, port_name, buf) except -1:
if port_name == 'in:left':
self.__in_left = buf
elif port_name == 'in:right':
@ -50,7 +53,11 @@ class PassThru(node.CustomNode):
else:
raise ValueError(port_name)
def run(self, ctxt):
length = 4 * ctxt.duration
self.__out_left[0:length] = self.__in_left[0:length]
self.__out_right[0:length] = self.__in_right[0:length]
return 0
cdef int run(self, ctxt) except -1:
cdef uint32_t length = 4 * ctxt.duration
string.memmove(self.__out_left.data, self.__in_left.data, length)
string.memmove(self.__out_right.data, self.__in_right.data, length)
return 0

8
noisicaa/audioproc/nodes/pipeline_crasher.py → noisicaa/audioproc/nodes/pipeline_crasher.pyx

@ -4,13 +4,13 @@ import os
import signal
import time
from .. import node
from .. cimport node
class PipelineCrasher(node.CustomNode):
cdef class PipelineCrasher(node.CustomNode):
class_name = 'pipeline_crasher'
def run(self, ctxt):
cdef int run(self, ctxt) except -1:
trigger = self.get_param('trigger')
if trigger > 0.5:
raise RuntimeError("Kaboom!")
@ -22,3 +22,5 @@ class PipelineCrasher(node.CustomNode):
output_port = self.outputs['out']
output_port.frame.resize(ctxt.duration)
output_port.frame.copy_from(input_port.frame)
return 0

4
noisicaa/audioproc/nodes/sample_player.pxd

@ -0,0 +1,4 @@
from . cimport csound
cdef class SamplePlayer(csound.CSoundBase):
cdef str __sample_path

8
noisicaa/audioproc/nodes/sample_player.py → noisicaa/audioproc/nodes/sample_player.pyx

@ -4,12 +4,12 @@ import logging
import textwrap
from noisicaa import node_db
from . import csound
from . cimport csound
logger = logging.getLogger(__name__)
class SamplePlayer(csound.CSoundBase):
cdef class SamplePlayer(csound.CSoundBase):
class_name = 'sample_player'
def __init__(self, *, sample_path, **kwargs):
@ -30,7 +30,7 @@ class SamplePlayer(csound.CSoundBase):
super().__init__(description=description, **kwargs)
self._sample_path = sample_path
self.__sample_path = sample_path
def setup(self):
super().setup()
@ -65,6 +65,6 @@ class SamplePlayer(csound.CSoundBase):
score = textwrap.dedent("""\
f 1 0 0 1 "{path}" 0 0 0
""").format(path=self._sample_path)
""").format(path=self.__sample_path)
self.set_code(orchestra, score)

18
noisicaa/audioproc/nodes/sample_player_test.py → noisicaa/audioproc/nodes/sample_player_test.pyx

@ -4,25 +4,31 @@ import unittest
from noisicaa.audioproc import data
from noisicaa.bindings import lv2
from . import sample_player
from . cimport sample_player
from ..vm cimport buffers
class SamplePlayerTest(unittest.TestCase):
def test_basic(self):
cdef:
sample_player.SamplePlayer node
buffers.Buffer buf_in
buffers.Buffer buf_out_l
buffers.Buffer buf_out_r
node = sample_player.SamplePlayer(
id='test',
sample_path='/usr/share/sounds/freedesktop/stereo/bell.oga')
node.setup()
try:
buf_in = bytearray(10240)
buf_out_l = bytearray(1024)
buf_out_r = bytearray(1024)
buf_in = buffers.Buffer(buffers.AtomData(10240))
buf_out_l = buffers.Buffer(buffers.FloatArray(256))
buf_out_r = buffers.Buffer(buffers.FloatArray(256))
node.connect_port('in', buf_in)
node.connect_port('out:left', buf_out_l)
node.connect_port('out:right', buf_out_r)
forge = lv2.AtomForge(lv2.static_mapper)
forge.set_buffer(buf_in, 10240)
forge.set_buffer(buf_in.data, 10240)
with forge.sequence():
forge.write_midi_event(0, bytes([0x90, 60, 100]), 3)
forge.write_midi_event(128, bytes([0x80, 60, 0]), 3)

0
noisicaa/audioproc/nodes/track_audio_source.py → noisicaa/audioproc/nodes/track_audio_source.pyx

0
noisicaa/audioproc/nodes/track_control_source.py → noisicaa/audioproc/nodes/track_control_source.pyx

0
noisicaa/audioproc/nodes/track_event_source.py → noisicaa/audioproc/nodes/track_event_source.pyx

22
noisicaa/audioproc/nodes/wavfile.pxd

@ -0,0 +1,22 @@
from libc.stdint cimport uint32_t
from .. cimport node
from ..vm cimport buffers
cdef class WavFileSource(node.CustomNode):
cdef object __path
cdef object __loop
cdef object __end_notification
cdef object __playing
cdef uint32_t __pos
cdef uint32_t __num_samples
cdef bytearray __samples_l
cdef bytearray __samples_r
cdef buffers.Buffer __out_left
cdef buffers.Buffer __out_right
cdef int connect_port(self, port_name, buf) except -1
cdef int run(self, ctxt) except -1

52
noisicaa/audioproc/nodes/wavfile.py → noisicaa/audioproc/nodes/wavfile.pyx

@ -1,18 +1,20 @@
#!/usr/bin/python3
from libc cimport string
import logging
import wave
from noisicaa import node_db
from .. import resample
from .. import node
from .. cimport node
from .. import audio_format
logger = logging.getLogger(__name__)
class WavFileSource(node.CustomNode):