diff --git a/LO2ClipSlotComponent.py b/LO2ClipSlotComponent.py old mode 100644 new mode 100755 index 54cccde..4c095d6 --- a/LO2ClipSlotComponent.py +++ b/LO2ClipSlotComponent.py @@ -21,13 +21,25 @@ def wrap(self, *a, **kw): def __init__(self, tid, sid, *a, **k): self._track_id = tid self._scene_id = sid - + self._has_clip = 0 super(LO2ClipSlotComponent, self).__init__(*a, **k) self.set_default('_track_id', '_scene_id') - callbacks = {'color': 'color', 'name': 'name', 'warping': 'warping', 'looping': 'looping', 'loopstart': 'loop_start', 'loopend': 'loop_end', 'start': 'start_marker', 'end': 'end_marker', 'loopjump': 'loop_jump'} + callbacks = { + 'color': 'color', + 'name': 'name', + 'warping': 'warping', + 'looping': 'looping', + 'loopstart': 'loop_start', + 'loopend': 'loop_end', + 'start': 'start_marker', + 'end': 'end_marker', + 'loopjump': 'loop_jump', + 'muted': 'muted' + } + for n,p in callbacks.iteritems(): self.add_simple_callback('/live/clip/'+n, '_clip_slot.clip', p, self._is_clip, getattr(self, '_on_clip_'+n+'_changed')) @@ -35,6 +47,9 @@ def __init__(self, tid, sid, *a, **k): self.add_callback('/live/clip/stop', self._stop) self.add_callback('/live/clip/pitch', self._pitch) self.add_callback('/live/clip/select', self._view) + self.add_callback('/live/clip/notes', self._notes) + self.add_callback('/live/clip/notes/add', self._notes_add) + self.add_callback('/live/clip/notes/remove', self._notes_remove) @@ -50,6 +65,7 @@ def _lo2__update_clip_property_slots(self): self._on_clip_start_changed.subject = clip self._on_clip_end_changed.subject = clip self._on_clip_gain_changed.subject = clip + self._on_clip_muted_changed.subject = clip def _is_clip(self, msg): @@ -107,18 +123,31 @@ def _lo2__on_clip_playing_state_changed(self): def _send_state(self): if self._scene_id == -1: return - - state = int(self._clip_slot.has_clip) if self._clip_slot is not None else 0 - + + state = LO2ClipSlotComponent.compute_state(self._clip_slot) + name = '' + if self._clip_slot.has_clip: - c = self._clip_slot.clip + name = self._clip_slot.clip.name + + self.send('/live/clip/state', self._track_id, self._scene_id, state) + + if self._clip_slot.has_clip != self._has_clip: + self._has_clip = self._clip_slot.has_clip + self.send_default('/live/clip/name', name) + + @staticmethod + def compute_state(clip_slot): + state = int(clip_slot.has_clip) if clip_slot is not None else 0 + + if clip_slot.has_clip: + c = clip_slot.clip if c.is_playing: state = 2 if c.is_triggered: state = 3 - - self.send('/live/clip/state', self._track_id, self._scene_id, state) + return state def _lo2__on_clip_color_changed(self): @@ -161,6 +190,10 @@ def _on_clip_end_changed(self): @subject_slot('gain') def _on_clip_gain_changed(self): self.send_default('/live/clip/gain', self._clip_slot.clip.gain) + + @subject_slot('muted') + def _on_clip_muted_changed(self): + self.send_default('/live/clip/muted', self._clip_slot.clip.muted) @@ -202,3 +235,46 @@ def _view(self, msg, src): self.song().view.selected_scene = self.song().scenes[self._scene_id] + def _notes(self, msg, src): + if self._is_clip(msg): + c = self._clip_slot.clip + + if c.is_midi_clip and len(msg) == 8: + notes = c.get_notes(msg[4], msg[5], msg[6], msg[7]) + data = [] + data.append(self._track_id) + data.append(self._scene_id) + + for n in notes: + for p in n: + data.append(p) + self.send('/live/clip/notes', data) + + + def _notes_add(self, msg, src): + if self._is_clip(msg): + c = self._clip_slot.clip + if c.is_midi_clip and len(msg) >= 9: + param_count = len(msg) - 4 + if param_count % 5 != 0: + return # bad param count/format + + note_count = param_count / 5 + + notes_to_add = [] + for x in range(note_count): + notes_to_add.append( + (msg[x * 5 + 4], msg[x * 5 + 5], msg[x * 5 + 6], msg[x * 5 + 7], msg[x * 5 + 8]) + ) + + c.set_notes(tuple(notes_to_add)) + + + def _notes_remove(self, msg, src): + if self._is_clip(msg): + c = self._clip_slot.clip + if c.is_midi_clip and len(msg) == 8: + c.remove_notes(msg[4], msg[5], msg[6], msg[7]) + + + diff --git a/LO2MixerComponent.py b/LO2MixerComponent.py old mode 100644 new mode 100755 index 85cd10c..9d2e65e --- a/LO2MixerComponent.py +++ b/LO2MixerComponent.py @@ -59,10 +59,8 @@ def _lo2__on_return_tracks_changed(self): # Callbacks def _lo2_on_track_list_changed(self): - if len(self.song().tracks) != self._track_count: - self.log_message('/live/tracks:' + str(len(self.song().tracks))) - self.send('/live/tracks', len(self.song().tracks)) - self._track_count = len(self.song().tracks) + self._track_count = len(self.song().tracks) + self.send('/live/tracks', self._track_count) def _lo2_on_selected_track_changed(self): diff --git a/LO2OSC.py b/LO2OSC.py old mode 100644 new mode 100755 index 62d8d74..723ad9e --- a/LO2OSC.py +++ b/LO2OSC.py @@ -47,6 +47,9 @@ def error(self): def send(self, address, msg): oscmsg = OSC.OSCMessage(address, msg) + if len(oscmsg.error) > 0: + self.log_message('OSCMessage Error: ' + ''.join(oscmsg.error)) + self._socket.sendto(oscmsg.getBinary(), self._remote_addr) def send_message(self, message): diff --git a/LO2SessionComponent.py b/LO2SessionComponent.py index a3ac9e2..feb4a41 100644 --- a/LO2SessionComponent.py +++ b/LO2SessionComponent.py @@ -3,6 +3,7 @@ from LO2SceneComponent import LO2SceneComponent from LO2Mixin import LO2Mixin, wrap_init +from LO2ClipSlotComponent import LO2ClipSlotComponent class LO2SessionComponent(SessionComponent, LO2Mixin): @@ -22,6 +23,7 @@ def __init__(self, *args, **kwargs): self.add_callback('/live/scene/name/block', self._scene_name_block) self.add_callback('/live/clip/name/block', self._clip_name_block) + self.add_callback('/live/clip/state/block', self._clip_state_block) self.add_function_callback('/live/scenes', self._lo2_on_scene_list_changed) @@ -59,9 +61,7 @@ def _reassign_scenes(self): # Listeners def _lo2_on_scene_list_changed(self): - if len(self.song().scenes) != self._scenes_count: - self.send('/live/scenes', len(self.song().scenes)) - self._scenes_count = len(self.song().scenes) + self.send('/live/scenes', len(self.song().scenes)) def _lo2_on_selected_scene_changed(self): @@ -104,19 +104,39 @@ def _clip_name_block(self, msg, src): """ Gets a block of clip names """ b = [] - for i in range(msg[2], msg[2]+msg[3]): + for i in range(msg[3], msg[3]+msg[5]): if i < len(self._scenes): - s = self.scene[i] - for j in range(msg[4], msg[4]+msg[5]): + s = self.scene(i) + for j in range(msg[2], msg[2]+msg[4]): if j < len(s._clip_slots): - c = s.clip_slots(j) - b.append(i, j, c.clip_name) + c = s.clip_slot(j) + b.append(str(c.clip_name)) else: - b.append(i, j, '') + b.append('') else: - b.append(i, j, '') - + b.append('') + self.send('/live/clip/name/block', b) - - + + + def _clip_state_block(self, msg, src): + """ Gets a block of clip states + """ + b = [] + + for i in range(msg[3], msg[3]+msg[5]): + if i < len(self._scenes): + s = self.scene(i) + for j in range(msg[2], msg[2]+msg[4]): + if j < len(s._clip_slots): + c = s.clip_slot(j) + b.append(LO2ClipSlotComponent.compute_state(c._clip_slot)) + else: + b.append(-1) + else: + b.append(-1) + + self.send('/live/clip/state/block', b) + + diff --git a/LO2TransportComponent.py b/LO2TransportComponent.py index a6f38e2..8e5744f 100644 --- a/LO2TransportComponent.py +++ b/LO2TransportComponent.py @@ -14,6 +14,7 @@ def __init__(self, *a, **kw): self._on_signature_numerator_changed.subject = s self._on_signature_denominator_changed.subject = s self._on_tempo_changed.subject = s + self._on_playing_changed.subject = s self.add_default_callback('/live/tempo', s, 'tempo', float) self.add_default_callback('/live/time', s, 'current_song_time', float) @@ -22,6 +23,10 @@ def __init__(self, *a, **kw): self.add_function_callback('/live/cue/next', s.jump_to_next_cue) self.add_function_callback('/live/cue/prev', s.jump_to_prev_cue) + self.add_callback('/live/cue', self._cue) + self.add_callback('/live/cue/jump', self._cue_jump) + + self.add_callback('/live/jump', self._jump) self.add_function_callback('/live/play', s.start_playing) self.add_function_callback('/live/play/continue', s.continue_playing) @@ -123,4 +128,25 @@ def _del_clip(self, msg, src): if msg[3] < len(self.song().scenes): c = tr.clip_slots[msg[3]] if c.has_clip: - c.delete_clip(msg[4]) \ No newline at end of file + c.delete_clip(msg[4]) + + def _cue(self, msg, src): + data = [] + for c in sorted(self.song().cue_points, key = lambda cue: cue.time): + data.append(c.name) + data.append(c.time) + + self.send('/live/cue', data) + + def _cue_jump(self, msg, src): + self.log_message('jump to: ' + msg[2]) + for c in sorted(self.song().cue_points, key = lambda cue: cue.time): + if c.name == msg[2]: + c.jump() + return + + def _jump(self, msg, src): + if len(msg) == 3: + self.song().jump_by(msg[2]) + + diff --git a/LiveOSC2.py b/LiveOSC2.py old mode 100644 new mode 100755 index 9af68bc..f0e65de --- a/LiveOSC2.py +++ b/LiveOSC2.py @@ -27,6 +27,10 @@ def __init__(self, c_instance): self._session = LO2SessionComponent(1,1) self._session.set_mixer(self._mixer) self._transport = LO2TransportComponent() + + self._mixin = LO2Mixin() + self._c_instance = c_instance + self._mixin.add_callback('/live/selection', self._live_selection) self.parse() @@ -41,4 +45,8 @@ def disconnect(self): def parse(self): self.osc_handler.process() - self.schedule_message(1, self.parse) \ No newline at end of file + self.schedule_message(1, self.parse) + + + def _live_selection(self, msg, src): + self._c_instance.set_session_highlight(msg[2], msg[3], msg[4], msg[5], 0) diff --git a/OSC.py b/OSC.py old mode 100644 new mode 100755 index ca9a842..4747542 --- a/OSC.py +++ b/OSC.py @@ -60,17 +60,25 @@ def __init__(self, address='', msg=()): self.address = address self.typetags = "," self.message = "" + self.error = [] if type(msg) in (str, int, float, bool, unicode): self.append(msg) elif type(msg) in (list,tuple): for m in msg: - if type(m) not in (str,int,float, bool, unicode): - #log("don't know how to encode message element " + str(m) + " " + str(type(m))) + if type(m) in (list,tuple): + for me in m: + if type(me) not in (str, int, float, bool, unicode): + self.error.append("don't know how to encode message element element " + str(m) + " " + str(type(m))) + return + self.append(me) + continue + if type(m) not in (str, int, float, bool, unicode): + self.error.append("don't know how to encode message element " + str(m) + " " + str(type(m))) return self.append(m) else: - #log("don't know how to encode message " + str(m) + " " + str(type(m))) + self.error.append ("don't know how to encode message " + str(m) + " " + str(type(m))) return def append(self, argument, typehint = None): diff --git a/README.md b/README.md index 48b8460..d86de4a 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,12 @@ groove cue points set / delete cue +/live/cue + Gets all the cue points in the song, sorted by time in the format (float beat_time, string name) + +/live/cue/jump (string name) + Jumps to the first occurrence of a cue point with the provided name + /live/cue/next /live/cue/prev Jumps to the next and previous cue points in arrangement view @@ -61,6 +67,8 @@ set / delete cue /live/play/select Starts playing the current selection in arrangement view +/live/jump (float beats) + Instantly jumps ahead n beats /live/undo /live/redo @@ -192,6 +200,8 @@ Clips /live/clip/state (int track_id, int scene_id) +/live/clip/state/block (int track_id, int scene_id, int width, int height) + /live/clip/play (int track_id, int scene_id) /live/clip/stop (int track_id, int scene_id) @@ -204,6 +214,26 @@ Clips /live/clip/color (int track_id, int scene_id) +/live/clip/muted (int track_id, int scene_id, int muted) + +/live/clip/notes (int track_id, int scene_id, float start_time, int start_pitch, float time_length, int pitch_length) + +/live/clip/notes/add (int track_id, int scene_id, int pitch, float time, float duration, int velocity, int mute) +``` +pitch is from 0 to 127 +time is in beats (0 is beat 1, 1 is beat 2, 0.5 is halfway between the two) +duration is in beats (0.25 is a quarter beat, 0.5 half beat, 1 whole beat, ...) +velocity is 0 127 +mutes is 0 or 1 +``` + +Example adding multiple notes + +``` +/live/clip/notes/add 0 0 0 0 0.5 100 0 12 0 0.5 100 0 24 0 0.5 100 0 +``` + +/live/clip/notes/remove (int track_id, int scene_id, float start_time, int start_pitch, float time_length, int pitch_length) /live/clip/looping (int track_id, int scene_id) /live/clip/loopstart (int track_id, int scene_id)