ableton-js 2.9.1 → 3.0.1

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 CHANGED
@@ -4,8 +4,23 @@ 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.0.1](https://github.com/leolabs/ableton.js/compare/v3.0.0...v3.0.1)
8
+
9
+ - :memo: Add breaking change reminder to the changelog [`3158328`](https://github.com/leolabs/ableton.js/commit/31583280950ac4c9a4d8d425592c05481ee7b3e7)
10
+ - :sparkles: Don't allow starting the server multiple times [`83a42b1`](https://github.com/leolabs/ableton.js/commit/83a42b1186ede6820efa13c6cec7b599693a1bb5)
11
+
12
+ ### [v3.0.0](https://github.com/leolabs/ableton.js/compare/v2.9.1...v3.0.0)
13
+
14
+ > 25 February 2023
15
+
16
+ - :sparkles: Don't rely on fixed ports, make server and client bind to a random free one [`54f7737`](https://github.com/leolabs/ableton.js/commit/54f773758ec359ab9b5bc3f5fd3e7c96cae4e8b8)
17
+ - :sparkles: Add a timeout param to the start method [`600e752`](https://github.com/leolabs/ableton.js/commit/600e752c5f7c0e349b0cb69aaf4b8f6238f6a1d4)
18
+ - :loud_sound: Add a logger interface for the library to log information [`e31e9cc`](https://github.com/leolabs/ableton.js/commit/e31e9cc1a33b90e0c482e87eae645976a8668ad2)
19
+
7
20
  #### [v2.9.1](https://github.com/leolabs/ableton.js/compare/v2.9.0...v2.9.1)
8
21
 
22
+ > 21 February 2023
23
+
9
24
  - :sparkles: Add support for available_(input/output)_routing_(channels/types) [`0c09881`](https://github.com/leolabs/ableton.js/commit/0c098819369cccf257284ad4510178833853d1e5)
10
25
  - Use enum-style values for NavDirection [`713f864`](https://github.com/leolabs/ableton.js/commit/713f86475036debabf4e401c87ddb8b08510d262)
11
26
 
package/README.md CHANGED
@@ -51,6 +51,9 @@ import { Ableton } from "ableton-js";
51
51
  const ableton = new Ableton();
52
52
 
53
53
  const test = async () => {
54
+ // Establishes a connection with Live
55
+ await ableton.start();
56
+
54
57
  // Observe the current playback state and tempo
55
58
  ableton.song.addListener("is_playing", (p) => console.log("Playing:", p));
56
59
  ableton.song.addListener("tempo", (t) => console.log("Tempo:", t));
@@ -94,6 +97,11 @@ Ableton.js uses UDP to communicate with the MIDI Script. Each message is a JSON
94
97
  object containing required data and a UUID so request and response can be
95
98
  associated with each other.
96
99
 
100
+ ### Used Ports
101
+
102
+ Both the client and the server bind to a random available port and store that
103
+ port in a local file so the other side knows which port to send messages to.
104
+
97
105
  ### Compression and Chunking
98
106
 
99
107
  To allow sending large JSON payloads, requests to and responses from the MIDI
package/index.d.ts CHANGED
@@ -6,6 +6,7 @@ import { Internal } from "./ns/internal";
6
6
  import { Application } from "./ns/application";
7
7
  import { Midi } from "./ns/midi";
8
8
  import { Cache } from "./util/cache";
9
+ import { Logger } from "./util/logger";
9
10
  interface Command {
10
11
  uuid: string;
11
12
  ns: string;
@@ -36,13 +37,14 @@ export declare class TimeoutError extends Error {
36
37
  constructor(message: string, payload: Command);
37
38
  }
38
39
  export interface AbletonOptions {
39
- host?: string;
40
- sendPort?: number;
41
- listenPort?: number;
40
+ serverPortFile?: string;
41
+ clientPortFile?: string;
42
42
  heartbeatInterval?: number;
43
43
  cacheOptions?: LruCache.Options<string, any>;
44
+ logger?: Logger;
44
45
  }
45
46
  export declare class Ableton extends EventEmitter implements ConnectionEventEmitter {
47
+ private options?;
46
48
  private client;
47
49
  private msgMap;
48
50
  private eventListeners;
@@ -51,16 +53,27 @@ export declare class Ableton extends EventEmitter implements ConnectionEventEmit
51
53
  private cancelConnectionEvent;
52
54
  private buffer;
53
55
  private latency;
54
- private host;
55
- private sendPort;
56
- private listenPort;
56
+ private serverPort;
57
57
  cache: Cache;
58
58
  song: Song;
59
59
  application: Application;
60
60
  internal: Internal;
61
61
  midi: Midi;
62
- constructor(options?: AbletonOptions);
63
- close(): void;
62
+ private clientPortFile;
63
+ private serverPortFile;
64
+ private logger;
65
+ private clientState;
66
+ constructor(options?: AbletonOptions | undefined);
67
+ /**
68
+ * Starts the server and waits for a connection with Live to be established.
69
+ *
70
+ * @param timeoutMs
71
+ * If set, the function will throw an error if it can't establish a connection
72
+ * in the given time. Should be higher than 2000ms to avoid false positives.
73
+ */
74
+ start(timeoutMs?: number): Promise<void>;
75
+ /** Closes the client */
76
+ close(): Promise<void>;
64
77
  /**
65
78
  * Returns the latency between the last command and its response.
66
79
  * This is a rough measurement, so don't rely too much on it.
package/index.js CHANGED
@@ -87,18 +87,24 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
87
87
  };
88
88
  Object.defineProperty(exports, "__esModule", { value: true });
89
89
  exports.getPackageVersion = exports.Ableton = exports.TimeoutError = void 0;
90
+ var os_1 = __importDefault(require("os"));
91
+ var path_1 = __importDefault(require("path"));
90
92
  var dgram_1 = __importDefault(require("dgram"));
91
93
  var events_1 = require("events");
92
94
  var uuid_1 = require("uuid");
93
95
  var semver_1 = __importDefault(require("semver"));
94
96
  var zlib_1 = require("zlib");
95
97
  var lru_cache_1 = __importDefault(require("lru-cache"));
98
+ var fs_1 = require("fs");
99
+ var promises_1 = require("fs/promises");
96
100
  var song_1 = require("./ns/song");
97
101
  var internal_1 = require("./ns/internal");
98
102
  var application_1 = require("./ns/application");
99
103
  var midi_1 = require("./ns/midi");
100
104
  var package_version_1 = require("./util/package-version");
101
105
  var cache_1 = require("./util/cache");
106
+ var SERVER_PORT_FILE = "ableton-js-server.port";
107
+ var CLIENT_PORT_FILE = "ableton-js-client.port";
102
108
  var TimeoutError = /** @class */ (function (_super) {
103
109
  __extends(TimeoutError, _super);
104
110
  function TimeoutError(message, payload) {
@@ -115,6 +121,7 @@ var Ableton = /** @class */ (function (_super) {
115
121
  function Ableton(options) {
116
122
  var _a, _b, _c, _d;
117
123
  var _this = _super.call(this) || this;
124
+ _this.options = options;
118
125
  _this.msgMap = new Map();
119
126
  _this.eventListeners = new Map();
120
127
  _this._isConnected = false;
@@ -125,61 +132,193 @@ var Ableton = /** @class */ (function (_super) {
125
132
  _this.application = new application_1.Application(_this);
126
133
  _this.internal = new internal_1.Internal(_this);
127
134
  _this.midi = new midi_1.Midi(_this);
128
- _this.host = (_a = options === null || options === void 0 ? void 0 : options.host) !== null && _a !== void 0 ? _a : "127.0.0.1";
129
- _this.sendPort = (_b = options === null || options === void 0 ? void 0 : options.sendPort) !== null && _b !== void 0 ? _b : 39041;
130
- _this.listenPort = (_c = options === null || options === void 0 ? void 0 : options.listenPort) !== null && _c !== void 0 ? _c : 39031;
131
- _this.client = dgram_1.default.createSocket({ type: "udp4" });
132
- _this.client.bind(_this.listenPort, _this.host);
133
- _this.client.addListener("message", _this.handleIncoming.bind(_this));
135
+ _this.clientState = "closed";
136
+ _this.logger = options === null || options === void 0 ? void 0 : options.logger;
134
137
  _this.cache = new lru_cache_1.default(__assign({ max: 500, ttl: 1000 * 60 * 10 }, options === null || options === void 0 ? void 0 : options.cacheOptions));
135
- var heartbeat = function () { return __awaiter(_this, void 0, void 0, function () {
136
- var e_1;
138
+ _this.clientPortFile = path_1.default.join(os_1.default.tmpdir(), (_b = (_a = _this.options) === null || _a === void 0 ? void 0 : _a.clientPortFile) !== null && _b !== void 0 ? _b : CLIENT_PORT_FILE);
139
+ _this.serverPortFile = path_1.default.join(os_1.default.tmpdir(), (_d = (_c = _this.options) === null || _c === void 0 ? void 0 : _c.serverPortFile) !== null && _d !== void 0 ? _d : SERVER_PORT_FILE);
140
+ return _this;
141
+ }
142
+ /**
143
+ * Starts the server and waits for a connection with Live to be established.
144
+ *
145
+ * @param timeoutMs
146
+ * If set, the function will throw an error if it can't establish a connection
147
+ * in the given time. Should be higher than 2000ms to avoid false positives.
148
+ */
149
+ Ableton.prototype.start = function (timeoutMs) {
150
+ var _a, _b, _c, _d, _e;
151
+ return __awaiter(this, void 0, void 0, function () {
152
+ var connection, timeout, heartbeat;
153
+ var _this = this;
154
+ return __generator(this, function (_f) {
155
+ switch (_f.label) {
156
+ case 0:
157
+ if (this.clientState !== "closed") {
158
+ (_a = this.logger) === null || _a === void 0 ? void 0 : _a.warn("Tried call start, but client is already " + this.clientState);
159
+ return [2 /*return*/];
160
+ }
161
+ this.clientState = "starting";
162
+ this.client = dgram_1.default.createSocket({ type: "udp4" });
163
+ this.client.addListener("message", this.handleIncoming.bind(this));
164
+ this.client.addListener("listening", function () { return __awaiter(_this, void 0, void 0, function () {
165
+ var clientPort;
166
+ var _a, _b;
167
+ return __generator(this, function (_c) {
168
+ switch (_c.label) {
169
+ case 0:
170
+ clientPort = (_a = this.client) === null || _a === void 0 ? void 0 : _a.address().port;
171
+ (_b = this.logger) === null || _b === void 0 ? void 0 : _b.info("Bound client to port:", { clientPort: clientPort });
172
+ // Write used port to a file to Live can read from it
173
+ return [4 /*yield*/, promises_1.writeFile(this.clientPortFile, String(clientPort))];
174
+ case 1:
175
+ // Write used port to a file to Live can read from it
176
+ _c.sent();
177
+ return [2 /*return*/];
178
+ }
179
+ });
180
+ }); });
181
+ this.client.bind(undefined, "127.0.0.1");
182
+ // Wait for the server port file to exist
183
+ return [4 /*yield*/, new Promise(function (res) { return __awaiter(_this, void 0, void 0, function () {
184
+ var serverPort, e_1;
185
+ var _this = this;
186
+ var _a;
187
+ return __generator(this, function (_b) {
188
+ switch (_b.label) {
189
+ case 0:
190
+ _b.trys.push([0, 2, , 3]);
191
+ return [4 /*yield*/, promises_1.readFile(this.serverPortFile)];
192
+ case 1:
193
+ serverPort = _b.sent();
194
+ this.serverPort = Number(serverPort.toString());
195
+ (_a = this.logger) === null || _a === void 0 ? void 0 : _a.info("Server port:", { port: this.serverPort });
196
+ res();
197
+ return [3 /*break*/, 3];
198
+ case 2:
199
+ e_1 = _b.sent();
200
+ return [3 /*break*/, 3];
201
+ case 3:
202
+ // Set up a watcher in case the server port changes
203
+ fs_1.watchFile(this.serverPortFile, function (curr) { return __awaiter(_this, void 0, void 0, function () {
204
+ var serverPort, newPort;
205
+ var _a;
206
+ return __generator(this, function (_b) {
207
+ switch (_b.label) {
208
+ case 0:
209
+ if (!curr.isFile()) return [3 /*break*/, 2];
210
+ return [4 /*yield*/, promises_1.readFile(this.serverPortFile)];
211
+ case 1:
212
+ serverPort = _b.sent();
213
+ newPort = Number(serverPort.toString());
214
+ if (!isNaN(newPort) && newPort !== this.serverPort) {
215
+ (_a = this.logger) === null || _a === void 0 ? void 0 : _a.info("Server port changed:", { port: newPort });
216
+ this.serverPort = Number(serverPort.toString());
217
+ }
218
+ res();
219
+ _b.label = 2;
220
+ case 2: return [2 /*return*/];
221
+ }
222
+ });
223
+ }); });
224
+ return [2 /*return*/];
225
+ }
226
+ });
227
+ }); })];
228
+ case 1:
229
+ // Wait for the server port file to exist
230
+ _f.sent();
231
+ (_b = this.logger) === null || _b === void 0 ? void 0 : _b.info("Checking connection...");
232
+ connection = new Promise(function (res) { return _this.once("connect", res); });
233
+ if (!timeoutMs) return [3 /*break*/, 3];
234
+ timeout = new Promise(function (_, rej) {
235
+ return setTimeout(function () { return rej("Connection timed out."); }, timeoutMs);
236
+ });
237
+ return [4 /*yield*/, Promise.race([connection, timeout])];
238
+ case 2:
239
+ _f.sent();
240
+ return [3 /*break*/, 5];
241
+ case 3: return [4 /*yield*/, connection];
242
+ case 4:
243
+ _f.sent();
244
+ _f.label = 5;
245
+ case 5:
246
+ (_c = this.logger) === null || _c === void 0 ? void 0 : _c.info("Got connection!");
247
+ this.clientState = "started";
248
+ heartbeat = function () { return __awaiter(_this, void 0, void 0, function () {
249
+ var e_2;
250
+ return __generator(this, function (_a) {
251
+ switch (_a.label) {
252
+ case 0:
253
+ this.cancelConnectionEvent = false;
254
+ _a.label = 1;
255
+ case 1:
256
+ _a.trys.push([1, 3, , 4]);
257
+ return [4 /*yield*/, this.internal.get("ping")];
258
+ case 2:
259
+ _a.sent();
260
+ if (!this._isConnected && !this.cancelConnectionEvent) {
261
+ this._isConnected = true;
262
+ this.emit("connect", "heartbeat");
263
+ }
264
+ return [3 /*break*/, 4];
265
+ case 3:
266
+ e_2 = _a.sent();
267
+ if (this._isConnected && !this.cancelConnectionEvent) {
268
+ this._isConnected = false;
269
+ this.eventListeners.clear();
270
+ this.msgMap.forEach(function (msg) { return msg.clearTimeout(); });
271
+ this.msgMap.clear();
272
+ this.emit("disconnect", "heartbeat");
273
+ }
274
+ return [3 /*break*/, 4];
275
+ case 4: return [2 /*return*/];
276
+ }
277
+ });
278
+ }); };
279
+ this.heartbeatInterval = setInterval(heartbeat, (_e = (_d = this.options) === null || _d === void 0 ? void 0 : _d.heartbeatInterval) !== null && _e !== void 0 ? _e : 2000);
280
+ heartbeat();
281
+ this.internal
282
+ .get("version")
283
+ .then(function (v) {
284
+ var _a;
285
+ var jsVersion = package_version_1.getPackageVersion();
286
+ if (semver_1.default.lt(v, jsVersion)) {
287
+ (_a = _this.logger) === null || _a === void 0 ? void 0 : _a.warn("The installed version of your AbletonJS plugin (" + v + ") is lower than the JS library (" + jsVersion + ").", "Please update your AbletonJS plugin to the latest version: https://git.io/JvaOu");
288
+ }
289
+ })
290
+ .catch(function () { });
291
+ return [2 /*return*/];
292
+ }
293
+ });
294
+ });
295
+ };
296
+ /** Closes the client */
297
+ Ableton.prototype.close = function () {
298
+ return __awaiter(this, void 0, void 0, function () {
299
+ var closePromise;
300
+ var _this = this;
137
301
  return __generator(this, function (_a) {
138
302
  switch (_a.label) {
139
303
  case 0:
140
- this.cancelConnectionEvent = false;
141
- _a.label = 1;
304
+ this.cancelConnectionEvent = true;
305
+ fs_1.unwatchFile(this.serverPortFile);
306
+ if (this.heartbeatInterval) {
307
+ clearInterval(this.heartbeatInterval);
308
+ }
309
+ if (!this.client) return [3 /*break*/, 2];
310
+ closePromise = new Promise(function (res) { var _a; return (_a = _this.client) === null || _a === void 0 ? void 0 : _a.once("close", res); });
311
+ this.client.close();
312
+ return [4 /*yield*/, closePromise];
142
313
  case 1:
143
- _a.trys.push([1, 3, , 4]);
144
- return [4 /*yield*/, this.song.get("current_song_time")];
145
- case 2:
146
314
  _a.sent();
147
- if (!this._isConnected && !this.cancelConnectionEvent) {
148
- this._isConnected = true;
149
- this.emit("connect", "heartbeat");
150
- }
151
- return [3 /*break*/, 4];
152
- case 3:
153
- e_1 = _a.sent();
154
- if (this._isConnected && !this.cancelConnectionEvent) {
155
- this._isConnected = false;
156
- this.eventListeners.clear();
157
- this.msgMap.forEach(function (msg) { return msg.clearTimeout(); });
158
- this.msgMap.clear();
159
- this.emit("disconnect", "heartbeat");
160
- }
161
- return [3 /*break*/, 4];
162
- case 4: return [2 /*return*/];
315
+ _a.label = 2;
316
+ case 2:
317
+ this.clientState = "closed";
318
+ return [2 /*return*/];
163
319
  }
164
320
  });
165
- }); };
166
- _this.heartbeatInterval = setInterval(heartbeat, (_d = options === null || options === void 0 ? void 0 : options.heartbeatInterval) !== null && _d !== void 0 ? _d : 2000);
167
- heartbeat();
168
- _this.internal
169
- .get("version")
170
- .then(function (v) {
171
- var jsVersion = package_version_1.getPackageVersion();
172
- if (semver_1.default.lt(v, jsVersion)) {
173
- console.warn("The installed version of your AbletonJS plugin (" + v + ") is lower than the JS library (" + jsVersion + ").", "Please update your AbletonJS plugin to the latest version: https://git.io/JvaOu");
174
- }
175
- })
176
- .catch(function () { });
177
- return _this;
178
- }
179
- Ableton.prototype.close = function () {
180
- this.cancelConnectionEvent = true;
181
- clearInterval(this.heartbeatInterval);
182
- this.client.close();
321
+ });
183
322
  };
184
323
  /**
185
324
  * Returns the latency between the last command and its response.
@@ -209,6 +348,7 @@ var Ableton = /** @class */ (function (_super) {
209
348
  }
210
349
  };
211
350
  Ableton.prototype.handleUncompressedMessage = function (msg) {
351
+ var _a;
212
352
  this.emit("raw_message", msg);
213
353
  var data = JSON.parse(msg);
214
354
  var functionCallback = this.msgMap.get(data.uuid);
@@ -233,6 +373,12 @@ var Ableton = /** @class */ (function (_super) {
233
373
  return;
234
374
  }
235
375
  if (data.event === "connect") {
376
+ if (data.data.port && data.data.port !== this.serverPort) {
377
+ (_a = this.logger) === null || _a === void 0 ? void 0 : _a.info("Got new server port via connect:", {
378
+ port: data.data.port,
379
+ });
380
+ this.serverPort = data.data.port;
381
+ }
236
382
  if (this._isConnected === false) {
237
383
  this._isConnected = true;
238
384
  this.cancelConnectionEvent = true;
@@ -269,8 +415,7 @@ var Ableton = /** @class */ (function (_super) {
269
415
  rej(new TimeoutError([
270
416
  "The command " + cls + "." + command.name + "(" + arg + ") timed out after " + timeout + " ms.",
271
417
  "Please make sure that Ableton is running and that you have the latest",
272
- "version of AbletonJS' midi script installed and renamed to \"AbletonJS\", listening on port",
273
- _this.sendPort + " and sending on port " + _this.listenPort + ".",
418
+ "version of AbletonJS' MIDI script installed and renamed to \"AbletonJS\".",
274
419
  ].join(" "), payload));
275
420
  }, timeout);
276
421
  var currentTimestamp = Date.now();
@@ -417,6 +562,9 @@ var Ableton = /** @class */ (function (_super) {
417
562
  this.eventListeners.clear();
418
563
  };
419
564
  Ableton.prototype.sendRaw = function (msg) {
565
+ if (!this.client || !this.serverPort) {
566
+ throw new Error("The client hasn't been started yet. Please call start() first.");
567
+ }
420
568
  var buffer = zlib_1.deflateSync(Buffer.from(msg));
421
569
  // Based on this thread, 7500 bytes seems like a safe value
422
570
  // https://stackoverflow.com/questions/22819214/udp-message-too-long
@@ -429,7 +577,7 @@ var Ableton = /** @class */ (function (_super) {
429
577
  Buffer.alloc(1, i + 1 === chunks ? 255 : i),
430
578
  buffer.slice(i * byteLimit, i * byteLimit + byteLimit),
431
579
  ]);
432
- this.client.send(chunk, 0, chunk.length, this.sendPort, this.host);
580
+ this.client.send(chunk, 0, chunk.length, this.serverPort, "127.0.0.1");
433
581
  }
434
582
  };
435
583
  Ableton.prototype.isConnected = function () {
@@ -52,8 +52,6 @@ class AbletonJS(ControlSurface):
52
52
 
53
53
  self.recv_loop.start()
54
54
 
55
- self.socket.send("connect")
56
-
57
55
  def build_midi_map(self, midi_map_handle):
58
56
  script_handle = self._c_instance.handle()
59
57
  for midi in self.tracked_midi:
@@ -76,9 +74,12 @@ class AbletonJS(ControlSurface):
76
74
  super(AbletonJS, self).disconnect()
77
75
 
78
76
  def command_handler(self, payload):
79
- self.log_message("Received command: " + str(payload))
80
77
  namespace = payload["ns"]
81
78
 
79
+ # Don't clutter the logs
80
+ if not (namespace == "internal" and payload["name"] == "get_prop" and payload["args"]["prop"] == "ping"):
81
+ self.log_message("Received command: " + str(payload))
82
+
82
83
  if namespace in self.handlers:
83
84
  handler = self.handlers[namespace]
84
85
  handler.handle(payload)
@@ -9,5 +9,8 @@ class Internal(Interface):
9
9
  def get_ns(self, nsid):
10
10
  return self
11
11
 
12
+ def get_ping(self, nsid):
13
+ return True
14
+
12
15
  def get_version(self, ns):
13
- return "2.9.1"
16
+ return "3.0.1"
@@ -2,7 +2,8 @@ import socket
2
2
  import json
3
3
  import struct
4
4
  import zlib
5
- import hashlib
5
+ import os
6
+ import tempfile
6
7
  from threading import Timer
7
8
 
8
9
  import Live
@@ -15,6 +16,13 @@ def split_by_n(seq, n):
15
16
  seq = seq[n:]
16
17
 
17
18
 
19
+ server_port_file = "ableton-js-server.port"
20
+ client_port_file = "ableton-js-client.port"
21
+
22
+ client_port_path = os.path.join(tempfile.gettempdir(), client_port_file)
23
+ server_port_path = os.path.join(tempfile.gettempdir(), server_port_file)
24
+
25
+
18
26
  class Socket(object):
19
27
 
20
28
  @staticmethod
@@ -25,12 +33,30 @@ class Socket(object):
25
33
  def set_message(func):
26
34
  Socket.show_message = func
27
35
 
28
- def __init__(self, handler, remotehost='127.0.0.1', remoteport=39031, localhost='127.0.0.1', localport=39041):
36
+ def __init__(self, handler):
29
37
  self.input_handler = handler
30
- self._local_addr = (localhost, localport)
31
- self._remote_addr = (remotehost, remoteport)
38
+ self._server_addr = ('127.0.0.1', 0)
39
+ self._client_addr = ('127.0.0.1', 39031)
40
+ self.read_remote_port()
32
41
  self.init_socket()
33
42
 
43
+ def read_remote_port(self):
44
+ '''Reads the port our client is listening on'''
45
+ try:
46
+ with open(client_port_path) as file:
47
+ port = int(file.read())
48
+ if port != self._client_addr[1]:
49
+ self.log_message("Client port changed: " + str(port))
50
+ self._client_addr = ('127.0.0.1', port)
51
+
52
+ if hasattr(self, "_socket"):
53
+ self.send("connect", {"port": self._server_addr[1]})
54
+ except Exception as e:
55
+ self.log_message("Couldn't read remote port:", str(e.args))
56
+
57
+ t = Timer(1, self.read_remote_port)
58
+ t.start()
59
+
34
60
  def init_socket(self):
35
61
  self._socket = socket.socket(
36
62
  socket.AF_INET, socket.SOCK_DGRAM)
@@ -43,12 +69,22 @@ class Socket(object):
43
69
 
44
70
  def bind(self):
45
71
  try:
46
- self._socket.bind(self._local_addr)
47
- self.log_message('Starting on: ' + str(self._local_addr) +
48
- ', remote addr: ' + str(self._remote_addr))
49
- except:
72
+ self._socket.bind(self._server_addr)
73
+ port = self._socket.getsockname()[1]
74
+
75
+ # Write the chosen port to a file
76
+ with open(server_port_path, "w") as file:
77
+ file.write(str(port))
78
+
79
+ self.send("connect", {"port": port})
80
+
81
+ self.log_message('Starting on: ' + str(self._socket.getsockname()) +
82
+ ', remote addr: ' + str(self._client_addr))
83
+ except Exception as e:
50
84
  msg = 'ERROR: Cannot bind to ' + \
51
- str(self._local_addr) + ', port in use. Trying again...'
85
+ str(self._server_addr) + ': ' + \
86
+ str(e.args) + ', trying again. ' + \
87
+ 'If this keeps happening, try restarting your computer.'
52
88
  self.show_message(msg)
53
89
  self.log_message(msg)
54
90
  t = Timer(5, self.bind)
@@ -62,13 +98,13 @@ class Socket(object):
62
98
  limit = 7500
63
99
 
64
100
  if len(compressed) < limit:
65
- self._socket.sendto(b'\xFF' + compressed, self._remote_addr)
101
+ self._socket.sendto(b'\xFF' + compressed, self._client_addr)
66
102
  else:
67
103
  chunks = list(split_by_n(compressed, limit))
68
104
  count = len(chunks)
69
105
  for i, chunk in enumerate(chunks):
70
106
  count_byte = struct.pack("B", i if i + 1 < count else 255)
71
- self._socket.sendto(count_byte + chunk, self._remote_addr)
107
+ self._socket.sendto(count_byte + chunk, self._client_addr)
72
108
 
73
109
  def send(self, name, obj=None, uuid=None):
74
110
  def jsonReplace(o):
package/ns/internal.d.ts CHANGED
@@ -2,6 +2,7 @@ import { Ableton } from "..";
2
2
  import { Namespace } from ".";
3
3
  export interface GettableProperties {
4
4
  version: string;
5
+ ping: boolean;
5
6
  }
6
7
  export interface TransformedProperties {
7
8
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ableton-js",
3
- "version": "2.9.1",
3
+ "version": "3.0.1",
4
4
  "description": "Control Ableton Live from Node",
5
5
  "main": "index.js",
6
6
  "author": "Leo Bernard <admin@leolabs.org>",
@@ -0,0 +1,6 @@
1
+ export interface Logger {
2
+ debug: (msg: string, ...args: any[]) => unknown;
3
+ info: (msg: string, ...args: any[]) => unknown;
4
+ warn: (msg: string, ...args: any[]) => unknown;
5
+ error: (msg: string, ...args: any[]) => unknown;
6
+ }
package/util/logger.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/util/tests.js CHANGED
@@ -45,17 +45,20 @@ var withAbleton = function (callback) { return __awaiter(void 0, void 0, void 0,
45
45
  case 0:
46
46
  ab = new __1.Ableton();
47
47
  ab.on("error", console.error);
48
- _a.label = 1;
48
+ return [4 /*yield*/, ab.start(2000)];
49
49
  case 1:
50
- _a.trys.push([1, , 3, 4]);
51
- return [4 /*yield*/, callback(ab)];
52
- case 2:
53
50
  _a.sent();
54
- return [3 /*break*/, 4];
51
+ _a.label = 2;
52
+ case 2:
53
+ _a.trys.push([2, , 4, 5]);
54
+ return [4 /*yield*/, callback(ab)];
55
55
  case 3:
56
+ _a.sent();
57
+ return [3 /*break*/, 5];
58
+ case 4:
56
59
  ab.close();
57
60
  return [7 /*endfinally*/];
58
- case 4: return [2 /*return*/];
61
+ case 5: return [2 /*return*/];
59
62
  }
60
63
  });
61
64
  }); };