Browse Source

Custom UI for mixer nodes and an Instrument node to bring back sample playback.

And various internal changes and cleanups to support this.
looper
Ben Niemann 4 years ago
parent
commit
aa380e9c07
  1. 2
      3rdparty/typeshed/PyQt5/QtGui.pyi
  2. 2
      3rdparty/typeshed/PyQt5/QtWidgets.pyi
  3. 19
      CMakeLists.txt
  4. 75
      bin/setup_env.sh
  5. 13
      data/csound/delay.csnd
  6. 5
      listdeps
  7. 3
      noisicaa/audioproc/__init__.py
  8. 13
      noisicaa/audioproc/audioproc_client.py
  9. 8
      noisicaa/audioproc/audioproc_process.py
  10. 13
      noisicaa/audioproc/engine/CMakeLists.txt
  11. 8
      noisicaa/audioproc/engine/backend.cpp
  12. 7
      noisicaa/audioproc/engine/backend.h
  13. 1
      noisicaa/audioproc/engine/backend.pxd
  14. 1
      noisicaa/audioproc/engine/backend.pyi
  15. 5
      noisicaa/audioproc/engine/backend.pyx
  16. 9
      noisicaa/audioproc/engine/backend_portaudio.cpp
  17. 4
      noisicaa/audioproc/engine/block_context.h
  18. 1
      noisicaa/audioproc/engine/buffers.pxd
  19. 4
      noisicaa/audioproc/engine/buffers_test.pyx
  20. 363
      noisicaa/audioproc/engine/csound_util.cpp
  21. 86
      noisicaa/audioproc/engine/csound_util.h
  22. 78
      noisicaa/audioproc/engine/engine.cpp
  23. 10
      noisicaa/audioproc/engine/engine.h
  24. 5
      noisicaa/audioproc/engine/engine.pyx
  25. 85
      noisicaa/audioproc/engine/fluidsynth_util.cpp
  26. 31
      noisicaa/audioproc/engine/fluidsynth_util.h
  27. 29
      noisicaa/audioproc/engine/graph.py
  28. 66
      noisicaa/audioproc/engine/opcodes.cpp
  29. 1
      noisicaa/audioproc/engine/opcodes.h
  30. 1
      noisicaa/audioproc/engine/opcodes.pxd
  31. 16
      noisicaa/audioproc/engine/processor.cpp
  32. 331
      noisicaa/audioproc/engine/processor_csound_base.cpp
  33. 42
      noisicaa/audioproc/engine/processor_csound_base.h
  34. 6
      noisicaa/audioproc/engine/processor_csound_test.py
  35. 8
      noisicaa/audioproc/engine/processor_cvgenerator_test.py
  36. 79
      noisicaa/audioproc/engine/processor_fluidsynth_test.py
  37. 202
      noisicaa/audioproc/engine/processor_instrument.cpp
  38. 75
      noisicaa/audioproc/engine/processor_instrument.h
  39. 41
      noisicaa/audioproc/engine/processor_instrument_test.py
  40. 193
      noisicaa/audioproc/engine/processor_mixer.cpp
  41. 36
      noisicaa/audioproc/engine/processor_mixer.h
  42. 22
      noisicaa/audioproc/engine/processor_mixer_test.py
  43. 9
      noisicaa/audioproc/engine/processor_pianoroll_test.py
  44. 86
      noisicaa/audioproc/engine/processor_sample_player.cpp
  45. 81
      noisicaa/audioproc/engine/processor_track_mixer.cpp
  46. 7
      noisicaa/audioproc/engine/realm.pyx
  47. 35
      noisicaa/audioproc/engine/realm_test.py
  48. 1
      noisicaa/audioproc/engine/spec.pyx
  49. 3
      noisicaa/audioproc/public/CMakeLists.txt
  50. 5
      noisicaa/audioproc/public/__init__.py
  51. 39
      noisicaa/audioproc/public/instrument_spec.proto
  52. 11
      noisicaa/audioproc/public/processor_message.proto
  53. 2
      noisicaa/bindings/CMakeLists.txt
  54. 8
      noisicaa/bindings/lilv.pxd
  55. 14
      noisicaa/bindings/lilv.pyx
  56. 1
      noisicaa/bindings/lilv_test.pyx
  57. 32
      noisicaa/bindings/lv2/CMakeLists.txt
  58. 28
      noisicaa/bindings/lv2/__init__.py
  59. 511
      noisicaa/bindings/lv2/atom.pxd
  60. 195
      noisicaa/bindings/lv2/atom.pyx
  61. 63
      noisicaa/bindings/lv2/atom_test.py
  62. 69
      noisicaa/bindings/lv2/urid_test.pyx
  63. 2
      noisicaa/bindings/sratom.pxd
  64. 4
      noisicaa/bindings/sratom.pyi
  65. 7
      noisicaa/bindings/sratom.pyx
  66. 8
      noisicaa/bindings/sratom_test.py
  67. 6
      noisicaa/core/CMakeLists.txt
  68. 6
      noisicaa/core/__init__.py
  69. 56
      noisicaa/core/message.py
  70. 36
      noisicaa/core/message_test.py
  71. 11
      noisicaa/core/process_manager.py
  72. 4
      noisicaa/host_system/host_system.pyx
  73. 1
      noisicaa/instrument_db/__init__.py
  74. 30
      noisicaa/instrument_db/instrument_description.py
  75. 17
      noisicaa/lv2/CMakeLists.txt
  76. 1
      noisicaa/lv2/__init__.py
  77. 306
      noisicaa/lv2/atom.pxd
  78. 133
      noisicaa/lv2/atom.pyx
  79. 88
      noisicaa/lv2/atom_test.py
  80. 0
      noisicaa/lv2/bufsize.pxd
  81. 0
      noisicaa/lv2/bufsize.pyx
  82. 0
      noisicaa/lv2/core.pxd
  83. 0
      noisicaa/lv2/core.pyx
  84. 5
      noisicaa/lv2/options.pxd
  85. 2
      noisicaa/lv2/options.pyx
  86. 28
      noisicaa/lv2/urid.pxd
  87. 50
      noisicaa/lv2/urid.pyx
  88. 33
      noisicaa/lv2/urid_mapper.pyi
  89. 0
      noisicaa/lv2/worker.pxd
  90. 0
      noisicaa/lv2/worker.pyx
  91. 2
      noisicaa/model/__init__.py
  92. 4
      noisicaa/model/project.proto
  93. 128
      noisicaa/model/project.py
  94. 2
      noisicaa/music/CMakeLists.txt
  95. 3
      noisicaa/music/__init__.py
  96. 50
      noisicaa/music/base_track.py
  97. 16
      noisicaa/music/beat_track.py
  98. 5
      noisicaa/music/commands.proto
  99. 19
      noisicaa/music/control_track.py
  100. 4
      noisicaa/music/control_track_test.py
  101. Some files were not shown because too many files have changed in this diff Show More

