[Experimental] Fast copy into Ableton live w/ Remote Scripts#41
Merged
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
Adds an experimental Ableton Live integration for the MLX-optimized SA3 workflow by introducing an Ableton MIDI Remote Script + a companion CLI to insert generated audio at the playhead, and improves WAV ingestion robustness by falling back to ffmpeg for non-16-bit/44.1kHz files.
Changes:
- Add Ableton Live “AudioInserter” Remote Script (socket server) to create a new audio track and insert audio into arrangement at the current playhead.
- Add
insert_audio.pyCLI + README instructions for sending audio files (and watching a folder) to Ableton over localhost. - Update
read_wav()to support more WAV formats via ffmpeg fallback.
Reviewed changes
Copilot reviewed 5 out of 6 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| optimized/mlx/scripts/sa3_mlx.py | Extends WAV reading to fall back to ffmpeg for non-native formats. |
| optimized/mlx/ableton/README.md | Documents setup and usage for the new Ableton integration workflow. |
| optimized/mlx/ableton/insert_audio.py | Implements a CLI client for sending insert/ping commands to Ableton. |
| optimized/mlx/ableton/AudioInserter/AudioInserter.py | Implements the Ableton Remote Script socket server and audio insertion logic. |
| optimized/mlx/ableton/AudioInserter/init.py | Provides create_instance() entrypoint for Ableton Remote Scripts. |
| .gitignore | Ignores Ableton .asd analysis/metadata files. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+255
to
+256
| except wave.Error: | ||
| pass # unsupported format (32-bit float, 24-bit PCM, etc.) |
Comment on lines
+6
to
+14
| python insert_audio.py # insert latest file in ~/Desktop | ||
| python insert_audio.py /path/to/file.wav # insert specific file | ||
| python insert_audio.py --watch ~/renders # watch folder, auto-insert on new file | ||
| python insert_audio.py --ping # check if Ableton is connected | ||
|
|
||
| Options: | ||
| --dir DIR Directory to search for latest file (default: ~/Desktop) | ||
| --ext EXT File extension filter, e.g. wav, aiff, mp3 (default: wav aiff mp3 flac) | ||
| --watch Watch mode: monitor DIR and auto-insert new files as they appear |
Comment on lines
+39
to
+52
| s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | ||
| s.settimeout(TIMEOUT) | ||
| s.connect((HOST, PORT)) | ||
| s.sendall((json.dumps(cmd) + "\n").encode("utf-8")) | ||
| response = b"" | ||
| while True: | ||
| chunk = s.recv(4096) | ||
| if not chunk: | ||
| break | ||
| response += chunk | ||
| if b"\n" in chunk: | ||
| break | ||
| s.close() | ||
| return json.loads(response.decode("utf-8").strip()) |
Comment on lines
+39
to
+48
| ```bash | ||
| # Insert a specific file at the current playhead | ||
| python3 insert_audio.py /path/to/out.wav | ||
|
|
||
| # Insert whatever wav was most recently dropped on your Desktop | ||
| python3 insert_audio.py | ||
|
|
||
| # Watch a folder and auto-insert each new file as it appears | ||
| python3 insert_audio.py --watch ~/Desktop | ||
| ``` |
Comment on lines
+54
to
+60
| ```bash | ||
| # Generate | ||
| ./sa3 --prompt "driving techno loop" --dit medium --decoder same-l --out out.wav | ||
|
|
||
| # Insert | ||
| python3 ableton/insert_audio.py out.wav | ||
| ``` |
Comment on lines
+37
to
+43
| while self._running: | ||
| try: | ||
| conn, _ = srv.accept() | ||
| data = conn.recv(BUFFER_SIZE).decode("utf-8").strip() | ||
| response = self._handle_command(data) | ||
| conn.sendall((response + "\n").encode("utf-8")) | ||
| conn.close() |
Comment on lines
+84
to
+90
| event.wait(timeout=10.0) | ||
|
|
||
| if result["ok"]: | ||
| msg = result.get("error") or ("Inserted: %s" % os.path.basename(file_path)) | ||
| return json.dumps({"ok": True, "message": msg}) | ||
| else: | ||
| return json.dumps({"ok": False, "error": result.get("error", "Unknown error")}) |
Comment on lines
+30
to
+49
| def _server_loop(self): | ||
| try: | ||
| srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | ||
| srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) | ||
| srv.bind((SOCKET_HOST, SOCKET_PORT)) | ||
| srv.listen(5) | ||
| srv.settimeout(1.0) | ||
| while self._running: | ||
| try: | ||
| conn, _ = srv.accept() | ||
| data = conn.recv(BUFFER_SIZE).decode("utf-8").strip() | ||
| response = self._handle_command(data) | ||
| conn.sendall((response + "\n").encode("utf-8")) | ||
| conn.close() | ||
| except socket.timeout: | ||
| continue | ||
| except Exception as e: | ||
| self.log_message("AudioInserter socket error: %s" % str(e)) | ||
| except Exception as e: | ||
| self.log_message("AudioInserter server error: %s" % str(e)) |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Comment on lines
+39
to
+43
| conn, _ = srv.accept() | ||
| data = conn.recv(BUFFER_SIZE).decode("utf-8").strip() | ||
| response = self._handle_command(data) | ||
| conn.sendall((response + "\n").encode("utf-8")) | ||
| conn.close() |
Comment on lines
+84
to
+90
| event.wait(timeout=10.0) | ||
|
|
||
| if result["ok"]: | ||
| msg = result.get("error") or ("Inserted: %s" % os.path.basename(file_path)) | ||
| return json.dumps({"ok": True, "message": msg}) | ||
| else: | ||
| return json.dumps({"ok": False, "error": result.get("error", "Unknown error")}) |
Comment on lines
+111
to
+116
| # Copy file to a unique path so each generation is preserved independently | ||
| ext = os.path.splitext(file_path)[1] | ||
| unique_name = "%s_%s%s" % (clean_name.replace(" ", "_"), int(time.time()), ext) | ||
| dest_dir = os.path.dirname(file_path) | ||
| unique_path = os.path.join(dest_dir, unique_name) | ||
| shutil.copy2(file_path, unique_path) |
Comment on lines
+38
to
+51
| s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | ||
| s.settimeout(TIMEOUT) | ||
| s.connect((HOST, PORT)) | ||
| s.sendall((json.dumps(cmd) + "\n").encode("utf-8")) | ||
| response = b"" | ||
| while True: | ||
| chunk = s.recv(4096) | ||
| if not chunk: | ||
| break | ||
| response += chunk | ||
| if b"\n" in chunk: | ||
| break | ||
| s.close() | ||
| return json.loads(response.decode("utf-8").strip()) |
| ) | ||
| except FileNotFoundError: | ||
| raise RuntimeError( | ||
| f"{path}: unsupported WAV format. Install ffmpeg to handle 24-bit/32-bit/48kHz audio:\n" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Uses Ableton Remote Scripts for a fast integration workflow