5 type_wrapper_debug
= False
7 ###############################################################################
8 # Ugly internals. Please skip this section for your own sanity.
9 ###############################################################################
12 """An object that calls a C layer command, receives a /uuid callback from it
13 and stores the passed UUID in its uuid attribute.
15 Example use: GetUUID('/command', arg1, arg2...).uuid
17 def __init__(self
, cmd
, *cmd_args
):
18 def callback(cmd
, fb
, args
):
19 if cmd
== "/uuid" and len(args
) == 1:
22 raise ValueException("Unexpected callback: %s" % cmd
)
23 self
.callback
= callback
25 do_cmd(cmd
, self
, list(cmd_args
))
26 def __call__(self
, *args
):
30 """A generic callback object that receives various forms of information from
31 C layer and converts then into object's Python attributes.
33 This is an obsolete interface, to be replaced by GetUUID or metaclass
34 based type-safe autoconverter. However, there are still some cases that
35 aren't (yet) handled by either.
38 def by_uuid(uuid
, cmd
, anames
, args
):
39 return GetThings(Document
.uuid_cmd(uuid
, cmd
), anames
, args
)
40 def __init__(self
, cmd
, anames
, args
):
43 setattr(self
, i
[1:], [])
44 elif i
.startswith("%"):
45 setattr(self
, i
[1:], {})
47 setattr(self
, i
, None)
50 def update_callback(cmd
, fb
, args
):
51 self
.seq
.append((cmd
, fb
, args
))
55 setattr(self
, cmd
, args
[0])
57 setattr(self
, cmd
, args
)
58 elif "*" + cmd
in anames
:
60 getattr(self
, cmd
).append(args
[0])
62 getattr(self
, cmd
).append(args
)
63 elif "%" + cmd
in anames
:
65 getattr(self
, cmd
)[args
[0]] = args
[1]
67 getattr(self
, cmd
)[args
[0]] = args
[1:]
69 setattr(self
, cmd
, args
[0])
70 do_cmd(cmd
, update_callback
, args
)
74 class PropertyDecorator(object):
75 """Abstract property decorator."""
76 def __init__(self
, base
):
80 def map_cmd(self
, cmd
):
83 class AltPropName(PropertyDecorator
):
84 """Command-name-changing property decorator. Binds a property to the
85 specified /path, different from the default one, which based on property name,
86 with -s and -es suffix removed for lists and dicts."""
87 def __init__(self
, alt_name
, base
):
88 PropertyDecorator
.__init
__(self
, base
)
89 self
.alt_name
= alt_name
90 def map_cmd(self
, cmd
):
92 def execute(self
, property, proptype
, klass
):
95 class SettableProperty(PropertyDecorator
):
96 """Decorator that creates a setter method for the property."""
97 def execute(self
, property, proptype
, klass
):
98 if type(proptype
) is dict:
99 setattr(klass
, 'set_' + property, lambda self
, key
, value
: self
.cmd('/' + property, None, key
, proptype(value
)))
100 elif type(proptype
) is bool:
101 setattr(klass
, 'set_' + property, lambda self
, value
: self
.cmd('/' + property, None, 1 if value
else 0))
103 setattr(klass
, 'set_' + property, lambda self
, value
: self
.cmd('/' + property, None, proptype(value
)))
105 def new_get_things(obj
, cmd
, settermap
, args
):
106 """Call C command with arguments 'args', populating a return object obj
107 using settermap to interpret callback commands and initialise the return
109 def update_callback(cmd2
, fb
, args2
):
111 if cmd2
in settermap
:
112 settermap
[cmd2
](obj
, args2
)
113 except Exception as error
:
114 traceback
.print_exc()
116 # Set initial values for the properties (None or empty dict/list)
117 for setterobj
in settermap
.values():
118 setattr(obj
, setterobj
.property, setterobj
.init_value())
119 # Call command and apply callback commands via setters to the object
120 do_cmd(cmd
, update_callback
, args
)
123 def _handle_object_wrapping(t
):
124 if type(t
) is CboxObjMetaclass
:
125 return lambda uuid
: Document
.map_uuid_and_check(uuid
, t
)
127 def _make_args_to_type_lambda(t
):
128 t
= _handle_object_wrapping(t
)
129 return lambda args
: t(*args
)
130 def _make_args_to_tuple_of_types_lambda(ts
):
131 ts
= list(map(_handle_object_wrapping
, ts
))
132 return lambda args
: tuple([ts
[i
](args
[i
]) for i
in range(max(len(ts
), len(args
)))])
134 class SetterWithConversion
:
135 """A setter object class that sets a specific property to a typed value or a tuple of typed value."""
136 def __init__(self
, property, extractor
):
137 self
.property = property
138 self
.extractor
= extractor
139 def init_value(self
):
141 def __call__(self
, obj
, args
):
142 # print ("Setting attr %s on object %s" % (self.property, obj))
143 setattr(obj
, self
.property, self
.extractor(args
))
145 class ListAdderWithConversion
:
146 """A setter object class that adds a tuple filled with type-converted arguments of the
147 callback to a list. E.g. ListAdderWithConversion('foo', (int, int))(obj, [1,2])
148 adds a tuple: (int(1), int(2)) to the list obj.foo"""
150 def __init__(self
, property, extractor
):
151 self
.property = property
152 self
.extractor
= extractor
153 def init_value(self
):
155 def __call__(self
, obj
, args
):
156 getattr(obj
, self
.property).append(self
.extractor(args
))
158 class DictAdderWithConversion
:
159 """A setter object class that adds a tuple filled with type-converted
160 arguments of the callback to a dictionary under a key passed as first argument
161 i.e. DictAdderWithConversion('foo', str, (int, int))(obj, ['bar',1,2]) adds
162 a tuple: (int(1), int(2)) under key 'bar' to obj.foo"""
164 def __init__(self
, property, keytype
, valueextractor
):
165 self
.property = property
166 self
.keytype
= keytype
167 self
.valueextractor
= valueextractor
168 def init_value(self
):
170 def __call__(self
, obj
, args
):
171 getattr(obj
, self
.property)[self
.keytype(args
[0])] = self
.valueextractor(args
[1:])
173 def _create_unmarshaller(name
, fields_to_unmarshall
, base_type
):
178 if type_wrapper_debug
:
179 print ("Wrapping type: %s" % name
)
181 for prop
in dir(fields_to_unmarshall
):
182 if prop
.startswith("__"):
184 value
= getattr(fields_to_unmarshall
, prop
)
187 if type(value
) is list or type(value
) is dict:
188 if propcmd
.endswith('s'):
189 if propcmd
.endswith('es'):
190 propcmd
= propcmd
[:-2]
192 propcmd
= propcmd
[:-1]
193 while isinstance(value
, PropertyDecorator
):
194 decorators
.append(value
)
195 propcmd
= value
.map_cmd(propcmd
)
196 value
= value
.get_base()
198 def _make_decoder(t
):
200 return _make_args_to_tuple_of_types_lambda(t
)
202 return _make_args_to_type_lambda(t
)
204 if type(value
) in [type, CboxObjMetaclass
, tuple]:
205 if type_wrapper_debug
:
206 print ("%s is type %s" % (prop
, repr(value
)))
207 status_fields
.append(prop
)
208 settermap
[propcmd
] = SetterWithConversion(prop
, _make_decoder(value
))
209 elif type(value
) is dict:
210 assert(len(value
) == 1)
211 value
= list(value
.items())[0]
212 if type_wrapper_debug
:
213 print ("%s is type: %s -> %s" % (prop
, repr(value
[0]), repr(value
[1])))
214 settermap
[propcmd
] = DictAdderWithConversion(prop
, value
[0], _make_decoder(value
[1]))
215 elif type(value
) is list:
216 assert(len(value
) == 1)
217 if type_wrapper_debug
:
218 print ("%s is array of %s" % (prop
, repr(value
)))
219 settermap
[propcmd
] = ListAdderWithConversion(prop
, _make_decoder(value
[0]))
221 raise ValueError("Don't know what to do with %s property '%s' of type %s" % (name
, prop
, repr(value
)))
222 all_decorators
[prop
] = decorators
223 prop_types
[prop
] = value
224 fields_to_unmarshall
.__str
__ = lambda self
: (str(name
) + ":" + " ".join(["%s=%s" % (v
.property, repr(getattr(self
, v
.property))) for v
in settermap
.values()]))
225 if type_wrapper_debug
:
227 cmdwrapper
= lambda cmd
: (lambda self
: new_get_things(base_type(), self
.path
+ cmd
, settermap
, []))
229 for propname
, decorators
in all_decorators
.items():
230 for decorator
in decorators
:
231 decorator
.execute(propname
, prop_types
[propname
], o
)
232 return exec_cmds
, cmdwrapper
234 class CboxObjMetaclass(type):
235 """Metaclass that creates Python wrapper classes for various C-side objects.
236 This class is responsible for automatically marshalling and type-checking/converting
237 fields of Status inner class on status() calls."""
238 def __new__(cls
, name
, bases
, namespace
, **kwds
):
239 status_class
= namespace
['Status']
240 classfinaliser
, cmdwrapper
= _create_unmarshaller(name
, status_class
, status_class
)
241 result
= type.__new
__(cls
, name
, bases
, namespace
, **kwds
)
242 classfinaliser(result
)
243 result
.status
= cmdwrapper('/status')
246 class NonDocObj(object, metaclass
= CboxObjMetaclass
):
247 """Root class for all wrapper classes that wrap objects that don't have
248 their own identity/UUID.
249 This covers various singletons and inner objects (e.g. engine in instrument)."""
252 def __init__(self
, path
):
255 def cmd(self
, cmd
, fb
= None, *args
):
256 do_cmd(self
.path
+ cmd
, fb
, list(args
))
258 def cmd_makeobj(self
, cmd
, *args
):
259 return Document
.map_uuid(GetUUID(self
.path
+ cmd
, *args
).uuid
)
261 def get_things(self
, cmd
, fields
, *args
):
262 return GetThings(self
.path
+ cmd
, fields
, list(args
))
264 def make_path(self
, path
):
265 return self
.path
+ path
267 class DocObj(NonDocObj
):
268 """Root class for all wrapper classes that wrap first-class document objects."""
271 def __init__(self
, uuid
):
272 NonDocObj
.__init
__(self
, Document
.uuid_cmd(uuid
, ''))
279 def __init__(self
, path
, args
= []):
282 def plus(self
, subpath
, *args
):
283 return VarPath(self
.path
if subpath
is None else self
.path
+ "/" + subpath
, self
.args
+ list(args
))
284 def set(self
, *values
):
285 do_cmd(self
.path
, None, self
.args
+ list(values
))
287 ###############################################################################
288 # And those are the proper user-accessible objects.
289 ###############################################################################
292 """INI file manipulation class."""
294 def sections(prefix
= ""):
295 """Return a list of configuration sections."""
296 return [CfgSection(name
) for name
in GetThings('/config/sections', ['*section'], [str(prefix
)]).section
]
299 def keys(section
, prefix
= ""):
300 """Return a list of configuration keys in a section, with optional prefix filtering."""
301 return GetThings('/config/keys', ['*key'], [str(section
), str(prefix
)]).key
304 def get(section
, key
):
305 """Return a string value of a given key."""
306 return GetThings('/config/get', ['value'], [str(section
), str(key
)]).value
309 def set(section
, key
, value
):
310 """Set a string value for a given key."""
311 do_cmd('/config/set', None, [str(section
), str(key
), str(value
)])
314 def delete(section
, key
):
315 """Delete a given key."""
316 do_cmd('/config/delete', None, [str(section
), str(key
)])
319 def save(filename
= None):
320 """Save config, either into current INI file or some other file."""
322 do_cmd('/config/save', None, [])
324 do_cmd('/config/save', None, [str(filename
)])
327 def add_section(section
, content
):
328 """Populate a config section based on a string with key=value lists.
329 This is a toy/debug function, it doesn't handle any edge cases."""
330 for line
in content
.splitlines():
332 if line
== '' or line
.startswith('#'):
335 key
, value
= line
.split("=", 2)
336 except ValueError as err
:
337 raise ValueError("Cannot parse config line '%s'" % line
)
338 Config
.set(section
, key
.strip(), value
.strip())
343 do_cmd('/master/seek_ppqn', None, [int(ppqn
)])
345 def seek_samples(samples
):
346 do_cmd('/master/seek_samples', None, [int(samples
)])
348 def set_tempo(tempo
):
349 do_cmd('/master/set_tempo', None, [float(tempo
)])
351 def set_timesig(nom
, denom
):
352 do_cmd('/master/set_timesig', None, [int(nom
), int(denom
)])
355 do_cmd('/master/play', None, [])
358 do_cmd('/master/stop', None, [])
361 do_cmd('/master/panic', None, [])
364 return GetThings("/master/status", ['pos', 'pos_ppqn', 'tempo', 'timesig', 'sample_rate', 'playing'], [])
367 return GetThings("/master/tell", ['pos', 'pos_ppqn', 'playing'], [])
369 def ppqn_to_samples(pos_ppqn
):
370 return GetThings("/master/ppqn_to_samples", ['value'], [pos_ppqn
]).value
372 def samples_to_ppqn(pos_samples
):
373 return GetThings("/master/samples_to_ppqn", ['value'], [pos_samples
]).value
375 # Currently responsible for both JACK and USB I/O - not all functionality is
378 AUDIO_TYPE
= "32 bit float mono audio"
379 MIDI_TYPE
= "8 bit raw midi"
382 PORT_IS_PHYSICAL
= 0x4
383 PORT_CAN_MONITOR
= 0x8
384 PORT_IS_TERMINAL
= 0x10
387 # Some of these only make sense for
388 return GetThings("/io/status", ['client_type', 'client_name', 'audio_inputs', 'audio_outputs', 'buffer_size', '*midi_output', '*midi_input', 'sample_rate', 'output_resolution'], [])
390 def create_midi_input(name
, autoconnect_spec
= None):
391 uuid
= GetUUID("/io/create_midi_input", name
).uuid
392 if autoconnect_spec
is not None and autoconnect_spec
!= '':
393 JackIO
.autoconnect(uuid
, autoconnect_spec
)
396 def create_midi_output(name
, autoconnect_spec
= None):
397 uuid
= GetUUID("/io/create_midi_output", name
).uuid
398 if autoconnect_spec
is not None and autoconnect_spec
!= '':
399 JackIO
.autoconnect(uuid
, autoconnect_spec
)
402 def autoconnect_midi_output(uuid
, autoconnect_spec
= None):
403 if autoconnect_spec
is not None:
404 do_cmd("/io/autoconnect", None, [uuid
, autoconnect_spec
])
406 do_cmd("/io/autoconnect", None, [uuid
, ''])
407 autoconnect_midi_input
= autoconnect_midi_output
409 def rename_midi_output(uuid
, new_name
):
410 do_cmd("/io/rename_midi_port", None, [uuid
, new_name
])
411 rename_midi_input
= rename_midi_output
413 def disconnect_midi_output(uuid
):
414 do_cmd("/io/disconnect_midi_port", None, [uuid
])
415 disconnect_midi_input
= disconnect_midi_output
417 def disconnect_midi_output(uuid
):
418 do_cmd("/io/disconnect_midi_output", None, [uuid
])
420 def delete_midi_input(uuid
):
421 do_cmd("/io/delete_midi_input", None, [uuid
])
423 def delete_midi_output(uuid
):
424 do_cmd("/io/delete_midi_output", None, [uuid
])
426 def port_connect(pfrom
, pto
):
427 do_cmd("/io/port_connect", None, [pfrom
, pto
])
429 def port_disconnect(pfrom
, pto
):
430 do_cmd("/io/port_disconnect", None, [pfrom
, pto
])
432 def get_ports(name_mask
= ".*", type_mask
= ".*", flag_mask
= 0):
433 return GetThings("/io/get_ports", ['*port'], [name_mask
, type_mask
, int(flag_mask
)]).port
435 def call_on_idle(callback
= None):
436 do_cmd("/on_idle", callback
, [])
438 def get_new_events():
439 return GetThings("/on_idle", ['seq'], []).seq
441 def send_midi_event(*data
, output
= None):
442 do_cmd('/send_event_to', None, [output
if output
is not None else ''] + list(data
))
445 def __init__(self
, name
):
448 def __getitem__(self
, key
):
449 return Config
.get(self
.name
, key
)
451 def __setitem__(self
, key
, value
):
452 Config
.set(self
.name
, key
, value
)
454 def __delitem__(self
, key
):
455 Config
.delete(self
.name
, key
)
457 def keys(self
, prefix
= ""):
458 return Config
.keys(self
.name
, prefix
)
464 pat_data
= GetThings("/get_pattern", ['pattern'], []).pattern
465 if pat_data
is not None:
466 pat_blob
, length
= pat_data
469 while ofs
< len(pat_blob
):
470 data
= list(struct
.unpack_from("iBBbb", pat_blob
, ofs
))
472 pat_data
.append(tuple(data
))
474 return pat_data
, length
478 def serialize_event(time
, *data
):
479 if len(data
) >= 1 and len(data
) <= 3:
480 return struct
.pack("iBBbb"[0:2 + len(data
)], int(time
), len(data
), *[int(v
) for v
in data
])
481 raise ValueError("Invalid length of an event (%d)" % len(data
))
484 """Document singleton."""
489 """Print all objects in the documents to stdout. Only used for debugging."""
490 do_cmd("/doc/dump", None, [])
492 def uuid_cmd(uuid
, cmd
):
493 """Internal: execute a given request on an object with specific UUID."""
494 return "/doc/uuid/%s%s" % (uuid
, cmd
)
497 """Internal: retrieve an UUID of an object that has specified path."""
498 return GetUUID('%s/get_uuid' % path
).uuid
500 def get_obj_class(uuid
):
501 """Internal: retrieve an internal class type of an object that has specified path."""
502 return GetThings(Document
.uuid_cmd(uuid
, "/get_class_name"), ["class_name"], []).class_name
505 """Retrieve the current song object of a given document. Each document can
506 only have one current song."""
507 return Document
.map_uuid(Document
.get_uuid("/song"))
510 """Retrieve the current scene object of a given document. Each document can
511 only have one current scene."""
512 return Document
.map_uuid(Document
.get_uuid("/scene"))
515 """Retrieve the RT singleton. RT is an object used to communicate between
516 realtime and user thread, and is currently also used to access the audio
518 return Document
.map_uuid(Document
.get_uuid("/rt"))
520 def new_scene(srate
, bufsize
):
521 """Create a new scene object. This new scene object cannot be used for
522 audio playback - that's only allowed for main document scene."""
523 return Document
.map_uuid(GetUUID('/new_scene', int(srate
), int(bufsize
)).uuid
)
526 """Create or retrieve a Python-side accessor proxy for a C-side object."""
527 if uuid
in Document
.objmap
:
528 return Document
.objmap
[uuid
]
530 oclass
= Document
.get_obj_class(uuid
)
531 except Exception as e
:
532 print ("Note: Cannot get class for " + uuid
)
535 o
= Document
.classmap
[oclass
](uuid
)
536 Document
.objmap
[uuid
] = o
537 if hasattr(o
, 'init_object'):
541 def map_uuid_and_check(uuid
, t
):
542 o
= Document
.map_uuid(uuid
)
543 if not isinstance(o
, t
):
544 raise TypeError("UUID %s is of type %s, expected %s" % (uuid
, o
.__class
__, t
))
547 class DocPattern(DocObj
):
552 def __init__(self
, uuid
):
553 DocObj
.__init
__(self
, uuid
)
554 def set_name(self
, name
):
555 self
.cmd("/name", None, name
)
556 Document
.classmap
['cbox_midi_pattern'] = DocPattern
559 def __init__(self
, pos
, offset
, length
, pattern
, clip
):
563 self
.pattern
= Document
.map_uuid(pattern
)
564 self
.clip
= Document
.map_uuid(clip
)
566 return "pos=%d offset=%d length=%d pattern=%s clip=%s" % (self
.pos
, self
.offset
, self
.length
, self
.pattern
.uuid
, self
.clip
.uuid
)
567 def __eq__(self
, other
):
568 return str(self
) == str(other
)
570 class DocTrackClip(DocObj
):
576 def __init__(self
, uuid
):
577 DocObj
.__init
__(self
, uuid
)
578 Document
.classmap
['cbox_track_item'] = DocTrackClip
580 class DocTrack(DocObj
):
583 name
= SettableProperty(str)
584 external_output
= SettableProperty(int)
585 def add_clip(self
, pos
, offset
, length
, pattern
):
586 return self
.cmd_makeobj("/add_clip", int(pos
), int(offset
), int(length
), pattern
.uuid
)
587 Document
.classmap
['cbox_track'] = DocTrack
590 def __init__(self
, name
, count
, track
):
593 self
.track
= Document
.map_uuid(track
)
596 def __init__(self
, name
, length
, pattern
):
599 self
.pattern
= Document
.map_uuid(pattern
)
602 def __init__(self
, pos
, tempo
, timesig_nom
, timesig_denom
):
605 self
.timesig_nom
= timesig_nom
606 self
.timesig_denom
= timesig_denom
608 return self
.pos
== o
.pos
and self
.tempo
== o
.tempo
and self
.timesig_nom
== o
.timesig_nom
and self
.timesig_denom
== o
.timesig_denom
614 class DocSong(DocObj
):
617 patterns
= [PatternItem
]
622 return self
.cmd("/clear", None)
623 def set_loop(self
, ls
, le
):
624 return self
.cmd("/set_loop", None, int(ls
), int(le
))
625 def set_mti(self
, pos
, tempo
= None, timesig_nom
= None, timesig_denom
= None):
626 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)
628 return self
.cmd_makeobj("/add_track")
629 def load_drum_pattern(self
, name
):
630 return self
.cmd_makeobj("/load_pattern", name
, 1)
631 def load_drum_track(self
, name
):
632 return self
.cmd_makeobj("/load_track", name
, 1)
633 def pattern_from_blob(self
, blob
, length
):
634 return self
.cmd_makeobj("/load_blob", bytearray(blob
), int(length
))
635 def loop_single_pattern(self
, loader
):
637 track
= self
.add_track()
639 length
= pat
.status().loop_end
640 track
.add_clip(0, 0, length
, pat
)
641 self
.set_loop(0, length
)
642 self
.update_playback()
643 def update_playback(self
):
644 # XXXKF Maybe make it a song-level API instead of global
645 do_cmd("/update_playback", None, [])
646 Document
.classmap
['cbox_song'] = DocSong
648 class DocInstrument(DocObj
):
654 def init_object(self
):
655 engine
= self
.status().engine
656 if engine
in engine_classes
:
657 self
.engine
= engine_classes
[engine
]("/doc/uuid/" + self
.uuid
+ "/engine")
658 Document
.classmap
['cbox_instrument'] = DocInstrument
660 class DocLayer(DocObj
):
663 instrument_name
= str
664 instrument
= AltPropName('/instrument_uuid', DocInstrument
)
665 enable
= SettableProperty(bool)
666 low_note
= SettableProperty(int)
667 high_note
= SettableProperty(int)
668 fixed_note
= SettableProperty(int)
669 in_channel
= SettableProperty(int)
670 out_channel
= SettableProperty(int)
671 disable_aftertouch
= SettableProperty(bool)
672 invert_sustain
= SettableProperty(bool)
673 consume
= SettableProperty(bool)
674 ignore_scene_transpose
= SettableProperty(bool)
675 ignore_program_changes
= SettableProperty(bool)
676 transpose
= SettableProperty(int)
677 def get_instrument(self
):
678 return self
.status().instrument
679 Document
.classmap
['cbox_layer'] = DocLayer
681 class SamplerEngine(NonDocObj
):
682 class Status(object):
683 """Maximum number of voices playing at the same time."""
685 """Current number of voices playing."""
687 """GM volume (14-bit) per MIDI channel."""
689 """GM pan (14-bit) per MIDI channel."""
691 """Current number of voices playing per MIDI channel."""
692 channel_voices
= AltPropName('/channel_voices', {int:int})
693 """MIDI channel -> (program number, program name)"""
694 patches
= {int:(int, str)}
696 def load_patch_from_cfg(self
, patch_no
, cfg_section
, display_name
):
697 """Load a sampler program from an 'spgm:' config section."""
698 return self
.cmd_makeobj("/load_patch", int(patch_no
), cfg_section
, display_name
)
700 def load_patch_from_string(self
, patch_no
, sample_dir
, sfz_data
, display_name
):
701 """Load a sampler program from a string, using given filesystem path for sample directory."""
702 return self
.cmd_makeobj("/load_patch_from_string", int(patch_no
), sample_dir
, sfz_data
, display_name
)
704 def load_patch_from_file(self
, patch_no
, sfz_name
, display_name
):
705 """Load a sampler program from a filesystem file."""
706 return self
.cmd_makeobj("/load_patch_from_file", int(patch_no
), sfz_name
, display_name
)
708 def set_patch(self
, channel
, patch_no
):
709 """Select patch identified by patch_no in a specified MIDI channel."""
710 self
.cmd("/set_patch", None, int(channel
), int(patch_no
))
711 def get_unused_program(self
):
712 """Returns first program number that has no program associated with it."""
713 return self
.get_things("/get_unused_program", ['program_no']).program_no
714 def set_polyphony(self
, polyphony
):
715 """Set a maximum number of voices that can be played at a given time."""
716 self
.cmd("/polyphony", None, int(polyphony
))
717 def get_patches(self
):
718 """Return a map of program identifiers to program objects."""
719 return self
.get_things("/patches", ['%patch']).patch
721 class FluidsynthEngine(NonDocObj
):
726 def load_soundfont(self
, filename
):
727 return self
.cmd_makeobj("/load_soundfont", filename
)
728 def set_patch(self
, channel
, patch_no
):
729 self
.cmd("/set_patch", None, int(channel
), int(patch_no
))
730 def set_polyphony(self
, polyphony
):
731 self
.cmd("/polyphony", None, int(polyphony
))
732 def get_patches(self
):
733 return self
.get_things("/patches", ['%patch']).patch
735 class StreamPlayerEngine(NonDocObj
):
745 def seek(self
, place
):
746 self
.cmd('/seek', None, int(place
))
747 def load(self
, filename
, loop_start
= -1):
748 self
.cmd('/load', None, filename
, int(loop_start
))
749 def unload(self
, filename
, loop_start
= -1):
752 class TonewheelOrganEngine(NonDocObj
):
754 upper_drawbar
= SettableProperty({int: int})
755 lower_drawbar
= SettableProperty({int: int})
756 pedal_drawbar
= SettableProperty({int: int})
757 upper_vibrato
= SettableProperty(bool)
758 lower_vibrato
= SettableProperty(bool)
759 vibrato_mode
= SettableProperty(int)
760 vibrato_chorus
= SettableProperty(int)
761 percussion_enable
= SettableProperty(bool)
762 percussion_3rd
= SettableProperty(bool)
765 'sampler' : SamplerEngine
,
766 'fluidsynth' : FluidsynthEngine
,
767 'stream_player' : StreamPlayerEngine
,
768 'tonewheel_organ' : TonewheelOrganEngine
,
771 class DocAuxBus(DocObj
):
774 def get_slot_engine(self
):
775 return self
.cmd_makeobj("/slot/engine/get_uuid")
776 def get_slot_status(self
):
777 return self
.get_things("/slot/status", ["insert_preset", "insert_engine"])
778 Document
.classmap
['cbox_aux_bus'] = DocAuxBus
780 class DocScene(DocObj
):
786 instruments
= {str: (str, DocInstrument
)}
787 auxes
= {str: (str, str, DocAuxBus
)}
789 self
.cmd("/clear", None)
790 def load(self
, name
):
791 self
.cmd("/load", None, name
)
792 def load_aux(self
, aux
):
793 return self
.cmd_makeobj("/load_aux", aux
)
794 def delete_aux(self
, aux
):
795 return self
.cmd("/delete_aux", None, aux
)
796 def delete_layer(self
, pos
):
797 self
.cmd("/delete_layer", None, int(1 + pos
))
798 def move_layer(self
, old_pos
, new_pos
):
799 self
.cmd("/move_layer", None, int(old_pos
+ 1), int(new_pos
+ 1))
801 def add_layer(self
, aux
, pos
= None):
803 return self
.cmd_makeobj("/add_layer", 0, aux
)
805 # Note: The positions in high-level API are zero-based.
806 return self
.cmd_makeobj("/add_layer", int(1 + pos
), aux
)
807 def add_instrument_layer(self
, name
, pos
= None):
809 return self
.cmd_makeobj("/add_instrument_layer", 0, name
)
811 return self
.cmd_makeobj("/add_instrument_layer", int(1 + pos
), name
)
812 def add_new_instrument_layer(self
, name
, engine
, pos
= None):
814 return self
.cmd_makeobj("/add_new_instrument_layer", 0, name
, engine
)
816 return self
.cmd_makeobj("/add_new_instrument_layer", int(1 + pos
), name
, engine
)
817 Document
.classmap
['cbox_scene'] = DocScene
821 audio_channels
= (int, int)
823 Document
.classmap
['cbox_rt'] = DocRt
825 class DocModule(DocObj
):
828 Document
.classmap
['cbox_module'] = DocModule
830 class SamplerProgram(DocObj
):
836 def get_regions(self
):
837 return map(Document
.map_uuid
, self
.get_things("/regions", ['*region']).region
)
838 def get_groups(self
):
839 g
= self
.get_things("/groups", ['*group', 'default_group'])
840 return [Document
.map_uuid(g
.default_group
)] + list(map(Document
.map_uuid
, g
.group
))
841 def get_control_inits(self
):
842 return self
.get_things("/control_inits", ['*control_init']).control_init
844 return self
.cmd_makeobj("/new_group")
845 def add_control_init(self
, controller
, value
):
846 return self
.cmd("/add_control_init", None, controller
, value
)
847 # which = -1 -> remove all controllers with that number from the list
848 def delete_control_init(self
, controller
, which
= 0):
849 return self
.cmd("/delete_control_init", None, controller
, which
)
850 Document
.classmap
['sampler_program'] = SamplerProgram
852 class SamplerLayer(DocObj
):
854 parent_program
= SamplerProgram
855 parent_group
= DocObj
856 def get_children(self
):
857 return map(Document
.map_uuid
, self
.get_things("/get_children", ['*region']).region
)
859 return self
.get_things("/as_string", ['value']).value
860 def as_string_full(self
):
861 return self
.get_things("/as_string_full", ['value']).value
862 def set_param(self
, key
, value
):
863 self
.cmd("/set_param", None, key
, str(value
))
864 def new_region(self
):
865 return self
.cmd_makeobj("/new_region")
866 Document
.classmap
['sampler_layer'] = SamplerLayer