2
3rdparty/typeshed/PyQt5/QtGui.pyi vendored

@ -4751,7 +4751,7 @@ class QPainter(sip.simplewrapper):
@typing.overload
def drawLine(self, p1: typing.Union[QtCore.QPointF, QtCore.QPoint], p2: typing.Union[QtCore.QPointF, QtCore.QPoint]) -> None: ...
def paintEngine(self) -> 'QPaintEngine': ...
def setRenderHints(self, hints: typing.Union['QPainter.RenderHints', 'QPainter.RenderHint'], on: bool = ...) -> None: ...
def setRenderHints(self, hints: typing.Union[int, 'QPainter.RenderHints', 'QPainter.RenderHint'], on: bool = ...) -> None: ...
def renderHints(self) -> 'QPainter.RenderHints': ...
def setRenderHint(self, hint: 'QPainter.RenderHint', on: bool = ...) -> None: ...
@typing.overload

2
3rdparty/typeshed/PyQt5/QtWidgets.pyi vendored

@ -3361,7 +3361,7 @@ class QFormLayout(QLayout):
def formAlignment(self) -> QtCore.Qt.Alignment: ...
def setFormAlignment(self, alignment: typing.Union[QtCore.Qt.Alignment, QtCore.Qt.AlignmentFlag]) -> None: ...
def labelAlignment(self) -> QtCore.Qt.Alignment: ...
def setLabelAlignment(self, alignment: typing.Union[QtCore.Qt.Alignment, QtCore.Qt.AlignmentFlag]) -> None: ...
def setLabelAlignment(self, alignment: typing.Union[int, QtCore.Qt.Alignment, QtCore.Qt.AlignmentFlag]) -> None: ...
def rowWrapPolicy(self) -> 'QFormLayout.RowWrapPolicy': ...
def setRowWrapPolicy(self, policy: 'QFormLayout.RowWrapPolicy') -> None: ...
def fieldGrowthPolicy(self) -> 'QFormLayout.FieldGrowthPolicy': ...

19
CMakeLists.txt

