ableton-js 3.3.2 → 3.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/midi-script/AbletonJS.py +8 -8
- package/midi-script/Application.py +0 -1
- package/midi-script/Interface.py +3 -3
- package/midi-script/Logging.py +3 -0
- package/midi-script/Midi.py +7 -5
- package/midi-script/Socket.py +35 -34
- package/midi-script/version.py +1 -1
- package/ns/clip.d.ts +6 -0
- package/ns/clip.js +13 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,8 +4,16 @@ All notable changes to this project will be documented in this file. Dates are d
|
|
|
4
4
|
|
|
5
5
|
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
|
6
6
|
|
|
7
|
+
#### [v3.3.3](https://github.com/leolabs/ableton.js/compare/v3.3.2...v3.3.3)
|
|
8
|
+
|
|
9
|
+
- :mute: Fix every message being logged twice [`3a2571f`](https://github.com/leolabs/ableton.js/commit/3a2571f0a4ad61fdf751a0dfbb5da00b6def8508)
|
|
10
|
+
- :wastebasket: Deprecate `removeNotes` and add `removeNotesExtended` as a replacement [`ac02271`](https://github.com/leolabs/ableton.js/commit/ac022718f0b16317d8fdf70ff1c8d77eaaa0f384)
|
|
11
|
+
- :bug: Address a hang in newer versions of Live when the UDP port is already being used [`5caeaeb`](https://github.com/leolabs/ableton.js/commit/5caeaebc172fc819693c988643071c565e4dbcd3)
|
|
12
|
+
|
|
7
13
|
#### [v3.3.2](https://github.com/leolabs/ableton.js/compare/v3.3.1...v3.3.2)
|
|
8
14
|
|
|
15
|
+
> 27 July 2023
|
|
16
|
+
|
|
9
17
|
- :package: Add lodash as a dependency, fixes #104 [`#104`](https://github.com/leolabs/ableton.js/issues/104)
|
|
10
18
|
|
|
11
19
|
#### [v3.3.1](https://github.com/leolabs/ableton.js/compare/v3.3.0...v3.3.1)
|
package/midi-script/AbletonJS.py
CHANGED
|
@@ -3,6 +3,7 @@ import time
|
|
|
3
3
|
|
|
4
4
|
from .version import version
|
|
5
5
|
from .Config import DEBUG, FAST_POLLING
|
|
6
|
+
from .Logging import logger
|
|
6
7
|
from .Socket import Socket
|
|
7
8
|
from .Interface import Interface
|
|
8
9
|
from .Application import Application
|
|
@@ -29,11 +30,10 @@ import Live
|
|
|
29
30
|
class AbletonJS(ControlSurface):
|
|
30
31
|
def __init__(self, c_instance):
|
|
31
32
|
super(AbletonJS, self).__init__(c_instance)
|
|
32
|
-
|
|
33
|
+
logger.info("Starting AbletonJS " + version + "...")
|
|
33
34
|
|
|
34
35
|
self.tracked_midi = set()
|
|
35
36
|
|
|
36
|
-
Socket.set_log(self.log_message)
|
|
37
37
|
Socket.set_message(self.show_message)
|
|
38
38
|
self.socket = Socket(self.command_handler)
|
|
39
39
|
|
|
@@ -69,8 +69,8 @@ class AbletonJS(ControlSurface):
|
|
|
69
69
|
tick_time = time.time() * 1000
|
|
70
70
|
|
|
71
71
|
if tick_time - self._last_tick > 200:
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
logger.warn("UDP tick is lagging, delta: " +
|
|
73
|
+
str(round(tick_time - self._last_tick)) + "ms")
|
|
74
74
|
|
|
75
75
|
self._last_tick = tick_time
|
|
76
76
|
self.socket.process()
|
|
@@ -78,8 +78,8 @@ class AbletonJS(ControlSurface):
|
|
|
78
78
|
process_time = time.time() * 1000
|
|
79
79
|
|
|
80
80
|
if process_time - tick_time > 100:
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
logger.warn("UDP processing is taking long, delta: " +
|
|
82
|
+
str(round(tick_time - process_time)) + "ms")
|
|
83
83
|
|
|
84
84
|
self.schedule_message(1, self.tick)
|
|
85
85
|
|
|
@@ -97,7 +97,7 @@ class AbletonJS(ControlSurface):
|
|
|
97
97
|
self.handlers["midi"].send_midi(midi_bytes)
|
|
98
98
|
|
|
99
99
|
def disconnect(self):
|
|
100
|
-
|
|
100
|
+
logger.info("Disconnecting")
|
|
101
101
|
if FAST_POLLING:
|
|
102
102
|
self.recv_loop.stop()
|
|
103
103
|
self.socket.send("disconnect")
|
|
@@ -110,7 +110,7 @@ class AbletonJS(ControlSurface):
|
|
|
110
110
|
|
|
111
111
|
# Don't clutter the logs
|
|
112
112
|
if not (namespace == "internal" and payload["name"] == "get_prop" and payload["args"]["prop"] == "ping") and DEBUG:
|
|
113
|
-
|
|
113
|
+
logger.debug("Received command: " + str(payload))
|
|
114
114
|
|
|
115
115
|
if namespace in self.handlers:
|
|
116
116
|
handler = self.handlers[namespace]
|
|
@@ -6,7 +6,6 @@ class Application(Interface):
|
|
|
6
6
|
def __init__(self, c_instance, socket, application):
|
|
7
7
|
super(Application, self).__init__(c_instance, socket)
|
|
8
8
|
self.application = application
|
|
9
|
-
self.log_message("Version: " + self.get_version(self.get_ns()))
|
|
10
9
|
|
|
11
10
|
def get_ns(self, nsid=None):
|
|
12
11
|
return self.application
|
package/midi-script/Interface.py
CHANGED
|
@@ -2,6 +2,7 @@ import hashlib
|
|
|
2
2
|
import json
|
|
3
3
|
|
|
4
4
|
from .Config import DEBUG
|
|
5
|
+
from .Logging import logger
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
class Interface(object):
|
|
@@ -25,11 +26,10 @@ class Interface(object):
|
|
|
25
26
|
def __init__(self, c_instance, socket):
|
|
26
27
|
self.ableton = c_instance
|
|
27
28
|
self.socket = socket
|
|
28
|
-
self.log_message = c_instance.log_message
|
|
29
29
|
|
|
30
30
|
def log_debug(self, message):
|
|
31
31
|
if DEBUG:
|
|
32
|
-
|
|
32
|
+
logger.debug(message)
|
|
33
33
|
|
|
34
34
|
def get_ns(self, nsid):
|
|
35
35
|
return Interface.obj_ids[nsid]
|
|
@@ -78,7 +78,7 @@ class Interface(object):
|
|
|
78
78
|
self.socket.send("error", "Function call failed: " + payload["name"] +
|
|
79
79
|
" doesn't exist or isn't callable", uuid)
|
|
80
80
|
except Exception as e:
|
|
81
|
-
|
|
81
|
+
logger.error("Handler Error: " + str(e.args))
|
|
82
82
|
self.socket.send("error", str(e.args[0]), uuid)
|
|
83
83
|
|
|
84
84
|
def add_listener(self, ns, prop, eventId, nsid="Default"):
|
package/midi-script/Midi.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import absolute_import
|
|
2
2
|
|
|
3
3
|
from .Interface import Interface
|
|
4
|
+
from .Logging import logger
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
class Midi(Interface):
|
|
@@ -22,11 +23,12 @@ class Midi(Interface):
|
|
|
22
23
|
midi_type = output.get("type")
|
|
23
24
|
if midi_type != "cc" and midi_type != "note":
|
|
24
25
|
raise ValueError("invalid midi type " + str(midi_type))
|
|
25
|
-
self.outputs.add((midi_type, output.get(
|
|
26
|
+
self.outputs.add((midi_type, output.get(
|
|
27
|
+
"channel"), output.get("target")))
|
|
26
28
|
except ValueError as e:
|
|
27
|
-
|
|
29
|
+
logger.error(e)
|
|
28
30
|
except:
|
|
29
|
-
|
|
31
|
+
logger.error("invalid midi output requested: " + str(output))
|
|
30
32
|
|
|
31
33
|
def remove_midi_listener(self, fn):
|
|
32
34
|
self.event_id = None
|
|
@@ -38,10 +40,10 @@ class Midi(Interface):
|
|
|
38
40
|
raise Exception("Listener " + str(prop) + " does not exist.")
|
|
39
41
|
|
|
40
42
|
if self.event_id is not None:
|
|
41
|
-
|
|
43
|
+
logger.warn("midi listener already exists")
|
|
42
44
|
return self.event_id
|
|
43
45
|
|
|
44
|
-
|
|
46
|
+
logger.info("Attaching midi listener")
|
|
45
47
|
|
|
46
48
|
self.tracked_midi.clear()
|
|
47
49
|
self.tracked_midi.update(self.outputs)
|
package/midi-script/Socket.py
CHANGED
|
@@ -4,7 +4,8 @@ import struct
|
|
|
4
4
|
import zlib
|
|
5
5
|
import os
|
|
6
6
|
import tempfile
|
|
7
|
-
|
|
7
|
+
|
|
8
|
+
from .Logging import logger
|
|
8
9
|
|
|
9
10
|
import Live
|
|
10
11
|
|
|
@@ -24,11 +25,6 @@ client_port_path = os.path.join(tempfile.gettempdir(), client_port_file)
|
|
|
24
25
|
|
|
25
26
|
|
|
26
27
|
class Socket(object):
|
|
27
|
-
|
|
28
|
-
@staticmethod
|
|
29
|
-
def set_log(func):
|
|
30
|
-
Socket.log_message = func
|
|
31
|
-
|
|
32
28
|
@staticmethod
|
|
33
29
|
def set_message(func):
|
|
34
30
|
Socket.show_message = func
|
|
@@ -39,17 +35,18 @@ class Socket(object):
|
|
|
39
35
|
self._client_addr = ("127.0.0.1", 39031)
|
|
40
36
|
self._last_error = ""
|
|
41
37
|
self._socket = None
|
|
38
|
+
self._chunk_limit = None
|
|
42
39
|
|
|
43
40
|
self.read_remote_port()
|
|
44
41
|
self.init_socket(True)
|
|
45
42
|
|
|
46
|
-
def
|
|
43
|
+
def log_error_once(self, msg):
|
|
47
44
|
if self._last_error != msg:
|
|
48
45
|
self._last_error = msg
|
|
49
|
-
|
|
46
|
+
logger.error(msg)
|
|
50
47
|
|
|
51
48
|
def set_client_port(self, port):
|
|
52
|
-
|
|
49
|
+
logger.info("Setting client port: ", str(port))
|
|
53
50
|
self.show_message("Client connected on port " + str(port))
|
|
54
51
|
self._client_addr = ("127.0.0.1", int(port))
|
|
55
52
|
|
|
@@ -58,10 +55,10 @@ class Socket(object):
|
|
|
58
55
|
with open(server_port_path) as file:
|
|
59
56
|
port = int(file.read())
|
|
60
57
|
|
|
61
|
-
|
|
58
|
+
logger.info("Stored server port: " + str(port))
|
|
62
59
|
return port
|
|
63
60
|
except Exception as e:
|
|
64
|
-
|
|
61
|
+
logger.info(
|
|
65
62
|
"Couldn't read stored server port: " + str(e.args))
|
|
66
63
|
return None
|
|
67
64
|
|
|
@@ -71,7 +68,8 @@ class Socket(object):
|
|
|
71
68
|
try:
|
|
72
69
|
os.stat(client_port_path)
|
|
73
70
|
except Exception as e:
|
|
74
|
-
self.
|
|
71
|
+
self.log_error_once(
|
|
72
|
+
"Couldn't stat remote port file: " + str(e.args))
|
|
75
73
|
return
|
|
76
74
|
|
|
77
75
|
try:
|
|
@@ -81,22 +79,23 @@ class Socket(object):
|
|
|
81
79
|
port = int(file.read())
|
|
82
80
|
|
|
83
81
|
if port != old_port:
|
|
84
|
-
|
|
85
|
-
|
|
82
|
+
logger.info("[" + str(id(self)) + "] Client port changed from " +
|
|
83
|
+
str(old_port) + " to " + str(port))
|
|
86
84
|
self._client_addr = ("127.0.0.1", port)
|
|
87
85
|
|
|
88
86
|
if self._socket:
|
|
89
87
|
self.send("connect", {"port": self._server_addr[1]})
|
|
90
88
|
except Exception as e:
|
|
91
|
-
self.
|
|
89
|
+
self.log_error_once(
|
|
90
|
+
"Couldn't read remote port file: " + str(e.args))
|
|
92
91
|
|
|
93
92
|
def shutdown(self):
|
|
94
|
-
|
|
93
|
+
logger.info("Shutting down...")
|
|
95
94
|
self._socket.close()
|
|
96
95
|
self._socket = None
|
|
97
96
|
|
|
98
97
|
def init_socket(self, try_stored=False):
|
|
99
|
-
|
|
98
|
+
logger.info(
|
|
100
99
|
"Initializing socket, from stored: " + str(try_stored))
|
|
101
100
|
|
|
102
101
|
try:
|
|
@@ -118,7 +117,7 @@ class Socket(object):
|
|
|
118
117
|
self._chunk_limit = self._socket.getsockopt(
|
|
119
118
|
socket.SOL_SOCKET, socket.SO_SNDBUF) - 1
|
|
120
119
|
|
|
121
|
-
|
|
120
|
+
logger.info("Chunk limit: " + str(self._chunk_limit))
|
|
122
121
|
|
|
123
122
|
# Write the chosen port to a file
|
|
124
123
|
try:
|
|
@@ -126,35 +125,37 @@ class Socket(object):
|
|
|
126
125
|
with open(server_port_path, "w") as file:
|
|
127
126
|
file.write(str(port))
|
|
128
127
|
except Exception as e:
|
|
129
|
-
self.
|
|
128
|
+
self.log_error_once(
|
|
129
|
+
"Couldn't save port in file: " + str(e.args))
|
|
130
130
|
raise e
|
|
131
131
|
|
|
132
132
|
try:
|
|
133
133
|
self.send("connect", {"port": self._server_addr[1]})
|
|
134
134
|
except Exception as e:
|
|
135
|
-
|
|
136
|
-
|
|
135
|
+
logger.info("Couldn't send connect to " +
|
|
136
|
+
str(self._client_addr) + ": " + str(e.args))
|
|
137
137
|
|
|
138
138
|
self.show_message("Started server on port " + str(port))
|
|
139
139
|
|
|
140
|
-
|
|
141
|
-
|
|
140
|
+
logger.info('Started server on: ' + str(self._socket.getsockname()) +
|
|
141
|
+
', client addr: ' + str(self._client_addr))
|
|
142
142
|
except Exception as e:
|
|
143
143
|
msg = 'ERROR: Cannot bind to ' + \
|
|
144
144
|
str(self._server_addr) + ': ' + \
|
|
145
145
|
str(e.args) + ', trying again. ' + \
|
|
146
146
|
'If this keeps happening, try restarting your computer.'
|
|
147
|
-
self.
|
|
148
|
-
|
|
147
|
+
self.log_error_once(msg + "(Client address: " +
|
|
148
|
+
str(self._client_addr) + ")")
|
|
149
149
|
self.show_message(msg)
|
|
150
|
-
t = Timer(
|
|
150
|
+
t = Live.Base.Timer(
|
|
151
|
+
callback=self.init_socket, interval=5000, repeat=False)
|
|
151
152
|
t.start()
|
|
152
153
|
|
|
153
154
|
def _sendto(self, msg):
|
|
154
155
|
'''Send a raw message to the client, compressed and chunked, if necessary'''
|
|
155
156
|
compressed = zlib.compress(msg.encode("utf8")) + b'\n'
|
|
156
157
|
|
|
157
|
-
if self._socket == None:
|
|
158
|
+
if self._socket == None or self._chunk_limit == None:
|
|
158
159
|
return
|
|
159
160
|
|
|
160
161
|
if len(compressed) < self._chunk_limit:
|
|
@@ -179,12 +180,12 @@ class Socket(object):
|
|
|
179
180
|
{"event": name, "data": obj, "uuid": uuid}, default=jsonReplace, ensure_ascii=False)
|
|
180
181
|
self._sendto(data)
|
|
181
182
|
except socket.error as e:
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
183
|
+
logger.info("Socket error: " + str(e.args) + ", server: " + str(self._server_addr) +
|
|
184
|
+
", client: " + str(self._client_addr) + ", socket: " + str(self._socket))
|
|
185
|
+
logger.info("Data:" + data)
|
|
185
186
|
except Exception as e:
|
|
186
187
|
error = str(type(e).__name__) + ': ' + str(e.args)
|
|
187
|
-
|
|
188
|
+
logger.info("Error " + name + "(" + str(uuid) + "): " + error)
|
|
188
189
|
|
|
189
190
|
def process(self):
|
|
190
191
|
try:
|
|
@@ -204,8 +205,8 @@ class Socket(object):
|
|
|
204
205
|
buffer = bytes()
|
|
205
206
|
num_messages = 0
|
|
206
207
|
except socket.error as e:
|
|
207
|
-
if (e.errno != 35):
|
|
208
|
-
|
|
208
|
+
if (e.errno != 35 and e.errno != 10035 and e.errno != 10054):
|
|
209
|
+
logger.info("Socket error: " + str(e.args))
|
|
209
210
|
return
|
|
210
211
|
except Exception as e:
|
|
211
|
-
|
|
212
|
+
logger.info("Error while processing: " + str(e.args))
|
package/midi-script/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
version = "3.3.
|
|
1
|
+
version = "3.3.3"
|
package/ns/clip.d.ts
CHANGED
|
@@ -218,8 +218,14 @@ export declare class Clip extends Namespace<GettableProperties, TransformedPrope
|
|
|
218
218
|
quantizePitch(pitch: number, grid: number, amount: number): Promise<void>;
|
|
219
219
|
/**
|
|
220
220
|
* Deletes all notes that start in the given area.
|
|
221
|
+
*
|
|
222
|
+
* @deprecated starting with Live 11, use `removeNotesExtended` instead
|
|
221
223
|
*/
|
|
222
224
|
removeNotes(fromTime: number, fromPitch: number, timeSpan: number, pitchSpan: number): Promise<any>;
|
|
225
|
+
/**
|
|
226
|
+
* Deletes all notes that start in the given area.
|
|
227
|
+
*/
|
|
228
|
+
removeNotesExtended(fromTime: number, fromPitch: number, timeSpan: number, pitchSpan: number): Promise<any>;
|
|
223
229
|
/**
|
|
224
230
|
* Replaces selected notes with an array of new notes.
|
|
225
231
|
*/
|
package/ns/clip.js
CHANGED
|
@@ -155,6 +155,8 @@ class Clip extends _1.Namespace {
|
|
|
155
155
|
}
|
|
156
156
|
/**
|
|
157
157
|
* Deletes all notes that start in the given area.
|
|
158
|
+
*
|
|
159
|
+
* @deprecated starting with Live 11, use `removeNotesExtended` instead
|
|
158
160
|
*/
|
|
159
161
|
removeNotes(fromTime, fromPitch, timeSpan, pitchSpan) {
|
|
160
162
|
return this.sendCommand("remove_notes", [
|
|
@@ -164,6 +166,17 @@ class Clip extends _1.Namespace {
|
|
|
164
166
|
pitchSpan,
|
|
165
167
|
]);
|
|
166
168
|
}
|
|
169
|
+
/**
|
|
170
|
+
* Deletes all notes that start in the given area.
|
|
171
|
+
*/
|
|
172
|
+
removeNotesExtended(fromTime, fromPitch, timeSpan, pitchSpan) {
|
|
173
|
+
return this.sendCommand("remove_notes_extended", [
|
|
174
|
+
fromTime,
|
|
175
|
+
fromPitch,
|
|
176
|
+
timeSpan,
|
|
177
|
+
pitchSpan,
|
|
178
|
+
]);
|
|
179
|
+
}
|
|
167
180
|
/**
|
|
168
181
|
* Replaces selected notes with an array of new notes.
|
|
169
182
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ableton-js",
|
|
3
|
-
"version": "3.3.
|
|
3
|
+
"version": "3.3.3",
|
|
4
4
|
"description": "Control Ableton Live from Node",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"author": "Leo Bernard <admin@leolabs.org>",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"ableton:copy-script": "set -- ~/Music/Ableton/User\\ Library/Remote\\ Scripts && mkdir -p \"$1\" && rm -rf \"$1/AbletonJS\" && cp -r \"$(pwd)/midi-script\" \"$1/AbletonJS\" && rm -rf \"$1/AbletonJS/_Framework\"",
|
|
20
20
|
"ableton10:launch": "set -- /Applications/Ableton*10* && open \"$1\"",
|
|
21
21
|
"ableton11:launch": "set -- /Applications/Ableton*11* && open \"$1\"",
|
|
22
|
-
"ableton:logs": "tail -n 50 -f ~/Library/Preferences/Ableton/*/Log.txt | grep --line-buffered -i -e
|
|
22
|
+
"ableton:logs": "tail -n 50 -f ~/Library/Preferences/Ableton/*/Log.txt | grep --line-buffered -i -e AbletonJS",
|
|
23
23
|
"ableton:kill": "pkill -KILL -f \"Ableton Live\"",
|
|
24
24
|
"ableton10:start": "yarn ableton:kill; yarn ableton:clean && yarn ableton:copy-script && yarn ableton10:launch && yarn ableton:logs",
|
|
25
25
|
"ableton11:start": "yarn ableton:kill; yarn ableton:clean && yarn ableton:copy-script && yarn ableton11:launch && yarn ableton:logs",
|