Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pluginsmanager/model/effect.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ def index(self):
Returns the first occurrence of the effect in your pedalboard
"""
if self.pedalboard is None:
raise IndexError('Effect not contains a pedalboard')
raise IndexError('Effect is not associated with a pedalboard')

return self.pedalboard.effects.index(self)

Expand Down
101 changes: 101 additions & 0 deletions pluginsmanager/observer/carla/callback_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
class CallbackManager:

def __init__(self, backend):
self.backend = backend
self.elements = Elements()
self.connections = {}

def callback(self, *args):
none, opcode, identifier, value1, value2, value3, value_str = args

if opcode == self.backend.ENGINE_CALLBACK_PATCHBAY_CLIENT_ADDED:
element = CarlaClient(self.backend, identifier, value_str)
self.elements[element.identifier] = element

elif opcode == self.backend.ENGINE_CALLBACK_PATCHBAY_PORT_ADDED:
element = self.elements[identifier]
element.add_port(CarlaPort(self.backend, identifier=value1, name=value_str, type=value2))

elif opcode == self.backend.ENGINE_CALLBACK_PLUGIN_ADDED \
or opcode == self.backend.ENGINE_CALLBACK_PARAMETER_VALUE_CHANGED:
# Audio plugin events not relevant now
pass

elif opcode == self.backend.ENGINE_CALLBACK_PATCHBAY_CONNECTION_ADDED:
connection = CarlaConnection(self.backend, identifier=identifier, name=value_str)
self.connections[connection.name] = connection

elif opcode == self.backend.ENGINE_CALLBACK_PATCHBAY_CONNECTION_REMOVED:
# The remotion is a CarlaHost responsability
#del self.connections[connection.name]
pass

elif opcode == self.backend.ENGINE_CALLBACK_ENGINE_STARTED \
or opcode == self.backend.ENGINE_CALLBACK_ENGINE_STOPPED:
# Relevant, but not about it implemented
pass

elif opcode == self.backend.ENGINE_CALLBACK_PATCHBAY_CLIENT_REMOVED \
or opcode == self.backend.ENGINE_CALLBACK_PATCHBAY_PORT_REMOVED:
# Occur when the engine is stoppend or a effect is removed
pass

else:
print('Args:', args)


class Elements:
def __init__(self):
self.elements_by_id = {}
self.elements_by_name = {}

def __getitem__(self, item):
return self.elements_by_id[item]

def __setitem__(self, key, value):
self.elements_by_id[key] = value
self.elements_by_name[value.name] = value

def by_name(self, name):
return self.elements_by_name[name]


class CarlaCallbackObject:

def __init__(self, backend, identifier, name):
self.backend = backend
self.identifier = identifier
self.name = name.decode('utf-8')


class CarlaClient(CarlaCallbackObject):
def __init__(self, backend, identifier, name):
super().__init__(backend, identifier, name)

self.inputs = {}
self.outputs = {}
self.midi_inputs = {}
self.midi_outputs = {}

def add_port(self, port: 'CarlaPort'):
# https://github.com/moddevices/mod-ui/blob/master/mod/host_carla.py#L401
if port.type & self.backend.PATCHBAY_PORT_TYPE_AUDIO:
if port.type & self.backend.PATCHBAY_PORT_IS_INPUT:
self.inputs[port.name] = port
else:
self.outputs[port.name] = port

elif port.type & self.backend.PATCHBAY_PORT_TYPE_MIDI:
if port.type & self.backend.PATCHBAY_PORT_IS_INPUT:
self.midi_inputs[port.name] = port
else:
self.midi_outputs[port.name] = port


class CarlaPort(CarlaCallbackObject):
def __init__(self, backend, identifier, name, type):
super().__init__(backend, identifier, name)
self.type = type

class CarlaConnection(CarlaCallbackObject):
pass
67 changes: 25 additions & 42 deletions pluginsmanager/observer/carla/carla.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,11 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from pathlib import Path

from pluginsmanager.observer.carla.carla_host import CarlaHost, CarlaError
from pluginsmanager.observer.host_observer.host_observer import HostObserver

#FIXME
#from carla_backend import CarlaHostDLL, ENGINE_OPTION_PATH_BINARIES, BINARY_NATIVE, PLUGIN_LV2


class CarlaError(Exception):
pass


class Carla(HostObserver):
"""
Expand Down Expand Up @@ -75,53 +70,41 @@ class Carla(HostObserver):
.. _Demystifying JACK – A Beginners Guide to Getting Started with JACK: http://libremusicproduction.com/articles/demystifying-jack-%E2%80%93-beginners-guide-getting-started-jack
"""

def __init__(self, path):
def __init__(self, path: Path):
super(Carla, self).__init__()

self.index = 0
self.host = CarlaHost(path, 'Pedal Pi')
self.index = 0 # Needs to start with zero to works correctly (carlaHostDLL.carla_get_current_plugin_count())

def connect(self):
raise CarlaError("Please, call start instead connect")

self.host = CarlaHostDLL(path / "libcarla_standalone2.so", False)
self.host.set_engine_option(ENGINE_OPTION_PATH_BINARIES, 0, path)
def start(self):
self.host.start()

def close(self):
self.host.stop()

def _connect(self, connection):
# TODO
# https://github.com/moddevices/mod-ui/blob/master/mod/host_carla.py#L185-L198
split_from = port_from.split("/")
if len(split_from) != 3:
return
if split_from[1] == "system":
groupIdA = self._client_id_system
portIdA = int(split_from[2].rsplit("_",1)[-1])
instance_from, port_from = port_from.rsplit("/", 1)
else:
groupIdB = self._getPluginId(split_from[:1].join("/"))
portIdB = int(split_from[2].rsplit("_",1)[-1])
instance_from, port_from = port_from.rsplit("/", 1)

self.host.patchbay_connect(groupIdA, portIdA, groupIdB, portIdB)
self.host.connect(connection.output, connection.input)

def _disconnect(self, connection):
pass
self.host.disconnect(connection.output, connection.input)

def _add_effect(self, effect):
if not self.host.add_plugin(
BINARY_NATIVE,
PLUGIN_LV2,
None,#"/usr/lib/lv2/gx_echo.lv2/gx_echo.so", # Fixme
"effect_{}".format(effect.index),
effect.plugin.json['uri'],
0,
None,
0):
CarlaError("Failed to load plugin, possible reasons:\n%s" % self.host.get_last_error())
plugin_bin = f"/usr/lib/lv2/{effect.plugin.data['bundle_name']}/{effect.plugin.data['binary']}"
plugin_uri = effect.plugin.data['uri']
effect_name = f"effect_{self.index}"

self.index += 1

self.host.add_effect(plugin_bin, plugin_uri, effect_name, effect)

def _remove_effect(self, effect):
self.host.add_plugin(effect.index)
self.host.remove_effect(effect)

def _set_effect_status(self, effect):
self.host.set_active(effect.index, effect.active)
self.host.set_active(effect, effect.active)

def _set_param_value(self, param):
effect_index = param.effect.index
param_index = param.data['index']
self.host.set_parameter_value(effect_index, param_index, param.value)
self.host.set_parameter_value(param)
117 changes: 117 additions & 0 deletions pluginsmanager/observer/carla/carla_host.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import sys
from pathlib import Path

from pluginsmanager.model.effect import Effect
from pluginsmanager.model.param import Param
from pluginsmanager.observer.carla.callback_manager import CallbackManager


class CarlaError(Exception):
pass


class CarlaHost(object):

def __init__(self, path: Path, client_name):
self.backend = self._lazy_import(path)
self.callback_manager = CallbackManager(self.backend)

self.host_dll = self._init_host(path)

self._path = path
self.client_name = client_name

self.pedalboard_metadata = {}

def _lazy_import(self, path_carla: Path):
path_carla_python = path_carla / 'source/frontend/'
sys.path.append(path_carla_python.as_posix())

import carla_backend as backend
return backend

def _init_host(self, path_carla: Path):
path_carla_bin = path_carla / "bin"

host = self.backend.CarlaHostDLL(path_carla_bin / "libcarla_standalone2.so", loadGlobal=False)
host.set_engine_callback(lambda *args: self.callback_manager.callback(*args))
host.set_engine_option(self.backend.ENGINE_OPTION_PATH_BINARIES, 0, path_carla_bin.as_posix())

return host

def start(self):
if not self.host_dll.engine_init("JACK", "Pedal Pi"):
raise CarlaError(f"Engine failed to initialize, possible reasons:\n{self.host_dll.get_last_error()}")

def stop(self):
self.host_dll.engine_close()

def add_effect(self, plugin_bin, plugin_uri, effect_name, effect):
self.pedalboard_metadata[effect] = effect_name

if not self.host_dll.add_plugin(
self.backend.BINARY_NATIVE, self.backend.PLUGIN_LV2,
plugin_bin, effect_name,
plugin_uri,
0, None, 0x0
):
raise CarlaError(f"Failed to load plugin, possible reasons:\n{self.host_dll.get_last_error()}")

def remove_effect(self, effect: Effect):
effect_id = self._effect_id(effect)

if not self.host_dll.remove_plugin(effect_id):
raise CarlaError(f"Failed to remove effect, possible reasons:\n{self.host_dll.get_last_error()}")

def connect(self, output_port, input_port):
output_effect_name = self.pedalboard_metadata[output_port.effect]
output_effect_carla = self.callback_manager.elements.by_name(output_effect_name)

input_effect_name = self.pedalboard_metadata[input_port.effect]
input_effect_carla = self.callback_manager.elements.by_name(input_effect_name)

output_port_carla = output_effect_carla.outputs[str(output_port)]
input_port_carla = input_effect_carla.inputs[str(input_port)]

if not self.host_dll.patchbay_connect(
output_effect_carla.identifier, output_port_carla.identifier,
input_effect_carla.identifier, input_port_carla.identifier
):
raise CarlaError(f"Failed to connect effects, possible reasons:\n{self.host_dll.get_last_error()}")

def disconnect(self, output_port, input_port):
output_effect_name = self.pedalboard_metadata[output_port.effect]
output_effect_carla = self.callback_manager.elements.by_name(output_effect_name)

input_effect_name = self.pedalboard_metadata[input_port.effect]
input_effect_carla = self.callback_manager.elements.by_name(input_effect_name)

output_port_carla = output_effect_carla.outputs[str(output_port)]
input_port_carla = input_effect_carla.inputs[str(input_port)]

name = f"{output_effect_carla.identifier}:{output_port_carla.identifier}:{input_effect_carla.identifier}:{input_port_carla.identifier}"

identifier = self.callback_manager.connections[name].identifier

if not self.host_dll.patchbay_disconnect(identifier):
raise CarlaError(f"Failed to disconnect effects ports, possible reasons:\n{self.host_dll.get_last_error()}")

del self.callback_manager.connections[name]

def set_active(self, effect: Effect, active: bool):
effect_id = self._effect_id(effect)

# Returns None
self.host_dll.set_active(effect_id, active)

def _effect_id(self, effect: Effect):
effect_name = self.pedalboard_metadata[effect]
effect_id = effect_name.split('_')[-1]
return int(effect_id)

def set_parameter_value(self, param: Param):
effect_id = self._effect_id(param.effect)
param_id = param.index

# Returns None
self.host_dll.set_parameter_value(effect_id, param_id, param.value)
2 changes: 1 addition & 1 deletion pluginsmanager/observer/mod_host/mod_host.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ def __del__(self):
If the mod-host process has been created with :meth:`~pluginsmanager.observer.mod_host.ModHost.start()`
method, it will be finished.
"""
self.close()
super().close()

def close(self):
"""
Expand Down