Browse Source

Persist plugin state.

- Now the state of the Helm synth is properly saved, although there is still odd behavior when using
  Helm's preset browser - which I blame Helm for.
- Control value changes are routed through the project process, so the model can be updated.
- Implement the LV2 state feature.
- Also add the concept of generation numbers to control value changes, so delayed updates don't
  cause weird issues with the UI.

Squashed commit of the following:

commit b87929098aeafa3375e209d5eb3362caeccbd6cd
Author: Ben Niemann <pink@odahoda.de>
Date:   Tue Apr 3 02:45:44 2018 +0200

    Change to plugin_state in model are propagated to the plugin host.

commit ae197b6a81a3a0a645cf8440a63b7aee53fab477
Author: Ben Niemann <pink@odahoda.de>
Date:   Tue Apr 3 01:37:46 2018 +0200

    Immediately unload plugin UI when hiding it.

    Currently crashes when closing the app while still waiting for the unload.

commit 12454b02ca8d388537389e9d031295d1f24a7e03
Author: Ben Niemann <pink@odahoda.de>
Date:   Tue Apr 3 01:37:25 2018 +0200

    Larger log files to make debugging easier.

commit 6ccae1f7c89cdd5ef0f699e676af8ea988f03351
Author: Ben Niemann <pink@odahoda.de>
Date:   Mon Apr 2 20:30:59 2018 +0200

    Set the initial plugin state from the state stored in the model.

    - Add PluginHost::set_state() methods and LV2 implementation.
    - Also make PluginHost::get_state() non-abstract.

    Somethings still missing, state isn't the same after reopening a project.

commit 25710a77549501bd80164105b50ab575f5f88c09
Author: Ben Niemann <pink@odahoda.de>
Date:   Mon Apr 2 02:14:24 2018 +0200

    Persist the plugin state in the model.

    - Regularly poll the plugin for its state.
    - If changed, post it back to project process.
    - The callback address to the project process is now set when creating the realm, and the stub
      is created by the PluginHost instance.
    - PluginHostLV2::get_state() does not create an empty lv2 message, if no properties are set.

commit 2fd537a73d77b30fa0b49193522b68ad928616a9
Author: Ben Niemann <pink@odahoda.de>
Date:   Mon Apr 2 02:11:48 2018 +0200

    Move PluginState message to noisicaa.audioproc.public.

commit 252946a849806745a7ae1d529dedf47406b98c6a
Author: Ben Niemann <pink@odahoda.de>
Date:   Sun Apr 1 19:22:02 2018 +0200

    Add API to dump LV2 plugin state into a proto message.

commit 6e0546cd47ce66e8481db2331496d7ba819c48b3
Author: Ben Niemann <pink@odahoda.de>
Date:   Sun Apr 1 19:18:11 2018 +0200

    Two test modules were missing from CMakeLists.txt.

commit fcb94a0f25322b7d60c124bc2af0344a4560e58a
Author: Ben Niemann <pink@odahoda.de>
Date:   Sun Apr 1 16:05:07 2018 +0200

    Control values are tracked as a (value, generation) pair.

    Implements a basic logical clock, so the UI can ignore delayed state updates.

    Also rename the BufferType classes for consistency.

commit 574078a6cd228a0af72d8c06d026ccac822cdcf2
Author: Ben Niemann <pink@odahoda.de>
Date:   Sat Mar 31 13:32:30 2018 +0200

    Post control value changes to the UI.

    Move the control port monitoring to PluginHostLV2, because it's only used by LV2 for now, and
    further improvements will be very LV2 specific.

commit 45b0ca8bf2769b6218ccc1b74aa91015a3c2af91
Author: Ben Niemann <pink@odahoda.de>
Date:   Fri Mar 30 17:52:40 2018 +0200

    PluginHost exposed control value changes via a slot.

    The slot is called in a non-rt context.

commit 31540a7ae5ff1e0f618faf4742628f41acee01ee
Author: Ben Niemann <pink@odahoda.de>
Date:   Fri Mar 30 16:50:01 2018 +0200

    ProjectProcess handles CONTROL_VALUE_CHANGE.

    Constructs and dispatches a SetPipelineGraphControlValue command, which automatically updates the
    audioproc and UI processes with the change.

commit 62b3f9d5ea3a3302f7bf5906c164cf7cbd2ec294
Author: Ben Niemann <pink@odahoda.de>
Date:   Fri Mar 30 16:18:50 2018 +0200

    Some more fixes, because PluginHostProcess doesn't take the audioproc server address anymore.

commit 1a4a687a5eb4a1a8188c0fb18a7f85223f841910
Author: Ben Niemann <pink@odahoda.de>
Date:   Fri Mar 30 15:56:46 2018 +0200

    PluginHostProcess expects a callback address for each UI.

    And uses that to post control value changes.
    Doesn't need the audioproc server address anymore.

commit ed1108e9ea97924204bf88a2d0bbf2594e221dae
Author: Ben Niemann <pink@odahoda.de>
Date:   Fri Mar 30 15:54:52 2018 +0200

    Allow lines with type annotation to exceed max length limit.

commit 07dd43e24f5368a2f1db2feb8a11ba35398b0fa9
Author: Ben Niemann <pink@odahoda.de>
Date:   Thu Mar 29 17:31:01 2018 +0200

    Remove obsolete class.
