Skip to content

Commit fbcbbe4

Browse files
authored
refactor: improve osc server design (#218)
* refactor: improve osc server design * pr remarks
1 parent 669c0ea commit fbcbbe4

File tree

6 files changed

+114
-74
lines changed

6 files changed

+114
-74
lines changed

.github/workflows/ci.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ jobs:
1616
- uses: actions/checkout@v4
1717
- if: matrix.os == 'ubuntu-latest'
1818
run: sudo apt install libsecret-1-0
19+
- uses: actions/setup-node@v4
20+
with:
21+
node-version: 22
22+
cache: 'npm'
23+
- run: npm install
1924
- name: Setup Pulsar Editor
2025
uses: pulsar-edit/[email protected]
2126
- name: Run the headless Pulsar Tests

lib/osc-eval.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
'use babel'
2+
3+
import Repl from './repl';
4+
import {Editors} from './editors';
5+
6+
export function oscEvalSubscriber(tidalRepl: Repl, editors: Editors) {
7+
return (message: {}): void => {
8+
if (message['tab'] !== undefined) {
9+
atom.workspace.getPanes()[0].setActiveItem(atom.workspace.getTextEditors()[message['tab']])
10+
}
11+
12+
if (message['row'] && message['column']) {
13+
editors.goTo(message['row'] - 1, message['column'])
14+
}
15+
16+
tidalRepl.eval(message['type'], false);
17+
}
18+
}

lib/osc-loader.js

Lines changed: 0 additions & 57 deletions
This file was deleted.

lib/osc-server.js

Lines changed: 46 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,55 @@
11
'use babel'
22

33
const dgram = require('dgram');
4-
const oscPortProperty = 'tidalcycles.oscEval.port';
5-
const oscIpProperty = 'tidalcycles.oscEval.ip';
4+
const osc = require('osc-min');
65

76
export default class OscServer {
87

98
port = null;
109
ip = null;
1110
sock = null;
1211
consoleView = null;
12+
subscribers: Map<string, Function> = new Map();
1313

14-
constructor(consoleView) {
15-
this.port = atom.config.get(oscPortProperty);
16-
this.ip = atom.config.get(oscIpProperty);
14+
constructor(consoleView, ip, port) {
15+
this.ip = ip;
16+
this.port = port;
1717
this.consoleView = consoleView;
1818
this.sock = dgram.createSocket('udp4');
1919
}
2020

21-
start() {
22-
this.sock.on('error', (err) => {
23-
this.consoleView.logStderr(`OSC server error: \n${err.stack}`)
24-
this.sock.close();
25-
});
21+
start(): Promise<void> {
22+
let promise = new Promise((resolve, reject) => {
23+
this.sock.on('listening', () => {
24+
resolve()
25+
});
2626

27-
this.sock.on('listening', () => {
28-
this.consoleView.logStdout(`Listening for external osc messages on ${this.ip}:${this.port}`)
27+
this.sock.on('error', (err) => {
28+
reject(err);
29+
});
2930
});
3031

3132
this.sock.bind(this.port, this.ip);
33+
34+
this.sock.on('message', (message) => {
35+
let oscMessage = osc.fromBuffer(message);
36+
37+
let subscriber = this.subscribers.get(oscMessage.address);
38+
if (subscriber) {
39+
subscriber(this.#asDictionary(oscMessage.args))
40+
} else {
41+
this.consoleView.logStderr(`Received OSC message on unsupported ${oscMessage.address} address`);
42+
}
43+
});
44+
45+
return promise
46+
.then(() => {
47+
this.consoleView.logStdout(`Listening for external osc messages on ${this.ip}:${this.port}`)
48+
})
49+
.catch(err => {
50+
this.consoleView.logStderr(`OSC server error: \n${err.stack}`)
51+
this.sock.close();
52+
});
3253
}
3354

3455
stop() {
@@ -40,4 +61,17 @@ export default class OscServer {
4061
destroy() {
4162
this.stop();
4263
}
64+
65+
register(address: string, listener: Function) {
66+
this.subscribers.set(address, listener)
67+
}
68+
69+
#asDictionary (oscMap): {} {
70+
let dictionary = {}
71+
for (let i = 0; i < oscMap.length; i += 2) {
72+
dictionary[oscMap[i].value] = oscMap[i+1].value
73+
}
74+
return dictionary
75+
}
76+
4377
}

lib/tidalcycles.js

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@
33
import ConsoleView from './console-view';
44
import Repl from './repl';
55
import TidalListenerRepl from './tidal-listener-repl';
6-
import OscLoader from './osc-loader';
6+
import {oscEvalSubscriber} from './osc-eval';
77
import AutocompleteProvider from './autocomplete-provider';
88
import Ghc from './ghc';
99
import BootTidal from './boot-tidal';
1010
import SoundBrowser from './sound-browser';
11+
import OscServer from "./osc-server";
12+
1113
const Status = require('./status')
1214
const SuperDirt = require('./superdirt')
1315
const { LINE, MULTI_LINE, WHOLE_EDITOR, Editors } = require('./editors')
@@ -20,6 +22,7 @@ export default {
2022
status: new Status(),
2123
editors: new Editors(),
2224
superDirt: new SuperDirt(),
25+
oscServers: [OscServer] = [],
2326

2427
activate() {
2528
if (atom.config.get('tidalcycles.superDirt.autostart')) {
@@ -38,14 +41,17 @@ export default {
3841
this.repl = new Repl(this.consoleView, this.ghc, this.bootTidal, this.status, this.editors);
3942
}
4043

41-
if (atom.config.get('tidalcycles.oscEval.enable')) {
42-
this.oscLoader = new OscLoader(this.consoleView, this.repl, this.editors);
43-
this.oscLoader.init();
44+
if (atom.clonfig.get('tidalcycles.oscEval.enable')) {
45+
let oscEvalServer = new OscServer(this.consoleView, atom.config.get('tidalcycles.oscEval.ip'), atom.config.get('tidalcycles.oscEval.port'));
46+
oscEvalServer.register(atom.config.get('tidalcycles.oscEval.address'), oscEvalSubscriber(this.repl, this.editors));
47+
this.oscServers.push(oscEvalServer);
4448
}
4549

4650
this.soundBrowser = new SoundBrowser();
4751
this.soundBrowser.init(atom.config.get('tidalcycles.soundBrowser.folders'));
4852

53+
this.oscServers.forEach(server => server.start());
54+
4955
atom.commands.add('atom-workspace', {
5056
'tidalcycles:boot': () => {
5157
if (this.editors.currentIsTidal()) {
@@ -104,8 +110,9 @@ export default {
104110
this.consoleView.destroy();
105111
this.superDirt.destroy();
106112
this.repl.destroy();
107-
this.oscLoader.destroy();
108113
this.soundBrowser.destroy();
114+
this.oscServers.forEach(server => server.destroy());
115+
this.oscServers = [];
109116
},
110117

111118
serialize() {

spec/osc-server-spec.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
const OscServer = require('../lib/osc-server')
2+
const osc = require('osc-min')
3+
const dgram = require('dgram');
4+
5+
describe('OscServer', () => {
6+
7+
const port = 36111;
8+
let oscServer = new OscServer({}, "127.0.0.1", port);
9+
10+
beforeEach((done) => {
11+
oscServer.start().finally(() => done());
12+
})
13+
14+
afterEach(() => {
15+
oscServer.stop();
16+
})
17+
18+
it('should start an osc server and receive a message', done => {
19+
let listener = (message) => {
20+
if (message['key'] === 'value') {
21+
done();
22+
} else {
23+
done("failure");
24+
}
25+
}
26+
27+
oscServer.register('/address', listener);
28+
29+
const message = osc.toBuffer({ address: "/address", args: ["key", "value"] });
30+
dgram.createSocket('udp4').send(message, 0, message.byteLength, port, "127.0.0.1")
31+
})
32+
33+
})

0 commit comments

Comments
 (0)