@ -101,25 +101,6 @@ macro(add_cython_module mod lang)
set(${mod}.so ${pkg_target}.${mod})
endmacro(add_cython_module)
macro(add_py_capnp src)
add_custom_command(
OUTPUT ${src}
COMMAND cp -f ${CMAKE_CURRENT_LIST_DIR}/${src} ${src}
DEPENDS ${CMAKE_CURRENT_LIST_DIR}/${src}
)
file(RELATIVE_PATH pkg_path ${CMAKE_SOURCE_DIR} ${CMAKE_CURRENT_LIST_DIR})
string(REGEX REPLACE "/" "." pkg_target ${pkg_path})
add_custom_target(${pkg_target}.${src} ALL DEPENDS ${src})
endmacro(add_py_capnp)
macro(add_cpp_capnp src)
add_custom_command(
OUTPUT ${src}.c++ ${src}.h
COMMAND capnp compile --verbose --import-path=${CMAKE_SOURCE_DIR} --output=c++:${CMAKE_BINARY_DIR} --src-prefix=${CMAKE_SOURCE_DIR} ${CMAKE_CURRENT_LIST_DIR}/${src}
DEPENDS ${CMAKE_CURRENT_LIST_DIR}/${src}
)
endmacro(add_cpp_capnp)
macro(py_proto src)
string(REGEX REPLACE "\\.proto$" "" base ${src})
add_custom_command(

75
bin/setup_env.sh

@ -1,75 +0,0 @@
#!/bin/bash
# @begin:license
#
# Copyright (c) 2015-2018, Benjamin Niemann <pink@odahoda.de>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# @end:license
if [ -z "$VIRTUAL_ENV" -o ! -d "$VIRTUAL_ENV" ]; then
echo >&2 "Not running in a virtualenv. Please set that up first."
exit 1
fi
BASEDIR=$(readlink -f "$(dirname "$0")/..")
LIBSDIR="$BASEDIR/libs"
LILV_DEPS="libserd-dev libsord-dev libsratom-dev lv2-examples mda-lv2"
LADSPA_DEPS="ladspa-sdk swh-plugins"
CSOUND_DEPS="libsndfile1-dev libsamplerate0-dev libboost-dev flex bison cmake"
CAPNP_DEPS="capnproto libcapnp-0.5.3 libcapnp-dev"
PYVERSION=3.5
PACKAGES_QT5="python3-pyqt5 python3-pyqt5.qtsvg"
PACKAGES="python$PYVERSION python$PYVERSION-venv python$PYVERSION-dev libxml2-dev libxslt1-dev portaudio19-dev libavutil-dev libavutil-ffmpeg54 libswresample-dev libswresample-ffmpeg1 libfluidsynth1 libfluidsynth-dev inkscape timgm6mb-soundfont fluid-soundfont-gs fluid-soundfont-gm flac zlib1g-dev $PACKAGES_QT5 $CSOUND_DEPS $LADSPA_DEPS $LILV_DEPS $CAPNP_DEPS"
function pkg-status() {
PKG="$1"
(
dpkg-query -W -f='${Status}\n' "$PKG" 2>/dev/null || echo "foo bar not-installed"
) | awk '{print $3}'
}
function main() {
set -e
############################################################################
# check prerequisites
declare -a MISSING
for PKG in $PACKAGES; do
STATUS=
if [ "$(pkg-status "$PKG")" != "installed" ]; then
MISSING+=( "$PKG" )
fi
done
if [ ${#MISSING[@]} -gt 0 ]; then
echo >&2 "Missing some packages: ${MISSING[@]}"
exit 1
fi
############################################################################
# install libraries
pip install --upgrade pip
pip install --upgrade -r $BASEDIR/requirements.txt
}
main

13
data/csound/delay.csnd

@ -25,10 +25,15 @@
<csound>
<display-name>Delay</display-name>
<ports>
<port name="in" type="audio" direction="input"/>
<port name="out" type="audio" direction="output">
<drywet port="in" default="0"/>
<bypass port="in"/>
<port name="in/left" type="audio" direction="input"/>
<port name="in/right" type="audio" direction="input"/>
<port name="out/left" type="audio" direction="output">
<drywet port="in/left" default="0"/>
<bypass port="in/left"/>
</port>
<port name="out/right" type="audio" direction="output">
<drywet port="in/right" default="0"/>
<bypass port="in/right"/>
</port>
<port name="delay" type="kratecontrol" direction="input">
<float-control min="0" max="10" default="1"/>

5
listdeps

@ -60,7 +60,6 @@ PIP_DEPS = {
PKG('protobuf'),
PKG('psutil'),
PKG('pyaudio'),
PKG('pycapnp'),
PKG('quamash'),
PKG('toposort'),
PKG('urwid'),
@ -133,10 +132,6 @@ SYS_DEPS = {
PKG('csound', 'ubuntu', '>=17.10'),
PKG('libcsound64-dev', 'ubuntu', '>=17.10'),
# capnp
PKG('capnproto'),
PKG('libcapnp-dev'),
# protocol buffers
PKG('autoconf', 'ubuntu', '<17.10'),
PKG('automake', 'ubuntu', '<17.10'),

3
noisicaa/audioproc/__init__.py

@ -40,6 +40,9 @@ from .public import (
PluginState,
PluginStateLV2,
PluginStateLV2Property,
InstrumentSpec,
SampleInstrumentSpec,
SF2InstrumentSpec,
ProcessorMessage,
ProcessorMessageList,
PlayerState,

13
noisicaa/audioproc/audioproc_client.py

@ -22,7 +22,7 @@
import asyncio
import logging
from typing import Any, Optional, Set, Tuple
from typing import Any, Optional, Set, Tuple, Dict
from noisicaa import core
from noisicaa.core import ipc
@ -119,10 +119,10 @@ class AudioProcClientBase(object):
async def set_backend_parameters(self, **parameters: Any) -> None:
raise NotImplementedError
async def update_player_state(self, state: player_state_pb2.PlayerState) -> None:
async def set_session_values(self, realm: str, values: Dict[str, Any]) -> None:
raise NotImplementedError
async def send_message(self, msg: Any) -> None:
async def update_player_state(self, state: player_state_pb2.PlayerState) -> None:
raise NotImplementedError
async def play_file(self, path: str) -> None:
@ -265,13 +265,12 @@ class AudioProcClientMixin(AudioProcClientBase):
async def set_backend_parameters(self, **parameters: Any) -> None:
await self._stub.call('SET_BACKEND_PARAMETERS', self._session_id, parameters)
async def set_session_values(self, realm: str, values: Dict[str, Any]) -> None:
await self._stub.call('SET_SESSION_VALUES', self._session_id, realm, values)
async def update_player_state(self, state: player_state_pb2.PlayerState) -> None:
await self._stub.call('UPDATE_PLAYER_STATE', self._session_id, state)
# TODO: msg is a capnp message, and capnp's import magic doesn't work with mypy.
async def send_message(self, msg: Any) -> None:
return await self._stub.call('SEND_MESSAGE', self._session_id, msg.to_bytes())
async def play_file(self, path: str) -> None:
await self._stub.call('PLAY_FILE', self._session_id, path)

8
noisicaa/audioproc/audioproc_process.py

@ -134,7 +134,7 @@ class AudioProcProcess(core.SessionHandlerMixin, core.ProcessBase):
self.server.add_command_handler('SET_BACKEND', self.handle_set_backend)
self.server.add_command_handler(
'SET_BACKEND_PARAMETERS', self.handle_set_backend_parameters)
self.server.add_command_handler('SEND_MESSAGE', self.handle_send_message)
self.server.add_command_handler('SET_SESSION_VALUES', self.handle_set_session_values)
self.server.add_command_handler('PLAY_FILE', self.handle_play_file)
self.server.add_command_handler('PIPELINE_MUTATION', self.handle_pipeline_mutation)
self.server.add_command_handler('SEND_NODE_MESSAGES', self.handle_send_node_messages)
@ -301,9 +301,11 @@ class AudioProcProcess(core.SessionHandlerMixin, core.ProcessBase):
self.get_session(session_id)
self.__engine.set_backend_parameters(**parameters)
def handle_send_message(self, session_id: str, msg: bytes) -> None:
def handle_set_session_values(
self, session_id: str, realm_name: str, values: Dict[str, Any]) -> None:
self.get_session(session_id)
self.__engine.send_message(msg)
realm = self.__engine.get_realm(realm_name)
realm.set_session_values(values)
def handle_update_player_state(
self, session_id: str, state: player_state_pb2.PlayerState) -> None:

13
noisicaa/audioproc/engine/CMakeLists.txt

@ -40,13 +40,12 @@ add_python_package(
processor_test.py
processor_csound_test.py
processor_cvgenerator_test.py
processor_fluidsynth_test.py
processor_pianoroll_test.py
processor_plugin_test.py
processor_sample_player_test.py
processor_instrument_test.py
processor_sample_script_test.py
processor_sound_file_test.py
processor_track_mixer_test.py
processor_mixer_test.py
profile.pyi
realm.pyi
realm_test.py
@ -62,8 +61,10 @@ set(LIB_SRCS
buffer_arena.cpp
buffers.cpp
control_value.cpp
csound_util.cpp
double_buffered_state_manager.cpp
engine.cpp
fluidsynth_util.cpp
misc.cpp
message_queue.cpp
opcodes.cpp
@ -80,11 +81,10 @@ set(LIB_SRCS
processor_csound_base.cpp
processor_csound.cpp
processor_custom_csound.cpp
processor_fluidsynth.cpp
processor_plugin.cpp
processor_sample_player.cpp
processor_instrument.cpp
processor_sound_file.cpp
processor_track_mixer.cpp
processor_mixer.cpp
processor_pianoroll.cpp
processor_cvgenerator.cpp
processor_sample_script.cpp
@ -114,7 +114,6 @@ target_link_libraries(noisicaa-audioproc-engine PRIVATE noisicaa-host_system)
target_link_libraries(noisicaa-audioproc-engine PRIVATE noisicaa-lv2)
target_link_libraries(noisicaa-audioproc-engine PRIVATE noisicaa-node_db)
target_link_libraries(noisicaa-audioproc-engine PRIVATE noisicaa-audioproc-public)
target_link_libraries(noisicaa-audioproc-engine PRIVATE capnp)
target_link_libraries(noisicaa-audioproc-engine PRIVATE pthread)
target_link_libraries(noisicaa-audioproc-engine PRIVATE rt)
target_link_libraries(noisicaa-audioproc-engine PRIVATE profiler)

8
noisicaa/audioproc/engine/backend.cpp

@ -20,8 +20,6 @@
* @end:license
*/
#include "capnp/serialize.h"
#include "noisicaa/core/message.capnp.h"
#include "noisicaa/audioproc/engine/backend.h"
#include "noisicaa/audioproc/engine/backend_null.h"
#include "noisicaa/audioproc/engine/backend_portaudio.h"
@ -59,10 +57,4 @@ Status Backend::setup(Realm* realm) {
void Backend::cleanup() {
}
Status Backend::send_message(const string& msg_bytes) {
lock_guard<mutex> lock(_msg_queue_mutex);
_msg_queue.emplace_back(msg_bytes);
return Status::Ok();
}
} // namespace noisicaa

7
noisicaa/audioproc/engine/backend.h

@ -25,9 +25,7 @@
#ifndef _NOISICAA_AUDIOPROC_ENGINE_BACKEND_H
#define _NOISICAA_AUDIOPROC_ENGINE_BACKEND_H
#include <mutex>
#include <string>
#include <vector>
#include "noisicaa/core/logging.h"
#include "noisicaa/core/status.h"
#include "noisicaa/audioproc/engine/buffers.h"
@ -55,8 +53,6 @@ public:
virtual Status setup(Realm* realm);
virtual void cleanup();
Status send_message(const string& msg);
virtual Status begin_block(BlockContext* ctxt) = 0;
virtual Status end_block(BlockContext* ctxt) = 0;
virtual Status output(BlockContext* ctxt, const string& channel, BufferPtr samples) = 0;
@ -68,9 +64,6 @@ protected:
Logger* _logger;
BackendSettings _settings;
Realm* _realm = nullptr;
mutex _msg_queue_mutex;
vector<string> _msg_queue;
};
} // namespace noisicaa

1
noisicaa/audioproc/engine/backend.pxd

@ -41,7 +41,6 @@ cdef extern from "noisicaa/audioproc/engine/backend.h" namespace "noisicaa" nogi
Status setup(Realm* realm)
void cleanup()
Status send_message(const string& msg)
Status begin_block(BlockContext* ctxt)
Status end_block(BlockContext* ctxt)
Status output(BlockContext* ctxt, const string& channel, BufferPtr samples)

1
noisicaa/audioproc/engine/backend.pyi

@ -45,7 +45,6 @@ class PyBackend(object):
def stopped(self) -> bool: ...
def release(self) -> None: ...
def released(self) -> bool: ...
def send_message(self, msg: bytes) -> None: ...
def begin_block(self, ctxt: block_context.PyBlockContext) -> None: ...
def end_block(self, ctxt: block_context.PyBlockContext) -> None: ...
def output(

5
noisicaa/audioproc/engine/backend.pyx

@ -83,11 +83,6 @@ cdef class PyBackend(object):
with nogil:
backend.cleanup()
def send_message(self, bytes msg):
cdef string c_msg = msg
with nogil:
check(self.__backend.send_message(c_msg))
def begin_block(self, block_context.PyBlockContext ctxt):
with nogil:
check(self.__backend.begin_block(ctxt.get()))

9
noisicaa/audioproc/engine/backend_portaudio.cpp

@ -132,15 +132,6 @@ Status PortAudioBackend::begin_block(BlockContext* ctxt) {
memset(_samples[c], 0, _host_system->block_size() * sizeof(float));
}
{
lock_guard<mutex> lock(_msg_queue_mutex);
ctxt->in_messages.clear();
for (const auto& msg : _msg_queue) {
ctxt->in_messages.emplace_back(msg);
}
_msg_queue.clear();
}
return Status::Ok();
}

4
noisicaa/audioproc/engine/block_context.h

@ -30,7 +30,6 @@
#include <memory>
#include <string>
#include <vector>
#include "noisicaa/core/message.capnp.h"
#include "noisicaa/audioproc/public/musical_time.h"
#include "noisicaa/audioproc/engine/buffers.h"
@ -65,9 +64,6 @@ struct BlockContext {
};
map<string, Buffer> buffers;
// TODO: Use MessageQueue
vector<string> in_messages;
MessageQueue* out_messages;
};

1
noisicaa/audioproc/engine/buffers.pxd

@ -21,7 +21,6 @@
from libcpp.memory cimport unique_ptr
from libc.stdint cimport uint8_t, uint32_t
from noisicaa.bindings.lv2 cimport urid
from noisicaa.core.status cimport Status
from noisicaa.host_system.host_system cimport HostSystem

4
noisicaa/audioproc/engine/buffers_test.pyx

@ -25,8 +25,8 @@ import sys
from noisidev import unittest
from noisidev cimport unittest_engine_mixins
from noisicaa.bindings.lv2 cimport atom
from noisicaa.bindings.lv2 import urid
#from noisicaa.bindings.lv2 cimport atom
#from noisicaa.bindings.lv2 import urid
from noisicaa.core.status cimport check
#from .buffers cimport *

363
noisicaa/audioproc/engine/csound_util.cpp

@ -0,0 +1,363 @@
/*
* @begin:license
*
* Copyright (c) 2015-2018, Benjamin Niemann <pink@odahoda.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* @end:license
*/
#include <assert.h>
#include <stdint.h>
#include <string.h>
#include "noisicaa/core/logging.h"
#include "noisicaa/core/perf_stats.h"
#include "noisicaa/host_system/host_system.h"
#include "noisicaa/audioproc/engine/csound_util.h"
#include "noisicaa/audioproc/engine/rtcheck.h"
namespace noisicaa {
CSoundUtil::CSoundUtil(HostSystem* host_system)
: _logger(LoggerRegistry::get_logger("noisicaa.audioproc.engine.csound_util")),
_host_system(host_system) {}
CSoundUtil::~CSoundUtil() {
if (_csnd != nullptr) {
_logger->info("Destroying csound instance %p", _csnd);
csoundDestroy(_csnd);
}
_event_input_ports.clear();
}
void CSoundUtil::_log_cb(CSOUND* csnd, int attr, const char* fmt, va_list args) {
CSoundUtil* proc = (CSoundUtil*)csoundGetHostData(csnd);
assert(proc != nullptr);
proc->_log_cb(attr, fmt, args);
}
void CSoundUtil::_log_cb(int attr, const char* fmt, va_list args) {
LogLevel level = LogLevel::INFO;
switch (attr & CSOUNDMSG_TYPE_MASK) {
case CSOUNDMSG_ORCH:
case CSOUNDMSG_REALTIME:
case CSOUNDMSG_DEFAULT:
level = LogLevel::INFO;
break;
case CSOUNDMSG_WARNING:
level = LogLevel::WARNING;
break;
case CSOUNDMSG_ERROR:
level = LogLevel::ERROR;
break;
}
size_t bytes_used = strlen(_log_buf);
vsnprintf(_log_buf + bytes_used, sizeof(_log_buf) - bytes_used, fmt, args);
while (_log_buf[0]) {
char *eol = strchr(_log_buf, '\n');
if (eol == nullptr) {
break;
}
*eol = 0;
_logger->log(level, "%s", _log_buf);
memmove(_log_buf, eol + 1, strlen(eol + 1) + 1);
}
}
Status CSoundUtil::setup(
const string& orchestra, const string& score, const vector<PortSpec>& ports) {
_ports = ports;
memset(_log_buf, 0, sizeof(_log_buf));
_event_input_ports.resize(_ports.size());
_csnd = csoundCreate(this);
if (_csnd == nullptr) {
return ERROR_STATUS("Failed to create Csound instance.");
}
_logger->info("Created csound instance %p", _csnd);
csoundSetMessageCallback(_csnd, CSoundUtil::_log_cb);
int rc = csoundSetOption(_csnd, "-n");
if (rc < 0) {
return ERROR_STATUS("Failed to set Csound options (code %d)", rc);
}
_logger->info("csound orchestra:\n%s", orchestra.c_str());
rc = csoundCompileOrc(_csnd, orchestra.c_str());
if (rc < 0) {
return ERROR_STATUS("Failed to compile Csound orchestra (code %d)", rc);
}
double zerodbfs = csoundGet0dBFS(_csnd);
if (zerodbfs != 1.0) {
return ERROR_STATUS("Csound orchestra must set 0dbfs=1.0 (found %f)", zerodbfs);
}
rc = csoundStart(_csnd);
if (rc < 0) {
return ERROR_STATUS("Failed to start Csound (code %d)", rc);
}
_logger->info("csound score:\n%s", score.c_str());
rc = csoundReadScore(_csnd, score.c_str());
if (rc < 0) {
return ERROR_STATUS("Failed to read Csound score (code %d)", rc);
}
_channel_ptr.resize(_ports.size());
_channel_lock.resize(_ports.size());
for (size_t port_idx = 0 ; port_idx < _ports.size() ; ++port_idx) {
const auto& port = _ports[port_idx];
if (port.type == pb::PortDescription::EVENTS) {
continue;
}
MYFLT* channel_ptr;
int type = csoundGetChannelPtr(_csnd, &channel_ptr, port.name.c_str(), 0);
if (type < 0) {
return ERROR_STATUS("Orchestra does not define the channel '%s'", port.name.c_str());
}
if (port.direction == pb::PortDescription::OUTPUT
&& !(type & CSOUND_OUTPUT_CHANNEL)) {
return ERROR_STATUS("Channel '%s' is not an output channel", port.name.c_str());
}
if (port.direction == pb::PortDescription::INPUT
&& !(type & CSOUND_INPUT_CHANNEL)) {
return ERROR_STATUS("Channel '%s' is not an input channel", port.name.c_str());
}
if (port.type == pb::PortDescription::AUDIO
|| port.type == pb::PortDescription::ARATE_CONTROL) {
if ((type & CSOUND_CHANNEL_TYPE_MASK) != CSOUND_AUDIO_CHANNEL) {
return ERROR_STATUS("Channel '%s' is not an audio channel", port.name.c_str());
}
} else if (port.type == pb::PortDescription::KRATE_CONTROL) {
if ((type & CSOUND_CHANNEL_TYPE_MASK) != CSOUND_CONTROL_CHANNEL) {
return ERROR_STATUS("Channel '%s' is not an control channel", port.name.c_str());
}
} else {
return ERROR_STATUS("Internal error, channel '%s' type %d", port.name.c_str(), port.type);
}
int rc = csoundGetChannelPtr(_csnd, &channel_ptr, port.name.c_str(), type);
if (rc < 0) {
return ERROR_STATUS("Failed to get channel pointer for port '%s'", port.name.c_str());
}
assert(channel_ptr != nullptr);
_channel_ptr[port_idx] = channel_ptr;
_channel_lock[port_idx] = csoundGetChannelLock(_csnd, port.name.c_str());
}
return Status::Ok();
}
Status CSoundUtil::process_block(
BlockContext* ctxt, TimeMapper* time_mapper, vector<BufferPtr>& buffers) {
assert(buffers.size() == (size_t)_ports.size());
for (size_t port_idx = 0 ; port_idx < _ports.size() ; ++port_idx) {
const auto& port = _ports[port_idx];
if (port.direction == pb::PortDescription::INPUT
&& port.type == pb::PortDescription::EVENTS) {
LV2_Atom_Sequence* seq = (LV2_Atom_Sequence*)buffers[port_idx];
if (seq->atom.type != _host_system->lv2->urid.atom_sequence) {
return ERROR_STATUS(
"Excepted sequence in port '%s', got %d.", port.name.c_str(), seq->atom.type);
}
LV2_Atom_Event* event = lv2_atom_sequence_begin(&seq->body);
int instr = 1; // TODO: use port.csound_instr
_event_input_ports[port_idx] = {seq, event, instr};
}
}
uint32_t pos = 0;
uint32_t ksmps = csoundGetKsmps(_csnd);
while (pos < _host_system->block_size()) {
// Copy input ports into Csound channels.
for (size_t port_idx = 0 ; port_idx < _ports.size() ; ++port_idx) {
const auto& port = _ports[port_idx];
if (port.direction == pb::PortDescription::INPUT) {
switch (port.type) {
case pb::PortDescription::AUDIO:
case pb::PortDescription::ARATE_CONTROL: {
float* buf = (float*)buffers[port_idx];
buf += pos;
MYFLT* channel_ptr = _channel_ptr[port_idx];
int *lock = _channel_lock[port_idx];
csoundSpinLock(lock);
for (uint32_t i = 0 ; i < ksmps ; ++i) {
*channel_ptr++ = *buf++;
}
csoundSpinUnLock(lock);
break;
}
case pb::PortDescription::KRATE_CONTROL: {
float* buf = (float*)buffers[port_idx];
MYFLT* channel_ptr = _channel_ptr[port_idx];
int *lock = _channel_lock[port_idx];
csoundSpinLock(lock);
*channel_ptr = *buf;
csoundSpinUnLock(lock);
break;
}
case pb::PortDescription::EVENTS: {
EventInputPort &ep = _event_input_ports[port_idx];
// TODO: is instrument started with one ksmps delay? needs further testing.
while (!lv2_atom_sequence_is_end(
&ep.seq->body, ep.seq->atom.size, ep.event)
&& ep.event->time.frames < pos + ksmps) {
LV2_Atom& atom = ep.event->body;
if (atom.type == _host_system->lv2->urid.midi_event) {
uint8_t* midi = (uint8_t*)LV2_ATOM_CONTENTS(LV2_Atom, &atom);
if ((midi[0] & 0xf0) == 0x90) {
MYFLT p[5] = {
/* p1: instr */ (MYFLT)ep.instr + (MYFLT)midi[1] / 1000.0,
/* p2: time */ 0.0,
/* p3: duration */ -1.0,
/* p4: pitch */ (MYFLT)midi[1],
/* p5: velocity */ (MYFLT)midi[2],
};
//_logger->info("i %f %f %f %f %f", p[0], p[1], p[2], p[3], p[4]);
int rc = csoundScoreEvent(_csnd, 'i', p, 5);
if (rc < 0) {
return ERROR_STATUS("csoundScoreEvent failed (code %d).", rc);
}
} else if ((midi[0] & 0xf0) == 0x80) {
MYFLT p[3] = {
/* p1: instr */ -((MYFLT)ep.instr + (MYFLT)midi[1] / 1000.0),
/* p2: time */ 0.0,
/* p3: duration */ 0.0,
};
//_logger->info("i %f %f %f", p[0], p[1], p[2]);
int rc = csoundScoreEvent(_csnd, 'i', p, 3);
if (rc < 0) {
return ERROR_STATUS("csoundScoreEvent failed (code %d).", rc);
}
} else {
_logger->warning("Ignoring unsupported midi event %d.", midi[0] & 0xf0);
}
} else {
_logger->warning("Ignoring event %d in sequence.", atom.type);
}
ep.event = lv2_atom_sequence_next(ep.event);
}
break;
}
default:
return ERROR_STATUS("Port %s has unsupported type %d", port.name.c_str(), port.type);
}
} else {
assert(port.direction == pb::PortDescription::OUTPUT);
switch (port.type) {
case pb::PortDescription::AUDIO:
case pb::PortDescription::ARATE_CONTROL: {
MYFLT* channel_ptr = _channel_ptr[port_idx];
int *lock = _channel_lock[port_idx];
csoundSpinLock(lock);
for (uint32_t i = 0 ; i < ksmps ; ++i) {
*channel_ptr++ = 0.0;
}
csoundSpinUnLock(lock);
break;
}
case pb::PortDescription::KRATE_CONTROL: {
MYFLT* channel_ptr = _channel_ptr[port_idx];
int *lock = _channel_lock[port_idx];
csoundSpinLock(lock);
*channel_ptr = 0.0;
csoundSpinUnLock(lock);
break;
}
default:
return ERROR_STATUS("Port %s has unsupported type %d", port.name.c_str(), port.type);
}
}
}
int rc;
{
RTUnsafe rtu; // csound might do RT unsafe stuff internally.
rc = csoundPerformKsmps(_csnd);
}
if (rc < 0) {
return ERROR_STATUS("Csound performance failed (code %d)", rc);
}
// Copy channel data from Csound into output ports.
for (size_t port_idx = 0 ; port_idx < _ports.size() ; ++port_idx) {
const auto& port = _ports[port_idx];
if (port.direction == pb::PortDescription::OUTPUT) {
switch (port.type) {
case pb::PortDescription::AUDIO:
case pb::PortDescription::ARATE_CONTROL: {
float* buf = (float*)buffers[port_idx];
buf += pos;
MYFLT* channel_ptr = _channel_ptr[port_idx];
int *lock = _channel_lock[port_idx];
csoundSpinLock(lock);
for (uint32_t i = 0 ; i < ksmps ; ++i) {
*buf++ = *channel_ptr++;
}
csoundSpinUnLock(lock);
break;
}
case pb::PortDescription::KRATE_CONTROL: {
float* buf = (float*)buffers[port_idx];
MYFLT* channel_ptr = _channel_ptr[port_idx];
int *lock = _channel_lock[port_idx];
csoundSpinLock(lock);
*buf = *channel_ptr;
csoundSpinUnLock(lock);
break;
}
default:
return ERROR_STATUS("Port %s has unsupported type %d", port.name.c_str(), port.type);
}
}
}
pos += ksmps;
}
assert(pos == _host_system->block_size());
return Status::Ok();
}
}

86
noisicaa/audioproc/engine/csound_util.h

@ -0,0 +1,86 @@
// -*- mode: c++ -*-
/*
* @begin:license
*
* Copyright (c) 2015-2018, Benjamin Niemann <pink@odahoda.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* @end:license
*/
#ifndef _NOISICAA_AUDIOPROC_ENGINE_CSOUND_UTIL_H
#define _NOISICAA_AUDIOPROC_ENGINE_CSOUND_UTIL_H
#include <atomic>
#include <string>
#include <vector>
#include <stdint.h>
#include "csound/csound.h"
#include "lv2/lv2plug.in/ns/ext/atom/forge.h"
#include "lv2/lv2plug.in/ns/ext/urid/urid.h"
#include "noisicaa/core/status.h"
#include "noisicaa/node_db/node_description.pb.h"
#include "noisicaa/audioproc/engine/buffers.h"
namespace noisicaa {
using namespace std;
class Logger;
class HostSystem;
class BlockContext;
class TimeMapper;
class CSoundUtil {
public:
CSoundUtil(HostSystem* host_system);
~CSoundUtil();
struct PortSpec {
string name;
pb::PortDescription::Type type;
pb::PortDescription::Direction direction;
};
Status setup(const string& orchestra, const string& score, const vector<PortSpec>& ports);
Status process_block(BlockContext* ctxt, TimeMapper* time_mapper, vector<BufferPtr>& buffers);
private:
Logger* _logger;
HostSystem* _host_system;
static void _log_cb(CSOUND* csnd, int attr, const char* fmt, va_list args);
void _log_cb(int attr, const char* fmt, va_list args);
char _log_buf[10240];
CSOUND* _csnd = nullptr;
vector<MYFLT*> _channel_ptr;
vector<int*> _channel_lock;
vector<PortSpec> _ports;
struct EventInputPort {
LV2_Atom_Sequence* seq;
LV2_Atom_Event* event;
int instr;
};
vector<EventInputPort> _event_input_ports;
};
} // namespace noisicaa
#endif

78
noisicaa/audioproc/engine/engine.cpp

@ -43,60 +43,60 @@ Engine::Engine(HostSystem* host_system, void (*callback)(void*, const string&),
_logger(LoggerRegistry::get_logger("noisicaa.audioproc.engine.engine")),
_callback(callback),
_userdata(userdata),
_queue_pump(nullptr),
_next_message_queue(new MessageQueue()),
_current_message_queue(nullptr),
_old_message_queue(new MessageQueue()) {}
_out_messages_pump(nullptr),
_next_out_messages(new MessageQueue()),
_current_out_messages(nullptr),
_old_out_messages(new MessageQueue()) {}
Engine::~Engine() {}
Status Engine::setup() {
_stop = false;
_queue_pump.reset(new thread(&Engine::queue_pump_main, this));
_out_messages_pump.reset(new thread(&Engine::out_messages_pump_main, this));
return Status::Ok();
}
void Engine::cleanup() {
if (_queue_pump.get() != nullptr) {
_logger->info("Stopping queue pump...");
if (_out_messages_pump.get() != nullptr) {
_logger->info("Stopping out_messages pump...");
{
lock_guard<mutex> lock(_cond_mutex);
_stop = true;
_cond.notify_all();
}
_queue_pump->join();
_queue_pump.reset();
_logger->info("Queue pump stopped.");
_out_messages_pump->join();
_out_messages_pump.reset();
_logger->info("out_messages pump stopped.");
}
MessageQueue* message_queue = _next_message_queue.exchange(nullptr);
if (message_queue != nullptr) {
delete message_queue;
MessageQueue* out_messages = _next_out_messages.exchange(nullptr);
if (out_messages != nullptr) {
delete out_messages;
}
message_queue = _current_message_queue.exchange(nullptr);
if (message_queue != nullptr) {
delete message_queue;
out_messages = _current_out_messages.exchange(nullptr);
if (out_messages != nullptr) {
delete out_messages;
}
message_queue = _old_message_queue.exchange(nullptr);
if (message_queue != nullptr) {
delete message_queue;
out_messages = _old_out_messages.exchange(nullptr);
if (out_messages != nullptr) {
delete out_messages;
}
}
void Engine::queue_pump_main() {
void Engine::out_messages_pump_main() {
unique_lock<mutex> lock(_cond_mutex);
while (true) {
_cond.wait_for(lock, chrono::milliseconds(500));
MessageQueue* queue = _old_message_queue.exchange(nullptr);
if (queue != nullptr) {
if (!queue->empty()) {
MessageQueue* out_messages = _old_out_messages.exchange(nullptr);
if (out_messages != nullptr) {
if (!out_messages->empty()) {
pb::EngineNotification notification;
Message* msg = queue->first();
while (!queue->is_end(msg)) {
Message* msg = out_messages->first();
while (!out_messages->is_end(msg)) {
switch (msg->type) {
case MessageType::ENGINE_LOAD: {
@ -139,18 +139,18 @@ void Engine::queue_pump_main() {
}
}
msg = queue->next(msg);
msg = out_messages->next(msg);
}
queue->clear();
out_messages->clear();
string notification_serialized;
assert(notification.SerializeToString(&notification_serialized));
_callback(_userdata, notification_serialized);
}
queue = _next_message_queue.exchange(queue);
if (queue != nullptr) {
assert(_old_message_queue.exchange(queue) == nullptr);
out_messages = _next_out_messages.exchange(out_messages);
if (out_messages != nullptr) {
assert(_old_out_messages.exchange(out_messages) == nullptr);
}
}
@ -195,19 +195,19 @@ Status Engine::loop(Realm* realm, Backend* backend) {
continue;
}
MessageQueue* current_queue = _next_message_queue.exchange(nullptr);
if (current_queue != nullptr) {
assert(current_queue->empty());
MessageQueue* old = _current_message_queue.exchange(nullptr);
MessageQueue* out_messages = _next_out_messages.exchange(nullptr);
if (out_messages != nullptr) {
assert(out_messages->empty());
MessageQueue* old = _current_out_messages.exchange(nullptr);
if (old != nullptr) {
assert(_old_message_queue.exchange(old) == nullptr);
assert(_old_out_messages.exchange(old) == nullptr);
_cond.notify_all();
}
} else {
current_queue = _current_message_queue.exchange(nullptr);
assert(current_queue != nullptr);
out_messages = _current_out_messages.exchange(nullptr);
assert(out_messages != nullptr);
}
ctxt->out_messages = current_queue;
ctxt->out_messages = out_messages;
if (ctxt->perf->num_spans() > 0) {
PerfStatsMessage::push(ctxt->out_messages, *ctxt->perf);
@ -250,7 +250,7 @@ Status Engine::loop(Realm* realm, Backend* backend) {
last_loop_time = chrono::high_resolution_clock::now();
ctxt->out_messages = nullptr;
assert(_current_message_queue.exchange(current_queue) == nullptr);
assert(_current_out_messages.exchange(out_messages) == nullptr);
}
return Status::Ok();

10
noisicaa/audioproc/engine/engine.h

@ -62,15 +62,15 @@ private:
bool _exit_loop;
unique_ptr<thread> _queue_pump;
unique_ptr<thread> _out_messages_pump;
bool _stop = false;
mutex _cond_mutex;
condition_variable _cond;
void queue_pump_main();
void out_messages_pump_main();
atomic<MessageQueue*> _next_message_queue;
atomic<MessageQueue*> _current_message_queue;
atomic<MessageQueue*> _old_message_queue;
atomic<MessageQueue*> _next_out_messages;
atomic<MessageQueue*> _current_out_messages;
atomic<MessageQueue*> _old_out_messages;
};

5
noisicaa/audioproc/engine/engine.pyx

@ -38,7 +38,6 @@ import time
import toposort
from noisicaa import core
from noisicaa.bindings import lv2
from noisicaa.core import ipc
from noisicaa.core.status cimport check
from noisicaa.core.perf_stats cimport PyPerfStats
@ -364,10 +363,6 @@ cdef class PyEngine(object):
if self.__backend is not None:
pass
def send_message(self, msg):
if self.__backend is not None:
self.__backend.send_message(msg)
@staticmethod
cdef void __notification_callback(void* c_self, const string& notification_serialized) with gil:
self = <object><PyObject*>c_self

85
noisicaa/audioproc/engine/processor_fluidsynth.cpp → noisicaa/audioproc/engine/fluidsynth_util.cpp

@ -20,30 +20,40 @@
* @end:license
*/
#include "noisicaa/core/perf_stats.h"
#include "noisicaa/audioproc/engine/processor_fluidsynth.h"
#include "noisicaa/core/logging.h"
#include "noisicaa/audioproc/engine/fluidsynth_util.h"
#include "noisicaa/host_system/host_system.h"
namespace noisicaa {
ProcessorFluidSynth::ProcessorFluidSynth(
const string& node_id, HostSystem* host_system, const pb::NodeDescription& desc)
: Processor(node_id, "noisicaa.audioproc.engine.processor.fluidsynth", host_system, desc) {}
FluidSynthUtil::FluidSynthUtil(HostSystem* host_system)
: _logger(LoggerRegistry::get_logger("noisicaa.audioproc.engine.fluidsynth_util")),
_host_system(host_system) {}
ProcessorFluidSynth::~ProcessorFluidSynth() {}
Status ProcessorFluidSynth::setup_internal() {
RETURN_IF_ERROR(Processor::setup_internal());
FluidSynthUtil::~FluidSynthUtil() {
if (_synth != nullptr) {
// 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
if (!_desc.has_fluidsynth()) {
return ERROR_STATUS("NodeDescription misses fluidsynth field.");
delete_fluid_synth(_synth);
_synth = nullptr;
}
const pb::FluidSynthDescription& fluid_desc = _desc.fluidsynth();
if (_settings != nullptr) {
delete_fluid_settings(_settings);
_settings = nullptr;
}
}
Status FluidSynthUtil::setup(const string& path, uint32_t bank, uint32_t preset) {
_logger->info(
"Setting up fluidsynth processor for %s, bank=%d, preset=%d",
fluid_desc.soundfont_path().c_str(), fluid_desc.bank(), fluid_desc.preset());
"Setting up fluidsynth util for %s, bank=%d, preset=%d",
path.c_str(), bank, preset);
_settings = new_fluid_settings();
if (_settings == nullptr) {
@ -67,7 +77,7 @@ Status ProcessorFluidSynth::setup_internal() {
return ERROR_STATUS("Failed to create fluid synth object.");
}
int sfid = fluid_synth_sfload(_synth, fluid_desc.soundfont_path().c_str(), true);
int sfid = fluid_synth_sfload(_synth, path.c_str(), true);
if (sfid == FLUID_FAILED) {
// TODO: error message?
return ERROR_STATUS("Failed to load soundfont.");
@ -95,7 +105,7 @@ Status ProcessorFluidSynth::setup_internal() {
return ERROR_STATUS("System reset failed.");
}
rc = fluid_synth_program_select(_synth, 0, sfid, fluid_desc.bank(), fluid_desc.preset());
rc = fluid_synth_program_select(_synth, 0, sfid, bank, preset);
if (rc == FLUID_FAILED)