looper
Ben Niemann 5 years ago
parent
commit
b29d0c6fc7
  1. 39
      NOTES.org
  2. 2
      bin/pylintrc
  3. 2
      noisicaa/audioproc/__init__.py
  4. 33
      noisicaa/audioproc/audioproc_client.py
  5. 10
      noisicaa/audioproc/audioproc_process.py
  6. 2
      noisicaa/audioproc/engine/CMakeLists.txt
  7. 6
      noisicaa/audioproc/engine/__init__.py
  8. 6
      noisicaa/audioproc/engine/backend_test.py
  9. 49
      noisicaa/audioproc/engine/buffers.cpp
  10. 11
      noisicaa/audioproc/engine/buffers.h
  11. 6
      noisicaa/audioproc/engine/buffers.pxd
  12. 18
      noisicaa/audioproc/engine/buffers.pyx
  13. 13
      noisicaa/audioproc/engine/control_value.cpp
  14. 20
      noisicaa/audioproc/engine/control_value.h
  15. 11
      noisicaa/audioproc/engine/control_value.pxd
  16. 18
      noisicaa/audioproc/engine/control_value.pyx
  17. 14
      noisicaa/audioproc/engine/engine.pyx
  18. 18
      noisicaa/audioproc/engine/graph.py
  19. 5
      noisicaa/audioproc/engine/opcodes.cpp
  20. 22
      noisicaa/audioproc/engine/plugin_host.cpp
  21. 11
      noisicaa/audioproc/engine/plugin_host.h
  22. 2
      noisicaa/audioproc/engine/plugin_host.proto
  23. 5
      noisicaa/audioproc/engine/plugin_host.pxd
  24. 19
      noisicaa/audioproc/engine/plugin_host.pyx
  25. 219
      noisicaa/audioproc/engine/plugin_host_lv2.cpp
  26. 64
      noisicaa/audioproc/engine/plugin_host_lv2.h
  27. 22
      noisicaa/audioproc/engine/plugin_host_lv2_test.py
  28. 162
      noisicaa/audioproc/engine/plugin_host_process.py
  29. 97
      noisicaa/audioproc/engine/plugin_host_process_test.py
  30. 1
      noisicaa/audioproc/engine/plugin_host_test.py
  31. 6
      noisicaa/audioproc/engine/plugin_ui_host.cpp
  32. 10
      noisicaa/audioproc/engine/plugin_ui_host.h
  33. 3
      noisicaa/audioproc/engine/plugin_ui_host.pxd
  34. 5
      noisicaa/audioproc/engine/plugin_ui_host.pyx
  35. 36
      noisicaa/audioproc/engine/plugin_ui_host_lv2.cpp
  36. 12
      noisicaa/audioproc/engine/plugin_ui_host_lv2.h
  37. 10
      noisicaa/audioproc/engine/processor_csound_test.py
  38. 2
      noisicaa/audioproc/engine/processor_cvgenerator_test.py
  39. 6
      noisicaa/audioproc/engine/processor_fluidsynth_test.py
  40. 2
      noisicaa/audioproc/engine/processor_pianoroll_test.py
  41. 26
      noisicaa/audioproc/engine/processor_plugin_test.py
  42. 6
      noisicaa/audioproc/engine/processor_sample_player_test.py
  43. 4
      noisicaa/audioproc/engine/processor_sample_script_test.py
  44. 4
      noisicaa/audioproc/engine/processor_sound_file_test.py
  45. 14
      noisicaa/audioproc/engine/processor_track_mixer_test.py
  46. 2
      noisicaa/audioproc/engine/pump.h
  47. 7
      noisicaa/audioproc/engine/realm.cpp
  48. 2
      noisicaa/audioproc/engine/realm.h
  49. 2
      noisicaa/audioproc/engine/realm.pxd
  50. 19
      noisicaa/audioproc/engine/realm.pyx
  51. 66
      noisicaa/audioproc/engine/realm_test.py
  52. 12
      noisicaa/audioproc/engine/spec_test.pyx
  53. 17
      noisicaa/audioproc/mutations.py
  54. 3
      noisicaa/audioproc/public/CMakeLists.txt
  55. 3
      noisicaa/audioproc/public/__init__.py
  56. 39
      noisicaa/audioproc/public/plugin_state.proto
  57. 2
      noisicaa/logging.py
  58. 3
      noisicaa/music/model.py
  59. 54
      noisicaa/music/pipeline_graph.py
  60. 18
      noisicaa/music/player.py
  61. 7
      noisicaa/music/project.py
  62. 53
      noisicaa/music/project_process.py
  63. 100
      noisicaa/ui/pipeline_graph_view.py
  64. 8
      noisidev/unittest_engine_utils.pyx
  65. 1
      testdata/CMakeLists.txt
  66. 35
      testdata/lv2/test-state.lv2/CMakeLists.txt
  67. 27
      testdata/lv2/test-state.lv2/manifest.ttl
  68. 161
      testdata/lv2/test-state.lv2/state.c
  69. 44
      testdata/lv2/test-state.lv2/state.ttl

39
NOTES.org

@ -1,13 +1,48 @@
# -*- org-tags-column: -98 -*-
NEXT: route control value changes to project, so they're persisted and propagated to the UI
THEN: investigate how other plugin state can be persisted
* Make control value generations work with undo :BUG:
- generation must also increment, when value change is caused by undo
- track generation outside of model
- but also synchronize generation with model listeners
- some special 'value plus generation' container?
- also use generation for plugin_state
* LV2: implement http://lv2plug.in/ns/ext/presets extension :FR:
- provide list of presets as drop down
- set state and control values
* LV2: implement state reset :FR:
- set all ports to default values
- call set_state with empty proto
* LV2: implement ui:portNotification :FR:
* LV2: implement ui:portSubscribe :FR:
* Crash when opening a project with an opened plugin ui :CRASH:
File "/home/pink/noisicaa/build/noisicaa/ui/pipeline_graph_view.py", line 307, in __loadUI
self.__loading = False
File "/home/pink/noisicaa/build/noisicaa/ui/project_view.py", line 1332, in createPluginUI
return await self.project_client.create_plugin_ui(self.__player_id, node_id)
File "/home/pink/noisicaa/build/noisicaa/music/project_client.py", line 285, in create_plugin_ui
return await self._stub.call('CREATE_PLUGIN_UI', self._session_id, player_id, node_id)
File "/home/pink/noisicaa/build/noisicaa/core/ipc.py", line 455, in call
raise RemoteException(self._server_address, response[4:].decode('utf-8'))
noisicaa.core.ipc.RemoteException: From server /tmp/noisicaa-20180330-173955-12839-pc15ov6v/project.299137cfc7b84addaeffae4eadc4139a.sock:
Traceback (most recent call last):
File "/home/pink/noisicaa/build/noisicaa/core/ipc.py", line 255, in handle_command
result = await handler(*args, **kwargs)
File "/home/pink/noisicaa/build/noisicaa/music/project_process.py", line 426, in handle_create_plugin_ui
p = session.get_player(player_id)
File "/home/pink/noisicaa/build/noisicaa/music/project_process.py", line 68, in get_player
return self._players[player_id][1]
KeyError: None
* Parallelize tests :TESTING:FR:
- memory growth when running all tests is way too high
- run pylint in a subprocess again
- to make tests faster again, run tests in parallel
- for each test, dump results into files (unittest, pylint, mypy, coverage)
- what std formats are there?
- libraries to load/merge/report?
- merge results from all tests at the end
- collect logs, stdout/err for each tests into separate files
- is mypy cache safe for concurrent usage?

2
bin/pylintrc

