A broadcast vezérlés fő összetevői: SuperCollider server az audio fájlok lejátszásra; Node.js java script a SuperCollider vezérlésére OSC (Open Sound Control) használatával. Ezek a háttérben futnak szolgáltatásként, és a megadott portokon lehet elérni a szolgáltatásokat.
A SuperCollider indításáról itt lehet olvasni bővebben.
SuperCollider: scplay3.1.scd
Az scplay rendszermag már a harmadik verzióban kerül továbbfejlesztésre. Több próbálkozás közül ez a verzió hozta meg a várt működést.
A fájl helye:
./mtm.project/systems/broadcast/core/scd/scplay3.x.scd
Indítása SuperCollider serveren
s.boot;
"/home/mtm/html/mtm.project/systems/broadcast/core/scd/scplay3.1.scd".load;
Ha az OSC parancsokat szeretnénk az sclang környezetében használni, akkor meg kell nyitni egy portot, melyen keresztül kommunikálni tudunk a SuperCollider serverrel
n = NetAddr("127.0.0.1", NetAddr.langPort);
Bufferelés
Az eredeti gondolat az volt, hogy 2 buffer kerül létrehozásra, melybe betöltető egy audio fájl (URL alapján) elindítható a lejátszás, dinamikus fade-out használatával meg lehet állítani a lejátszást.
Az scplay 3. verziója több szálat is képes bufferelni, és a bufferek mindegyikét külön-külön tudja kezelni (lejátszani és megállítani) a buffer nevével, melyet a fájl betöltésénél kell megadni (lásd: setfile).
A bufferek egy Dictionary típusú változóban kerülnek tárolására.
~buffers = Dictionary.new;
A SuperCollider kód Dokumentáció
Ez a SuperCollider szkript egy egyszerű OSC-vezérelt hanglejátszó rendszert valósít meg. A kód a SuperCollider szerveren fut, ahol az OSC (Open Sound Control) üzeneetek által kapott parancsokat hajtja végre. A szkript képes hanglefájlokat beolvasni, lejátszani, megállítani és figyelni a lejtátszási pozíciót.
1. Globális Változók
~buffers = Dictionary.new;
~players = Dictionary.new;
~buffers: Egy szótár („Dictionary”), amely a betöltött hangfájlokat Buffer objektumok formájában tárolja.
~players: Egy szótár, amely a futó Synth objektumokat tárolja, amelyek a hangfájlokat lejátsszák.
2. OSC Definíciók
2.1. Hangfájl beállítása
OSCdef(\setFile, { |msg|
var bufferName;
~filePath = msg[1];
bufferName = msg[2];
"File set to: ".post; ~filePath.postln;
("Buffer: " + bufferName).postln;
~buffers[bufferName] = Buffer.read(s, ~filePath);
}, '/setFile');
OSCdef(\setFile, { |msg| ... }, '/setFile');: Egy OSC hallgató, amely a /setFile üzeneetre reagál.
msg[1]: A beolvasandó hangfájl elérési útja.
msg[2]: A buffer neve, amely alatt a fájl elérhető lesz.
Buffer.read(s, ~filePath): A hangfájl betöltése a szerverre, majd a ~buffers szótárban elmentése.
2.2. Hangfájl leátszása
OSCdef(\play, { |msg|
var bufferName;
bufferName = msg[1];
if (~filePath.notNil) {
~players[bufferName] = Synth(\playerSynth, [\buf, ~buffers[bufferName]]);
"Playing file: ".post; ~filePath.postln;
("Playing buffer: " + bufferName).postln;
} {
"No file set!".postln;
};
}, '/play');
OSCdef(\play, { |msg| ... }, '/play');: Egy OSC hallgató, amely a /play üzenetre reagál.
msg[1]: A buffer neve, amelyet le kell játszani.
Synth(\playerSynth, [\buf, ~buffers[bufferName]]): Egy Synth példányt hoz létre, amely lejátssza a bufferName nevű bufferben lévő hangot.
~players[bufferName]: A futó Synth példányt tárolja a megfelelő kulcs alatt.
2.3. Lejátszás leállítása
OSCdef(\stop, { |msg|
var fadeTime = msg[1].asFloat;
var bufferName = msg[2];
fadeTime = fadeTime.max(0.1);
if (~players[bufferName].notNil) {
"Stopping " + bufferName + "] with fade-out: ".post; fadeTime.postln;
~players[bufferName].set(\gate, 0, \fadeTime, fadeTime);
~players[bufferName] = nil;
} {
("No active player! [" + bufferName + "]").postln;
};
}, '/stop');
fadeTime = msg[1].asFloat: Az elhalvás ideje.
fadeTime = fadeTime.max(0.1): Biztosítja, hogy ne legyen 0 vagy negatív.
~players[bufferName].set(\gate, 0, \fadeTime, fadeTime): Az envelope gate lezárásával elindít egy fade-out-ot.
~players[bufferName] = nil;: A referencia törlése.
3. Lejátszás definíciója (SynthDef)
SynthDef(\playerSynth, {
|buf, fadeTime = 1.0, gate = 1|
var sig, env, pos, rate;
rate = BufRateScale.kr(buf);
env = EnvGen.kr(Env.asr(0.01, 1, fadeTime, curve: -4), gate, doneAction: 2);
sig = PlayBuf.ar(2, buf, rate, loop: 0);
pos = Phasor.ar(0, rate, 0, BufFrames.kr(buf));
SendReply.kr(Impulse.kr(1), '/playbackPosition', pos / BufSampleRate.kr(buf));
Out.ar(0, sig * env);
}).add;
BufRateScale.kr(buf): A buffer sebességi arányának kiszámítása.
EnvGen.kr(...): Az envelope generálása a fade-out kezelésére.
PlayBuf.ar(...): A buffer lejátszása.
Phasor.ar(...): A lejátszási pozíció kiszámítása.
SendReply.kr(...): A lejtátszási időközön OSC üzenetet küld.
Out.ar(0, sig * env): A jel kiküldése a hangkimenetre.
4. Lejátszási Pozíció Figyelése
OSCdef(\positionListener, { |msg|
var time, min, sec;
time = msg[3].asInteger;
min = time.div(60);
sec = time.mod(60);
("Playback Position: " ++ min.asString ++ ":" ++ sec.asString).postln;
}, '/playbackPosition');
Ez az OSC hallgató figyeli a /playbackPosition üzeneetet, és formázottan kiírja az aktuális időpozíciót.
Node.js OSC és WebSocket szerver dokumentáció
A fájl helye:
./mtm.project/systems/broadcast/core/osc/wss_osc.js
Áttekintés
Ez a Node.js alkalmazás egy Open Sound Control (OSC) protokollt használó szerver, amely kommunikál egy SuperCollider (SC) szerverrel. Az alkalmazás egy WebSocket szervert is futtat, amely lehetővé teszi, hogy a kliensek valós időben küldjenek parancsokat és kapjanak visszajelzéseket a lejátszási pozícióról.
Függőségek
Az alkalmazás két fő Node.js modult használ:
osc: Az OSC protokoll kezeléséhez
ws: WebSocket kommunikációhoz
A szükséges csomagok telepíthetők a következő paranccsal:
npm install osc ws
OSC Szerver Konfiguráció
const osc = require('osc');
Az osc modult importáljuk, hogy az OSC protokollal tudjunk kommunikálni.
const udpPort = new osc.UDPPort({
localAddress: "0.0.0.0",
localPort: 57121, // Az SC által figyelt port
remoteAddress: "127.0.0.1",
remotePort: 57122 // SC alapértelmezett OSC portja
});
Létrehozunk egy UDP alapú OSC kapcsolatot:
localAddress: "0.0.0.0": A szerver minden hálózati interfészen figyel.
localPort: 57121: A helyi OSC szerver ezen a porton hallgat.
remoteAddress: "127.0.0.1": A cél SC szerver azonosítója (localhost).
remotePort: 57122: Az SC alapértelmezett OSC portja.
udpPort.open();
Megnyitjuk az OSC kapcsolatot.
udpPort.on("ready", () => {
console.log("OSC vezérlő készen áll");
});
Amikor az OSC kapcsolat készen áll, egy üzenetet írunk a konzolra.
OSC Parancsok Küldése
Az alábbi függvények az OSC üzenetek küldését végzik:
setFile(filePath, bufferName)
function setFile(filePath, bufferName) {
udpPort.send({
address: "/setFile",
args: [filePath, bufferName]
});
console.log(`Fájl beállítva: ${filePath}, buffer: ${bufferName}`);
}
Egy fájl beállítását végzi egy adott buffer névre.
play(bufferName)
function play(bufferName) {
udpPort.send({
address: "/play",
args: [bufferName]
});
console.log(`Lejátszás indítása: ${bufferName}`);
}
Lejátszási parancs küldése egy adott buffer névre.
stop(fadeTime, bufferName)
function stop(fadeTime, bufferName) {
udpPort.send({
address: "/stop",
args: [fadeTime, bufferName]
});
console.log(`Lejátszás leállítása: ${bufferName}, fade-out: ${fadeTime}s`);
}
Leállítási parancs küldése egy adott bufferre, egy fade-out idővel.
OSC Üzenetek Fogadása
udpPort.on("message", (oscMsg) => {
if (oscMsg.address === "/playbackPosition") {
let time = Math.floor(oscMsg.args[0]);
let min = Math.floor(time / 60);
let sec = time % 60;
let formattedTime = `${min}:${sec.toString().padStart(2, '0')}`;
console.log(`Lejátszási pozíció: ${formattedTime}`);
// Küldjük az összes csatlakozott WebSocket kliensnek
wss.clients.forEach(client => {
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify({ type: "position", time: formattedTime }));
}
});
}
});
Ha egy /playbackPosition üzenetet kapunk:
- Az idő másodpercekben érkezik.
- Átalakítjuk percekké és másodpercekké.
- Kiírjuk a konzolra.
- Minden WebSocket kliensnek elküldjük az időt JSON formátumban.
WebSocket Szerver Beállítása
const WebSocket = require('ws');
Importáljuk a WebSocket modult.
const wss = new WebSocket.Server({ port: 8080 });
Létrehozunk egy WebSocket szervert a 8080-as porton.
wss.on("connection", (ws) => {
console.log("Új WebSocket kapcsolat");
Amikor egy új WebSocket kliens csatlakozik, kiírjuk a konzolra.
WebSocket Üzenetek Kezelése
ws.on("message", (message) => {
try {
const data = JSON.parse(message);
console.log("Kliens üzenet:", data);
Ha a kliens küld egy üzenetet, JSON-ként dolgozzuk fel.
if (data.command === "setFile") {
setFile(data.filePath, data.bufferName);
} else if (data.command === "play") {
play(data.bufferName);
} else if (data.command === "stop") {
stop(data.fadeTime, data.bufferName);
}
A beérkező parancsokat a megfelelő függvényekkel továbbítjuk az OSC szerver felé.
} catch (err) {
console.error("Hibás üzenet:", err);
}
});
Ha a JSON formátum hibás, hibaüzenetet írunk ki.
WebSocket Kapcsolat Bontása
ws.on("close", () => {
console.log("WebSocket kapcsolat bezárva");
});
Ha a kapcsolat megszakad, kiírjuk a konzolra.
WebSocket Szerver Indítása
console.log("WebSocket szerver elindítva a 8080-as porton.");
Induláskor egy üzenetet írunk ki a konzolra.
Összegzés
Ez a Node.js alkalmazás kétirányú kommunikációt biztosít:
- OSC-n keresztül SuperColliderhez csatlakozik, és fájlok betöltését, lejátszást, illetve leállítást vezérel.
- WebSocket szerverként működik, és kliensekkel kommunikál, lehetővé téve számukra az SC vezérlését és a lejátszási információk fogadását.
Az alkalmazás lehetővé teszi, hogy valós időben történjen a zenei fájlok lejátszása és vezérlése egy távoli SC szerveren keresztül.
Node.js indítása
pm2 start wss_osc.js --name osc-controller
Node.js környezet kialakítása
https://chatgpt.com/share/67acf90a-a484-8008-afc6-508c209dd448
https://chatgpt.com/share/67acf93c-3f0c-8008-8c0c-ac79c8ab96df