Communicatie
Supercollider programmeren en besturen vanuit de eigen interface SCLang is handig voor het ontdekken van het systeem, voor het doen van experimenten, voor live coding en voor het opbouwen van programma's. Soms wil je echter een setup vanuit andere programma's of met externe hardware besturen. MIDI is een mogelijkheid: met een MIDI controller of keyboard bijvoorbeeld. Een andere mogelijkheid is OSC, waarbij je het protocol helemaal zelf kunt vastleggen en niet gebonden bent aan ÊÊn server.
OSC
OSC ontvangen in Supercollider
Supercollider is een client/server systeem. De client is hier de Supercollider interface (SCLang) waarin we meestal werken. De server of servers maken geluid. Het is de server waar we OSC-commands naartoe sturen.Dat doe je vanuit SCLang met set
bijvoorbeeld zo:
a.set(\freq, 800);
Maar vanuit andere programma's (Max, PD, NodeJS, Python, C++) kunnen we ook OSC-berichten naar de server sturen. We moeten dan weten:
- waar draait die server: welk IP-adres en port
- wat voor berichten begrijpt die server
Begin met het aangeven naar welke poort je server luistert:
thisProcess.openUDPPort(9001);
Als het goed is zie je in de console met het volgende commando jouw port in het lijstje staan:
thisProcess.openPorts;
localhost
Als het programma dat OSC-berichten naar de server stuurt op dezelfde computer draait als de server dan kan het als OSC-client werken en berichten sturen naar localhost
(of 127.0.0.1) op port 9001
die we net in de Supercollider-server hebben geopend. Als je vanuit een andere computer komt moet je het IP-adres gebruiken van de computer waarop jouw Supercollider-server draait.
OSC ontvangen
OSC ontvangen doen we door in SCLang een functie OSCFunc te maken. Je moet deze toekennen aan een variabele zodat je hem later weer kunt vrijgeven. Als je dat vrijgeven niet doet, dan blijft de OSCFunc actief en gaan dingen door elkaar lopen.
~mijnOscFunctie //of zoiets.
De volgende functie laat zien wat er ontvangen wordt maar doet er verder niks mee. Met ...args haal je alle argumenten binnen die er binnenkomen en plaats je deze in een array. Vervolgens stuur je die array naar de post-window.
~mijnOscFunctie = OSCFunc({ |...args|
args.postln;
}, '/freq');
Als je klaar bent met ~mijnOscFunctie dan kun je die weer opruimen:
~mijnOscFunctie.free;
Het is aan te raden om het vrijgeven en aanmaken van de OSCFunc te combineren. Zodat je zeker weet dat de Func eerst wordt vrijgegeven voordat er een nieuwe start. Dat doe je als volgt:
(
~mijnOscFunctie.free;
~mijnOscFunctie = OSCFunc({ |...args|
args.postln;
}, '/freq');
)
Voordat je verder gaat is het aan te raden om eerst een OSC client (in Max/JS bijvoorbeeld) te maken die berichten naar je Supercollider-server stuurt zodat je weet dat er iets binnenkomt.
Stuur vanuit Max bijvoorbeeld /freq 1 4
en kijk wat er in de postwindow in SuperCollider verschijnt.
OSC commando's gebruiken
Gegeven een eenvoudige SynthDef "mySine" waarmee we een synth a maken:
SynthDef("mySine", {|freq=400, mul=0.1|
Out.ar(0, SinOsc.ar(freq, 0, mul))
}).add;
~synth_a = Synth("mySine", [\freq, 440]);
Vanuit SCLang kun je de parameters freq en mul aanpassen:
a.set(\freq, 800);
a.set(\mul, 0.1);
Als we een functie maken die OSC ontvangt en deze set-functies gebruikt dan kunnen we OSC-berichten gebruiken om parameters van de Synth aan te passen:
~mijnOscFunctie = OSCFunc({ arg msg;
a.set(\freq, msg[1]);
a.set(\mul, msg[2]);
}, '/freq');
Opruimen:
~mijnOscFunctie.free; // dit doe je pas als je er helemaal klaar mee bent ;-)
OSC versturen vanuit SuperCollider
Om data vanuit SuperCollider d.m.v. OSC te versturen maak je gebruik van een NetAddress. Dit is een functie die een UDP-verbinding opzet en je vervolgens de mogelijkheid geeft om via die verbinding data te versturen.
Aanmaken
Met de volgende code maak je een netAddress aan:
~sender = NetAddr.new("127.0.0.1", 9002);
Versturen
Met de volgende code verstuur je data:
~sender.sendMsg("/test",1,2,3);
MIDI
Met behulp van midi-communicatie kan je gemakkelijk je SynthDefs aansturen via bijvoorbeeld je DAW of externe controller. Met behulp van note-on en note-off-messages kan je Synths starten en stoppen, met continious control-messages (cc) kan je of waardes van lopende synths aansturen, of globale variabelen aanpassen om daarmee waardes van nieuw te starten Synths aan te passen.
Configuratie
Om midi uit te kunnen lezen moet SuperCollider eerst weten naar welke devices er geluisterd moet worden. De makkelijkste manier is om naar alle gekoppelde midi-devices tegelijk te luisteren. Dit doe je met de volgende code:
MIDIIn.connectAll;
Vervolgens zal SuperCollider in de post window laten zien welke devices er verbonden zijn. Als er tussentijds nieuwe devices aangekoppeld zijn, wil het soms gebeuren dat er een foutmelding verschijnt. De (voor nu) makkelijkste oplossing hiervoor is om de interpreter te herstarten. Dit door je door: Language -> Reboot Interpreter
te kiezen uit het menu bovenin.
Soms is het wenselijk om slechts naar ÊÊn apparaat te luisteren. Dit is ook mogelijk maar vereist iets meer stappen:
//initieer midi-client
MIDIClient.init;
//laat alle beschikbare devices zien:
MIDIClient.source;
//kies uit de lijst het nummer dat voor jouw device staat en vul in:
MIDIIn.connect(0,MIDIClient.sources.at(0));
met MIDIIn.disconnectAll
of MIDIIn.disconnect(0,MIDIClient.sources.at(0))
kan je (een) midi-device(s) weer loskoppelen.
Note-on
Net als bij OSC werk je bij MIDI met Func's
. Elke type midi-bericht heeft een eigen func.
Voor Note-on-messages is dit dus MIDIFunc.noteOn
.
Je slaat een specifieke MIDIFunc
op in een globale variabele. Als je daar een nieuwe overheen opslaat, zal de oude blijven bestaan en uitgevoerd worden tot je SuperCollider opnieuw opstart. Daarom is het goed om een MIDIFunc
te verwijderen voor je een nieuwe aanmaakt. Als er nog geen MIDIFunc
in een variabele staat kan het geen kwaad als je deze probeert te verwijderen.
MIDIFunc gebruiken
De code om te bekijken welke data er binnenkomen bij het ontvangen van een note-on-message ziet er als volgt uit:
~midinoot.free;
~midinoot = MIDIFunc.noteOn({|...args|
args.postln;
});
met ...args
verzamel je alle parameters die binnenkomen in ÊÊn array. Je kan zo eerst bekijken welke data er binnenkomt, om daar vervolgens uit te halen wat je nodig hebt.
Als je deze code uitgevoerd hebt en ziet wat er binnenkomt, kan dat er als volgt uitzien:
//1. 2. 3. 4.
[ 100, 21, 0, -336859671 ]
1. velocity
2. note
3. channel
4. source id van device
Om een Synth te starten heb je eigenlijk alleen de velocity en de noot nodig. Dus je kan de MIDIFunc
zo aanpassen dat alleen nog die twee parameters te gebruiken zijn:
~midinoot.free;
~midinoot = MIDIFunc.noteOn({|vel, note|
vel.postln;
note.postln;
});
Synth aansturen
Vervolgens kan die noot-informatie gebruikt worden om en Synth op te starten. We gaan hier in het begin uit van een Synth met een percussieve envelope, die geen sustain heeft.
De SynthDef hiervoor ziet er als volgt uit:
SynthDef(\testSynth,{|freq=220,amp=0.5|
var sig, env;
env = EnvGen.ar(Env.perc(level:amp),1,doneAction:2);
sig = LFSaw.ar(freq,0,env);
Out.ar(0,sig!2);
}).add;
Als je deze SynthDef willen koppelen aan een MIDIFunc
, voer je de volgende code uit:
~midinoot.free;
~midinoot = MIDIFunc.noteOn({|vel,note|
Synth(\testSynth,[\freq,note.midicps,\amp,vel/127]);
});
Met .midicps
zet je een midinoot om naar cycles per second, en dus naar frequentie
vel/127
is nodig om de velocity van 0 - 127 om te zetten naar een getal tussen 0. en 1.
Je hebt nu een midi-aanstuurbare Synth gemaakt.
Note-off
Als je in plaats van een Synth met een percussieve envelope gebruik wil maken van bijvoorbeeld een adsr-envelope, waarbij je dus bij een note-on de Synth start en bij een note-off de Synth stopt, dan zal je moeten weten welke noot er gestart is en welke er gestopt moet worden. Je zal dus de Synth die je afspeelt in een variabele moeten opslaan. Als je hier ÊÊn variabele voor gebruikt dan heb je geen polyfonie meer, omdat bij elke nieuwe note-on er een oude Synth overschreven wordt. Je zal dus een lijst met noten moeten maken om dit werkend te krijgen. Een lege array aanmaken, waar de noten in kunnen worden opgeslagen doe je zo in SuperCollider:
~polyArray = Array.fill(128, 0);
De 128 geeft het aantal elementen dat er moet worden aangemaakt. Aangezien MIDI van 0 - 127 gaat, is 128 genoeg. Het tweede getal geeft aan wat de standaardwaarde van elk element moet zijn, 0 is daarbij prima.
Hieronder een voorbeeld voor een SynthDef met een adsr-envelope:
SynthDef(\adsrSynth,{|freq=220,amp=0.5,gate=1|
var sig, env;
env = EnvGen.ar(Env.adsr(sustainLevel:amp),gate,doneAction:2);
sig = LFSaw.ar(freq,0,env);
Out.ar(0,sig!2);
}).add;
je ziet hierbij een klein aantal aanpassingen ten opzichte van de vorige SynthDef.
- waar je bij de percussieve envelope met
level
de amplitude kon aangeven gaat dat nu metsustainLevel
. - waarbij je bij de percussieve envelope het getal 1 als trigger kon gebruiken, moet je daar nu een variabele parameter
gate
voor gebruiken, zodat de envelope ook uit kan.
En hieronder de voorbeeldcode om deze SynthDef met een note-on en note-off aan te sturen:
//maak een lege lijst aan
~polyArray = Array.fill(128,0);
~midinoot.free;
~midinoot = MIDIFunc.noteOn({|vel,note|
//start Synth op de positie in de array die overeenkomt met de midi-noot
~polyArray[note] = Synth(\adsrSynth,[\freq,note.midicps,\amp,vel/127]);
});
~midinootoff.free;
~midinootoff = MIDIFunc.noteOff({|vel,note|
//zet de gate op 0 bij de juiste Synth.
~polyArray[note].set(\gate,0);
});