/* elemental, a quintet for four performers and five slork-stations .................................................................... Each performer/slork-station is assigned a corrolary speaker location within the virtual environment. Osc projectile and client data for each individual game client is streamed from the server back to each client on port 6662. - projectile data is sent continuously (updating x,y,z position) for each ball as well as specifically when a bounce/collision is registered. - user coordinate data (x,y,z) is sent continuously ....... q3osc map: dirt_dome.bsp ....... speaker locations: [0,0], [0, 900], [900,0], [0,-900], [-900,0] ....... __________ [0, 900] (1) __________ __________ [-900, 0] (4) [0,0] (0) [900, 0] (2) __________ __________ [0, -900] (3) INDIVIDUAL SPEAKER LOCATIONS: __________ (1) __________ (2) (5) __________ (6) __________ (3) (4) speakerset 0: ............. (1) ............. (2) (5) ............. (6) ............. (3) (4) speakerset 1: ............. (4) (3) ............. (6) ............. (5) (2) ............. (1) speakerset 2: ............. (5) (4) ............. (1) (6) ............. (2) (3) speakerset 3: ............. (1) ............. (2) (5) ............. (6) ............. (3) (4) speakerset 4: ............. (3) (2) ............. (6) (1) ............. (4) (5) Amplitude for each client will be calculated using a distance function from client or projectile source to each of six virtual speaker positions within each 6-channel hemispherical speaker. The distance from the center of the hemisphere will be first calculated to the client or projectile and then an offset will be used to calculate the added distance from each speaker in the six-channel speaker array. All osc-streams will have to be sent to all clients?... is this going to work????? << OR >> - Set a radius variable in the game engine... if x,y,z coordinates for user or projectile fall within the radius of a given speaker location, then route those osc messages to that user IP address. - Then in client, proceed as normal, as we're only processing data for one speaker-set */ 0 => int SPEAKERSET; 900. => float SOUNDINGRADIUS; if(me.args() == 0) { <<< "Please enter a Speakerset number from 0-4: " >>>; me.exit(); } else { for( 0 => int i; i < me.args(); i++) { if(i==0) { if(Std.atoi(me.arg(i)) > 4) { <<< "Please enter a Speakerset number from 0-4: " >>>; me.exit(); } else { Std.atoi(me.arg(i)) => SPEAKERSET; <<< "Speakerset: ", SPEAKERSET >>>; } } else if(i==1) { Std.atoi(me.arg(i)) => SOUNDINGRADIUS; <<< "SoundingRadius: ", SOUNDINGRADIUS >>>; } } } 0 => int dbug;// set to 0 for no debug messages fun void debug(string loc, string msg) { if(dbug==1) <<>>; } // user's unique UID will be set by the osc stream // the same UID will be used to set SPEAKERSET number; // SPEAKEROFFSET is the amount of scaled offset for each of the 6 speakers of the SPEAKERSET. int UID; // OR... we don't need this and we can set SPEAKERSET manually?? just make sure to correlate with q3osc IP addresses // STEP 1: set SPEAKERSET id (1-5) to match the ip address set in osc_client_hostname<> //2 => int SPEAKERSET; // STEP 2: set SPEAKEROFFSET - Offset can be changed to adjust sensitivity/distance for each of the 6 speakers on the speaker-set 4. => float SPEAKEROFFSET; // STEP 3: set SOUNDINGRADIUS (float) as the distance from the speakerset within which client and projectile events will sound //900. => float SOUNDINGRADIUS; // declare global coordinates for all 5 speaker-sets, each user will only use one // z is always static and ~ 0, or +/- a bit, based on the specific map. [[0.,0.],[0.,900.],[900.,0.],[0.,-900.],[-900.,0.]] @=> float speakersets[][]; 24.0 => float z_static; // extended event class ballEvent extends Event { int newball; string type; int owner; int id; float x; float y; float z; int target; int bounce; int explode; float speakersetDistance; } // declare constants 8 => int SPEAKERCOUNT; 32 => int CLIENTCOUNT; 1000 => int PROJECTILECOUNT; 2 => int LISTENERCOUNT; "plasma" => string PLASMA; "bfg" => string BFG; "rocket" => string ROCKET; "chaingun" => string CHAINGUN; .5 => float ampPad; .1 => float ampScalar; 50. => float minFreq; JCRev reverb1 => dac.chan(0); JCRev reverb2 => dac.chan(1); JCRev reverb3 => dac.chan(2); JCRev reverb4 => dac.chan(3); JCRev reverb5 => dac.chan(4); JCRev reverb6 => dac.chan(5); .7 => reverb1.gain => reverb2.gain => reverb3.gain => reverb4.gain => reverb5.gain => reverb6.gain; .075 => reverb1.mix => reverb2.mix => reverb3.mix => reverb4.mix => reverb5.mix => reverb6.mix; // declare "global" vars int gClients; int gProjectiles; // create our OSC receiver OscRecv recv; 6662 => recv.port; recv.listen(); recv.event( "/projectile, s i i f f f i i i" ) @=> OscEvent oeProjectile; recv.event( "/player, s i f f f" ) @=> OscEvent oeClient; string type; int owner; int id; float X; float Y; float Z; int target; int bounce; int explode; int projectiles[PROJECTILECOUNT]; ballEvent @ bprojectiles[PROJECTILECOUNT]; int temp; 0=>int inc; // method to listen to broadcast OSC messages //--------------------------------------------------------------- fun void ball(ballEvent e) { float noteduration; TriOsc s => ADSR bob => Gain gain1; bob => Gain gain2; bob => Gain gain3; bob => Gain gain4; bob => Gain gain5; bob => Gain gain6; gain1 => reverb1; gain2 => reverb2; gain3 => reverb3; gain4 => reverb4; gain5 => reverb5; gain6 => reverb6; 0 =>s.gain; bob.set(10::ms, 5::ms, .5, 200::ms); // e.owner => UID => SPEAKERSET; // set global UID and SPEAKERSET e.id => int id; while ( true ) { // wait for ball event e e => now; // incoming ball data over OSC is sent to all sporked balls. If this is the existing ball with the same id as the data just // processed, then update its values. Otherwise, ignore the data. if(e.id != id) continue; // if the ball is exploding... if (e.explode == 1) { // if explode message, kill sound <<<"explode: ball_num = ", e.id>>>; 3::second => now; // decay break; } if (e.bounce == 1) { <<< "e.x: ", e.x >>>; <<< "e.y: ", e.y >>>; // first retrieve main distance from bounce to SPEAKERSET. If this distance is negative, then set gains to 0 // or maybe don't keyOn() note event <<< "radius: ", SOUNDINGRADIUS>>>; <<< "speakersetDistance: ", e.speakersetDistance >>>; <<< "(SOUNDINGRADIUS - e.speakersetDistance)/SOUNDINGRADIUS): ", (SOUNDINGRADIUS - e.speakersetDistance)/SOUNDINGRADIUS>>>; if(SOUNDINGRADIUS >= e.speakersetDistance) { // calculate bounce point distance from each speaker withing the SPEAKERSET // and set gains for each of them based on distance to their offset // (1./Math.sqrt(e.speakersetDistance))*ampScalar => float amp; ((SOUNDINGRADIUS - e.speakersetDistance)/SOUNDINGRADIUS)*ampScalar => float amp; // (900 - distance)/900 <<< "amp: ", amp>>>; /* speakerDistances(1, e) => gain1.gain; speakerDistances(2, e) => gain2.gain; speakerDistances(3, e) => gain3.gain; speakerDistances(4, e) => gain4.gain; speakerDistances(5, e) => gain5.gain; speakerDistances(6, e) => gain6.gain; */ amp => gain1.gain; amp => gain2.gain; amp => gain3.gain; amp => gain4.gain; amp => gain5.gain; amp => gain6.gain; //<<< "amp:", Math.fabs(e.y + roomMinY)/scaledRoomY>>>; //Math.fabs(e.y + roomMinY)/scaledRoomY => float tpan; // go 0 to 1; //tpan + ampPad=> lGain.gain; //1-tpan=>rGain.gain; // if bounce, play bounce sound <<<"bounce: ball_num = ", e.id>>>; 1. =>s.gain; bob.keyOn(); // e.x + minFreq => s.freq; (SOUNDINGRADIUS - e.speakersetDistance) + minFreq => s.freq; // advance time // (e.y - roomMinY)/(100*(scaledRoomY)) => noteduration; // <<<"noteduration: ", noteduration >>>; // noteduration::ms => now; 50::ms => now; //(1000*noteduration+5)::ms => now; bob.keyOff(); } } } gain1 =< reverb1; gain2 =< reverb2; gain3 =< reverb3; gain4 =< reverb4; gain5 =< reverb5; gain6 =< reverb6; } // calculate distance from ball or client's X,Y,Z (default for now) coords to current SPEAKERSET fun float speakersetDistance(float ballX, float ballY, float ballZ) { speakersets[SPEAKERSET][0] => float speakerX; speakersets[SPEAKERSET][1] => float speakerY; // basic Euclidean 3-dimensional distance calculation return Math.sqrt(Math.pow((speakerX - ballX), 2) + Math.pow((speakerY - ballY), 2) + Math.pow((z_static - ballZ), 2)); } fun float speakerDistances(int channel, ballEvent e) { /* speakerset 0: ............. (1) ............. (2) (5) ............. (6) ............. (3) (4) speakerset 1: ............. (4) (3) ............. (6) ............. (5) (2) ............. (1) speakerset 2: ............. (5) (4) ............. (1) (6) ............. (2) (3) speakerset 3: ............. (1) ............. (2) (5) ............. (6) ............. (3) (4) speakerset 4: ............. (3) (2) ............. (6) (1) ............. (4) (5) */ // basic Euclidean 3-dimensional distance calculation //return Math.sqrt(Math.pow((speakerX - e.x), 2) + Math.pow((speakerY - e.y), 2) + Math.pow((z_static - e.z), 2)); return .1; } ballEvent e; while ( true ) { oeProjectile => now; while ( oeProjectile.nextMsg() != 0 ) { //<<< "type:", oeProjectile.getString()>>>; oeProjectile.getString() => type; oeProjectile.getInt() => owner; oeProjectile.getInt() => id; oeProjectile.getFloat() => X; oeProjectile.getFloat() => Y; oeProjectile.getFloat() => Z; oeProjectile.getInt() => target; oeProjectile.getInt() => bounce; oeProjectile.getInt() => explode; speakersetDistance(X, Y, Z) => float currentSpeakerDistance; //DONT CHECK THIS NOW, LET ALL BALLS BE SPORKED EVEN IF THEY"RE OUTSIDE OF THE SPEAKERRADIUS // THEN JUST WITHING THE BALL EVENT, ONLY FIRE THE NOTE EVENT IF THE DISTANCE IS WITHIN THE RADIUS // HOPEFULLY THIS WON'T OVERLOAD EVERYTHING AND BURN DOWN THE HOUSE //// check coordinates of bounce for its distance to the soundingRadius of the speakerset //// if within the sounding radius, continue sporking a new event, if not, ignore this bounce //// if(SOUNDINGRADIUS - currentSpeakerDistance) >= 0) { if(bprojectiles[id]==null) { // create new ball 1 => projectiles[id]; ballEvent @tempBall; new ballEvent @=> tempBall @=>bprojectiles[id]; type => tempBall.type; owner => tempBall.owner; id => tempBall.id; X =>tempBall.x; Y => tempBall.y; Z =>tempBall.z; target => tempBall.target; bounce => tempBall.bounce; explode => tempBall.explode; currentSpeakerDistance => tempBall.speakersetDistance; inc++; //<<<"added projectile: ", id, " - size: ", inc >>>; //<<<"value at: ", id, " = ", projectiles[id]>>>;1 => tempBall.newball; spork ~ ball( tempBall ); me.yield(); tempBall.broadcast(); // send events to all balls } else { bprojectiles[id] @=> ballEvent @ temp2Ball; if(explode==1) { 0 => projectiles[id]; null @=> bprojectiles[id]; inc--; // <<<"removed projectile: ", id, " - size: ", inc >>>; // <<<"value at: ", id, " = ", projectiles[id]>>>; } type => temp2Ball.type; owner => temp2Ball.owner; id => temp2Ball.id; X =>temp2Ball.x; Y => temp2Ball.y; Z =>temp2Ball.z; target => temp2Ball.target; bounce => temp2Ball.bounce; explode => temp2Ball.explode; currentSpeakerDistance => temp2Ball.speakersetDistance; -1 => temp2Ball.newball; // not a new ball temp2Ball.broadcast(); // send events to all balls } } }