@ -113,7 +113,7 @@ notes=FIXME,XXX,TODO
max-line-length=100
# Regexp for a line that is allowed to be longer than the limit.
ignore-long-lines=^\s*(# )?<?https?://\S+>?$
ignore-long-lines=(^\s*(# )?<?https?://\S+>?$|#\s+type:\s)
# Allow the body of an if to be on the same line as the test if there is no
# else.

2
noisicaa/audioproc/__init__.py

@ -26,10 +26,12 @@ from .mutations import (
DisconnectPorts,
SetPortProperty,
SetControlValue,
SetPluginState,
)
from .public import (
MusicalDuration,
MusicalTime,
PluginState,
ProcessorMessage,
ProcessorMessageList,
PlayerState,

33
noisicaa/audioproc/audioproc_client.py

@ -87,8 +87,9 @@ class AudioProcClientMixin(object):
async def ping(self):
await self._stub.ping()
async def create_realm(self, *, name, parent=None, enable_player=False):
await self._stub.call('CREATE_REALM', self._session_id, name, parent, enable_player)
async def create_realm(self, *, name, parent=None, enable_player=False, callback_address=None):
await self._stub.call(
'CREATE_REALM', self._session_id, name, parent, enable_player, callback_address)
async def delete_realm(self, name):
await self._stub.call('DELETE_REALM', self._session_id, name)
@ -113,13 +114,12 @@ class AudioProcClientMixin(object):
return await self.pipeline_mutation(
realm, mutations.SetPortProperty(node_id, port_name, **kwargs))
async def set_control_value(self, realm, name, value):
async def set_control_value(self, realm, name, value, generation):
return await self.pipeline_mutation(
realm, mutations.SetControlValue(name, value))
realm, mutations.SetControlValue(name, value, generation))
async def pipeline_mutation(self, realm, mutation):
return await self._stub.call(
'PIPELINE_MUTATION', self._session_id, realm, mutation)
return await self._stub.call('PIPELINE_MUTATION', self._session_id, realm, mutation)
async def create_plugin_ui(self, realm, node_id):
return await self._stub.call('CREATE_PLUGIN_UI', self._session_id, realm, node_id)
@ -128,38 +128,31 @@ class AudioProcClientMixin(object):
return await self._stub.call('DELETE_PLUGIN_UI', self._session_id, realm, node_id)
async def send_node_messages(self, realm, messages):
return await self._stub.call(
'SEND_NODE_MESSAGES', self._session_id, realm, messages)
return await self._stub.call('SEND_NODE_MESSAGES', self._session_id, realm, messages)
async def set_host_parameters(self, **parameters):
return await self._stub.call(
'SET_HOST_PARAMETERS', self._session_id, parameters)
return await self._stub.call('SET_HOST_PARAMETERS', self._session_id, parameters)
async def set_backend(self, name, **parameters):
return await self._stub.call(
'SET_BACKEND', self._session_id, name, parameters)
return await self._stub.call('SET_BACKEND', self._session_id, name, parameters)
async def set_backend_parameters(self, **parameters):
return await self._stub.call(
'SET_BACKEND_PARAMETERS', self._session_id, parameters)
return await self._stub.call('SET_BACKEND_PARAMETERS', self._session_id, parameters)
async def update_player_state(self, realm, state):
return await self._stub.call(
'UPDATE_PLAYER_STATE', self._session_id, realm, state)
return await self._stub.call('UPDATE_PLAYER_STATE', self._session_id, realm, state)
async def send_message(self, msg):
return await self._stub.call('SEND_MESSAGE', self._session_id, msg.to_bytes())
async def play_file(self, path):
return await self._stub.call(
'PLAY_FILE', self._session_id, path)
return await self._stub.call('PLAY_FILE', self._session_id, path)
async def dump(self):
return await self._stub.call('DUMP', self._session_id)
async def update_project_properties(self, realm, **kwargs):
return await self._stub.call(
'UPDATE_PROJECT_PROPERTIES', self._session_id, realm, kwargs)
return await self._stub.call('UPDATE_PROJECT_PROPERTIES', self._session_id, realm, kwargs)
def handle_pipeline_mutation(self, mutation):
logger.info("Mutation received: %s", mutation)

10
noisicaa/audioproc/audioproc_process.py

@ -202,12 +202,13 @@ class AudioProcProcess(core.SessionHandlerMixin, core.ProcessBase):
for session in self.sessions:
session.publish_player_state(realm, state)
async def __handle_create_realm(self, session_id, name, parent, enable_player):
async def __handle_create_realm(self, session_id, name, parent, enable_player, callback_address):
session = self.get_session(session_id)
await self.__engine.create_realm(
name=name,
parent=parent,
enable_player=enable_player)
enable_player=enable_player,
callback_address=callback_address)
session.owned_realms.add(name)
async def __handle_delete_realm(self, session_id, name):
@ -271,7 +272,10 @@ class AudioProcProcess(core.SessionHandlerMixin, core.ProcessBase):
port.set_prop(**mutation.kwargs)
elif isinstance(mutation, mutations.SetControlValue):
realm.set_control_value(mutation.name, mutation.value)
realm.set_control_value(mutation.name, mutation.value, mutation.generation)
elif isinstance(mutation, mutations.SetPluginState):
await realm.set_plugin_state(mutation.node, mutation.state)
else:
raise ValueError(type(mutation))

2
noisicaa/audioproc/engine/CMakeLists.txt

@ -26,6 +26,8 @@ add_python_package(
plugin_host_process.py
plugin_host_process_test.py
plugin_host_test.py
plugin_host_ladspa_test.py
plugin_host_lv2_test.py
processor_test.py
processor_csound_test.py
processor_cvgenerator_test.py

6
noisicaa/audioproc/engine/__init__.py

@ -27,9 +27,9 @@ from .graph import (
)
from .buffers import (
PyBufferType as BufferType,
PyFloat as Float,
PyFloatAudioBlock as FloatAudioBlock,
PyAtomData as AtomData,
PyFloatControlValueBuffer as FloatControlValueBuffer,
PyFloatAudioBlockBuffer as FloatAudioBlockBuffer,
PyAtomDataBuffer as AtomDataBuffer,
PyPluginCondBuffer as PluginCondBuffer,
)
from .control_value import (

6
noisicaa/audioproc/engine/backend_test.py

@ -23,7 +23,7 @@
from noisidev import unittest
from noisidev import unittest_engine_mixins
from noisidev import unittest_engine_utils
from .buffers import PyFloatAudioBlock
from . import buffers
from .realm import PyRealm
from .backend import PyBackend, PyBackendSettings
from .block_context import PyBlockContext
@ -33,14 +33,14 @@ class BackendTest(unittest_engine_mixins.HostSystemMixin, unittest.TestCase):
def test_output(self):
realm = PyRealm(
name='root', host_system=self.host_system,
engine=None, parent=None, player=None)
engine=None, parent=None, player=None, callback_address=None)
backend_settings = PyBackendSettings(time_scale=0)
backend = PyBackend(self.host_system, 'null', backend_settings)
backend.setup(realm)
bufmgr = unittest_engine_utils.BufferManager(self.host_system)
bufmgr.allocate('samples', PyFloatAudioBlock())
bufmgr.allocate('samples', buffers.PyFloatAudioBlockBuffer())
samples = bufmgr['samples']
ctxt = PyBlockContext()

49
noisicaa/audioproc/engine/buffers.cpp

@ -38,34 +38,39 @@ Status BufferType::setup(HostSystem* host_system, BufferPtr buf) const {
void BufferType::cleanup(HostSystem* host_system, BufferPtr buf) const {
}
uint32_t Float::size(HostSystem* host_system) const {
return sizeof(float);
uint32_t FloatControlValueBuffer::size(HostSystem* host_system) const {
return sizeof(ControlValue);
}
Status Float::clear_buffer(HostSystem* host_system, BufferPtr buf) const {
float* ptr = (float*)buf;
ptr[0] = 0.0;
Status FloatControlValueBuffer::clear_buffer(HostSystem* host_system, BufferPtr buf) const {
ControlValue* ptr = (ControlValue*)buf;
ptr->value = 0.0;
ptr->generation = 0;
return Status::Ok();
}
Status Float::mix_buffers(HostSystem* host_system, const BufferPtr buf1, BufferPtr buf2) const {
float* ptr1 = (float*)buf1;
float* ptr2 = (float*)buf2;
ptr2[0] += ptr1[0];
Status FloatControlValueBuffer::mix_buffers(
HostSystem* host_system, const BufferPtr buf1, BufferPtr buf2) const {
ControlValue* ptr1 = (ControlValue*)buf1;
ControlValue* ptr2 = (ControlValue*)buf2;
ptr2->value += ptr1->value;
ptr2->generation = max(ptr1->generation, ptr2->generation) + 1;
return Status::Ok();
}
Status Float::mul_buffer(HostSystem* host_system, BufferPtr buf, float factor) const {
float* ptr = (float*)buf;
ptr[0] *= factor;
Status FloatControlValueBuffer::mul_buffer(
HostSystem* host_system, BufferPtr buf, float factor) const {
ControlValue* ptr = (ControlValue*)buf;
ptr->value *= factor;
ptr->generation += 1;
return Status::Ok();
}
uint32_t FloatAudioBlock::size(HostSystem* host_system) const {
uint32_t FloatAudioBlockBuffer::size(HostSystem* host_system) const {
return host_system->block_size() * sizeof(float);
}
Status FloatAudioBlock::clear_buffer(HostSystem* host_system, BufferPtr buf) const {
Status FloatAudioBlockBuffer::clear_buffer(HostSystem* host_system, BufferPtr buf) const {
float* ptr = (float*)buf;
for (uint32_t i = 0 ; i < host_system->block_size() ; ++i) {
*ptr++ = 0.0;
@ -73,7 +78,8 @@ Status FloatAudioBlock::clear_buffer(HostSystem* host_system, BufferPtr buf) con
return Status::Ok();
}
Status FloatAudioBlock::mix_buffers(HostSystem* host_system, const BufferPtr buf1, BufferPtr buf2) const {
Status FloatAudioBlockBuffer::mix_buffers(
HostSystem* host_system, const BufferPtr buf1, BufferPtr buf2) const {
float* ptr1 = (float*)buf1;
float* ptr2 = (float*)buf2;
for (uint32_t i = 0 ; i < host_system->block_size() ; ++i) {
@ -82,7 +88,7 @@ Status FloatAudioBlock::mix_buffers(HostSystem* host_system, const BufferPtr buf
return Status::Ok();
}
Status FloatAudioBlock::mul_buffer(HostSystem* host_system, BufferPtr buf, float factor) const {
Status FloatAudioBlockBuffer::mul_buffer(HostSystem* host_system, BufferPtr buf, float factor) const {
float* ptr = (float*)buf;
for (uint32_t i = 0 ; i < host_system->block_size() ; ++i) {
*ptr++ *= factor;
@ -90,11 +96,11 @@ Status FloatAudioBlock::mul_buffer(HostSystem* host_system, BufferPtr buf, float
return Status::Ok();
}
uint32_t AtomData::size(HostSystem* host_system) const {
uint32_t AtomDataBuffer::size(HostSystem* host_system) const {
return 10240;
}
Status AtomData::clear_buffer(HostSystem* host_system, BufferPtr buf) const {
Status AtomDataBuffer::clear_buffer(HostSystem* host_system, BufferPtr buf) const {
memset(buf, 0, 10240);
LV2_Atom_Forge forge;
@ -109,7 +115,8 @@ Status AtomData::clear_buffer(HostSystem* host_system, BufferPtr buf) const {
return Status::Ok();
}
Status AtomData::mix_buffers(HostSystem* host_system, const BufferPtr buf1, BufferPtr buf2) const {
Status AtomDataBuffer::mix_buffers(
HostSystem* host_system, const BufferPtr buf1, BufferPtr buf2) const {
LV2_Atom_Sequence* seq1 = (LV2_Atom_Sequence*)buf1;
if (seq1->atom.type != host_system->lv2->urid.atom_sequence) {
return ERROR_STATUS("Excepted sequence, got %d.", seq1->atom.type);
@ -166,8 +173,8 @@ Status AtomData::mix_buffers(HostSystem* host_system, const BufferPtr buf1, Buff
return Status::Ok();
}
Status AtomData::mul_buffer(HostSystem* host_system, BufferPtr buf, float factor) const {
return ERROR_STATUS("Operation not supported for AtomData");
Status AtomDataBuffer::mul_buffer(HostSystem* host_system, BufferPtr buf, float factor) const {
return ERROR_STATUS("Operation not supported for AtomDataBuffer");
}
uint32_t PluginCondBuffer::size(HostSystem* host_system) const {

11
noisicaa/audioproc/engine/buffers.h

@ -54,8 +54,13 @@ public:
virtual Status mul_buffer(HostSystem* host_system, BufferPtr buf, float factor) const = 0;
};
class Float : public BufferType {
class FloatControlValueBuffer : public BufferType {
public:
struct ControlValue {
float value;
uint32_t generation;
};
uint32_t size(HostSystem* host_system) const override;
Status clear_buffer(HostSystem* host_system, BufferPtr buf) const override;
@ -63,7 +68,7 @@ public:
Status mul_buffer(HostSystem* host_system, BufferPtr buf, float factor) const override;
};
class FloatAudioBlock : public BufferType {
class FloatAudioBlockBuffer : public BufferType {
public:
uint32_t size(HostSystem* host_system) const override;
@ -72,7 +77,7 @@ public:
Status mul_buffer(HostSystem* host_system, BufferPtr buf, float factor) const override;
};
class AtomData : public BufferType {
class AtomDataBuffer : public BufferType {
public:
uint32_t size(HostSystem* host_system) const override;

6
noisicaa/audioproc/engine/buffers.pxd

@ -38,13 +38,13 @@ cdef extern from "noisicaa/audioproc/engine/buffers.h" namespace "noisicaa" nogi
Status mix_buffers(HostSystem* host_system, const BufferPtr buf1, BufferPtr buf2) const
Status mul_buffer(HostSystem* host_system, BufferPtr buf, float factor) const
cppclass Float(BufferType):
cppclass FloatControlValueBuffer(BufferType):
pass
cppclass FloatAudioBlock(BufferType):
cppclass FloatAudioBlockBuffer(BufferType):
pass
cppclass AtomData(BufferType):
cppclass AtomDataBuffer(BufferType):
pass
cppclass PluginCondBuffer(BufferType):

18
noisicaa/audioproc/engine/buffers.pyx

@ -33,39 +33,39 @@ cdef class PyBufferType(object):
raise NotImplementedError
cdef class PyFloat(PyBufferType):
cdef class PyFloatControlValueBuffer(PyBufferType):
def __init__(self):
self.__type_ref.reset(new Float())
self.__type_ref.reset(new FloatControlValueBuffer())
self.__type = self.__type_ref.get()
def __str__(self):
return 'Float'
return 'FloatControlValueBuffer'
@property
def view_type(self):
return 'f'
cdef class PyFloatAudioBlock(PyBufferType):
cdef class PyFloatAudioBlockBuffer(PyBufferType):
def __init__(self):
self.__type_ref.reset(new FloatAudioBlock())
self.__type_ref.reset(new FloatAudioBlockBuffer())
self.__type = self.__type_ref.get()
def __str__(self):
return 'FloatAudioBlock'
return 'FloatAudioBlockBuffer'
@property
def view_type(self):
return 'f'
cdef class PyAtomData(PyBufferType):
cdef class PyAtomDataBuffer(PyBufferType):
def __init__(self):
self.__type_ref.reset(new AtomData())
self.__type_ref.reset(new AtomDataBuffer())
self.__type = self.__type_ref.get()
def __str__(self):
return 'AtomData'
return 'AtomDataBuffer'
@property
def view_type(self):

13
noisicaa/audioproc/engine/control_value.cpp

@ -24,18 +24,19 @@
namespace noisicaa {
ControlValue::ControlValue(ControlValueType type, const string& name)
ControlValue::ControlValue(ControlValueType type, const string& name, uint32_t generation)
: _type(type),
_name(name) {}
_name(name),
_generation(generation) {}
ControlValue::~ControlValue() {}
FloatControlValue::FloatControlValue(const string& name, float value)
: ControlValue(ControlValueType::FloatCV, name),
FloatControlValue::FloatControlValue(const string& name, float value, uint32_t generation)
: ControlValue(ControlValueType::FloatCV, name, generation),
_value(value) {}
IntControlValue::IntControlValue(const string& name, int64_t value)
: ControlValue(ControlValueType::IntCV, name),
IntControlValue::IntControlValue(const string& name, int64_t value, uint32_t generation)
: ControlValue(ControlValueType::IntCV, name, generation),
_value(value) {}
} // namespace noisicaa

20
noisicaa/audioproc/engine/control_value.h

@ -43,21 +43,28 @@ public:
ControlValueType type() const { return _type; }
const string& name() const { return _name; }
uint32_t generation() const { return _generation; }
protected:
ControlValue(ControlValueType type, const string& name);
ControlValue(ControlValueType type, const string& name, uint32_t generation);
void set_generation(uint32_t generation) { _generation = generation; }
private:
ControlValueType _type;
string _name;
uint32_t _generation;
};
class FloatControlValue : public ControlValue {
public:
FloatControlValue(const string& name, float value);
FloatControlValue(const string& name, float value, uint32_t generation);
float value() const { return _value; }
void set_value(float value) { _value = value; }
void set_value(float value, uint32_t generation) {
_value = value;
set_generation(generation);
}
private:
float _value;
@ -65,10 +72,13 @@ private:
class IntControlValue : public ControlValue {
public:
IntControlValue(const string& name, int64_t value);
IntControlValue(const string& name, int64_t value, uint32_t generation);
int64_t value() const { return _value; }
void set_value(int64_t value) { _value = value; }
void set_value(int64_t value, uint32_t generation) {
_value = value;
set_generation(generation);
}
private:
int64_t _value;

11
noisicaa/audioproc/engine/control_value.pxd

@ -18,7 +18,7 @@
#
# @end:license
from libc.stdint cimport int64_t
from libc.stdint cimport uint32_t, int64_t
from libcpp.memory cimport unique_ptr
from libcpp.string cimport string
@ -30,18 +30,19 @@ cdef extern from "noisicaa/audioproc/engine/control_value.h" namespace "noisicaa
cppclass ControlValue:
ControlValueType type() const
const string& name() const
uint32_t generation() const
cppclass FloatControlValue(ControlValue):
FloatControlValue(const string& name, float value)
FloatControlValue(const string& name, float value, uint32_t generation)
float value() const
void set_value(float value)
void set_value(float value, uint32_t generation)
cppclass IntControlValue(ControlValue):
IntControlValue(const string& name, int64_t value)
IntControlValue(const string& name, int64_t value, uint32_t generation)
int64_t value() const
void set_value(int64_t value)
void set_value(int64_t value, uint32_t generation)
cdef class PyControlValue(object):

18
noisicaa/audioproc/engine/control_value.pyx

@ -38,8 +38,8 @@ cdef class PyControlValue(object):
cdef class PyFloatControlValue(PyControlValue):
def __init__(self, name, value):
self.__cv_ptr.reset(new FloatControlValue(name.encode('utf-8'), value))
def __init__(self, name, value, generation):
self.__cv_ptr.reset(new FloatControlValue(name.encode('utf-8'), value, generation))
self.__cv = self.__cv_ptr.get()
@property
@ -47,15 +47,14 @@ cdef class PyFloatControlValue(PyControlValue):
cdef FloatControlValue* cv = <FloatControlValue*>self.__cv;
return float(cv.value())
@value.setter
def value(self, v):
def set_value(self, v, generation):
cdef FloatControlValue* cv = <FloatControlValue*>self.__cv;
cv.set_value(<float>v)
cv.set_value(<float>v, generation)
cdef class PyIntControlValue(PyControlValue):
def __init__(self, name, value):
self.__cv_ptr.reset(new IntControlValue(name.encode('utf-8'), value))
def __init__(self, name, value, generation):
self.__cv_ptr.reset(new IntControlValue(name.encode('utf-8'), value, generation))
self.__cv = self.__cv_ptr.get()
@property
@ -63,8 +62,7 @@ cdef class PyIntControlValue(PyControlValue):
cdef IntControlValue* cv = <IntControlValue*>self.__cv;
return int(cv.value())
@value.setter
def value(self, v):
def set_value(self, v, generation):
cdef IntControlValue* cv = <IntControlValue*>self.__cv;
cv.set_value(<int64_t>v)
cv.set_value(<int64_t>v, generation)

14
noisicaa/audioproc/engine/engine.pyx

@ -163,8 +163,7 @@ class Engine(object):
async def get_plugin_host(self):
if self.__plugin_host is None:
address = await self.__manager.call(
'CREATE_PLUGIN_HOST_PROCESS', audioproc_address=self.__server_address)
address = await self.__manager.call('CREATE_PLUGIN_HOST_PROCESS')
self.__plugin_host = ipc.Stub(self.__event_loop, address)
await self.__plugin_host.connect()
@ -182,7 +181,10 @@ class Engine(object):
except KeyError as exc:
raise RealmNotFound("Realm '%s' does not exist" % name) from exc
async def create_realm(self, *, name: str, parent: str, enable_player: bool = False):
async def create_realm(
self, *,
name: str, parent: str, enable_player: bool = False, callback_address: str = None
) -> realm_lib.PyRealm:
if name in self.__realms:
raise DuplicateRealmName("Realm '%s' already exists" % name)
@ -211,7 +213,8 @@ class Engine(object):
name=name,
parent=parent_realm,
host_system=self.__host_system,
player=player)
player=player,
callback_address=callback_address)
self.__realms[name] = realm
self.__realm_listeners['%s:node_state_changed' % name] = realm.listeners.add(
'node_state_changed',
@ -418,7 +421,8 @@ class Engine(object):
self.__root_realm.process_block(program)
for channel in ('left', 'right'):
sink_buf = self.__root_realm.get_buffer('sink:in:' + channel, buffers.PyFloatAudioBlock())
sink_buf = self.__root_realm.get_buffer(
'sink:in:' + channel, buffers.PyFloatAudioBlockBuffer())
backend.output(ctxt, channel, sink_buf)
finally:

18
noisicaa/audioproc/engine/graph.py

@ -123,22 +123,22 @@ class OutputPortMixin(Port):
class AudioPortMixin(Port):
def get_buf_type(self):
return buffers.PyFloatAudioBlock()
return buffers.PyFloatAudioBlockBuffer()
class ARateControlPortMixin(Port):
def get_buf_type(self):
return buffers.PyFloatAudioBlock()
return buffers.PyFloatAudioBlockBuffer()
class KRateControlPortMixin(Port):
def get_buf_type(self):
return buffers.PyFloat()
return buffers.PyFloatControlValueBuffer()
class EventPortMixin(Port):
def get_buf_type(self):
return buffers.PyAtomData()
return buffers.PyAtomDataBuffer()
class AudioInputPort(AudioPortMixin, InputPortMixin, Port):
@ -219,13 +219,14 @@ class Node(object):
def __init__(
self, *,
host_system, description, id, # pylint: disable=redefined-builtin
name=None):
name=None, initial_state=None):
assert isinstance(description, node_db.NodeDescription), description
self._host_system = host_system
self.description = description
self.name = name or type(self).__name__
self.id = id
self.initial_state = initial_state
self.__realm = None
self.broken = False
@ -330,7 +331,7 @@ class Node(object):
if isinstance(port, KRateControlInputPort):
logger.info("Float control value '%s'", port.buf_name)
cv = control_value.PyFloatControlValue(
port.buf_name, port.description.float_value.default)
port.buf_name, port.description.float_value.default, 1)
self.__control_values[port.buf_name] = cv
self.realm.add_active_control_value(cv)
@ -421,7 +422,10 @@ class PluginNode(ProcessorNode):
spec.realm = self.realm.name
spec.node_id = self.id
spec.node_description.CopyFrom(self.description)
self.__plugin_pipe_path = await self.__plugin_host.call('CREATE_PLUGIN', spec)
if self.initial_state is not None:
spec.initial_state.CopyFrom(self.initial_state)
self.__plugin_pipe_path = await self.__plugin_host.call(
'CREATE_PLUGIN', spec, self.realm.callback_address)
self.processor.set_parameters(
processor_pb2.ProcessorParameters(

5
noisicaa/audioproc/engine/opcodes.cpp

@ -155,8 +155,9 @@ Status run_FETCH_CONTROL_VALUE(BlockContext* ctxt, ProgramState* state, const ve
switch (cv->type()) {
case ControlValueType::FloatCV: {
FloatControlValue* fcv = (FloatControlValue*)cv;
float* data = (float*)buf->data();
*data = fcv->value();
FloatControlValueBuffer::ControlValue* data = (FloatControlValueBuffer::ControlValue*)buf->data();
data->value = fcv->value();
data->generation = fcv->generation();
return Status::Ok();
}
case ControlValueType::IntCV:

22
noisicaa/audioproc/engine/plugin_host.cpp

@ -31,6 +31,7 @@
#include "noisicaa/core/logging.h"
#include "noisicaa/node_db/node_description.pb.h"
#include "noisicaa/host_system/host_system.h"
#include "noisicaa/audioproc/public/plugin_state.pb.h"
#include "noisicaa/audioproc/engine/plugin_host.h"
#include "noisicaa/audioproc/engine/plugin_host_lv2.h"
#include "noisicaa/audioproc/engine/plugin_host_ladspa.h"
@ -68,7 +69,7 @@ StatusOr<PluginHost*> PluginHost::create(const string& spec_serialized, HostSyst
}
StatusOr<PluginUIHost*> PluginHost::create_ui(
void* handle, void (*control_value_change_cb)(void*, uint32_t, float)) {
void* handle, void (*control_value_change_cb)(void*, uint32_t, float, uint32_t)) {
return ERROR_STATUS("Plugin does not support UIs.");
}
@ -95,6 +96,25 @@ void PluginHost::cleanup() {
_logger->info("Plugin host %s cleaned up.", _spec.node_id().c_str());
}
bool PluginHost::has_state() const {
return false;
}
StatusOr<string> PluginHost::get_state() {
return ERROR_STATUS("Not supported by this plugin.");
}
Status PluginHost::set_state(const string& serialized_string) {
pb::PluginState state;
assert(state.ParseFromString(serialized_string));
return set_state(state);
}
Status PluginHost::set_state(const pb::PluginState& state) {
return ERROR_STATUS("Not supported by this plugin.");
}
Status PluginHost::main_loop(int pipe_fd) {
_logger->info("Entering main loop...");

11
noisicaa/audioproc/engine/plugin_host.h

@ -29,6 +29,7 @@
#include <pthread.h>
#include <sys/mman.h>
#include <atomic>
#include <string>
#include "noisicaa/core/status.h"
#include "noisicaa/audioproc/engine/buffers.h"
#include "noisicaa/audioproc/engine/plugin_host.pb.h"
@ -40,6 +41,9 @@ using namespace std;
class Logger;
class HostSystem;
class PluginUIHost;
namespace pb {
class PluginState;
}
struct PluginMemoryMapping {
char shmem_path[PATH_MAX];
@ -71,7 +75,7 @@ public:
virtual StatusOr<PluginUIHost*> create_ui(
void* handle,
void (*control_value_change_cb)(void*, uint32_t, float));
void (*control_value_change_cb)(void*, uint32_t, float, uint32_t));
virtual Status setup();
virtual void cleanup();
@ -82,6 +86,11 @@ public:
virtual Status connect_port(uint32_t port_idx, BufferPtr buf) = 0;
virtual Status process_block(uint32_t block_size) = 0;
virtual bool has_state() const;
virtual StatusOr<string> get_state();
Status set_state(const string& serialized_state);
virtual Status set_state(const pb::PluginState& state);
protected:
PluginHost(const pb::PluginInstanceSpec& spec, HostSystem* host_system, const char* logger_name);

2
noisicaa/audioproc/engine/plugin_host.proto

@ -25,9 +25,11 @@ syntax = "proto2";
package noisicaa.pb;
import "noisicaa/node_db/node_description.proto";
import "noisicaa/audioproc/public/plugin_state.proto";
message PluginInstanceSpec {
required string realm = 1;
required string node_id = 2;
required NodeDescription node_description = 3;
optional PluginState initial_state = 4;
}

5
noisicaa/audioproc/engine/plugin_host.pxd

@ -61,7 +61,7 @@ cdef extern from "noisicaa/audioproc/engine/plugin_host.h" namespace "noisicaa"
const string& node_id() const
StatusOr[PluginUIHost*] create_ui(
void* handle,
void (*control_value_change_cb)(void*, uint32_t, float))
void (*control_value_change_cb)(void*, uint32_t, float, uint32_t))
Status setup()
void cleanup()
@ -73,6 +73,9 @@ cdef extern from "noisicaa/audioproc/engine/plugin_host.h" namespace "noisicaa"
Status connect_port(uint32_t port_idx, BufferPtr buf)
Status process_block(uint32_t block_size)
bool has_state()
StatusOr[string] get_state()
Status set_state(const string& serialized_state)
cdef class PyPluginHost(object):

19
noisicaa/audioproc/engine/plugin_host.pyx

@ -26,6 +26,7 @@ from cpython.ref cimport PyObject
from noisicaa.core.status cimport check
from noisicaa.host_system.host_system cimport PyHostSystem
from noisicaa.audioproc.public import plugin_state_pb2
from .plugin_ui_host cimport PluginUIHost, PyPluginUIHost
logger = logging.getLogger(__name__)
@ -190,3 +191,21 @@ cdef class PyPluginHost(object):
cdef uint32_t c_block_size = block_size
with nogil:
check(self.__plugin_host.process_block(c_block_size))
def has_state(self):
return self.__plugin_host.has_state()
def get_state(self):
cdef StatusOr[string] stor_serialized_state
with nogil:
stor_serialized_state = self.__plugin_host.get_state()
check(stor_serialized_state)
state = plugin_state_pb2.PluginState()
state.MergeFromString(stor_serialized_state.result())
return state
def set_state(self, state):
cdef string serialized_state = state.SerializeToString()
with nogil:
check(self.__plugin_host.set_state(serialized_state))

219
noisicaa/audioproc/engine/plugin_host_lv2.cpp

@ -22,22 +22,27 @@
#include "noisicaa/core/logging.h"
#include "noisicaa/core/perf_stats.h"
#include "noisicaa/core/slots.inl.h"
#include "noisicaa/lv2/feature_manager.h"
#include "noisicaa/host_system/host_system.h"
#include "noisicaa/audioproc/public/plugin_state.pb.h"
#include "noisicaa/audioproc/engine/pump.inl.h"
#include "noisicaa/audioproc/engine/plugin_ui_host_lv2.h"
#include "noisicaa/audioproc/engine/plugin_host_lv2.h"
namespace noisicaa {
PluginHostLV2::PluginHostLV2(const pb::PluginInstanceSpec& spec, HostSystem* host_system)
: PluginHost(spec, host_system, "noisicaa.audioproc.plugins.lv2") {
: PluginHost(spec, host_system, "noisicaa.audioproc.plugins.lv2"),
_control_value_pump(
_logger, bind(&PluginHostLV2::control_value_change, this, placeholders::_1)) {
}
PluginHostLV2::~PluginHostLV2() {}
StatusOr<PluginUIHost*> PluginHostLV2::create_ui(
void* handle,
void (*control_value_change_cb)(void*, uint32_t, float)) {
void (*control_value_change_cb)(void*, uint32_t, float, uint32_t)) {
return new PluginUIHostLV2(this, _host_system, handle, control_value_change_cb);
}
@ -72,8 +77,35 @@ Status PluginHostLV2::setup() {
return ERROR_STATUS("Failed to instantiate '%s'.", lv2_desc.uri().c_str());
}
_state_interface = (LV2_State_Interface*)lilv_instance_get_extension_data(
_instance, LV2_STATE__interface);
if (_state_interface != nullptr) {
_logger->info("Plugin supports interface %s", LV2_STATE__interface);
} else {
_logger->info("Plugin does not support interface %s", LV2_STATE__interface);
}
lilv_instance_activate(_instance);
if (_state_interface != nullptr && _spec.has_initial_state()) {
RETURN_IF_ERROR(set_state(_spec.initial_state()));
}
_portmap.resize(_spec.node_description().ports_size());
for (int idx = 0 ; idx < _spec.node_description().ports_size() ; ++idx ) {
const auto& port = _spec.node_description().ports(idx);
if (port.direction() == pb::PortDescription::INPUT
and port.type() == pb::PortDescription::KRATE_CONTROL) {
_rt_control_values[idx] = ControlValue{0.0, 0};
_control_values[idx] = ControlValue{0.0, 1};
}
_portmap[idx] = nullptr;
}
RETURN_IF_ERROR(_control_value_pump.setup());
return Status::Ok();
}
@ -90,18 +122,201 @@ void PluginHostLV2::cleanup() {
_feature_manager.reset();
_portmap.clear();
_control_value_pump.cleanup();
_rt_control_values.clear();
_control_values.clear();
PluginHost::cleanup();
}
Slot<int, float, uint32_t>::Listener PluginHostLV2::subscribe_to_control_value_changes(
function<void(int, float, uint32_t)> callback) {
lock_guard<mutex> guard(_control_values_mutex);
for (const auto& it : _control_values) {
callback(it.first, it.second.value, it.second.generation);
}
return _control_value_changed.connect(callback);
}
void PluginHostLV2::unsubscribe_from_control_value_changes(
Slot<int, float, uint32_t>::Listener listener) {
_control_value_changed.disconnect(listener);
}
void PluginHostLV2::control_value_change(const ControlValueChange& change) {
{
lock_guard<mutex> guard(_control_values_mutex);
const auto& it = _control_values.find(change.port_idx);
assert(it != _control_values.end());
it->second.value = change.value;
it->second.generation = change.generation;
}
_logger->info(
"control_value_change(%d, %f, %d)", change.port_idx, change.value, change.generation);
_control_value_changed.emit(change.port_idx, change.value, change.generation);
}
Status PluginHostLV2::connect_port(uint32_t port_idx, BufferPtr buf) {
_portmap[port_idx] = buf;
lilv_instance_connect_port(_instance, port_idx, buf);
return Status::Ok();
}
Status PluginHostLV2::process_block(uint32_t block_size) {
//PerfTracker tracker(ctxt->perf.get(), "lv2");
for (auto& it : _rt_control_values) {
ControlValue* new_value = ((ControlValue*)_portmap[it.first]);
ControlValue* old_value = &it.second;
if (new_value->generation > old_value->generation) {
ControlValueChange change = { it.first, new_value->value, new_value->generation };
_control_value_pump.push(change);
old_value->value = new_value->value;
old_value->generation = new_value->generation;
}
}
lilv_instance_run(_instance, block_size);
return Status::Ok();
}
LV2_State_Status PluginHostLV2::store_property(
LV2_State_Handle handle,
uint32_t key,
const void* value,
size_t size,
uint32_t type,
uint32_t flags) {
StoreContext* ctxt = (StoreContext*)handle;
const char* key_uri = ctxt->host_system->lv2->unmap(key);
if (key_uri == nullptr) {
ctxt->logger->warning("Failed to unmap key URID %u", key);
return LV2_STATE_ERR_UNKNOWN;
}
const char* type_uri = ctxt->host_system->lv2->unmap(type);
if (type_uri == nullptr) {
ctxt->logger->warning("Failed to unmap type URID %u", type);
return LV2_STATE_ERR_UNKNOWN;
}
if (!(flags & LV2_STATE_IS_PORTABLE)) {
ctxt->logger->warning("Property %s is not portable", key_uri);
return LV2_STATE_ERR_BAD_FLAGS;
}
if (!(flags & LV2_STATE_IS_POD)) {
ctxt->logger->warning("Property %s is not a POD", key_uri);
return LV2_STATE_ERR_BAD_FLAGS;
}
pb::PluginStateLV2* lv2_state = ctxt->state->mutable_lv2();
pb::PluginStateLV2Property* prop = lv2_state->add_properties();
prop->set_key(key_uri);
prop->set_type(type_uri);
prop->set_value((const char*)value, size);
return LV2_STATE_SUCCESS;
}
bool PluginHostLV2::has_state() const {
return _state_interface != nullptr;
}
StatusOr<string> PluginHostLV2::get_state() {
if (_state_interface == nullptr) {
return ERROR_STATUS("Plugin does not support the state interface.");
}
pb::PluginState state;
StoreContext ctxt;
ctxt.state = &state;
ctxt.host_system = _host_system;
ctxt.logger = _logger;
LV2_State_Status status = _state_interface->save(
_instance->lv2_handle,
&PluginHostLV2::store_property,
&ctxt,
LV2_STATE_IS_PORTABLE | LV2_STATE_IS_POD,
nullptr);
if (status != LV2_STATE_SUCCESS) {
return ERROR_STATUS("Failed to save state, error code %d", status);
}
string serialized_state;
assert(state.SerializeToString(&serialized_state));
return serialized_state;
}
const void* PluginHostLV2::retrieve_property(
LV2_State_Handle handle,
uint32_t key,
size_t* size,
uint32_t* type,
uint32_t* flags) {
StoreContext* ctxt = (StoreContext*)handle;
if (!ctxt->state->has_lv2()) {
return nullptr;
}
const char* key_uri = ctxt->host_system->lv2->unmap(key);
if (key_uri == nullptr) {
ctxt->logger->warning("Failed to unmap key URID %u", key);
return nullptr;
}
for (const auto& property : ctxt->state->lv2().properties()) {
if (strcmp(property.key().c_str(), key_uri) == 0) {
if (size != nullptr) {
*size = property.value().size();
}
if (type != nullptr) {
LV2_URID type_urid = ctxt->host_system->lv2->map(property.type().c_str());
if (type_urid > 0) {
*type = type_urid;
} else {
ctxt->logger->warning("Failed to map type URI %s", property.type().c_str());
}
}
return property.value().c_str();
}
}
return nullptr;
}
Status PluginHostLV2::set_state(const pb::PluginState& state) {
if (_state_interface == nullptr) {
return ERROR_STATUS("Plugin does not support the state interface.");
}
RetrieveContext ctxt;
ctxt.state = &state;
ctxt.host_system = _host_system;
ctxt.logger = _logger;
LV2_State_Status status = _state_interface->restore(
_instance->lv2_handle,
&PluginHostLV2::retrieve_property,
&ctxt,
9,
nullptr);
if (status != LV2_STATE_SUCCESS) {
return ERROR_STATUS("Failed to restre state, error code %d", status);
}
return Status::Ok();
}
}

64
noisicaa/audioproc/engine/plugin_host_lv2.h

@ -25,16 +25,25 @@
#ifndef _NOISICAA_AUDIOPROC_ENGINE_PLUGIN_HOST_LV2_H
#define _NOISICAA_AUDIOPROC_ENGINE_PLUGIN_HOST_LV2_H
#include <functional>
#include <memory>
#include <mutex>
#include <vector>
#include "lilv/lilv.h"
#include "lv2/lv2plug.in/ns/ext/state/state.h"
#include "noisicaa/core/slots.h"
#include "noisicaa/core/status.h"
#include "noisicaa/audioproc/engine/plugin_host.h"
#include "noisicaa/audioproc/engine/pump.h"
namespace noisicaa {
using namespace std;