ableton-js 2.9.1 → 3.0.0
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/README.md +8 -0
- package/index.d.ts +20 -8
- package/index.js +184 -51
- package/midi-script/AbletonJS.py +4 -3
- package/midi-script/Internal.py +4 -1
- package/midi-script/Socket.py +47 -11
- package/ns/internal.d.ts +1 -0
- package/package.json +1 -1
- package/util/logger.d.ts +6 -0
- package/util/logger.js +2 -0
- package/util/tests.js +9 -6
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.0.0](https://github.com/leolabs/ableton.js/compare/v2.9.1...v3.0.0)
|
|
8
|
+
|
|
9
|
+
- :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)
|
|
10
|
+
- :sparkles: Add a timeout param to the start method [`600e752`](https://github.com/leolabs/ableton.js/commit/600e752c5f7c0e349b0cb69aaf4b8f6238f6a1d4)
|
|
11
|
+
- :loud_sound: Add a logger interface for the library to log information [`e31e9cc`](https://github.com/leolabs/ableton.js/commit/e31e9cc1a33b90e0c482e87eae645976a8668ad2)
|
|
12
|
+
|
|
7
13
|
#### [v2.9.1](https://github.com/leolabs/ableton.js/compare/v2.9.0...v2.9.1)
|
|
8
14
|
|
|
15
|
+
> 21 February 2023
|
|
16
|
+
|
|
9
17
|
- :sparkles: Add support for available_(input/output)_routing_(channels/types) [`0c09881`](https://github.com/leolabs/ableton.js/commit/0c098819369cccf257284ad4510178833853d1e5)
|
|
10
18
|
- Use enum-style values for NavDirection [`713f864`](https://github.com/leolabs/ableton.js/commit/713f86475036debabf4e401c87ddb8b08510d262)
|
|
11
19
|
|
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
|
-
|
|
40
|
-
|
|
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,26 @@ export declare class Ableton extends EventEmitter implements ConnectionEventEmit
|
|
|
51
53
|
private cancelConnectionEvent;
|
|
52
54
|
private buffer;
|
|
53
55
|
private latency;
|
|
54
|
-
private
|
|
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
|
-
|
|
63
|
-
|
|
62
|
+
private clientPortFile;
|
|
63
|
+
private serverPortFile;
|
|
64
|
+
private logger;
|
|
65
|
+
constructor(options?: AbletonOptions | undefined);
|
|
66
|
+
/**
|
|
67
|
+
* Starts the server and waits for a connection with Live to be established.
|
|
68
|
+
*
|
|
69
|
+
* @param timeoutMs
|
|
70
|
+
* If set, the function will throw an error if it can't establish a connection
|
|
71
|
+
* in the given time. Should be higher than 2000ms to avoid false positives.
|
|
72
|
+
*/
|
|
73
|
+
start(timeoutMs?: number): Promise<void>;
|
|
74
|
+
/** Closes the client */
|
|
75
|
+
close(): Promise<unknown>;
|
|
64
76
|
/**
|
|
65
77
|
* Returns the latency between the last command and its response.
|
|
66
78
|
* 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,178 @@ 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.
|
|
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.logger = options === null || options === void 0 ? void 0 : options.logger;
|
|
134
136
|
_this.cache = new lru_cache_1.default(__assign({ max: 500, ttl: 1000 * 60 * 10 }, options === null || options === void 0 ? void 0 : options.cacheOptions));
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
137
|
+
_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);
|
|
138
|
+
_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);
|
|
139
|
+
return _this;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Starts the server and waits for a connection with Live to be established.
|
|
143
|
+
*
|
|
144
|
+
* @param timeoutMs
|
|
145
|
+
* If set, the function will throw an error if it can't establish a connection
|
|
146
|
+
* in the given time. Should be higher than 2000ms to avoid false positives.
|
|
147
|
+
*/
|
|
148
|
+
Ableton.prototype.start = function (timeoutMs) {
|
|
149
|
+
var _a, _b, _c, _d;
|
|
150
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
151
|
+
var connection, timeout, heartbeat;
|
|
152
|
+
var _this = this;
|
|
153
|
+
return __generator(this, function (_e) {
|
|
154
|
+
switch (_e.label) {
|
|
139
155
|
case 0:
|
|
140
|
-
this.
|
|
141
|
-
|
|
156
|
+
this.client = dgram_1.default.createSocket({ type: "udp4" });
|
|
157
|
+
this.client.addListener("message", this.handleIncoming.bind(this));
|
|
158
|
+
this.client.addListener("listening", function () { return __awaiter(_this, void 0, void 0, function () {
|
|
159
|
+
var clientPort;
|
|
160
|
+
var _a, _b;
|
|
161
|
+
return __generator(this, function (_c) {
|
|
162
|
+
switch (_c.label) {
|
|
163
|
+
case 0:
|
|
164
|
+
clientPort = (_a = this.client) === null || _a === void 0 ? void 0 : _a.address().port;
|
|
165
|
+
(_b = this.logger) === null || _b === void 0 ? void 0 : _b.info("Bound client to port:", { clientPort: clientPort });
|
|
166
|
+
// Write used port to a file to Live can read from it
|
|
167
|
+
return [4 /*yield*/, promises_1.writeFile(this.clientPortFile, String(clientPort))];
|
|
168
|
+
case 1:
|
|
169
|
+
// Write used port to a file to Live can read from it
|
|
170
|
+
_c.sent();
|
|
171
|
+
return [2 /*return*/];
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
}); });
|
|
175
|
+
this.client.bind(undefined, "127.0.0.1");
|
|
176
|
+
// Wait for the server port file to exist
|
|
177
|
+
return [4 /*yield*/, new Promise(function (res) { return __awaiter(_this, void 0, void 0, function () {
|
|
178
|
+
var serverPort, e_1;
|
|
179
|
+
var _this = this;
|
|
180
|
+
var _a;
|
|
181
|
+
return __generator(this, function (_b) {
|
|
182
|
+
switch (_b.label) {
|
|
183
|
+
case 0:
|
|
184
|
+
_b.trys.push([0, 2, , 3]);
|
|
185
|
+
return [4 /*yield*/, promises_1.readFile(this.serverPortFile)];
|
|
186
|
+
case 1:
|
|
187
|
+
serverPort = _b.sent();
|
|
188
|
+
this.serverPort = Number(serverPort.toString());
|
|
189
|
+
(_a = this.logger) === null || _a === void 0 ? void 0 : _a.info("Server port:", { port: this.serverPort });
|
|
190
|
+
res();
|
|
191
|
+
return [3 /*break*/, 3];
|
|
192
|
+
case 2:
|
|
193
|
+
e_1 = _b.sent();
|
|
194
|
+
return [3 /*break*/, 3];
|
|
195
|
+
case 3:
|
|
196
|
+
// Set up a watcher in case the server port changes
|
|
197
|
+
fs_1.watchFile(this.serverPortFile, function (curr) { return __awaiter(_this, void 0, void 0, function () {
|
|
198
|
+
var serverPort, newPort;
|
|
199
|
+
var _a;
|
|
200
|
+
return __generator(this, function (_b) {
|
|
201
|
+
switch (_b.label) {
|
|
202
|
+
case 0:
|
|
203
|
+
if (!curr.isFile()) return [3 /*break*/, 2];
|
|
204
|
+
return [4 /*yield*/, promises_1.readFile(this.serverPortFile)];
|
|
205
|
+
case 1:
|
|
206
|
+
serverPort = _b.sent();
|
|
207
|
+
newPort = Number(serverPort.toString());
|
|
208
|
+
if (!isNaN(newPort) && newPort !== this.serverPort) {
|
|
209
|
+
(_a = this.logger) === null || _a === void 0 ? void 0 : _a.info("Server port changed:", { port: newPort });
|
|
210
|
+
this.serverPort = Number(serverPort.toString());
|
|
211
|
+
}
|
|
212
|
+
res();
|
|
213
|
+
_b.label = 2;
|
|
214
|
+
case 2: return [2 /*return*/];
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
}); });
|
|
218
|
+
return [2 /*return*/];
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
}); })];
|
|
142
222
|
case 1:
|
|
143
|
-
|
|
144
|
-
|
|
223
|
+
// Wait for the server port file to exist
|
|
224
|
+
_e.sent();
|
|
225
|
+
(_a = this.logger) === null || _a === void 0 ? void 0 : _a.info("Checking connection...");
|
|
226
|
+
connection = new Promise(function (res) { return _this.once("connect", res); });
|
|
227
|
+
if (!timeoutMs) return [3 /*break*/, 3];
|
|
228
|
+
timeout = new Promise(function (_, rej) {
|
|
229
|
+
return setTimeout(function () { return rej("Connection timed out."); }, timeoutMs);
|
|
230
|
+
});
|
|
231
|
+
return [4 /*yield*/, Promise.race([connection, timeout])];
|
|
145
232
|
case 2:
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
case
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
this
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
233
|
+
_e.sent();
|
|
234
|
+
return [3 /*break*/, 5];
|
|
235
|
+
case 3: return [4 /*yield*/, connection];
|
|
236
|
+
case 4:
|
|
237
|
+
_e.sent();
|
|
238
|
+
_e.label = 5;
|
|
239
|
+
case 5:
|
|
240
|
+
(_b = this.logger) === null || _b === void 0 ? void 0 : _b.info("Got connection!");
|
|
241
|
+
heartbeat = function () { return __awaiter(_this, void 0, void 0, function () {
|
|
242
|
+
var e_2;
|
|
243
|
+
return __generator(this, function (_a) {
|
|
244
|
+
switch (_a.label) {
|
|
245
|
+
case 0:
|
|
246
|
+
this.cancelConnectionEvent = false;
|
|
247
|
+
_a.label = 1;
|
|
248
|
+
case 1:
|
|
249
|
+
_a.trys.push([1, 3, , 4]);
|
|
250
|
+
return [4 /*yield*/, this.internal.get("ping")];
|
|
251
|
+
case 2:
|
|
252
|
+
_a.sent();
|
|
253
|
+
if (!this._isConnected && !this.cancelConnectionEvent) {
|
|
254
|
+
this._isConnected = true;
|
|
255
|
+
this.emit("connect", "heartbeat");
|
|
256
|
+
}
|
|
257
|
+
return [3 /*break*/, 4];
|
|
258
|
+
case 3:
|
|
259
|
+
e_2 = _a.sent();
|
|
260
|
+
if (this._isConnected && !this.cancelConnectionEvent) {
|
|
261
|
+
this._isConnected = false;
|
|
262
|
+
this.eventListeners.clear();
|
|
263
|
+
this.msgMap.forEach(function (msg) { return msg.clearTimeout(); });
|
|
264
|
+
this.msgMap.clear();
|
|
265
|
+
this.emit("disconnect", "heartbeat");
|
|
266
|
+
}
|
|
267
|
+
return [3 /*break*/, 4];
|
|
268
|
+
case 4: return [2 /*return*/];
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
}); };
|
|
272
|
+
this.heartbeatInterval = setInterval(heartbeat, (_d = (_c = this.options) === null || _c === void 0 ? void 0 : _c.heartbeatInterval) !== null && _d !== void 0 ? _d : 2000);
|
|
273
|
+
heartbeat();
|
|
274
|
+
this.internal
|
|
275
|
+
.get("version")
|
|
276
|
+
.then(function (v) {
|
|
277
|
+
var jsVersion = package_version_1.getPackageVersion();
|
|
278
|
+
if (semver_1.default.lt(v, jsVersion)) {
|
|
279
|
+
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");
|
|
280
|
+
}
|
|
281
|
+
})
|
|
282
|
+
.catch(function () { });
|
|
283
|
+
return [2 /*return*/];
|
|
163
284
|
}
|
|
164
285
|
});
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
|
|
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
|
-
}
|
|
286
|
+
});
|
|
287
|
+
};
|
|
288
|
+
/** Closes the client */
|
|
179
289
|
Ableton.prototype.close = function () {
|
|
180
|
-
this
|
|
181
|
-
|
|
182
|
-
|
|
290
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
291
|
+
var closePromise;
|
|
292
|
+
var _this = this;
|
|
293
|
+
return __generator(this, function (_a) {
|
|
294
|
+
this.cancelConnectionEvent = true;
|
|
295
|
+
fs_1.unwatchFile(this.serverPortFile);
|
|
296
|
+
if (this.heartbeatInterval) {
|
|
297
|
+
clearInterval(this.heartbeatInterval);
|
|
298
|
+
}
|
|
299
|
+
if (this.client) {
|
|
300
|
+
closePromise = new Promise(function (res) { var _a; return (_a = _this.client) === null || _a === void 0 ? void 0 : _a.once("close", res); });
|
|
301
|
+
this.client.close();
|
|
302
|
+
return [2 /*return*/, closePromise];
|
|
303
|
+
}
|
|
304
|
+
return [2 /*return*/];
|
|
305
|
+
});
|
|
306
|
+
});
|
|
183
307
|
};
|
|
184
308
|
/**
|
|
185
309
|
* Returns the latency between the last command and its response.
|
|
@@ -209,6 +333,7 @@ var Ableton = /** @class */ (function (_super) {
|
|
|
209
333
|
}
|
|
210
334
|
};
|
|
211
335
|
Ableton.prototype.handleUncompressedMessage = function (msg) {
|
|
336
|
+
var _a;
|
|
212
337
|
this.emit("raw_message", msg);
|
|
213
338
|
var data = JSON.parse(msg);
|
|
214
339
|
var functionCallback = this.msgMap.get(data.uuid);
|
|
@@ -233,6 +358,12 @@ var Ableton = /** @class */ (function (_super) {
|
|
|
233
358
|
return;
|
|
234
359
|
}
|
|
235
360
|
if (data.event === "connect") {
|
|
361
|
+
if (data.data.port && data.data.port !== this.serverPort) {
|
|
362
|
+
(_a = this.logger) === null || _a === void 0 ? void 0 : _a.info("Got new server port via connect:", {
|
|
363
|
+
port: data.data.port,
|
|
364
|
+
});
|
|
365
|
+
this.serverPort = data.data.port;
|
|
366
|
+
}
|
|
236
367
|
if (this._isConnected === false) {
|
|
237
368
|
this._isConnected = true;
|
|
238
369
|
this.cancelConnectionEvent = true;
|
|
@@ -269,8 +400,7 @@ var Ableton = /** @class */ (function (_super) {
|
|
|
269
400
|
rej(new TimeoutError([
|
|
270
401
|
"The command " + cls + "." + command.name + "(" + arg + ") timed out after " + timeout + " ms.",
|
|
271
402
|
"Please make sure that Ableton is running and that you have the latest",
|
|
272
|
-
"version of AbletonJS'
|
|
273
|
-
_this.sendPort + " and sending on port " + _this.listenPort + ".",
|
|
403
|
+
"version of AbletonJS' MIDI script installed and renamed to \"AbletonJS\".",
|
|
274
404
|
].join(" "), payload));
|
|
275
405
|
}, timeout);
|
|
276
406
|
var currentTimestamp = Date.now();
|
|
@@ -417,6 +547,9 @@ var Ableton = /** @class */ (function (_super) {
|
|
|
417
547
|
this.eventListeners.clear();
|
|
418
548
|
};
|
|
419
549
|
Ableton.prototype.sendRaw = function (msg) {
|
|
550
|
+
if (!this.client || !this.serverPort) {
|
|
551
|
+
throw new Error("The client hasn't been started yet. Please call start() first.");
|
|
552
|
+
}
|
|
420
553
|
var buffer = zlib_1.deflateSync(Buffer.from(msg));
|
|
421
554
|
// Based on this thread, 7500 bytes seems like a safe value
|
|
422
555
|
// https://stackoverflow.com/questions/22819214/udp-message-too-long
|
|
@@ -429,7 +562,7 @@ var Ableton = /** @class */ (function (_super) {
|
|
|
429
562
|
Buffer.alloc(1, i + 1 === chunks ? 255 : i),
|
|
430
563
|
buffer.slice(i * byteLimit, i * byteLimit + byteLimit),
|
|
431
564
|
]);
|
|
432
|
-
this.client.send(chunk, 0, chunk.length, this.
|
|
565
|
+
this.client.send(chunk, 0, chunk.length, this.serverPort, "127.0.0.1");
|
|
433
566
|
}
|
|
434
567
|
};
|
|
435
568
|
Ableton.prototype.isConnected = function () {
|
package/midi-script/AbletonJS.py
CHANGED
|
@@ -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)
|
package/midi-script/Internal.py
CHANGED
package/midi-script/Socket.py
CHANGED
|
@@ -2,7 +2,8 @@ import socket
|
|
|
2
2
|
import json
|
|
3
3
|
import struct
|
|
4
4
|
import zlib
|
|
5
|
-
import
|
|
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
|
|
36
|
+
def __init__(self, handler):
|
|
29
37
|
self.input_handler = handler
|
|
30
|
-
self.
|
|
31
|
-
self.
|
|
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.
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
package/package.json
CHANGED
package/util/logger.d.ts
ADDED
package/util/logger.js
ADDED
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
|
-
|
|
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
|
-
|
|
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
|
|
61
|
+
case 5: return [2 /*return*/];
|
|
59
62
|
}
|
|
60
63
|
});
|
|
61
64
|
}); };
|