diff --git a/ToposServer/OSCtoTopos.js b/ToposServer/OSCtoTopos.js new file mode 100644 index 0000000..3c374b1 --- /dev/null +++ b/ToposServer/OSCtoTopos.js @@ -0,0 +1,33 @@ +const WebSocket = require("ws"); +const osc = require("osc"); + +const cleanIncomingOSC = (oscMsg) => { + let data = oscMsg.args; + // Remove information about type of data + data = data.map((item) => { + return item.value; + }) + return {data: data, address: oscMsg.address}; +} + +// ============================================== +// Receiving and forwarding OSC UDP messages +// Create an osc.js UDP Port listening on port 57121. +console.log("> OSC Input: 127.0.0.1:30000"); +const wss = new WebSocket.Server({ port: 3001 }); +var udpPort = new osc.UDPPort({ + localAddress: "0.0.0.0", + localPort: 30000, + metadata: true +}); +udpPort.on("message", function (oscMsg, timeTag, info) { + console.log(`> Incoming OSC to ${oscMsg.address}:`, oscMsg.args.map( + (item) => {return item.value}) + ); + wss.clients.forEach(client => { + if (client.readyState === WebSocket.OPEN) { + client.send(JSON.stringify(cleanIncomingOSC(oscMsg))); + } + }); +}); +udpPort.open(); \ No newline at end of file diff --git a/ToposServer/ToposToOSC.js b/ToposServer/ToposToOSC.js new file mode 100644 index 0000000..553b1b5 --- /dev/null +++ b/ToposServer/ToposToOSC.js @@ -0,0 +1,81 @@ +const WebSocket = require("ws"); +const osc = require("osc"); + +// Listening to WebSocket messages +const wss = new WebSocket.Server({ port: 3000 }); + +// Setting up for message broadcasting +wss.on("connection", function (ws) { + console.log("> Client connected"); + ws.on("message", function (data) { + try { + const message = JSON.parse(data); + sendOscMessage( + formatAndTypeMessage(message), + message.address, + message.port + ); + console.log(`> Message sent to ${message.address}:${message.port}: ${JSON.stringify(message.args)}`) + } catch (error) { + console.error("> Error processing message:", error); + } + }); +}); + +wss.on("error", function (error) { + console.error("> Server error:", error); +}) + +wss.on("close", function () { + // Close the websocket server + wss.close(); + console.log("> Closing websocket server") +}); + +let udpPort = new osc.UDPPort({ + localAddress: "0.0.0.0", + localPort: 3000, + metadata: true, + remoteAddress: "0.0.0.0", + remotePort: 57120, +}); +udpPort.on("error", function (error) { + console.error("> UDP Port error:", error); +}); +udpPort.on("ready", function () { + //console.log(`> UDP Receive: ${udpPort.options.localPort}`); + console.log("> WebSocket server: 127.0.0.1:3000"); +}); + +udpPort.open(); + +function sendOscMessage(message, address, port) { + try { + udpPort.options.remotePort = port + message.address = address; + udpPort.send(message); + } catch (error) { + console.error("> Error sending OSC message:", error); + } +} + +const formatAndTypeMessage = (message) => { + let newMessage = {}; + delete message.args['address']; + delete message.args['port']; + newMessage.address = message.address; + newMessage.timestamp = osc.timeTag(message.timetag); + + args = [...Object.entries(message.args)].flat().map((arg) => { + if (typeof arg === 'string') + return {type: 's', value: arg}; + if (typeof arg === 'number') + return {type: 'f', value: arg}; + if (typeof arg === 'boolean') + return value ? {type: 's', value: 1} : {type: 's', value: 0}; + }) + + newMessage.args = args + + return newMessage; +} \ No newline at end of file diff --git a/ToposServer/banner.js b/ToposServer/banner.js new file mode 100644 index 0000000..90be549 --- /dev/null +++ b/ToposServer/banner.js @@ -0,0 +1,14 @@ +var pjson = require('./package.json'); +let banner = ` +┏┳┓ ┏┓┏┓┏┓ + ┃ ┏┓┏┓┏┓┏ ┃┃┗┓┃ + ┻ ┗┛┣┛┗┛┛ ┗┛┗┛┗┛ + ┛ + ${pjson.version}\n` +function greet() { + console.log(banner) +} + +module.exports = { + greet: greet +} \ No newline at end of file diff --git a/ToposServer/server.js b/ToposServer/server.js index ef8b4aa..ad949ad 100644 --- a/ToposServer/server.js +++ b/ToposServer/server.js @@ -1,114 +1,9 @@ const WebSocket = require("ws"); const osc = require("osc"); -var pjson = require('./package.json'); -let banner = ` -┏┳┓ ┏┓┏┓┏┓ - ┃ ┏┓┏┓┏┓┏ ┃┃┗┓┃ - ┻ ┗┛┣┛┗┛┛ ┗┛┗┛┗┛ - ┛ - ${pjson.version}\n` +require('./banner').greet(); +// Topos to OSC +require('./ToposToOSC') +// OSC to Topos +require("./OSCtoTopos") -console.log(banner) -console.log("Listening to: ws://localhost:3000. Open Topos.\n"); - -// Listening to WebSocket messages -const wss = new WebSocket.Server({ port: 3000 }); - -// Sending WebSocket messages -const inputWss = new WebSocket.Server({ port: 3001 }); - -inputWss.on("connection", function (ws) { - inputWss.clients.forEach(function each(client) { - // Send message to all clients except sender - if (client !== ws && client.readyState === WebSocket.OPEN) { - client.send("New client connected"); - } - }) -}); - -// Setting up for message broadcasting -wss.on("connection", function (ws) { - console.log("> Client connected"); - ws.on("message", function (data) { - try { - const message = JSON.parse(data); - sendOscMessage( - formatAndTypeMessage(message), - message.address, - message.port - ); - console.log(`> Message sent to ${message.address}:${message.port}: ${JSON.stringify(message.args)}`) - } catch (error) { - console.error("> Error processing message:", error); - } - }); -}); - -wss.on("error", function (error) { - console.error("> Server error:", error); -}) - -wss.on("close", function () { - // Close the websocket server - wss.close(); - console.log("> Closing websocket server") -}); - -// Setting up for OSC messages - -let udpPort = new osc.UDPPort({ - localAddress: "0.0.0.0", - localPort: 3000, - metadata: true, - remoteAddress: "0.0.0.0", - remotePort: 57120, -}); - -udpPort.on("error", function (error) { - console.error("> UDP Port error:", error); -}); - -udpPort.on("ready", function () { - console.log(`> UDP Port opened on port ${udpPort.options.localPort}`); -}); - -udpPort.open(); - -function sendOscMessage(message, address, port) { - try { - udpPort.options.remotePort = port - message.address = address; - udpPort.send(message); - } catch (error) { - console.error("> Error sending OSC message:", error); - } -} - -const formatAndTypeMessage = (message) => { - let newMessage = {}; - delete message.args['address']; - delete message.args['port']; - newMessage.address = message.address; - newMessage.timestamp = osc.timeTag(message.timetag); - - args = [...Object.entries(message.args)].flat().map((arg) => { - if (typeof arg === 'string') - return {type: 's', value: arg}; - if (typeof arg === 'number') - return {type: 'f', value: arg}; - if (typeof arg === 'boolean') - return value ? {type: 's', value: 1} : {type: 's', value: 0}; - }) - - newMessage.args = args - - return newMessage; -} - -// console.log(formatAndTypeMessage({ -// address: '/baba', -// port: 2000, -// args: { s: 'fhardkick', dur: 0.5, port: 2000, address: 'baba' }, -// timetag: 1701696184583 -// })) diff --git a/src/API.ts b/src/API.ts index 11dbb79..2700f5a 100644 --- a/src/API.ts +++ b/src/API.ts @@ -1,5 +1,5 @@ import { EditorView } from "@codemirror/view"; -import { sendToServer, type OSCMessage } from "./IO/OSC"; +import { sendToServer, type OSCMessage, oscMessages } from "./IO/OSC"; import { getAllScaleNotes, nearScales, seededRandom } from "zifferjs"; import { MidiCCEvent, @@ -2109,6 +2109,18 @@ export class UserAPI { } as OSCMessage); } + public getOSC = (address?: string): any[] => { + /** + * Give access to incoming OSC messages. If no address is specified, returns the raw oscMessages array. If an address is specified, returns only the messages who contain the address and filter the address itself. + */ + if (address) { + let messages = oscMessages.filter((msg) => msg.address === address); + messages = messages.map((msg) => msg.data); + return messages + } else { + return oscMessages; + } + } // ============================================================= // Transport functions diff --git a/src/IO/OSC.ts b/src/IO/OSC.ts index b5814a7..0056488 100644 --- a/src/IO/OSC.ts +++ b/src/IO/OSC.ts @@ -9,9 +9,18 @@ export interface OSCMessage { export let outputSocket = new WebSocket("ws://localhost:3000"); export let inputSocket = new WebSocket("ws://localhost:3001"); -inputSocket.onmessage= function (event) { - console.log("Received: ", event.data); -} +// Queue of 1000 last messages +export let oscMessages : any[] = []; + +inputSocket.addEventListener('message', (event) => { + let data = JSON.parse(event.data); + if (oscMessages.length > 1000) { + oscMessages.shift(); + } + oscMessages.push(data); +}); + + // @ts-ignore outputSocket.onopen = function (event) { @@ -20,7 +29,7 @@ outputSocket.onopen = function (event) { outputSocket.send( JSON.stringify({ address: "/successful_connexion", - args: true, + port: 3000, args: {} }) ); diff --git a/src/documentation/osc.ts b/src/documentation/osc.ts index 9848616..11a5794 100644 --- a/src/documentation/osc.ts +++ b/src/documentation/osc.ts @@ -16,9 +16,12 @@ To use **OSC** with Topos, you will need to download the ToposServer by - 3) Start the server using npm start. - 4) Open the Topos application in your web browser. +This server can be used both for **OSC** _input_ and _output_. ## Input +Send an **OSC** message to the server at the address localhost:30000. You will receive your message in Topos as an Array containing the address and data of your message. + ## Output Once the server is loaded, you are ready to send an **OSC** message: diff --git a/src/main.ts b/src/main.ts index 13972c2..a0f2b8c 100644 --- a/src/main.ts +++ b/src/main.ts @@ -4,7 +4,7 @@ import { scriptBlinkers } from "./Visuals/Blinkers"; import { javascript } from "@codemirror/lang-javascript"; import { markdown } from "@codemirror/lang-markdown"; import { Extension } from "@codemirror/state"; -import { outputSocket } from "./IO/OSC"; +import { outputSocket, oscMessages } from "./IO/OSC"; import { initializeSelectedUniverse, AppSettings, @@ -206,6 +206,7 @@ export class Editor { // Loading universe from URL (if needed) loadUniverserFromUrl(this); + } private getBuffer(type: string): any {