ableton-js 3.7.0 → 3.7.2
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 +17 -0
- package/index.d.ts +3 -1
- package/index.js +33 -14
- package/midi-script/Session.py +1 -1
- package/midi-script/Socket.py +85 -53
- package/midi-script/version.py +1 -1
- package/ns/song.d.ts +4 -3
- package/ns/song.js +3 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,8 +4,25 @@ 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.7.2](https://github.com/leolabs/ableton.js/compare/v3.7.1...v3.7.2)
|
|
8
|
+
|
|
9
|
+
- Fix a missing str() coercion [`#136`](https://github.com/leolabs/ableton.js/pull/136)
|
|
10
|
+
- :bug: Fix issues with larger payloads not being received properly [`bed4e22`](https://github.com/leolabs/ableton.js/commit/bed4e226cb7febb448700c32979ce50a6a67ef69)
|
|
11
|
+
- :zap: Always assign a new port to the Python UDP server on start and store it in the port file [`47566f9`](https://github.com/leolabs/ableton.js/commit/47566f9eb347784746f6d50093491f3b2d591c58)
|
|
12
|
+
- :label: Fix types of `re_enable_automation_enabled` property [`4e547e0`](https://github.com/leolabs/ableton.js/commit/4e547e051f6df9be69ad11797479b7f5d6262ba8)
|
|
13
|
+
- :mute: Reduce logging around packet processing [`c916d67`](https://github.com/leolabs/ableton.js/commit/c916d675905f4660b130e26a904ada6e9f64ac6f)
|
|
14
|
+
- :wrench: Fix `ableton12:start` starting Live 11 instead [`7a0b8a3`](https://github.com/leolabs/ableton.js/commit/7a0b8a34bcee6bd3224fdfc25662363a714440db)
|
|
15
|
+
|
|
16
|
+
#### [v3.7.1](https://github.com/leolabs/ableton.js/compare/v3.7.0...v3.7.1)
|
|
17
|
+
|
|
18
|
+
> 9 July 2025
|
|
19
|
+
|
|
20
|
+
- :sparkles: Add `reEnableAutomation` song function [`b3aa668`](https://github.com/leolabs/ableton.js/commit/b3aa668bfc1844d60722ac557b2be047c0630da8)
|
|
21
|
+
|
|
7
22
|
#### [v3.7.0](https://github.com/leolabs/ableton.js/compare/v3.6.1...v3.7.0)
|
|
8
23
|
|
|
24
|
+
> 16 June 2025
|
|
25
|
+
|
|
9
26
|
- Add more clip and note operation features in Ableton Live 11 and above versions [`#133`](https://github.com/leolabs/ableton.js/pull/133)
|
|
10
27
|
- Add new functions and fix bugs [`#130`](https://github.com/leolabs/ableton.js/pull/130)
|
|
11
28
|
- adds ability to launch v12 via yarn [`#131`](https://github.com/leolabs/ableton.js/pull/131)
|
package/index.d.ts
CHANGED
|
@@ -96,11 +96,13 @@ export declare class Ableton extends EventEmitter implements ConnectionEventEmit
|
|
|
96
96
|
private options?;
|
|
97
97
|
private client;
|
|
98
98
|
private msgMap;
|
|
99
|
+
private timeoutMap;
|
|
99
100
|
private eventListeners;
|
|
100
101
|
private heartbeatInterval;
|
|
101
102
|
private _isConnected;
|
|
102
103
|
private buffer;
|
|
103
104
|
private latency;
|
|
105
|
+
private messageId;
|
|
104
106
|
private serverPort;
|
|
105
107
|
cache?: Cache;
|
|
106
108
|
song: Song;
|
|
@@ -155,7 +157,7 @@ export declare class Ableton extends EventEmitter implements ConnectionEventEmit
|
|
|
155
157
|
* disconnects, for example.
|
|
156
158
|
*/
|
|
157
159
|
removeAllPropListeners(): void;
|
|
158
|
-
sendRaw(msg: string): Promise<void>;
|
|
160
|
+
sendRaw(msg: string, messageId: number): Promise<void>;
|
|
159
161
|
isConnected(): boolean;
|
|
160
162
|
}
|
|
161
163
|
export { getPackageVersion } from "./util/package-version";
|
package/index.js
CHANGED
|
@@ -48,11 +48,13 @@ class Ableton extends events_1.EventEmitter {
|
|
|
48
48
|
options;
|
|
49
49
|
client;
|
|
50
50
|
msgMap = new Map();
|
|
51
|
+
timeoutMap = new Map();
|
|
51
52
|
eventListeners = new Map();
|
|
52
53
|
heartbeatInterval;
|
|
53
54
|
_isConnected = false;
|
|
54
55
|
buffer = [];
|
|
55
56
|
latency = 0;
|
|
57
|
+
messageId = 0;
|
|
56
58
|
serverPort;
|
|
57
59
|
cache;
|
|
58
60
|
song = new song_1.Song(this);
|
|
@@ -266,6 +268,8 @@ class Ableton extends events_1.EventEmitter {
|
|
|
266
268
|
const messageIndex = msg[1];
|
|
267
269
|
const totalMessages = msg[2];
|
|
268
270
|
const message = msg.subarray(3);
|
|
271
|
+
// Reset the timeout when receiving a new message
|
|
272
|
+
this.timeoutMap.get(messageId)?.();
|
|
269
273
|
if (messageIndex === 0 && totalMessages === 1) {
|
|
270
274
|
this.handleUncompressedMessage((0, zlib_1.unzipSync)(message).toString());
|
|
271
275
|
return;
|
|
@@ -274,8 +278,7 @@ class Ableton extends events_1.EventEmitter {
|
|
|
274
278
|
this.buffer[messageId] = [];
|
|
275
279
|
}
|
|
276
280
|
this.buffer[messageId][messageIndex] = message;
|
|
277
|
-
if (
|
|
278
|
-
this.buffer[messageId].length === totalMessages) {
|
|
281
|
+
if (this.buffer[messageId].filter(Boolean).length === totalMessages) {
|
|
279
282
|
this.handleUncompressedMessage((0, zlib_1.unzipSync)(Buffer.concat(this.buffer[messageId])).toString());
|
|
280
283
|
delete this.buffer[messageId];
|
|
281
284
|
}
|
|
@@ -338,9 +341,20 @@ class Ableton extends events_1.EventEmitter {
|
|
|
338
341
|
const timeout = this.options?.commandTimeoutMs ?? 2000;
|
|
339
342
|
const arg = (0, lodash_1.truncate)(JSON.stringify(command.args), { length: 100 });
|
|
340
343
|
const cls = command.nsid ? `${command.ns}(${command.nsid})` : command.ns;
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
+
this.messageId = (this.messageId + 1) % 256;
|
|
345
|
+
let timeoutId = null;
|
|
346
|
+
const clearCurrentTimeout = () => {
|
|
347
|
+
if (timeoutId) {
|
|
348
|
+
clearTimeout(timeoutId);
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
const startTimeout = () => {
|
|
352
|
+
clearCurrentTimeout();
|
|
353
|
+
timeoutId = setTimeout(() => {
|
|
354
|
+
rej(new TimeoutError(`The command ${cls}.${command.name}(${arg}) timed out after ${timeout} ms.`, payload));
|
|
355
|
+
}, timeout);
|
|
356
|
+
};
|
|
357
|
+
this.timeoutMap.set(this.messageId, startTimeout);
|
|
344
358
|
const currentTimestamp = Date.now();
|
|
345
359
|
this.msgMap.set(msgId, {
|
|
346
360
|
res: (result) => {
|
|
@@ -352,16 +366,16 @@ class Ableton extends events_1.EventEmitter {
|
|
|
352
366
|
});
|
|
353
367
|
}
|
|
354
368
|
this.setPing(duration);
|
|
355
|
-
|
|
369
|
+
clearCurrentTimeout();
|
|
356
370
|
res(result);
|
|
357
371
|
},
|
|
358
372
|
rej,
|
|
359
373
|
clearTimeout: () => {
|
|
360
|
-
|
|
374
|
+
clearCurrentTimeout();
|
|
361
375
|
rej(new DisconnectError(`Live disconnected before being able to respond to ${cls}.${command.name}(${arg})`, payload));
|
|
362
376
|
},
|
|
363
377
|
});
|
|
364
|
-
this.sendRaw(msg);
|
|
378
|
+
this.sendRaw(msg, this.messageId).finally(startTimeout);
|
|
365
379
|
});
|
|
366
380
|
}
|
|
367
381
|
async sendCachedCommand(command) {
|
|
@@ -452,24 +466,29 @@ class Ableton extends events_1.EventEmitter {
|
|
|
452
466
|
removeAllPropListeners() {
|
|
453
467
|
this.eventListeners.clear();
|
|
454
468
|
}
|
|
455
|
-
async sendRaw(msg) {
|
|
469
|
+
async sendRaw(msg, messageId) {
|
|
456
470
|
if (!this.client || !this.serverPort) {
|
|
457
471
|
throw new Error("The client hasn't been started yet. Please call start() first.");
|
|
458
472
|
}
|
|
459
473
|
const buffer = (0, zlib_1.deflateSync)(Buffer.from(msg));
|
|
460
474
|
const byteLimit = this.client.getSendBufferSize() - 100;
|
|
461
|
-
const
|
|
475
|
+
const totalChunks = Math.ceil(buffer.byteLength / byteLimit);
|
|
462
476
|
// Split the message into chunks if it becomes too large
|
|
463
|
-
for (let i = 0; i <
|
|
477
|
+
for (let i = 0; i < totalChunks; i++) {
|
|
464
478
|
const chunk = Buffer.concat([
|
|
465
|
-
//
|
|
466
|
-
Buffer.alloc(1,
|
|
479
|
+
// Message ID (1 byte) - identifies which message this chunk belongs to
|
|
480
|
+
Buffer.alloc(1, messageId),
|
|
481
|
+
// Chunk index (1 byte) - 0, 1, 2, ... for regular chunks
|
|
482
|
+
Buffer.alloc(1, i),
|
|
483
|
+
// Total chunks (1 byte) - number of chunks in this message
|
|
484
|
+
Buffer.alloc(1, totalChunks),
|
|
485
|
+
// Chunk data
|
|
467
486
|
buffer.subarray(i * byteLimit, i * byteLimit + byteLimit),
|
|
468
487
|
]);
|
|
469
488
|
this.client.send(chunk, 0, chunk.length, this.serverPort, "127.0.0.1");
|
|
470
489
|
// Add a bit of a delay between sent chunks to reduce the chance of the
|
|
471
490
|
// receiving buffer filling up which would cause chunks to be discarded.
|
|
472
|
-
await new Promise((res) => setTimeout(res,
|
|
491
|
+
await new Promise((res) => setTimeout(res, 1));
|
|
473
492
|
}
|
|
474
493
|
}
|
|
475
494
|
isConnected() {
|
package/midi-script/Session.py
CHANGED
|
@@ -33,7 +33,7 @@ class Session(Interface):
|
|
|
33
33
|
Sets the offset of the SessionComponent instance.
|
|
34
34
|
"""
|
|
35
35
|
logger.info(
|
|
36
|
-
"Moving session box offset to " + str(track_offset) + " and " + scene_offset + ".")
|
|
36
|
+
"Moving session box offset to " + str(track_offset) + " and " + str(scene_offset) + ".")
|
|
37
37
|
|
|
38
38
|
if hasattr(self, 'session'):
|
|
39
39
|
self.session.set_offsets(track_offset, scene_offset)
|
package/midi-script/Socket.py
CHANGED
|
@@ -37,11 +37,14 @@ class Socket(object):
|
|
|
37
37
|
self._last_error = ""
|
|
38
38
|
self._socket = None
|
|
39
39
|
self._chunk_limit = None
|
|
40
|
+
self._send_buffer = []
|
|
40
41
|
self._message_id = 0
|
|
41
42
|
self._receive_buffer = bytearray()
|
|
43
|
+
# Dictionary to store chunks per message: {message_id: {chunk_index: chunk_data}}
|
|
44
|
+
self._chunks = {}
|
|
42
45
|
|
|
43
46
|
self.read_remote_port()
|
|
44
|
-
self.init_socket(
|
|
47
|
+
self.init_socket()
|
|
45
48
|
|
|
46
49
|
def log_error_once(self, msg):
|
|
47
50
|
if self._last_error != msg:
|
|
@@ -53,18 +56,6 @@ class Socket(object):
|
|
|
53
56
|
self.show_message("Client connected on port " + str(port))
|
|
54
57
|
self._client_addr = ("127.0.0.1", int(port))
|
|
55
58
|
|
|
56
|
-
def read_last_server_port(self):
|
|
57
|
-
try:
|
|
58
|
-
with open(server_port_path) as file:
|
|
59
|
-
port = int(file.read())
|
|
60
|
-
|
|
61
|
-
logger.info("Stored server port: " + str(port))
|
|
62
|
-
return port
|
|
63
|
-
except Exception as e:
|
|
64
|
-
logger.error("Couldn't read stored server port:")
|
|
65
|
-
logger.exception(e)
|
|
66
|
-
return None
|
|
67
|
-
|
|
68
59
|
def read_remote_port(self):
|
|
69
60
|
'''Reads the port our client is listening on'''
|
|
70
61
|
|
|
@@ -96,18 +87,11 @@ class Socket(object):
|
|
|
96
87
|
self._socket.close()
|
|
97
88
|
self._socket = None
|
|
98
89
|
|
|
99
|
-
def init_socket(self
|
|
100
|
-
logger.info(
|
|
101
|
-
"Initializing socket, from stored: " + str(try_stored))
|
|
90
|
+
def init_socket(self):
|
|
91
|
+
logger.info("Initializing socket")
|
|
102
92
|
|
|
103
93
|
try:
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
# Try the port we used last time first
|
|
107
|
-
if try_stored and stored_port:
|
|
108
|
-
self._server_addr = ("127.0.0.1", stored_port)
|
|
109
|
-
else:
|
|
110
|
-
self._server_addr = ("127.0.0.1", 0)
|
|
94
|
+
self._server_addr = ("127.0.0.1", 0)
|
|
111
95
|
|
|
112
96
|
self._socket = socket.socket(
|
|
113
97
|
socket.AF_INET, socket.SOCK_DGRAM)
|
|
@@ -123,16 +107,15 @@ class Socket(object):
|
|
|
123
107
|
|
|
124
108
|
# Write the chosen port to a file
|
|
125
109
|
try:
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
file.write(str(port))
|
|
110
|
+
with open(server_port_path, "w") as file:
|
|
111
|
+
file.write(str(port))
|
|
129
112
|
except Exception as e:
|
|
130
113
|
self.log_error_once(
|
|
131
114
|
"Couldn't save port in file: " + str(e.args))
|
|
132
115
|
raise e
|
|
133
116
|
|
|
134
117
|
try:
|
|
135
|
-
self.send("connect", {"port":
|
|
118
|
+
self.send("connect", {"port": port})
|
|
136
119
|
except Exception as e:
|
|
137
120
|
logger.error("Couldn't send connect to " +
|
|
138
121
|
str(self._client_addr) + ":")
|
|
@@ -165,18 +148,16 @@ class Socket(object):
|
|
|
165
148
|
message_id_byte = struct.pack("B", self._message_id)
|
|
166
149
|
|
|
167
150
|
if len(compressed) < self._chunk_limit:
|
|
168
|
-
self.
|
|
169
|
-
message_id_byte + b'\x00\x01' + compressed
|
|
151
|
+
self._send_buffer.append(
|
|
152
|
+
message_id_byte + b'\x00\x01' + compressed)
|
|
170
153
|
else:
|
|
171
154
|
chunks = list(split_by_n(compressed, self._chunk_limit))
|
|
172
155
|
count = len(chunks)
|
|
173
156
|
count_byte = struct.pack("B", count)
|
|
174
157
|
for i, chunk in enumerate(chunks):
|
|
175
|
-
logger.info("Sending packet " + str(self._message_id) +
|
|
176
|
-
" - " + str(i) + "/" + str(count))
|
|
177
158
|
packet_byte = struct.pack("B", i)
|
|
178
|
-
self.
|
|
179
|
-
message_id_byte + packet_byte + count_byte + chunk
|
|
159
|
+
self._send_buffer.append(
|
|
160
|
+
message_id_byte + packet_byte + count_byte + chunk)
|
|
180
161
|
|
|
181
162
|
def send(self, name, obj=None, uuid=None):
|
|
182
163
|
def jsonReplace(o):
|
|
@@ -206,33 +187,84 @@ class Socket(object):
|
|
|
206
187
|
def process(self):
|
|
207
188
|
try:
|
|
208
189
|
while 1:
|
|
190
|
+
try:
|
|
191
|
+
# Send 5 UDP packets at a time, to avoid
|
|
192
|
+
for i in range(5):
|
|
193
|
+
self._socket.sendto(
|
|
194
|
+
self._send_buffer.pop(0), self._client_addr)
|
|
195
|
+
except:
|
|
196
|
+
pass
|
|
197
|
+
|
|
209
198
|
data = self._socket.recv(65536)
|
|
210
199
|
if len(data) and self.input_handler:
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
200
|
+
# Parse packet format: [messageId][chunkIndex][totalChunks][chunkData]
|
|
201
|
+
if len(data) < 3:
|
|
202
|
+
# Packet too short, skip it
|
|
203
|
+
continue
|
|
204
|
+
|
|
205
|
+
# Get message ID, chunk index, and total chunks from first 3 bytes
|
|
206
|
+
message_id = data[0]
|
|
207
|
+
chunk_index = data[1]
|
|
208
|
+
total_chunks = data[2]
|
|
209
|
+
|
|
210
|
+
# Handle Python 2/3 compatibility
|
|
211
|
+
if isinstance(message_id, bytes):
|
|
212
|
+
message_id = ord(message_id)
|
|
213
|
+
if isinstance(chunk_index, bytes):
|
|
214
|
+
chunk_index = ord(chunk_index)
|
|
215
|
+
if isinstance(total_chunks, bytes):
|
|
216
|
+
total_chunks = ord(total_chunks)
|
|
217
|
+
|
|
218
|
+
chunk_data = data[3:]
|
|
219
|
+
|
|
220
|
+
# Initialize message tracking if this is the first chunk for this message
|
|
221
|
+
if message_id not in self._chunks:
|
|
222
|
+
self._chunks[message_id] = {}
|
|
223
|
+
|
|
224
|
+
# Store the chunk
|
|
225
|
+
self._chunks[message_id][chunk_index] = chunk_data
|
|
226
|
+
|
|
227
|
+
# Check if we have all chunks for this message
|
|
228
|
+
if len(self._chunks[message_id]) == total_chunks:
|
|
229
|
+
# We have all chunks! Reassemble in order
|
|
230
|
+
packet_parts = []
|
|
231
|
+
for i in range(total_chunks):
|
|
232
|
+
if i in self._chunks[message_id]:
|
|
233
|
+
packet_parts.append(
|
|
234
|
+
self._chunks[message_id][i])
|
|
235
|
+
else:
|
|
236
|
+
# Missing chunk - this shouldn't happen if total_chunks is correct
|
|
237
|
+
logger.error(
|
|
238
|
+
"Missing chunk %d for message %d" % (i, message_id))
|
|
239
|
+
break
|
|
240
|
+
else:
|
|
241
|
+
# All chunks present, reassemble
|
|
242
|
+
packet = b''.join(packet_parts)
|
|
243
|
+
|
|
244
|
+
# Remove this message from tracking
|
|
245
|
+
del self._chunks[message_id]
|
|
246
|
+
|
|
247
|
+
# Handle Python 2/3 compatibility for zlib.decompress
|
|
248
|
+
if sys.version_info[0] < 3:
|
|
249
|
+
packet = str(packet)
|
|
250
|
+
|
|
251
|
+
unzipped = zlib.decompress(packet)
|
|
252
|
+
|
|
253
|
+
# Handle bytes to string conversion for Python 3
|
|
254
|
+
if sys.version_info[0] >= 3 and isinstance(unzipped, bytes):
|
|
255
|
+
unzipped = unzipped.decode('utf-8')
|
|
256
|
+
|
|
257
|
+
payload = json.loads(unzipped)
|
|
258
|
+
self.input_handler(payload)
|
|
230
259
|
|
|
231
260
|
except socket.error as e:
|
|
232
|
-
if (e.errno != 35 and e.errno != 10035 and e.errno != 10054):
|
|
261
|
+
if (e.errno != 35 and e.errno != 10035 and e.errno != 10054 and e.errno != 10022):
|
|
233
262
|
logger.error("Socket error:")
|
|
234
263
|
logger.exception(e)
|
|
235
264
|
return
|
|
236
265
|
except Exception as e:
|
|
237
266
|
logger.error("Error processing request:")
|
|
238
267
|
logger.exception(e)
|
|
268
|
+
# Clear chunks on error to prevent stuck state
|
|
269
|
+
# Optionally, we could clear only the problematic message_id, but for safety, clear all
|
|
270
|
+
self._chunks = {}
|
package/midi-script/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
version = "3.7.
|
|
1
|
+
version = "3.7.2"
|
package/ns/song.d.ts
CHANGED
|
@@ -35,7 +35,7 @@ export interface GettableProperties {
|
|
|
35
35
|
overdub: boolean;
|
|
36
36
|
punch_in: boolean;
|
|
37
37
|
punch_out: boolean;
|
|
38
|
-
re_enable_automation_enabled:
|
|
38
|
+
re_enable_automation_enabled: boolean;
|
|
39
39
|
record_mode: number;
|
|
40
40
|
return_tracks: RawTrack[];
|
|
41
41
|
root_note: number;
|
|
@@ -86,7 +86,7 @@ export interface SettableProperties {
|
|
|
86
86
|
overdub: boolean;
|
|
87
87
|
punch_in: boolean;
|
|
88
88
|
punch_out: boolean;
|
|
89
|
-
re_enable_automation_enabled:
|
|
89
|
+
re_enable_automation_enabled: boolean;
|
|
90
90
|
record_mode: number;
|
|
91
91
|
return_tracks: number;
|
|
92
92
|
root_note: number;
|
|
@@ -129,7 +129,7 @@ export interface ObservableProperties {
|
|
|
129
129
|
overdub: boolean;
|
|
130
130
|
punch_in: boolean;
|
|
131
131
|
punch_out: boolean;
|
|
132
|
-
re_enable_automation_enabled:
|
|
132
|
+
re_enable_automation_enabled: boolean;
|
|
133
133
|
record_mode: number;
|
|
134
134
|
return_tracks: RawTrack[];
|
|
135
135
|
scenes: RawScene[];
|
|
@@ -207,6 +207,7 @@ export declare class Song extends Namespace<GettableProperties, TransformedPrope
|
|
|
207
207
|
jumpToNextCue(): Promise<any>;
|
|
208
208
|
jumpToPrevCue(): Promise<any>;
|
|
209
209
|
playSelection(): Promise<any>;
|
|
210
|
+
reEnableAutomation(): Promise<any>;
|
|
210
211
|
redo(): Promise<any>;
|
|
211
212
|
scrubBy(amount: number): Promise<any>;
|
|
212
213
|
setData(key: string, value: any): Promise<any>;
|
package/ns/song.js
CHANGED
|
@@ -126,6 +126,9 @@ class Song extends _1.Namespace {
|
|
|
126
126
|
async playSelection() {
|
|
127
127
|
return this.sendCommand("play_selection");
|
|
128
128
|
}
|
|
129
|
+
async reEnableAutomation() {
|
|
130
|
+
return this.sendCommand("re_enable_automation");
|
|
131
|
+
}
|
|
129
132
|
async redo() {
|
|
130
133
|
return this.sendCommand("redo");
|
|
131
134
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ableton-js",
|
|
3
|
-
"version": "3.7.
|
|
3
|
+
"version": "3.7.2",
|
|
4
4
|
"description": "Control Ableton Live from Node",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"author": "Leo Bernard <admin@leolabs.org>",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"ableton:kill": "pkill -KILL -f \"Ableton Live\"",
|
|
25
25
|
"ableton10:start": "yarn ableton:kill; yarn ableton:clean && yarn ableton:copy-script && yarn ableton10:launch && yarn ableton:logs",
|
|
26
26
|
"ableton11:start": "yarn ableton:kill; yarn ableton:clean && yarn ableton:copy-script && yarn ableton11:launch && yarn ableton:logs",
|
|
27
|
-
"ableton12:start": "yarn ableton:kill; yarn ableton:clean && yarn ableton:copy-script && yarn
|
|
27
|
+
"ableton12:start": "yarn ableton:kill; yarn ableton:clean && yarn ableton:copy-script && yarn ableton12:launch && yarn ableton:logs",
|
|
28
28
|
"prepublishOnly": "yarn build",
|
|
29
29
|
"build:doc": "jsdoc2md --files src/**/*.ts --configure ./jsdoc2md.json > ./API.md",
|
|
30
30
|
"version": "node hooks/prepublish.js && git add midi-script/version.py && auto-changelog -p -l 100 && git add CHANGELOG.md",
|