Skip to content

Commit

Permalink
Make mfp-0.0.7-rc2 (#301)
Browse files Browse the repository at this point in the history
* Fixes for LV2 loading

* Fixes for lv2 plugin editing, backend shutdown

* Fix lv2 plumbing
  • Loading branch information
bgribble committed Feb 4, 2024
1 parent 58612f0 commit 745733c
Show file tree
Hide file tree
Showing 22 changed files with 176 additions and 198 deletions.
5 changes: 2 additions & 3 deletions doc/README.lv2
@@ -1,8 +1,7 @@
Beginning with version 0.05, an MFP patch can be saved as an LV2
plugin. Note that MFP can still not *host* LV2 plugins, only
LADSPA at this time (via the [plugin~ object)Note that as of this
release the scope is not included, so there may be a problem with
. But it can act as a plugin in other hosts.
LADSPA at this time (via the [plugin~ object). But it can act as
a plugin in other hosts.

The support is pretty rudimentary, and is mainly intended to
allow some exploration of "Max for Live" type interactions with
Expand Down
8 changes: 4 additions & 4 deletions mfp/gui/base_element.py
Expand Up @@ -255,16 +255,16 @@ async def move(self, x, y, update_state=True, **kwargs):
self.position_y = y

@mutates('obj_state')
async def delete(self):
async def delete(self, delete_obj=True):
# FIXME this is because self.app_window is the backend, not the app window
MFPGUI().appwin.unregister(self)
if self.obj_id is not None and not self.is_export:
if delete_obj and self.obj_id is not None and not self.is_export:
await MFPGUI().mfp.delete(self.obj_id)

for conn in [c for c in self.connections_out]:
await conn.delete()
await conn.delete(delete_obj=delete_obj)
for conn in [c for c in self.connections_in]:
await conn.delete()
await conn.delete(delete_obj=delete_obj)

self.obj_id = None
self.obj_state = self.OBJ_DELETED
Expand Down
4 changes: 2 additions & 2 deletions mfp/gui/clutter/base_element.py
Expand Up @@ -25,8 +25,8 @@ def __init__(self, window, x, y):

super().__init__(window, x, y)

async def delete(self):
await super().delete()
async def delete(self, **kwargs):
await super().delete(**kwargs)

if self.badge:
if self.badge in self.app_window.event_sources:
Expand Down
2 changes: 1 addition & 1 deletion mfp/gui/clutter/button_element.py
Expand Up @@ -159,4 +159,4 @@ def draw_cb(self, texture, ct, width, height):
if self.indicator:
ct.fill()
else:
ct.stiroke()
ct.stroke()
4 changes: 2 additions & 2 deletions mfp/gui/clutter/connection_element.py
Expand Up @@ -45,12 +45,12 @@ def __init__(self, window, obj_1, port_1, obj_2, port_2, dashed=False):
async def update(self):
await self.draw()

async def delete(self):
async def delete(self, **kwargs):
if self.texture:
self.group.set_content(None)
self.texture = None

await super().delete()
await super().delete(**kwargs)

def select(self):
super().select()
Expand Down
4 changes: 2 additions & 2 deletions mfp/gui/clutter/message_element.py
Expand Up @@ -40,7 +40,7 @@ def __init__(self, window, x, y):
self.group.set_position(x, y)
self.redraw()

async def delete(self):
async def delete(self, **kwargs):
if self.texture:
self.group.set_content(None)
self.texture = None
Expand All @@ -49,7 +49,7 @@ async def delete(self):
await self.label.delete()
self.label = None

await super().delete()
await super().delete(**kwargs)

def redraw(self):
if not self.texture:
Expand Down
4 changes: 2 additions & 2 deletions mfp/gui/clutter/processor_element.py
Expand Up @@ -38,7 +38,7 @@ def __init__(self, window, x, y):

self.redraw()

async def delete(self):
async def delete(self, **kwargs):
if self.texture:
self.group.set_content(None)
self.texture = None
Expand All @@ -47,7 +47,7 @@ async def delete(self):
await self.label.delete()
self.label = None

await super().delete()
await super().delete(**kwargs)

def redraw(self):
super().redraw()
Expand Down
4 changes: 2 additions & 2 deletions mfp/gui/connection_element.py
Expand Up @@ -47,8 +47,8 @@ def __init__(self, window, obj_1, port_1, obj_2, port_2, dashed=False):
def get_factory(cls):
return ConnectionElementImpl.get_backend(MFPGUI().appwin.backend_name)

async def delete(self):
if (not self.dashed and self.obj_1 and self.obj_2 and
async def delete(self, delete_obj=True):
if (delete_obj and not self.dashed and self.obj_1 and self.obj_2 and
self.obj_1.obj_id is not None and self.obj_2.obj_id is not None):
await MFPGUI().mfp.disconnect(
self.obj_1.obj_id, self.port_1,
Expand Down
10 changes: 5 additions & 5 deletions mfp/gui/patch_display.py
Expand Up @@ -98,15 +98,15 @@ async def configure(self, params):

self.app_window.refresh(self)

async def delete(self):
async def delete(self, delete_obj=True):
if self.obj_id is None:
return

# delete all the processor elements
for layer in self.layers:
to_delete = [o for o in layer.objects]
for o in to_delete:
await o.delete()
await o.delete(delete_obj=delete_obj)
layer.hide()
layer.delete()

Expand All @@ -115,7 +115,7 @@ async def delete(self):
self.layer_view.remove(self)

# last, delete the patch on the control side
if self.obj_id is not None:
to_delete = self.obj_id
self.obj_id = None
to_delete = self.obj_id
self.obj_id = None
if delete_obj and to_delete is not None:
await MFPGUI().mfp.delete(to_delete)
6 changes: 4 additions & 2 deletions mfp/gui_command.py
Expand Up @@ -163,15 +163,17 @@ async def connect(self, obj_1_id, obj_1_port, obj_2_id, obj_2_port):
await c.update()

async def delete(self, obj_id):
from mfp import log
from .gui_main import MFPGUI
from .gui.patch_display import PatchDisplay

obj = MFPGUI().recall(obj_id)
if isinstance(obj, PatchDisplay):
await obj.delete()
await obj.delete(delete_obj=False)
if obj in MFPGUI().appwin.patches:
MFPGUI().appwin.patches.remove(obj)
elif obj is not None:
await obj.delete()
await obj.delete(delete_obj=False)

async def select(self, obj_id):
from .gui_main import MFPGUI
Expand Down
16 changes: 5 additions & 11 deletions mfp/mfp_app.py
Expand Up @@ -555,19 +555,13 @@ async def finish(self):

log.debug("MFPApp.finish: all children reaped, good-bye!")

def finish_soon(self):
import threading
async def finish_soon(self):
import asyncio

async def wait_and_finish(*args, **kwargs):
await asyncio.sleep(0.5)
await self.finish()
log.debug("MFPApp.finish_soon: done with app.finish", threading._active)
return True

qt = threading.Thread(target=lambda *args, **kwargs: asyncio.run(wait_and_finish()))
qt.start()
self.leftover_threads.append(qt)
await asyncio.sleep(0.5)
await self.finish()
log.debug("MFPApp.finish_soon: done with app.finish")
return True

def send(self, msg, port):
if isinstance(msg, MethodCall):
Expand Down
9 changes: 5 additions & 4 deletions mfp/mfp_command.py
Expand Up @@ -106,7 +106,6 @@ def set_params(self, obj_id, params):
if isinstance(obj, Processor):
obj.gui_params = params

@noresp
def set_gui_created(self, obj_id, value):
from .mfp_app import MFPApp
obj = MFPApp().recall(obj_id)
Expand Down Expand Up @@ -211,12 +210,12 @@ async def show_editor(self, obj_id, show):
await patch.delete_gui()

@noresp
def save_lv2(self, patch_name, plugin_name):
async def save_lv2(self, patch_name, plugin_name):
from .mfp_app import MFPApp
patch = MFPApp().patches.get(patch_name)
file_name = plugin_name + ".mfp"
if patch:
patch.save_lv2(plugin_name, file_name)
await patch.save_lv2(plugin_name, file_name)

def clipboard_copy(self, pointer_pos, objlist):
from .mfp_app import MFPApp
Expand All @@ -243,12 +242,14 @@ def open_context(self, node_id, context_id, owner_pid, samplerate):
MFPApp().samplerate = samplerate

if DSPContext.create(node_id, context_id, ctxt_name):
log.debug(f"open_context: created context, node={node_id} id={context_id} name={ctxt_name}")
return True
return False

async def load_context(self, file_name, node_id, context_id):
from .mfp_app import MFPApp
from .dsp_object import DSPContext
log.debug(f"load_context: loading {file_name} in context node={node_id} ctxt_id={context_id}")
ctxt = DSPContext.lookup(node_id, context_id)
patch = await MFPApp().open_file(file_name, ctxt, False)
patch.hot_inlets = list(range(len(patch.inlets)))
Expand All @@ -272,7 +273,7 @@ async def close_context(self, node_id, context_id):
del MFPApp().patches[pid]

if not len(MFPApp().patches):
MFPApp().finish_soon()
await MFPApp().finish_soon()
return None

def open_patches(self):
Expand Down
66 changes: 33 additions & 33 deletions mfp/patch_lv2.py
Expand Up @@ -11,44 +11,44 @@

def find_mfplib():
from subprocess import Popen, PIPE
import re
sub = Popen(['/bin/bash', '-c',
import re
sub = Popen(['/bin/bash', '-c',
"ldd `type -p mfpdsp` | grep libmfpdsp | cut -d '>' -f 2"],
stdout=PIPE, stderr=PIPE)
stdout, stderr = sub.communicate()
#m = re.search(r"([^ ]\+) \(([0-9a-fx]\+)\)$", stdout.strip())
m = re.search(r"^(.*) ([()0-9a-fx]+)$", stdout.strip())
if m:
return m.group(1)
else:
return None
m = re.search(r"^(.*) ([()0-9a-fx]+)$", stdout.strip().decode('utf-8'))
if m:
return m.group(1)
else:
return None

def create_path(fullpath):
import os.path
if os.path.isdir(fullpath):
return True
return True
elif os.path.exists(fullpath):
return False
return False
elif not fullpath:
return True
else:
else:
if fullpath[-1] == '/':
fullpath = fullpath[:-1]
head, tail = os.path.split(fullpath)
head_ok = create_path(head)
if head_ok:
try:
head_ok = create_path(head)
if head_ok:
try:
os.mkdir(fullpath)
return True
except OSError:
return True
except OSError:
pass
return False
return False

@extends(Patch)
def lv2_create_dir(self, plugname):
from .mfp_app import MFPApp
import os
import os.path
import os
import os.path

lv2_basedir = MFPApp().lv2_savepath
lv2_dirname = plugname + ".lv2"
Expand All @@ -64,15 +64,15 @@ def lv2_create_dir(self, plugname):


ttl_template = """
# manifest.ttl -- an LV2 plugin definition file for MFP
# manifest.ttl -- an LV2 plugin definition file for MFP
# THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT
@prefix doap: <http://usefulinc.com/ns/doap#> .
@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
<http://www.billgribble.com/mfp/%(ttl_plugname)s.lv2>
<http://www.billgribble.com/mfp/%(ttl_plugname)s.lv2>
a lv2:Plugin;
lv2:binary <%(ttl_libname)s>;
lv2:project <http://github.com/bgribble/mfp> ;
Expand Down Expand Up @@ -106,30 +106,30 @@ def lv2_create_dir(self, plugname):
def lv2_write_ttl(self, ttlpath, plugname, filename):
port_list = []
libname = "lib%s_lv2.so" % plugname
ttl_params = dict(ttl_plugname=plugname, ttl_filename=filename,
ttl_params = dict(ttl_plugname=plugname, ttl_filename=filename,
ttl_libname=libname,
ttl_description=(self.properties.get("description") or self.name))
portnum = 0
for p in self.inlet_objects + self.outlet_objects:
for p in self.inlet_objects + self.outlet_objects:
port_params = dict(port_number=portnum)
port_types = ""
if p.init_type in ("inlet~", "outlet~"):
needs_bounds = False
needs_bounds = False
port_types = "lv2:AudioPort"
else:
needs_bounds = True
needs_bounds = True
port_types = "lv2:ControlPort"
if p.init_type in ("inlet~", "inlet"):
port_types += ", lv2:InputPort"
else:
port_types += ", lv2:OutputPort"
port_params['port_types'] = port_types
port_params['port_symbol'] = p.name
port_params['port_types'] = port_types
port_params['port_symbol'] = p.name
port_params['port_name'] = p.properties.get("description") or p.name
port_params['port_property'] = ''
if needs_bounds:
bounds=dict(bounds_default=p.properties.get('bounds_default', 0.0),
bounds_minimum=p.properties.get('bounds_minimum', 0.0),
bounds=dict(bounds_default=p.properties.get('bounds_default', 0.0),
bounds_minimum=p.properties.get('bounds_minimum', 0.0),
bounds_maximum=p.properties.get('bounds_maximum', 1.0))
port_params['port_bounds'] = bounds_template % bounds
else:
Expand All @@ -138,7 +138,7 @@ def lv2_write_ttl(self, ttlpath, plugname, filename):
portnum += 1
port_list.append(port_template % port_params)

# Edit button
# Edit button
port_params=dict(port_number=portnum)
port_params['port_types'] = "lv2:InputPort, lv2:ControlPort"
port_params['port_symbol'] = 'patch_edit'
Expand All @@ -148,24 +148,24 @@ def lv2_write_ttl(self, ttlpath, plugname, filename):
port_list.append(port_template % port_params)

ttl_params['ports'] = ',\n'.join(port_list)

with open(ttlpath, "w") as ttlfile:
ttlfile.write(ttl_template % ttl_params)

# make the symlink to libmfpdsp.sp
import os, os.path
ttldir = os.path.dirname(ttlpath)
mfplib_path = os.path.relpath(find_mfplib(), ttldir)
if not mfplib_path:
return None
if not mfplib_path:
return None
else:
linkpath = os.path.join(ttldir, libname)
if not os.path.exists(linkpath):
os.symlink(mfplib_path, linkpath)

@extends(Patch)
def lv2_bless(self, plugname):
pass
pass



Expand Down

0 comments on commit 745733c

Please sign in to comment.