6 type_wrapper_debug
= False
8 ###############################################################################
9 # Ugly internals. Please skip this section for your own sanity.
10 ###############################################################################
13 """An object that calls a C layer command, receives a /uuid callback from it
14 and stores the passed UUID in its uuid attribute.
16 Example use: GetUUID('/command', arg1, arg2...).uuid
18 def __init__(self
, cmd
, *cmd_args
):
19 def callback(cmd
, fb
, args
):
20 if cmd
== "/uuid" and len(args
) == 1:
23 raise ValueException("Unexpected callback: %s" % cmd
)
24 self
.callback
= callback
26 do_cmd(cmd
, self
, list(cmd_args
))
27 def __call__(self
, *args
):
31 """A generic callback object that receives various forms of information from
32 C layer and converts then into object's Python attributes.
34 This is an obsolete interface, to be replaced by GetUUID or metaclass
35 based type-safe autoconverter. However, there are still some cases that
36 aren't (yet) handled by either.
39 def by_uuid(uuid
, cmd
, anames
, args
):
40 return GetThings(Document
.uuid_cmd(uuid
, cmd
), anames
, args
)
41 def __init__(self
, cmd
, anames
, args
):
44 setattr(self
, i
[1:], [])
45 elif i
.startswith("%"):
46 setattr(self
, i
[1:], {})
48 setattr(self
, i
, None)
51 def update_callback(cmd
, fb
, args
):
52 self
.seq
.append((cmd
, fb
, args
))
56 setattr(self
, cmd
, args
[0])
58 setattr(self
, cmd
, args
)
59 elif "*" + cmd
in anames
:
61 getattr(self
, cmd
).append(args
[0])
63 getattr(self
, cmd
).append(args
)
64 elif "%" + cmd
in anames
:
66 getattr(self
, cmd
)[args
[0]] = args
[1]
68 getattr(self
, cmd
)[args
[0]] = args
[1:]
70 setattr(self
, cmd
, args
[0])
71 do_cmd(cmd
, update_callback
, args
)
75 class PropertyDecorator(object):
76 """Abstract property decorator."""
77 def __init__(self
, base
):
81 def map_cmd(self
, cmd
):
84 class AltPropName(PropertyDecorator
):
85 """Command-name-changing property decorator. Binds a property to the
86 specified /path, different from the default one, which based on property name,
87 with -s and -es suffix removed for lists and dicts."""
88 def __init__(self
, alt_name
, base
):
89 PropertyDecorator
.__init
__(self
, base
)
90 self
.alt_name
= alt_name
91 def map_cmd(self
, cmd
):
93 def execute(self
, property, proptype
, klass
):
96 class SettableProperty(PropertyDecorator
):
97 """Decorator that creates a setter method for the property."""
98 def execute(self
, property, proptype
, klass
):
99 if type(proptype
) is dict:
100 setattr(klass
, 'set_' + property, lambda self
, key
, value
: self
.cmd('/' + property, None, key
, value
))
101 elif type(proptype
) is bool:
102 setattr(klass
, 'set_' + property, lambda self
, value
: self
.cmd('/' + property, None, 1 if value
else 0))
104 setattr(klass
, 'set_' + property, lambda self
, value
: self
.cmd('/' + property, None, proptype(value
)))
106 def new_get_things(obj
, cmd
, settermap
, args
):
107 """Call C command with arguments 'args', populating a return object obj
108 using settermap to interpret callback commands and initialise the return
110 def update_callback(cmd2
, fb
, args2
):
112 if cmd2
in settermap
:
113 settermap
[cmd2
](obj
, args2
)
114 elif cmd2
!= '/uuid': # Ignore UUID as it's usually safe to do so
115 print ("Unexpected command: %s" % cmd2
)
116 except Exception as error
:
117 traceback
.print_exc()
119 # Set initial values for the properties (None or empty dict/list)
120 for setterobj
in settermap
.values():
121 setattr(obj
, setterobj
.property, setterobj
.init_value())
122 # Call command and apply callback commands via setters to the object
123 do_cmd(cmd
, update_callback
, args
)
126 def _error_arg_mismatch(required
, passed
):
127 raise ValueError("Types required: %s, values passed: %s" % (repr(required
), repr(passed
)))
128 def _handle_object_wrapping(t
):
129 if type(t
) is CboxObjMetaclass
:
130 return lambda uuid
: Document
.map_uuid_and_check(uuid
, t
)
132 def _make_args_to_type_lambda(t
):
133 t
= _handle_object_wrapping(t
)
134 return lambda args
: t(*args
)
135 def _make_args_to_tuple_of_types_lambda(ts
):
136 ts
= list(map(_handle_object_wrapping
, ts
))
137 return lambda args
: tuple([ts
[i
](args
[i
]) for i
in range(max(len(ts
), len(args
)))]) if len(ts
) == len(args
) else _error_arg_mismatch(ts
, args
)
138 def _make_args_decoder(t
):
140 return _make_args_to_tuple_of_types_lambda(t
)
142 return _make_args_to_type_lambda(t
)
144 def get_thing(cmd
, fieldcmd
, datatype
, *args
):
145 if type(datatype
) is list:
146 assert (len(datatype
) == 1)
147 decoder
= _make_args_decoder(datatype
[0])
150 value
.append(decoder(data
))
151 elif type(datatype
) is dict:
152 assert (len(datatype
) == 1)
153 key_type
, value_type
= list(datatype
.items())[0]
154 key_decoder
= _make_args_decoder(key_type
)
155 value_decoder
= _make_args_decoder(value_type
)
158 value
[key_decoder([data
[0]])] = value_decoder(data
[1:])
160 decoder
= _make_args_decoder(datatype
)
163 value
= decoder(data
)
165 def callback(cmd2
, fb
, args2
):
169 print ("Unexpected command %s" % cmd2
)
170 do_cmd(cmd
, callback
, list(args
))
173 class SetterWithConversion
:
174 """A setter object class that sets a specific property to a typed value or a tuple of typed value."""
175 def __init__(self
, property, extractor
):
176 self
.property = property
177 self
.extractor
= extractor
178 def init_value(self
):
180 def __call__(self
, obj
, args
):
181 # print ("Setting attr %s on object %s" % (self.property, obj))
182 setattr(obj
, self
.property, self
.extractor(args
))
184 class ListAdderWithConversion
:
185 """A setter object class that adds a tuple filled with type-converted arguments of the
186 callback to a list. E.g. ListAdderWithConversion('foo', (int, int))(obj, [1,2])
187 adds a tuple: (int(1), int(2)) to the list obj.foo"""
189 def __init__(self
, property, extractor
):
190 self
.property = property
191 self
.extractor
= extractor
192 def init_value(self
):
194 def __call__(self
, obj
, args
):
195 getattr(obj
, self
.property).append(self
.extractor(args
))
197 class DictAdderWithConversion
:
198 """A setter object class that adds a tuple filled with type-converted
199 arguments of the callback to a dictionary under a key passed as first argument
200 i.e. DictAdderWithConversion('foo', str, (int, int))(obj, ['bar',1,2]) adds
201 a tuple: (int(1), int(2)) under key 'bar' to obj.foo"""
203 def __init__(self
, property, keytype
, valueextractor
):
204 self
.property = property
205 self
.keytype
= keytype
206 self
.valueextractor
= valueextractor
207 def init_value(self
):
209 def __call__(self
, obj
, args
):
210 getattr(obj
, self
.property)[self
.keytype(args
[0])] = self
.valueextractor(args
[1:])
212 def _type_properties(base_type
):
213 return {prop
: getattr(base_type
, prop
) for prop
in dir(base_type
) if not prop
.startswith("__")}
215 def _create_setter(prop
, t
):
216 if type(t
) in [type, CboxObjMetaclass
, tuple]:
217 if type_wrapper_debug
:
218 print ("%s is type %s" % (prop
, repr(t
)))
219 return SetterWithConversion(prop
, _make_args_decoder(t
))
220 elif type(t
) is dict:
222 tkey
, tvalue
= list(t
.items())[0]
223 if type_wrapper_debug
:
224 print ("%s is type: %s -> %s" % (prop
, repr(tkey
), repr(tvalue
)))
225 return DictAdderWithConversion(prop
, tkey
, _make_args_decoder(tvalue
))
226 elif type(t
) is list:
228 if type_wrapper_debug
:
229 print ("%s is array of %s" % (prop
, repr(t
[0])))
230 return ListAdderWithConversion(prop
, _make_args_decoder(t
[0]))
232 raise ValueError("Don't know what to do with %s property '%s' of type %s" % (name
, prop
, repr(t
)))
234 def _create_unmarshaller(name
, base_type
, object_wrapper
= False, property_grabber
= _type_properties
):
238 if type_wrapper_debug
:
239 print ("Wrapping type: %s" % name
)
241 for prop
, proptype
in property_grabber(base_type
).items():
244 if type(proptype
) in [list, dict]:
245 if propcmd
.endswith('s'):
246 if propcmd
.endswith('es'):
247 propcmd
= propcmd
[:-2]
249 propcmd
= propcmd
[:-1]
250 while isinstance(proptype
, PropertyDecorator
):
251 decorators
.append(proptype
)
252 propcmd
= proptype
.map_cmd(propcmd
)
253 proptype
= proptype
.get_base()
255 settermap
[propcmd
] = _create_setter(prop
, proptype
)
256 all_decorators
[prop
] = decorators
257 prop_types
[prop
] = proptype
258 base_type
.__str
__ = lambda self
: (str(name
) + ":" + " ".join(["%s=%s" % (v
.property, str(getattr(self
, v
.property))) for v
in settermap
.values()]))
259 if type_wrapper_debug
:
262 for propname
, decorators
in all_decorators
.items():
263 for decorator
in decorators
:
264 decorator
.execute(propname
, prop_types
[propname
], o
)
266 return exec_cmds
, lambda cmd
: (lambda self
, *args
: new_get_things(base_type(), self
.path
+ cmd
, settermap
, list(args
)))
268 return lambda cmd
, *args
: new_get_things(base_type(), cmd
, settermap
, list(args
))
270 class CboxObjMetaclass(type):
271 """Metaclass that creates Python wrapper classes for various C-side objects.
272 This class is responsible for automatically marshalling and type-checking/converting
273 fields of Status inner class on status() calls."""
274 def __new__(cls
, name
, bases
, namespace
, **kwds
):
275 status_class
= namespace
['Status']
276 classfinaliser
, cmdwrapper
= _create_unmarshaller(name
, status_class
, True)
277 result
= type.__new
__(cls
, name
, bases
, namespace
, **kwds
)
278 classfinaliser(result
)
279 result
.status
= cmdwrapper('/status')
283 class NonDocObj(object, metaclass
= CboxObjMetaclass
):
284 """Root class for all wrapper classes that wrap objects that don't have
285 their own identity/UUID.
286 This covers various singletons and inner objects (e.g. engine in instruments)."""
289 def __init__(self
, path
):
292 def cmd(self
, cmd
, fb
= None, *args
):
293 do_cmd(self
.path
+ cmd
, fb
, list(args
))
295 def cmd_makeobj(self
, cmd
, *args
):
296 return Document
.map_uuid(GetUUID(self
.path
+ cmd
, *args
).uuid
)
298 def get_things(self
, cmd
, fields
, *args
):
299 return GetThings(self
.path
+ cmd
, fields
, list(args
))
301 def get_thing(self
, cmd
, fieldcmd
, type, *args
):
302 return get_thing(self
.path
+ cmd
, fieldcmd
, type, *args
)
304 def make_path(self
, path
):
305 return self
.path
+ path
308 return "%s<%s>" % (self
.__class
__.__name
__, self
.path
)
310 class DocObj(NonDocObj
):
311 """Root class for all wrapper classes that wrap first-class document objects."""
314 def __init__(self
, uuid
):
315 NonDocObj
.__init
__(self
, Document
.uuid_cmd(uuid
, ''))
322 return "%s<%s>" % (self
.__class
__.__name
__, self
.uuid
)
325 def __init__(self
, path
, args
= []):
328 def plus(self
, subpath
, *args
):
329 return VarPath(self
.path
if subpath
is None else self
.path
+ "/" + subpath
, self
.args
+ list(args
))
330 def set(self
, *values
):
331 do_cmd(self
.path
, None, self
.args
+ list(values
))
333 ###############################################################################
334 # And those are the proper user-accessible objects.
335 ###############################################################################
338 class KeysUnmarshaller
:
340 keys_unmarshaller
= _create_unmarshaller('Config.keys()', KeysUnmarshaller
)
342 """INI file manipulation class."""
344 def sections(prefix
= ""):
345 """Return a list of configuration sections."""
346 return [CfgSection(name
) for name
in get_thing('/config/sections', '/section', [str], prefix
)]
349 def keys(section
, prefix
= ""):
350 """Return a list of configuration keys in a section, with optional prefix filtering."""
351 return Config
.keys_unmarshaller('/config/keys', str(section
), str(prefix
)).keys
354 def get(section
, key
):
355 """Return a string value of a given key."""
356 return get_thing('/config/get', '/value', str, str(section
), str(key
))
359 def set(section
, key
, value
):
360 """Set a string value for a given key."""
361 do_cmd('/config/set', None, [str(section
), str(key
), str(value
)])
364 def delete(section
, key
):
365 """Delete a given key."""
366 do_cmd('/config/delete', None, [str(section
), str(key
)])
369 def save(filename
= None):
370 """Save config, either into current INI file or some other file."""
372 do_cmd('/config/save', None, [])
374 do_cmd('/config/save', None, [str(filename
)])
377 def add_section(section
, content
):
378 """Populate a config section based on a string with key=value lists.
379 This is a toy/debug function, it doesn't handle any edge cases."""
380 for line
in content
.splitlines():
382 if line
== '' or line
.startswith('#'):
385 key
, value
= line
.split("=", 2)
386 except ValueError as err
:
387 raise ValueError("Cannot parse config line '%s'" % line
)
388 Config
.set(section
, key
.strip(), value
.strip())
393 do_cmd('/master/seek_ppqn', None, [int(ppqn
)])
395 def seek_samples(samples
):
396 do_cmd('/master/seek_samples', None, [int(samples
)])
398 def set_tempo(tempo
):
399 do_cmd('/master/set_tempo', None, [float(tempo
)])
401 def set_timesig(nom
, denom
):
402 do_cmd('/master/set_timesig', None, [int(nom
), int(denom
)])
404 def set_ppqn_factor(factor
):
405 do_cmd('/master/set_ppqn_factor', None, [int(factor
)])
408 do_cmd('/master/play', None, [])
411 do_cmd('/master/stop', None, [])
414 do_cmd('/master/panic', None, [])
417 return GetThings("/master/status", ['pos', 'pos_ppqn', 'tempo', 'timesig', 'sample_rate', 'playing', 'ppqn_factor'], [])
420 return GetThings("/master/tell", ['pos', 'pos_ppqn', 'playing'], [])
422 def ppqn_to_samples(pos_ppqn
):
423 return get_thing("/master/ppqn_to_samples", '/value', int, pos_ppqn
)
425 def samples_to_ppqn(pos_samples
):
426 return get_thing("/master/samples_to_ppqn", '/value', int, pos_samples
)
428 # Currently responsible for both JACK and USB I/O - not all functionality is
431 AUDIO_TYPE
= "32 bit float mono audio"
432 MIDI_TYPE
= "8 bit raw midi"
435 PORT_IS_PHYSICAL
= 0x4
436 PORT_CAN_MONITOR
= 0x8
437 PORT_IS_TERMINAL
= 0x10
440 # Some of these only make sense for
441 return GetThings("/io/status", ['client_type', 'client_name', 'audio_inputs', 'audio_outputs', 'buffer_size', '*midi_output', '*midi_input', 'sample_rate', 'output_resolution', '*usb_midi_input', '*usb_midi_output'], [])
443 def create_midi_input(name
, autoconnect_spec
= None):
444 uuid
= GetUUID("/io/create_midi_input", name
).uuid
445 if autoconnect_spec
is not None and autoconnect_spec
!= '':
446 JackIO
.autoconnect(uuid
, autoconnect_spec
)
449 def create_midi_output(name
, autoconnect_spec
= None):
450 uuid
= GetUUID("/io/create_midi_output", name
).uuid
451 if autoconnect_spec
is not None and autoconnect_spec
!= '':
452 JackIO
.autoconnect(uuid
, autoconnect_spec
)
455 def autoconnect_midi_output(uuid
, autoconnect_spec
= None):
456 if autoconnect_spec
is not None:
457 do_cmd("/io/autoconnect", None, [uuid
, autoconnect_spec
])
459 do_cmd("/io/autoconnect", None, [uuid
, ''])
460 autoconnect_midi_input
= autoconnect_midi_output
462 def rename_midi_output(uuid
, new_name
):
463 do_cmd("/io/rename_midi_port", None, [uuid
, new_name
])
464 rename_midi_input
= rename_midi_output
466 def disconnect_midi_output(uuid
):
467 do_cmd("/io/disconnect_midi_port", None, [uuid
])
468 disconnect_midi_input
= disconnect_midi_output
470 def disconnect_midi_output(uuid
):
471 do_cmd("/io/disconnect_midi_output", None, [uuid
])
473 def delete_midi_input(uuid
):
474 do_cmd("/io/delete_midi_input", None, [uuid
])
476 def delete_midi_output(uuid
):
477 do_cmd("/io/delete_midi_output", None, [uuid
])
479 def route_midi_input(input_uuid
, scene_uuid
):
480 do_cmd("/io/route_midi_input", None, [input_uuid
, scene_uuid
])
482 def set_appsink_for_midi_input(input_uuid
, enabled
):
483 do_cmd("/io/set_appsink_for_midi_input", None, [input_uuid
, 1 if enabled
else 0])
485 def get_new_events(input_uuid
):
487 do_cmd("/io/get_new_events", (lambda cmd
, fb
, args
: seq
.append((cmd
, fb
, args
))), [input_uuid
])
490 def port_connect(pfrom
, pto
):
491 do_cmd("/io/port_connect", None, [pfrom
, pto
])
493 def port_disconnect(pfrom
, pto
):
494 do_cmd("/io/port_disconnect", None, [pfrom
, pto
])
496 def get_ports(name_mask
= ".*", type_mask
= ".*", flag_mask
= 0):
497 return get_thing("/io/get_ports", '/port', [str], name_mask
, type_mask
, int(flag_mask
))
499 def get_connected_ports(port
):
500 return get_thing("/io/get_connected_ports", '/port', [str], port
)
502 def call_on_idle(callback
= None):
503 do_cmd("/on_idle", callback
, [])
505 def get_new_events():
507 do_cmd("/on_idle", (lambda cmd
, fb
, args
: seq
.append((cmd
, fb
, args
))), [])
510 def send_midi_event(*data
, output
= None):
511 do_cmd('/send_event_to', None, [output
if output
is not None else ''] + list(data
))
513 def send_sysex(data
, output
= None):
514 do_cmd('/send_sysex_to', None, [output
if output
is not None else '', bytearray(data
)])
517 def __init__(self
, name
):
520 def __getitem__(self
, key
):
521 return Config
.get(self
.name
, key
)
523 def __setitem__(self
, key
, value
):
524 Config
.set(self
.name
, key
, value
)
526 def __delitem__(self
, key
):
527 Config
.delete(self
.name
, key
)
529 def keys(self
, prefix
= ""):
530 return Config
.keys(self
.name
, prefix
)
536 pat_data
= get_thing("/get_pattern", '/pattern', (bytes
, int))
537 if pat_data
is not None:
538 pat_blob
, length
= pat_data
541 while ofs
< len(pat_blob
):
542 data
= list(struct
.unpack_from("iBBbb", pat_blob
, ofs
))
544 pat_data
.append(tuple(data
))
546 return pat_data
, length
550 def serialize_event(time
, *data
):
551 if len(data
) >= 1 and len(data
) <= 3:
552 return struct
.pack("iBBbb"[0:2 + len(data
)], int(time
), len(data
), *[int(v
) for v
in data
])
553 raise ValueError("Invalid length of an event (%d)" % len(data
))
556 """Document singleton."""
561 """Print all objects in the documents to stdout. Only used for debugging."""
562 do_cmd("/doc/dump", None, [])
564 def uuid_cmd(uuid
, cmd
):
565 """Internal: execute a given request on an object with specific UUID."""
566 return "/doc/uuid/%s%s" % (uuid
, cmd
)
569 """Internal: retrieve an UUID of an object that has specified path."""
570 return GetUUID('%s/get_uuid' % path
).uuid
572 def map_path(path
, *args
):
573 """Internal: return an object corresponding to a path"""
574 return Document
.map_uuid(Document
.get_uuid(path
))
576 def cmd_makeobj(cmd
, *args
):
577 """Internal: create an object from the UUID result of a command"""
578 return Document
.map_uuid(GetUUID(cmd
, *args
).uuid
)
580 def get_obj_class(uuid
):
581 """Internal: retrieve an internal class type of an object that has specified path."""
582 return get_thing(Document
.uuid_cmd(uuid
, "/get_class_name"), '/class_name', str)
585 """Retrieve the current song object of a given document. Each document can
586 only have one current song."""
587 return Document
.map_path("/song")
590 """Retrieve the first scene object of a default engine. This function
591 is considered obsolete-ish, because of multiple scene support."""
592 return Document
.map_path("/scene")
595 """Retrieve the current RT engine object of a given document. Each document can
596 only have one current RT engine."""
597 return Document
.map_path("/rt/engine")
600 """Retrieve the RT singleton. RT is an object used to communicate between
601 realtime and user thread, and is currently also used to access the audio
603 return Document
.map_path("/rt")
605 def new_engine(srate
, bufsize
):
606 """Create a new off-line engine object. This new engine object cannot be used for
607 audio playback - that's only allowed for default engine."""
608 return Document
.cmd_makeobj('/new_engine', int(srate
), int(bufsize
))
611 """Create or retrieve a Python-side accessor proxy for a C-side object."""
614 if uuid
in Document
.objmap
:
615 return Document
.objmap
[uuid
]
617 oclass
= Document
.get_obj_class(uuid
)
618 except Exception as e
:
619 print ("Note: Cannot get class for " + uuid
)
622 o
= Document
.classmap
[oclass
](uuid
)
623 Document
.objmap
[uuid
] = o
624 if hasattr(o
, 'init_object'):
628 def map_uuid_and_check(uuid
, t
):
629 o
= Document
.map_uuid(uuid
)
630 if not isinstance(o
, t
):
631 raise TypeError("UUID %s is of type %s, expected %s" % (uuid
, o
.__class
__, t
))
634 class DocPattern(DocObj
):
639 def __init__(self
, uuid
):
640 DocObj
.__init
__(self
, uuid
)
641 def set_name(self
, name
):
642 self
.cmd("/name", None, name
)
643 Document
.classmap
['cbox_midi_pattern'] = DocPattern
646 def __init__(self
, pos
, offset
, length
, pattern
, clip
):
650 self
.pattern
= Document
.map_uuid(pattern
)
651 self
.clip
= Document
.map_uuid(clip
)
653 return "pos=%d offset=%d length=%d pattern=%s clip=%s" % (self
.pos
, self
.offset
, self
.length
, self
.pattern
.uuid
, self
.clip
.uuid
)
654 def __eq__(self
, other
):
655 return str(self
) == str(other
)
657 class DocTrackClip(DocObj
):
663 def __init__(self
, uuid
):
664 DocObj
.__init
__(self
, uuid
)
665 Document
.classmap
['cbox_track_item'] = DocTrackClip
667 class DocTrack(DocObj
):
670 name
= SettableProperty(str)
671 external_output
= SettableProperty(str)
672 def add_clip(self
, pos
, offset
, length
, pattern
):
673 return self
.cmd_makeobj("/add_clip", int(pos
), int(offset
), int(length
), pattern
.uuid
)
674 Document
.classmap
['cbox_track'] = DocTrack
677 def __init__(self
, name
, count
, track
):
680 self
.track
= Document
.map_uuid(track
)
683 def __init__(self
, name
, length
, pattern
):
686 self
.pattern
= Document
.map_uuid(pattern
)
689 def __init__(self
, pos
, tempo
, timesig_nom
, timesig_denom
):
692 self
.timesig_nom
= timesig_nom
693 self
.timesig_denom
= timesig_denom
695 return self
.pos
== o
.pos
and self
.tempo
== o
.tempo
and self
.timesig_nom
== o
.timesig_nom
and self
.timesig_denom
== o
.timesig_denom
701 class DocSong(DocObj
):
704 patterns
= [PatternItem
]
709 return self
.cmd("/clear", None)
710 def set_loop(self
, ls
, le
):
711 return self
.cmd("/set_loop", None, int(ls
), int(le
))
712 def set_mti(self
, pos
, tempo
= None, timesig_nom
= None, timesig_denom
= None):
713 self
.cmd("/set_mti", None, int(pos
), float(tempo
) if tempo
is not None else -1.0, int(timesig_nom
) if timesig_nom
is not None else -1, int(timesig_denom
) if timesig_denom
else -1)
715 return self
.cmd_makeobj("/add_track")
716 def load_drum_pattern(self
, name
):
717 return self
.cmd_makeobj("/load_pattern", name
, 1)
718 def load_drum_track(self
, name
):
719 return self
.cmd_makeobj("/load_track", name
, 1)
720 def pattern_from_blob(self
, blob
, length
):
721 return self
.cmd_makeobj("/load_blob", bytearray(blob
), int(length
))
722 def loop_single_pattern(self
, loader
):
724 track
= self
.add_track()
726 length
= pat
.status().loop_end
727 track
.add_clip(0, 0, length
, pat
)
728 self
.set_loop(0, length
)
729 self
.update_playback()
730 def update_playback(self
):
731 # XXXKF Maybe make it a song-level API instead of global
732 do_cmd("/update_playback", None, [])
733 Document
.classmap
['cbox_song'] = DocSong
735 class UnknownModule(NonDocObj
):
739 class EffectSlot(NonDocObj
):
741 insert_preset
= SettableProperty(str)
742 insert_engine
= SettableProperty(str)
743 bypass
= SettableProperty(bool)
744 def init_object(self
):
745 # XXXKF add wrapper classes for effect engines
746 self
.engine
= UnknownModule(self
.path
+ "/engine")
748 class InstrumentOutput(EffectSlot
):
749 class Status(EffectSlot
.Status
):
754 class DocInstrument(DocObj
):
760 def init_object(self
):
763 if engine
in engine_classes
:
764 self
.engine
= engine_classes
[engine
]("/doc/uuid/" + self
.uuid
+ "/engine")
765 self
.output_slots
= []
766 for i
in range(s
.outputs
):
767 io
= InstrumentOutput(self
.make_path('/output/%d' % (i
+ 1)))
769 self
.output_slots
.append(io
)
770 def move_to(self
, target_scene
, pos
= 0):
771 return self
.cmd_makeobj("/move_to", target_scene
.uuid
, pos
+ 1)
772 def get_output_slot(self
, slot
):
773 return self
.output_slots
[slot
]
774 Document
.classmap
['cbox_instrument'] = DocInstrument
776 class DocLayer(DocObj
):
779 instrument_name
= str
780 instrument
= AltPropName('/instrument_uuid', DocInstrument
)
781 enable
= SettableProperty(bool)
782 low_note
= SettableProperty(int)
783 high_note
= SettableProperty(int)
784 fixed_note
= SettableProperty(int)
785 in_channel
= SettableProperty(int)
786 out_channel
= SettableProperty(int)
787 disable_aftertouch
= SettableProperty(bool)
788 invert_sustain
= SettableProperty(bool)
789 consume
= SettableProperty(bool)
790 ignore_scene_transpose
= SettableProperty(bool)
791 ignore_program_changes
= SettableProperty(bool)
792 transpose
= SettableProperty(int)
793 def get_instrument(self
):
794 return self
.status().instrument
795 Document
.classmap
['cbox_layer'] = DocLayer
797 class SamplerEngine(NonDocObj
):
798 class Status(object):
799 """Maximum number of voices playing at the same time."""
801 """Current number of voices playing."""
803 """Current number of disk streams."""
805 """GM volume (14-bit) per MIDI channel."""
807 """GM pan (14-bit) per MIDI channel."""
809 """Output offset per MIDI channel."""
811 """Current number of voices playing per MIDI channel."""
812 channel_voices
= AltPropName('/channel_voices', {int:int})
813 """MIDI channel -> (program number, program name)"""
814 patches
= {int:(int, str)}
816 def load_patch_from_cfg(self
, patch_no
, cfg_section
, display_name
):
817 """Load a sampler program from an 'spgm:' config section."""
818 return self
.cmd_makeobj("/load_patch", int(patch_no
), cfg_section
, display_name
)
820 def load_patch_from_string(self
, patch_no
, sample_dir
, sfz_data
, display_name
):
821 """Load a sampler program from a string, using given filesystem path for sample directory."""
822 return self
.cmd_makeobj("/load_patch_from_string", int(patch_no
), sample_dir
, sfz_data
, display_name
)
824 def load_patch_from_file(self
, patch_no
, sfz_name
, display_name
):
825 """Load a sampler program from a filesystem file."""
826 return self
.cmd_makeobj("/load_patch_from_file", int(patch_no
), sfz_name
, display_name
)
828 def load_patch_from_tar(self
, patch_no
, tar_name
, sfz_name
, display_name
):
829 """Load a sampler program from a tar file."""
830 return self
.cmd_makeobj("/load_patch_from_file", int(patch_no
), "sbtar:%s;%s" % (tar_name
, sfz_name
), display_name
)
832 def set_patch(self
, channel
, patch_no
):
833 """Select patch identified by patch_no in a specified MIDI channel."""
834 self
.cmd("/set_patch", None, int(channel
), int(patch_no
))
835 def set_output(self
, channel
, output
):
836 """Set output offset value in a specified MIDI channel."""
837 self
.cmd("/set_output", None, int(channel
), int(output
))
838 def get_unused_program(self
):
839 """Returns first program number that has no program associated with it."""
840 return self
.get_thing("/get_unused_program", '/program_no', int)
841 def set_polyphony(self
, polyphony
):
842 """Set a maximum number of voices that can be played at a given time."""
843 self
.cmd("/polyphony", None, int(polyphony
))
844 def get_patches(self
):
845 """Return a map of program identifiers to program objects."""
846 return self
.get_thing("/patches", '/patch', {int : (str, SamplerProgram
, int)})
848 class FluidsynthEngine(NonDocObj
):
853 def load_soundfont(self
, filename
):
854 return self
.cmd_makeobj("/load_soundfont", filename
)
855 def set_patch(self
, channel
, patch_no
):
856 self
.cmd("/set_patch", None, int(channel
), int(patch_no
))
857 def set_polyphony(self
, polyphony
):
858 self
.cmd("/polyphony", None, int(polyphony
))
859 def get_patches(self
):
860 return self
.get_thing("/patches", '/patch', [str])
862 class StreamPlayerEngine(NonDocObj
):
872 def seek(self
, place
):
873 self
.cmd('/seek', None, int(place
))
874 def load(self
, filename
, loop_start
= -1):
875 self
.cmd('/load', None, filename
, int(loop_start
))
879 class TonewheelOrganEngine(NonDocObj
):
881 upper_drawbar
= SettableProperty({int: int})
882 lower_drawbar
= SettableProperty({int: int})
883 pedal_drawbar
= SettableProperty({int: int})
884 upper_vibrato
= SettableProperty(bool)
885 lower_vibrato
= SettableProperty(bool)
886 vibrato_mode
= SettableProperty(int)
887 vibrato_chorus
= SettableProperty(int)
888 percussion_enable
= SettableProperty(bool)
889 percussion_3rd
= SettableProperty(bool)
892 'sampler' : SamplerEngine
,
893 'fluidsynth' : FluidsynthEngine
,
894 'stream_player' : StreamPlayerEngine
,
895 'tonewheel_organ' : TonewheelOrganEngine
,
898 class DocAuxBus(DocObj
):
901 def init_object(self
):
902 self
.slot
= EffectSlot("/doc/uuid/" + self
.uuid
+ "/slot")
903 self
.slot
.init_object()
905 Document
.classmap
['cbox_aux_bus'] = DocAuxBus
907 class DocScene(DocObj
):
913 instruments
= {str: (str, DocInstrument
)}
914 auxes
= {str: DocAuxBus
}
915 enable_default_song_input
= SettableProperty(bool)
916 enable_default_external_input
= SettableProperty(bool)
918 self
.cmd("/clear", None)
919 def load(self
, name
):
920 self
.cmd("/load", None, name
)
921 def load_aux(self
, aux
):
922 return self
.cmd_makeobj("/load_aux", aux
)
923 def delete_aux(self
, aux
):
924 return self
.cmd("/delete_aux", None, aux
)
925 def delete_layer(self
, pos
):
926 self
.cmd("/delete_layer", None, int(1 + pos
))
927 def move_layer(self
, old_pos
, new_pos
):
928 self
.cmd("/move_layer", None, int(old_pos
+ 1), int(new_pos
+ 1))
930 def add_layer(self
, aux
, pos
= None):
932 return self
.cmd_makeobj("/add_layer", 0, aux
)
934 # Note: The positions in high-level API are zero-based.
935 return self
.cmd_makeobj("/add_layer", int(1 + pos
), aux
)
936 def add_instrument_layer(self
, name
, pos
= None):
938 return self
.cmd_makeobj("/add_instrument_layer", 0, name
)
940 return self
.cmd_makeobj("/add_instrument_layer", int(1 + pos
), name
)
941 def add_new_instrument_layer(self
, name
, engine
, pos
= None):
943 return self
.cmd_makeobj("/add_new_instrument_layer", 0, name
, engine
)
945 return self
.cmd_makeobj("/add_new_instrument_layer", int(1 + pos
), name
, engine
)
946 def send_midi_event(self
, *data
):
947 self
.cmd('/send_event', None, *data
)
948 def play_pattern(self
, pattern
, tempo
, id = 0):
949 self
.cmd('/play_pattern', None, pattern
.uuid
, float(tempo
), int(id))
950 Document
.classmap
['cbox_scene'] = DocScene
954 audio_channels
= (int, int)
956 Document
.classmap
['cbox_rt'] = DocRt
958 class DocModule(DocObj
):
961 Document
.classmap
['cbox_module'] = DocModule
963 class DocEngine(DocObj
):
965 scenes
= AltPropName('/scene', [DocScene
])
966 def init_object(self
):
967 self
.master_effect
= EffectSlot(self
.path
+ "/master_effect")
968 self
.master_effect
.init_object()
970 return self
.cmd_makeobj('/new_scene')
971 def new_recorder(self
, filename
):
972 return self
.cmd_makeobj("/new_recorder", filename
)
973 def render_stereo(self
, samples
):
974 return self
.get_thing("/render_stereo", '/data', bytes
, samples
)
975 Document
.classmap
['cbox_engine'] = DocEngine
977 class DocRecorder(DocObj
):
980 Document
.classmap
['cbox_recorder'] = DocRecorder
982 class SamplerProgram(DocObj
):
989 def get_regions(self
):
990 return self
.get_thing("/regions", '/region', [SamplerLayer
])
991 def get_groups(self
):
992 g
= self
.get_things("/groups", ['*group', 'default_group'])
993 return [Document
.map_uuid(g
.default_group
)] + list(map(Document
.map_uuid
, g
.group
))
994 def get_control_inits(self
):
995 return self
.get_thing("/control_inits", '/control_init', [(int, int)])
997 return self
.cmd_makeobj("/new_group")
998 def add_control_init(self
, controller
, value
):
999 return self
.cmd("/add_control_init", None, controller
, value
)
1000 # which = -1 -> remove all controllers with that number from the list
1001 def delete_control_init(self
, controller
, which
= 0):
1002 return self
.cmd("/delete_control_init", None, controller
, which
)
1003 def load_file(self
, filename
, max_size
= -1):
1004 """Return an in-memory file corresponding to a given file inside sfbank.
1005 This can be used for things like scripts, images, descriptions etc."""
1006 data
= self
.get_thing("/load_file", '/data', bytes
, filename
, max_size
)
1009 return BytesIO(data
)
1010 def clone_to(self
, dest_module
, prog_index
):
1011 return self
.cmd_makeobj('/clone_to', dest_module
.uuid
, int(prog_index
))
1012 Document
.classmap
['sampler_program'] = SamplerProgram
1014 class SamplerLayer(DocObj
):
1016 parent_program
= SamplerProgram
1017 parent_group
= DocObj
1018 def get_children(self
):
1019 return self
.get_thing("/get_children", '/region', [SamplerLayer
])
1020 def as_string(self
):
1021 return self
.get_thing("/as_string", '/value', str)
1022 def as_string_full(self
):
1023 return self
.get_thing("/as_string_full", '/value', str)
1024 def set_param(self
, key
, value
):
1025 self
.cmd("/set_param", None, key
, str(value
))
1026 def new_region(self
):
1027 return self
.cmd_makeobj("/new_region")
1028 Document
.classmap
['sampler_layer'] = SamplerLayer