Interactive Swarm Space | |||||||||||
|
Interactive Swarm Space | |||||||||||
|
Basics | ISO Flock | ISO Synth | Networking | API | Publications |
|
||||||||||||||
ISO to ISO Communication Table of Contents:
Setting Up a Sender/Receiver for ISO Flock Setting Up a Sender/Receiver for ISO Flock OSC Control For simplicity's sake, we have combined the OSC receiver and a sender for error messages into one object. To set up OSC communication for your ISO Flock you need one single line of code: simulation.com().createOSCControl( 7400, "127.0.0.1", 7800 ); // receiving port, error msg receiver's IP adress, error msg sending port In this example code snippet, we are sending to the IP adress 127.0.0.1 i.e. the localhost. Don't use the same port for sending OSC messages and passing back error messsages - also make sure that the ports are unused by other network applications. If necessary refer to this list of UDP and TCP ports commonly used. And last but not least a little no-brainer: Make sure the port numbers match (i.e. your external software sends to a port on which the ISO Flock has a OSC receiver registered)! Setting up a receiver Simply insert the following line of code below Simulation::init(1.0); simulation.com().createReceiver( RECEIVER_NAME_STRING, LISTENER_PORT_INT, PROTOCOL_TYPE, FORMAT_TYPE);
RECEIVER_NAME_STRING = an arbitrary name Example: simulation.com().createReceiver("FlockReceiver", 7500, com::UDP, com::DEFAULT); This creates a receiver called "FlockReceiver", that is listening for UDP messages on port 7500 and has the ISO-standard format. Setting up a sender for ISOFlock simulation.com().createSender( SENDER_NAME_STRING, LISTENER_PORT_INT, PROTOCOL_TYPE, FORMAT_TYPE ); After you have set up your swarms and behaviors, insert the following line of code in order to enable for sending a specific parameter: simulation.com().registerParameter( SENDER_NAME_STRING, SWARM_NAME_STRING, PARAM_NAME_STRING ); Example: simulation.com().registerParameter( "FlockSender", "mySwarm", "position" ); That's the most basic form of registering a parameter. You have some options though. You can define parameter value limits for example, by specifying two limiting vectors the same way we have implemented motion boundaries in our Hello World 2: Motion Boundaries example: simulation.com().registerParameter( "FlockSender", "myswarm", "position", math::Vector<float>(3, -5.0, -5.0, -5.0), math::Vector<float>(3, 5.0, 5.0, 5.0) ); In the example above both the minimum and maximum values are a "math::Vector<real>&" vector or a subclass of it. Since position (usually) has three dimensions, we are using math::Vector3<float> (in ISO Flock "real" is just a typedef on float). These bounds are to be interpreted as follows: The position of an agent in is truncated, meaning if, say, the current xyz-position is (1.3, 2.8, 5.5) it will be truncated down to (1.3, 2.8, 5.0). However, and this is very important, this bounds are also used to normalize agent positions in the range between 0 and 1! So all message values are only within the range of 0 and 1! Look out for that. If you would like to track one-dimensional parameters however, you would use the math::Vector<float>. Setting Up a Receiver for ISO Synth On the synth side it's a little more verbose. Let's start by looking at a rather simple example: We would like our 50-agent flock to control the amplitudes and frequencies of 50 oscilators inside an additive synthesis patch. We assume you have followed the instructions in "Setting up a sender for ISOFlock". So you have your synthesis patch (in our example AdditiveSynthesisPatch.h and .cpp) and as usual, you use your main() method in the iso_synth_app project to create an instance of your AdditiveSynthesisPatch class. Consequently it is your main() method, where you need to take care of setting up and linking a message receiver to your patch: int main( int argc, char **argv ) { Synth& synth = Synth::get(); try { QApplication a(argc,argv); synth.com().createReceiver("FlockReceiver", 7500, com::UDP, com::Default ); AdditiveSynthesisPatch* patch = new AdditiveSynthesisPatch(10, 440.0); OutputUnit* outputUnit = new JackOutputUnit( 2, "system" ); patch->unit("AdditiveSynthesisPatch_out")->connect( outputUnit, new ChannelMap(2, 1, 1) ); Synth::get().com().registerMessageListener( "FlockReceiver", *patch ); Synth::get().start(); sleep(700000); Synth::get().stop(); Synth::destroy(); return a.exec(); } catch(base::Exception& e) { Synth::get().exceptionReport(e); } return 0; } Let's look closely at the code fragements in bold: //Get synth singleton and create a receiver called "Flock Receiver" on port 7500, //using the UDP protocol and the default ISO message format: synth.com().createReceiver("FlockReceiver", 7500, com::UDP, com::Default ); //create an instance of your patch: AdditiveSynthesisPatch* patch = new AdditiveSynthesisPatch(10, 440.0); //create an audio output object with two channels (stereo): OutputUnit* outputUnit = new JackOutputUnit( 2, "system" ); //connect your patch's mono output to the two inputs of your output object: patch->unit("AdditiveSynthesisPatch_out")->connect(outputUnit, new ChannelMap(2, 1, 1) ); //assign (or register) the message listener to your patch //(side-note: patch is a pointer, so "*pointer" de-references it) Synth::get().com().registerMessageListener( "FlockReceiver", *patch ); Now what's left is to define how the data in a message is used to control the synth units in your patch. This is defined in your patch's notify() method. Until now, the actual dissection of the message and applying the collected data to your units is somewhat tedious since you have to know the message structure in order to pick out the right kind of data from the stream. Because of that, here's a quick overview over the standard message structure: ISO message protocol
This is the format in which the message is sent to your patch's notify() method. You can see that there are 6 value groups, but only the last one contains the actual message data, the ones before however will help you in decomposing it. And here's how it's done: //inside your patch's cpp file: void AdditiveSynthesisPatch::notify(const com::Message& pMessage) { if(mActive == false) return; //if patch is inactive, ignoring message try { // a message has always 6 value groups: if(pMessage.valueGroupCount() < 6) return; // skip if already received: if( mPreviousMessageId == ( pMessage.values<long>(0) )[0] ) return; // copy reference to local object: com::Message message = pMessage; // get num of agents by reading value group 4 ("Num Agents"): int parCount = ( message.values<int>(4) )[0]; // get "Parameter Values" as float array: const float* parValues = message.values<float>(5); // get parameter dimension (see comment bellow): int parDim = message.valueCount(5) / parCount; // some local helpers for this method: sample harmonicFrequency = 0.0; sample frequency; sample amplitude; // some iteration indices unsigned int oscilatorIndex = 0; unsigned int paramValueIndex = 0; // iterate over all values and set frequency/amplitude of oscilators. // in our patch: mNumberOfPartitials = 50 oscilators while(oscilatorIndex < mNumberOfPartials) { // calculate frequency of partitial (for the very first one, this // is the root frequency itself, since harmonicFrequency was initialized as 0.0 harmonicFrequency += mRootFrequency; // read parameter and map to amplitude amplitude = parValues[paramValueIndex]; // agent x pos // read next parameter value and map to frequency // ... is used to modulate the harmonic frequency frequency = parValues[paramValueIndex + 1] * harmonicFrequency; // agent y pos // notice: agent z pos is not used (...[paramtValueIndex+2]) // set oscilator's "frequency" portmFrequencyPorts[oscilatorIndex]->set(frequency); //set oscilator's "amplitude" port mAmplitudePorts[oscilatorIndex]->set(amplitude); oscilatorIndex++; //increment index paramValueIndex += parDim; //increment param index by 3 (for "x, y, z") } } catch(com::ComException& e) { Synth::get().exceptionReport( e ); } catch(base::Exception& e) { Synth::get().exceptionReport( e ); } } There are quite a lot of points to mention here and we hope to be able to streamline the notify() method in the future:
|
Last updated: May 15, 2024 |