Broadcast Core: SuperCollider és Node.js

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