From 0c2ff28dbb481530c7417adba083ee86de62f7a8 Mon Sep 17 00:00:00 2001 From: Hung Vo Date: Wed, 7 Jun 2023 16:20:05 -0400 Subject: [PATCH 1/7] /live/clip/get/notes should return all notes --- abletonosc/clip.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/abletonosc/clip.py b/abletonosc/clip.py index 4cbc9a3..6bacb98 100644 --- a/abletonosc/clip.py +++ b/abletonosc/clip.py @@ -107,7 +107,7 @@ def clip_callback(params: Tuple[Any]) -> Tuple: create_clip_callback(self._set_property, prop)) def clip_get_notes(clip, params: Tuple[Any] = ()): - notes = clip.get_notes(0, 0, clip.length, 127) + notes = clip.get_notes(0, 0, clip.end_marker, 127) return tuple(item for sublist in notes for item in sublist) def clip_add_notes(clip, params: Tuple[Any] = ()): From 31cf3076de0f4f74da4ae9c29f830beb9d01c563 Mon Sep 17 00:00:00 2001 From: Hung Vo Date: Wed, 7 Jun 2023 17:43:47 -0400 Subject: [PATCH 2/7] - Fix bugs: notes are still missing if they are pick up notes or they appear after clip.end_marker - Add clip start_marker, end_marker get / set --- abletonosc/clip.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/abletonosc/clip.py b/abletonosc/clip.py index 6bacb98..0f16a0d 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: @@ -107,7 +109,13 @@ def clip_callback(params: Tuple[Any]) -> Tuple: create_clip_callback(self._set_property, prop)) def clip_get_notes(clip, params: Tuple[Any] = ()): - notes = clip.get_notes(0, 0, clip.end_marker, 127) + estimatedMinTime = -16000 # pick up notes can start before 0 + estimatedMaxTime = 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 + notes = clip.get_notes(estimatedMinTime, 0, estimatedMaxTime, 127) return tuple(item for sublist in notes for item in sublist) def clip_add_notes(clip, params: Tuple[Any] = ()): From 2cc85ba7022e095cc907722123562aed24dddc81 Mon Sep 17 00:00:00 2001 From: Hung Vo Date: Thu, 8 Jun 2023 15:31:25 -0400 Subject: [PATCH 3/7] - add optional params to /live/clip/get/notes - update README.md --- README.md | 6 +++++- abletonosc/clip.py | 17 +++++++++++++---- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 12ad355..d41bfa8 100644 --- a/README.md +++ b/README.md @@ -348,7 +348,7 @@ 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/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 | @@ -374,6 +374,10 @@ Represents an audio or MIDI clip. Can be used to start/stop clips, and query/mod | /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/start_marker | track_id, clip_id | track_id, clip_id, start_marker | Get clip's marker start | +| /live/clip/set/start_marker | track_id, clip_id, start_marker | track_id, clip_id, start_maker | Set clip's marker start | +| /live/clip/get/end_marker | track_id, clip_id | track_id, clip_id, end_marker | Get clip's marker end | +| /live/clip/set/end_marker | track_id, clip_id, end_marker | track_id, clip_id, end_marker | Set clip's marker end | --- diff --git a/abletonosc/clip.py b/abletonosc/clip.py index 0f16a0d..b8c1832 100644 --- a/abletonosc/clip.py +++ b/abletonosc/clip.py @@ -109,13 +109,22 @@ def clip_callback(params: Tuple[Any]) -> Tuple: create_clip_callback(self._set_property, prop)) def clip_get_notes(clip, params: Tuple[Any] = ()): - estimatedMinTime = -16000 # pick up notes can start before 0 - estimatedMaxTime = 1000000 # Ableton clip max length is 24 hours. This is more than enough at over 200bpm + # Define default values + estimated_min_from_time = -16000 + estimated_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 - notes = clip.get_notes(estimatedMinTime, 0, estimatedMaxTime, 127) + + + # 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 = estimated_min_from_time, 0, estimated_max_time_span, 128 + + notes = clip.get_notes(from_time, from_pitch, time_span, pitch_span) return tuple(item for sublist in notes for item in sublist) def clip_add_notes(clip, params: Tuple[Any] = ()): @@ -216,4 +225,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 From 02917a91d5d3722ebb02b06cf3ec5e0484e2bc95 Mon Sep 17 00:00:00 2001 From: Hung Vo Date: Thu, 8 Jun 2023 17:35:07 -0400 Subject: [PATCH 4/7] add /live/clip/get/notes_range --- README.md | 1 + abletonosc/clip.py | 33 ++++++++++++++++++++++++++++++--- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d41bfa8..bc46bc4 100644 --- a/README.md +++ b/README.md @@ -349,6 +349,7 @@ Represents an audio or MIDI clip. Can be used to start/stop clips, and query/mod | /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, [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 | diff --git a/abletonosc/clip.py b/abletonosc/clip.py index b8c1832..3186b95 100644 --- a/abletonosc/clip.py +++ b/abletonosc/clip.py @@ -108,7 +108,7 @@ 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(clip, params: Tuple[Any] = ()): + def clip_get_notes_helper(clip, params: Tuple[Any] = ()): # Define default values estimated_min_from_time = -16000 estimated_max_time_span = 1000000 # Ableton clip max length is 24 hours. This is more than enough at over 200bpm @@ -124,8 +124,34 @@ def clip_get_notes(clip, params: Tuple[Any] = ()): else: from_time, from_pitch, time_span, pitch_span = estimated_min_from_time, 0, estimated_max_time_span, 128 - notes = clip.get_notes(from_time, from_pitch, time_span, pitch_span) - return tuple(item for sublist in notes for item in sublist) + return clip.get_notes(from_time, from_pitch, time_span, pitch_span) + + def clip_get_notes(clip, params: Tuple[Any] = ()): + 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 = [] @@ -144,6 +170,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)) From 0f2022bdac065b04e0c51bf389a3203a6409b348 Mon Sep 17 00:00:00 2001 From: Hung Vo Date: Sat, 10 Jun 2023 12:36:13 -0400 Subject: [PATCH 5/7] return empty result rather than throwing error when clip is None --- abletonosc/clip.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/abletonosc/clip.py b/abletonosc/clip.py index 3186b95..1a09ba3 100644 --- a/abletonosc/clip.py +++ b/abletonosc/clip.py @@ -109,6 +109,9 @@ def clip_callback(params: Tuple[Any]) -> Tuple: 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 estimated_min_from_time = -16000 estimated_max_time_span = 1000000 # Ableton clip max length is 24 hours. This is more than enough at over 200bpm From 9a4d707bb2f82529c6267cbeec9c521eb4ecf8ac Mon Sep 17 00:00:00 2001 From: Hung Vo Date: Fri, 23 Jun 2023 12:13:45 -0400 Subject: [PATCH 6/7] update readme, loop and markers are floating-point beats --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index bc46bc4..db4f4e9 100644 --- a/README.md +++ b/README.md @@ -371,14 +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/start_marker | track_id, clip_id | track_id, clip_id, start_marker | Get clip's marker start | -| /live/clip/set/start_marker | track_id, clip_id, start_marker | track_id, clip_id, start_maker | Set clip's marker start | -| /live/clip/get/end_marker | track_id, clip_id | track_id, clip_id, end_marker | Get clip's marker end | -| /live/clip/set/end_marker | track_id, clip_id, end_marker | track_id, clip_id, end_marker | Set clip's marker 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 | --- From 51e6a5cd44e8866cdbc470a15a7f624336878539 Mon Sep 17 00:00:00 2001 From: Hung Vo Date: Fri, 23 Jun 2023 12:17:17 -0400 Subject: [PATCH 7/7] change variable name prefix 'estimated' -> 'default' --- abletonosc/clip.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/abletonosc/clip.py b/abletonosc/clip.py index 1a09ba3..343a967 100644 --- a/abletonosc/clip.py +++ b/abletonosc/clip.py @@ -113,8 +113,8 @@ def clip_get_notes_helper(clip, params: Tuple[Any] = ()): self.logger.info("Trying to get notes from an empty clip") return () # Define default values - estimated_min_from_time = -16000 - estimated_max_time_span = 1000000 # Ableton clip max length is 24 hours. This is more than enough at over 200bpm + 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 @@ -125,7 +125,7 @@ def clip_get_notes_helper(clip, params: Tuple[Any] = ()): if len(params) == 4: from_time, from_pitch, time_span, pitch_span = params else: - from_time, from_pitch, time_span, pitch_span = estimated_min_from_time, 0, estimated_max_time_span, 128 + 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)