diff --git a/README.md b/README.md index 12ad355..db4f4e9 100644 --- a/README.md +++ b/README.md @@ -348,7 +348,8 @@ Represents an audio or MIDI clip. Can be used to start/stop clips, and query/mod | /live/clip/fire | track_id, clip_id | | Start clip playback | | /live/clip/stop | track_id, clip_id | | Stop clip playback | | /live/clip/duplicate_loop | track_id, clip_id | | Duplicates clip loop | -| /live/clip/get/notes | track_id, clip_id | track_id, clip_id, pitch, start_time, duration, velocity, mute, [pitch, start_time...] | Query the notes in a given clip. | +| /live/clip/get/notes | track_id, clip_id, [from_time, from_pitch, time_span, pitch_span] | track_id, clip_id, pitch, start_time, duration, velocity, mute, [pitch, start_time...] | Query the notes in a given clip. | +| /live/clip/get/notes_range | track_id, clip_id, [from_time, from_pitch, time_span, pitch_span] | track_id, clip_id, min_start_time, max_end_time, min_pitch, max_pitch | Query the note range within a given clip. This is helpful in conjunction with /live/clip/get/notes to paginate and handle a large number of notes | | /live/clip/add/notes | track_id, clip_id, pitch, start_time, duration, velocity, mute, ... | | Add new MIDI notes to a clip. pitch is MIDI note index, start_time and duration are beats in floats, velocity is MIDI velocity index, mute is true/false | | /live/clip/remove/notes | start_pitch, pitch_span, start_time, time_span | | Remove notes from a clip in a given range of pitches and times. | | /live/clip/get/color | track_id, clip_id | track_id, clip_id, color | Get clip color | @@ -370,10 +371,14 @@ Represents an audio or MIDI clip. Can be used to start/stop clips, and query/mod | /live/clip/get/playing_position | track_id, clip_id | track_id, clip_id, playing_position | Get clip's playing position | | /live/clip/start_listen/playing_position | track_id, clip_id | | Start listening for clip's playing position. Replies are sent to /live/clip/get/playing_position, with args: track_id, clip_id, playing_position | | /live/clip/stop_listen/playing_position | track_id, clip_id | | Stop listening for clip's playing position. | -| /live/clip/get/loop_start | track_id, clip_id | track_id, clip_id, loop_start | Get clip's loop start | -| /live/clip/set/loop_start | track_id, clip_id, loop_start | track_id, clip_id, loop_start | Set clip's loop start | -| /live/clip/get/loop_end | track_id, clip_id | track_id, clip_id, loop_end | Get clip's loop end | -| /live/clip/set/loop_end | track_id, clip_id, loop_end | track_id, clip_id, loop_end | Set clip's loop end | +| /live/clip/get/loop_start | track_id, clip_id | track_id, clip_id, loop_start | Get clip's loop start, expressed in floating-point beats +| /live/clip/set/loop_start | track_id, clip_id, loop_start | track_id, clip_id, loop_start | Set clip's loop start, expressed in floating-point beats | +| /live/clip/get/loop_end | track_id, clip_id | track_id, clip_id, loop_end | Get clip's loop end, expressed in floating-point beats | +| /live/clip/set/loop_end | track_id, clip_id, loop_end | track_id, clip_id, loop_end | Set clip's loop end, expressed in floating-point beats | +| /live/clip/get/start_marker | track_id, clip_id | track_id, clip_id, start_marker | Get clip's marker start, expressed in floating-point beats | +| /live/clip/set/start_marker | track_id, clip_id, start_marker | track_id, clip_id, start_maker | Set clip's marker start, expressed in floating-point beats | +| /live/clip/get/end_marker | track_id, clip_id | track_id, clip_id, end_marker | Get clip's marker end, expressed in floating-point beats | +| /live/clip/set/end_marker | track_id, clip_id, end_marker | track_id, clip_id, end_marker | Set clip's marker end, expressed in floating-point beats | --- diff --git a/abletonosc/clip.py b/abletonosc/clip.py index 4cbc9a3..343a967 100644 --- a/abletonosc/clip.py +++ b/abletonosc/clip.py @@ -88,7 +88,9 @@ def clip_callback(params: Tuple[Any]) -> Tuple: "looping", "loop_start", "loop_end", - "warping" + "warping", + "start_marker", + "end_marker" ] for method in methods: @@ -106,9 +108,53 @@ def clip_callback(params: Tuple[Any]) -> Tuple: self.osc_server.add_handler("/live/clip/set/%s" % prop, create_clip_callback(self._set_property, prop)) + def clip_get_notes_helper(clip, params: Tuple[Any] = ()): + if clip is None: + self.logger.info("Trying to get notes from an empty clip") + return () + # Define default values + default_min_from_time = -16000 + default_max_time_span = 1000000 # Ableton clip max length is 24 hours. This is more than enough at over 200bpm + # These numbers were came up after a bunch of try and error. They look arbitrary but it works. + # I have tried different comnination of min and max time including using sys.maxsize, sys.float_info.min, sys.float_info.max + # clip.end_marker, clip_start_marker... but those don't work well. Notes are still missing + # https://github.com/ideoforms/AbletonOSC/issues/86 + + + # Check if parameters are provided in the params tuple + if len(params) == 4: + from_time, from_pitch, time_span, pitch_span = params + else: + from_time, from_pitch, time_span, pitch_span = default_min_from_time, 0, default_max_time_span, 128 + + return clip.get_notes(from_time, from_pitch, time_span, pitch_span) + def clip_get_notes(clip, params: Tuple[Any] = ()): - notes = clip.get_notes(0, 0, clip.length, 127) - return tuple(item for sublist in notes for item in sublist) + notes = clip_get_notes_helper(clip, params) + return tuple(item for note in notes for item in note) + + def clip_get_notes_range(clip, params: Tuple[Any] = ()): + notes = clip_get_notes_helper(clip, params) + + min_start_time = float('inf') # Initialize with a large value + max_end_time = float('-inf') # Initialize with a small value + min_pitch = float('inf') # Initialize with a large value + max_pitch = float('-inf') # Initialize with a small value + + for note in notes: + pitch, start_time, duration, velocity, mute = note + + # Update smallest start time + min_start_time = min(min_start_time, start_time) + + # Update largest end time (start_time + duration) + max_end_time = max(max_end_time, start_time + duration) + + # Update minimum and maximum pitch + min_pitch = min(min_pitch, pitch) + max_pitch = max(max_pitch, pitch) + + return (min_start_time, max_end_time, min_pitch, max_pitch) def clip_add_notes(clip, params: Tuple[Any] = ()): notes = [] @@ -127,6 +173,7 @@ def clip_remove_notes(clip, params: Tuple[Any] = ()): clip.remove_notes_extended(start_pitch, pitch_span, start_time, time_span) self.osc_server.add_handler("/live/clip/get/notes", create_clip_callback(clip_get_notes)) + self.osc_server.add_handler("/live/clip/get/notes_range", create_clip_callback(clip_get_notes_range)) self.osc_server.add_handler("/live/clip/add/notes", create_clip_callback(clip_add_notes)) self.osc_server.add_handler("/live/clip/remove/notes", create_clip_callback(clip_remove_notes)) @@ -208,4 +255,4 @@ def _build_clip_name_cache(self): clip_notes_str = re.sub("[1-9]", "", clip_notes_str) clip_notes_list = clip_notes_str.split("-") clip_notes_list = [note_name_to_midi(name) for name in clip_notes_list] - self._clip_notes_cache[-1][-1] = clip_notes_list \ No newline at end of file + self._clip_notes_cache[-1][-1] = clip_notes_list