summaryrefslogtreecommitdiffstats
path: root/scripts
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2012-12-21 03:51:41 +0000
committerDavid Robillard <d@drobilla.net>2012-12-21 03:51:41 +0000
commit89703f69e36514584ec5c399f4bdc8fb2cbc4036 (patch)
tree0b4f44b64dd2fa8604b16ed86196b527ecbe0b51 /scripts
parent595e4f539b73a46c428a2e84b5e0f37e0f48a9b8 (diff)
downloadingen-89703f69e36514584ec5c399f4bdc8fb2cbc4036.tar.gz
ingen-89703f69e36514584ec5c399f4bdc8fb2cbc4036.tar.bz2
ingen-89703f69e36514584ec5c399f4bdc8fb2cbc4036.zip
Add preliminary AMS loader script.
git-svn-id: http://svn.drobilla.net/lad/trunk/ingen@4870 a436a847-0d15-0410-975c-d299462d15a1
Diffstat (limited to 'scripts')
-rw-r--r--scripts/ingen.py218
-rwxr-xr-xscripts/ingenams260
-rwxr-xr-xscripts/ingenish106
3 files changed, 489 insertions, 95 deletions
diff --git a/scripts/ingen.py b/scripts/ingen.py
new file mode 100644
index 00000000..594e369d
--- /dev/null
+++ b/scripts/ingen.py
@@ -0,0 +1,218 @@
+#!/usr/bin/env python
+# Ingen Python Interface
+# Copyright 2012 David Robillard <http://drobilla.net>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+import StringIO
+import os
+import rdflib
+import socket
+import sys
+
+class NS:
+ ingen = rdflib.Namespace('http://drobilla.net/ns/ingen#')
+ lv2 = rdflib.Namespace('http://lv2plug.in/ns/lv2core#')
+ patch = rdflib.Namespace('http://lv2plug.in/ns/ext/patch#')
+ rdf = rdflib.Namespace('http://www.w3.org/1999/02/22-rdf-syntax-ns#')
+ xsd = rdflib.Namespace('http://www.w3.org/2001/XMLSchema#')
+
+class Interface:
+ 'The core Ingen interface'
+ def put(self, path, body):
+ pass
+
+ def set(self, path, body):
+ pass
+
+ def connect(self, tail, head):
+ pass
+
+ def disconnect(self, tail, head):
+ pass
+
+ def delete(self, path):
+ pass
+
+class Error(Exception):
+ def __init__(self, code, cause):
+ Exception.__init__(self, 'error %d, cause: %s' % (code, cause))
+
+class Remote(Interface):
+ def __init__(self, uri='unix:///tmp/ingen.sock'):
+ self.msg_id = 1
+ self.server_base = uri + '/'
+ self.model = rdflib.Graph()
+ self.ns_manager = rdflib.syntax.NamespaceManager.NamespaceManager(self.model)
+ self.ns_manager.bind('server', self.server_base)
+ for (k, v) in NS.__dict__.items():
+ self.ns_manager.bind(k, v)
+ if uri.startswith('unix://'):
+ self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ self.sock.connect(uri[len('unix://'):])
+ elif uri.startswith('tcp://'):
+ self.sock = Socket(socket.AF_INET, socket.SOCK_STREAM)
+ parsed = re.split('[:/]', uri[len('tcp://'):])
+ addr = (parsed[0], int(parsed[1]))
+ self.sock.connect(addr)
+ else:
+ raise Exception('Unsupported server URI `%s' % uri)
+
+ def __del__(self):
+ self.sock.close()
+
+ def msgencode(self, msg):
+ if sys.version_info[0] == 3:
+ return bytes(msg, 'utf-8')
+ else:
+ return msg
+
+ def update_model(self, update):
+ for i in update.triples([None, NS.rdf.type, NS.patch.Put]):
+ put = i[0]
+ subject = update.value(put, NS.patch.subject, None)
+ body = update.value(put, NS.patch.body, None)
+ desc = {}
+ for i in update.triples([body, None, None]):
+ self.model.add([subject, i[1], i[2]])
+ return update
+
+ def uri_to_path(self, uri):
+ if uri.startswith(self.server_base):
+ return uri[len(self.server_base):]
+ return uri
+
+ def recv(self):
+ 'Read from socket until a NULL terminator is received'
+ msg = ''
+ while True:
+ c = self.sock.recv(1, 0)
+ if not c or ord(c[0]) == 0: # End of transmission
+ break
+ else:
+ msg += c[0]
+ return msg
+
+ def blank_closure(self, graph, node):
+ def blank_walk(node, g):
+ for i in g.triples([node, None, None]):
+ if type(i[2]) == rdflib.BNode and i[2] != node:
+ yield i[2]
+ blank_walk(i[2], g)
+
+ closure = [node]
+ for b in graph.transitiveClosure(blank_walk, node):
+ closure += [b]
+
+ return closure
+
+ def send(self, msg):
+ # Send message to server
+ payload = msg
+ if sys.version_info[0] == 3:
+ payload = bytes(msg, 'utf-8')
+ self.sock.send(self.msgencode(msg))
+
+ # Receive response and parse into a model
+ response_str = self.recv()
+ response_model = rdflib.Graph()
+ response_model.namespace_manager = self.ns_manager
+ response_model.parse(StringIO.StringIO(response_str), self.server_base, format='n3')
+
+ # Handle response (though there should be only one)
+ blanks = []
+ response_desc = []
+ for i in response_model.triples([None, NS.rdf.type, NS.patch.Response]):
+ response = i[0]
+ subject = response_model.value(response, NS.patch.subject, None)
+ body = response_model.value(response, NS.patch.body, None)
+
+ response_desc += [i]
+ blanks += [response]
+ if body != 0:
+ raise Error(int(body), msg) # Raise exception on server error
+
+ # Find the blank node closure of all responses
+ blank_closure = []
+ for b in blanks:
+ blank_closure += self.blank_closure(response_model, b)
+
+ # Remove response descriptions from model
+ for b in blank_closure:
+ for t in response_model.triples([b, None, None]):
+ response_model.remove(t)
+
+ # Remove triples describing responses from response model
+ for i in response_desc:
+ response_model.remove(i)
+
+ # Update model with remaining information, e.g. patch:Put updates
+ return self.update_model(response_model)
+
+ def get(self, path):
+ return self.send('''
+[]
+ a patch:Get ;
+ patch:subject <ingen:root%s> .
+''' % path)
+
+ def put(self, path, body):
+ return self.send('''
+[]
+ a patch:Put ;
+ patch:subject <ingen:root%s> ;
+ patch:body [
+%s
+] .
+''' % (path, body))
+
+ def set(self, path, body):
+ return self.send('''
+[]
+ a patch:Set ;
+ patch:subject <ingen:root%s> ;
+ patch:body [
+%s
+ ] .
+''' % (path, body))
+
+ def connect(self, tail, head):
+ return self.send('''
+[]
+ a patch:Put ;
+ patch:subject <ingen:root%s> ;
+ patch:body [
+ a ingen:Edge ;
+ ingen:tail <%s> ;
+ ingen:head <%s> ;
+ ] .
+''' % (os.path.commonprefix([tail, head]), tail, head))
+
+ def disconnect(self, tail, head):
+ return self.send('''
+[]
+ a patch:Delete ;
+ patch:body [
+ a ingen:Edge ;
+ ingen:tail <%s> ;
+ ingen:head <%s> ;
+ ] .
+''' % (tail, head))
+
+ def delete(self, path):
+ return self.send('''
+[]
+ a patch:Delete ;
+ patch:subject <ingen:root%s> .
+''' % path)
+
diff --git a/scripts/ingenams b/scripts/ingenams
new file mode 100755
index 00000000..bad29406
--- /dev/null
+++ b/scripts/ingenams
@@ -0,0 +1,260 @@
+#!/usr/bin/env python
+# Load an AlsaModularSynth patch file into Ingen
+# Copyright 2012 David Robillard <http://drobilla.net>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+import ingen
+import rdflib
+import sys
+
+avw_prefix = 'http://avwlv2.sourceforge.net/plugins/avw/'
+fomp_prefix = 'http://drobilla.net/plugins/fomp/'
+
+class World:
+ def __init__(self, server_uri):
+ self.server_uri = server_uri
+ self.server = ingen.Remote(server_uri)
+ self.pending_arcs = []
+ self.server.get('/')
+
+ def mod_sym(self, mod_id):
+ return 'mod%d' % int(mod_id)
+
+ def add_block(self, mod_id, plugin_uri, x, y):
+ self.server.put('/' + self.mod_sym(mod_id),
+ 'a ingen:Block ;\n'
+ + 'ingen:prototype <%s> ;\n' % plugin_uri
+ + 'ingen:canvasX %f ;\n' % x
+ + 'ingen:canvasY %f' % y)
+
+ def add_arc(self,
+ head_port_id, tail_port_id,
+ head_mod_id, tail_mod_id,
+ jack_color, cable_color):
+ self.pending_arcs += [(head_port_id, tail_port_id,
+ head_mod_id, tail_mod_id,
+ jack_color, cable_color)]
+
+ def get_ports(self, mod_uri, port_type):
+ ports = []
+ for i in self.server.model.triples([None, ingen.NS.rdf.type, port_type]):
+ if str(i[0]).startswith(mod_uri + '/'):
+ if not [i[0], ingen.NS.rdf.type, ingen.NS.lv2.ControlPort] in self.server.model:
+ # Unfortunately ingen.NS.lv2.index is a method
+ index = self.server.model.value(i[0], ingen.NS.lv2['index'], None)
+ ports += [[int(index), i[0]]]
+ return ports
+
+ def input_by_id(self, mod_uri, port_id):
+ # Get all input ports on this module sorted by index
+ inputs = sorted(self.get_ports(mod_uri, ingen.NS.lv2.InputPort))
+
+ # Return the port_id'th port in the list
+ index = 0
+ for i in inputs:
+ if index == int(port_id):
+ return i[1]
+ index += 1
+
+ return None
+
+ def output_by_id(self, mod_uri, port_id):
+ # Get all output ports on this module sorted by index
+ outputs = sorted(self.get_ports(mod_uri, ingen.NS.lv2.OutputPort))
+
+ # Return the port_id'th port in the list
+ index = 0
+ for i in outputs:
+ if index == int(port_id):
+ return i[1]
+ index += 1
+
+ return None
+
+ def create_arcs(self):
+ for (head_port_id, tail_port_id,
+ head_mod_id, tail_mod_id,
+ jack_color, cable_color) in self.pending_arcs:
+ print 'ARC %s:%s => %s:%s' % (tail_mod_id, tail_port_id, head_mod_id, head_port_id)
+ try:
+ tail_mod = rdflib.URIRef(self.server.server_base + self.mod_sym(tail_mod_id))
+ head_mod = rdflib.URIRef(self.server.server_base + self.mod_sym(head_mod_id))
+ tail = self.output_by_id(tail_mod, tail_port_id)
+ head = self.input_by_id(head_mod, head_port_id)
+ if tail and head:
+ self.server.connect(self.server.uri_to_path(tail),
+ self.server.uri_to_path(head))
+ except:
+ pass
+
+# Static enumeration of special module type IDs
+class Special:
+ CUSTOM = 0
+ LADSPA = 6
+ SCMCV = 30
+ SCQUANTIZER = 30
+
+# Module types list, indexed by numeric ID in file
+# Except where otherwise commented, these correspond to internal modules,
+# and the string is the suffix of the corresponding AVW LV2 plugin URI
+module_types = [
+ "custom", # 0 = custom (unsupported)
+ "vco",
+ "vca",
+ "lfo",
+ "delay",
+ "ringmod",
+ "ladspa", # 6 = LADSPA plugin
+ "pcmout",
+ "mix",
+ "vcf",
+ "mcv",
+ "env",
+ "seq",
+ "inv",
+ "noise",
+ "slew",
+ "quantizer",
+ "pcmin",
+ "cvs",
+ "sh",
+ "vcorgan",
+ "dynamicwaves",
+ "advenv",
+ "wavout",
+ "scope",
+ "spectrum",
+ "vcswitch",
+ "jackin",
+ "jackout",
+ "midiout",
+ "scmcv", # Scala module (different line format)
+ "scquantizer", # Scala module (different line format)
+ "stereomix",
+ "conv",
+ "vcenv",
+ "advmcv",
+ "function",
+ "vcpanning",
+ "vcenv2",
+ "vcdoubledecay",
+ "vquant",
+ "amp",
+ "ad",
+ "mphlfo",
+ "noise2",
+ "vco2"
+]
+
+class Module:
+ def __init__(self, num, plugin_uri, properties={}):
+ self.num = num
+ self.plugin_uri = plugin_uri
+ self.properties = properties
+ self.ports = []
+
+class Patch:
+ def __init__(self):
+ self.modules = []
+
+def ladspa_module(world, mod_id, x, y, poly, lib, label):
+ lv2_uri = ''
+ # Kludge LADSPA library and label to Fomp LV2 URIs where applicable
+ if lib == 'blvco':
+ lv2_uri = fomp_prefix + label.lower().replace('-', '_')
+ elif lib == 'mvclpf24' or lib == 'mvchpf24':
+ lv2_uri = fomp_prefix + label.lower().replace('-', '')
+ elif lib == 'cs_chorus' or lib == 'cs_phaser':
+ lv2_uri = fomp_prefix + 'cs_' + label.lower().replace('+', '_')
+
+ if lv2_uri:
+ world.add_block(mod_id, lv2_uri, x, y)
+ else:
+ print 'MOD %3d LADSPA %s %s %s' % (mod_id, poly, lib, label)
+
+def scala_module(world, mod_id, scala_name):
+ #print 'MOD %3d SCALA %s' % (d, scala_name)
+ pass
+
+def standard_module(world, mod_id, x, y, name, arg):
+ lv2_uri = avw_prefix + name
+ world.add_block(mod_id, lv2_uri, x, y)
+
+def float_control(world, mod_id, port_index, value,
+ logarithmic, minimum, maximum, midi_sign):
+ #print 'FLOAT CONTROL %s:%s = %s' % (mod_id, port_index, value)
+ pass
+
+def control(world, mod_id, port_index, value, midi_sign):
+ #print 'CONTROL %s:%s = %s' % (mod_id, port_index, value)
+ pass
+
+if len(sys.argv) != 2 and len(sys.argv) != 3:
+ sys.stderr.write('Usage: %s AMS_PATCH_FILE [SERVER_URI]\n')
+ sys.exit(1)
+
+in_path = sys.argv[1]
+server_uri = 'unix:///tmp/ingen.sock'
+if len(sys.argv) == 3:
+ server_uri = sys.argv[2]
+
+world = World(server_uri)
+in_file = open(in_path, 'r')
+
+in_comment = False
+for l in in_file:
+ try:
+ expr = l.split()
+ if not expr:
+ continue
+ elif expr[0] == '#PARA#':
+ in_comment = True
+ elif in_comment and expr[0] == '#ARAP#':
+ in_comment = False
+ elif expr[0] == 'Module':
+ mod_type = int(expr[1])
+ mod_id = int(expr[2])
+ mod_x = int(expr[3])
+ mod_y = int(expr[4])
+ if mod_type > len(module_types):
+ sys.stderr.write('warning: unknown module type %d\n', mod_type)
+ elif mod_type == Special.CUSTOM:
+ sys.stderr.write('warning: custom module %d unsupported\n' % mod_id)
+ if mod_type == Special.LADSPA:
+ ladspa_module(world, mod_id, mod_x, mod_y, int(expr[5]), expr[6], expr[7])
+ elif mod_type == Special.SCMCV or mod_type == Special.SCQUANTIZER:
+ scale_module(world, mod_id, module_types[mod_type], expr[5])
+ scala_name = expr[5]
+ else:
+ standard_module(world, mod_id, mod_x, mod_y, module_types[mod_type], expr[5])
+ elif expr[0] == 'ColorP':
+ world.add_arc(expr[1], expr[2], expr[3], expr[4],
+ (expr[5], expr[6], expr[7]),
+ (expr[8], expr[9], expr[10]))
+ elif expr[0] == 'FSlider':
+ float_control(world, mod_id,
+ expr[2], expr[3], expr[4], expr[5], expr[6], expr[7])
+ elif expr[0] == 'ISlider' or expr[0] == 'LSlider':
+ control(world, mod_id, expr[2], expr[3], expr[4])
+ #else:
+ # sys.stderr.write('warning: unsupported form %s\n' % expr[0])
+ except ingen.Error:
+ e = sys.exc_info()[1]
+ sys.stderr.write('ingen error: %s\n' % e.message)
+
+world.create_arcs()
+
+#print world.server.model.serialize(format='n3')
+
+in_file.close()
diff --git a/scripts/ingenish b/scripts/ingenish
index ad226c99..3e572dbf 100755
--- a/scripts/ingenish
+++ b/scripts/ingenish
@@ -14,103 +14,17 @@
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+import ingen
import os.path
+import re
import shlex
-import socket
import sys
import time
-import re
try:
import readline
except:
pass
-class Client:
- def __init__(self, uri='unix:///tmp/ingen.sock'):
- self.msg_id = 1
- if uri.startswith('unix://'):
- self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
- self.sock.connect(uri[len('unix://'):])
- elif uri.startswith('tcp://'):
- self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- parsed = re.split('[:/]', uri[len('tcp://'):])
- addr = (parsed[0], int(parsed[1]))
- self.sock.connect(addr)
-
- def __del__(self):
- self.sock.close()
-
- def msgencode(self, msg):
- if sys.version_info[0] == 3:
- return bytes(msg, 'utf-8')
- else:
- return msg
-
- def msg_subject(self):
- #self.msg_id += 1
- #return '_:msg%d' % self.msg_id
- return '[]'
-
- def send(self, msg):
- self.sock.send(self.msgencode(msg))
- return True
- #response = self.sock.recv(1024)
- # if response != self.msgencode('OK'):
- # print('Error: %s' % response)
- # return False
- # else:
- # return True
-
- def put(self, path, body):
- return self.send('''
-%s
- a patch:Put ;
- patch:subject <ingen:root%s> ;
- patch:body [
-%s
-] .
-''' % (self.msg_subject(), path, body))
-
- def set(self, path, body):
- return self.send('''
-%s
- a patch:Set ;
- patch:subject <ingen:root%s> ;
- patch:body [
-%s
- ] .
-''' % (self.msg_subject(), path, body))
-
- def connect(self, tail, head):
- return self.send('''
-%s
- a patch:Put ;
- patch:subject <ingen:root%s> ;
- patch:body [
- a ingen:Edge ;
- ingen:tail <%s> ;
- ingen:head <%s> ;
- ] .
-''' % (self.msg_subject(), os.path.commonprefix([tail, head]), tail, head))
-
- def disconnect(self, tail, head):
- return self.send('''
-%s
- a patch:Delete ;
- patch:body [
- a ingen:Edge ;
- ingen:tail <%s> ;
- ingen:head <%s> ;
- ] .
-''' % (self.msg_subject(), tail, head))
-
- def delete(self, path):
- return self.send('''
-%s
- a patch:Delete ;
- patch:subject <ingen:root%s> .
-''' % (self.msg_subject(), path))
-
def print_usage():
print('''Usage: ingenish [OPTION]... [COMMAND [ARGUMENT]...]
@@ -140,8 +54,8 @@ Example:
ingenish put /left_in 'a lv2:InputPort ; a lv2:AudioPort'
ingenish put /left_out 'a lv2:OutputPort ; a lv2:AudioPort'
- ingenish put /tone 'a ingen:Node ; ingen:prototype <http://drobilla.net/plugins/mda/Shepard>'
- ingenish put /combo 'a ingen:Node ; ingen:prototype <http://drobilla.net/plugins/mda/Combo>'
+ ingenish put /tone 'a ingen:Block ; ingen:prototype <http://drobilla.net/plugins/mda/Shepard>'
+ ingenish put /combo 'a ingen:Block ; ingen:prototype <http://drobilla.net/plugins/mda/Combo>'
ingenish connect /left_in /tone/left_in
ingenish connect /tone/left_out /combo/left_in
ingenish connect /combo/left_out /left_out
@@ -163,8 +77,7 @@ def run(cmd):
return ingen.disconnect(cmd[1], cmd[2])
elif cmd[0] == 'delete' and len(cmd) == 2:
return ingen.delete(cmd[1])
- else:
- return False
+ return False
a = 1
server = 'unix:///tmp/ingen.sock'
@@ -176,7 +89,7 @@ if len(sys.argv) > 1:
print_usage()
sys.exit(1)
-ingen = Client(server)
+ingen = ingen.Remote(server)
if len(sys.argv) - a == 0:
print('Ingen server at %s' % server)
@@ -184,11 +97,14 @@ if len(sys.argv) - a == 0:
try:
run(shlex.split(raw_input('> ')))
except (EOFError, KeyboardInterrupt, SystemExit):
- print('')
+ print('error')
break
except:
print('error: %s' % sys.exc_info()[0])
else:
- if not run(sys.argv[1:]):
+ update = run(sys.argv[1:])
+ if update:
+ print update.serialize(format='n3')
+ else:
print_usage()
sys.exit(1)