annotate src/melodyTriangle.cpp @ 23:460c05dd74d0

Various enhancements and code refactorings: * added some compiler warnings. * text display is centred and with settable TrueType font * removed highlight member from Voice state - value is derived from other data and passed to Voice::draw() * Changed voice radius from member to defined constant * default number of voices is now 4 * adjusted some colours and buffer zone width * new keyboard commands: reset, quit. * when keyboard disabled, keys are now passed to server via OSC * added handlers for various new OSC messages: - fullscreen, reset, quit, keyboard enable - notify (voice state) : several sub-messages * call reset and calibrate on window resize (fits triangle to window)
author samer
date Sat, 04 Feb 2012 23:14:38 +0000
parents 4dcc4312b5fa
children f4ebb87adec1
rev   line source
hekeus@6 1 #include "melodyTriangle.h"
hekeus@6 2 #include <GLUT/GLUT.h>
hekeus@6 3
samer@23 4 #define BUFFER_ZONE 64 // have to drag this far to snap out of triange.
samer@22 5
samer@22 6 melodyTriangle::melodyTriangle(const char *host, int port, int numVoices,
samer@22 7 bool enableKeys,int voiceIdOffset,int receivePort):
samer@22 8 numVoices(numVoices), enableKeys(enableKeys), receivePort(receivePort),
samer@23 9 display_msg(""), display_frames(0)
samer@22 10 {
samer@23 11 printf("in constructor: %s %i %i %i %i %i\n",
samer@23 12 host,port,numVoices,enableKeys,voiceIdOffset,receivePort);
samer@22 13 for (int i=0;i<numVoices;i++) voices[i]=new Voice(i+1+voiceIdOffset);
samer@22 14
hekeus@6 15 sender.setup( host,port );
hekeus@8 16 receiver.setup( receivePort );
samer@23 17 display_font.loadFont("/System/Library/Fonts/HelveticaLight.ttf",24);
hekeus@6 18 }
hekeus@6 19
samer@22 20 melodyTriangle::~melodyTriangle() {
samer@22 21 printf("Deleting voice objects...\n");
samer@22 22 for (int i=0;i<numVoices;i++) delete voices[i];
samer@22 23 }
samer@22 24
hekeus@6 25 //--------------------------------------------------------------
hekeus@6 26 void melodyTriangle::setup(){
samer@22 27 ofSetCircleResolution(64);
hekeus@6 28 ofBackground(0,0,0);
hekeus@6 29 ofSetWindowTitle("Melody Triangle");
samer@23 30 ofSetFrameRate(40); // caps framerate if vertical sync is off.
samer@18 31 ofEnableSmoothing();
samer@18 32
samer@18 33 // Set up triange coordinates.
samer@18 34 // NB. whatever happens here, the triangle must be
samer@18 35 // isosceles and left-right symmetric around x=x1.
samer@18 36 // Otherwise the clipping won't work
samer@22 37 fitTriangleIn(ofGetWidth(),ofGetHeight());
samer@22 38 sendCalibrate();
samer@23 39 sendReplyTo();
hekeus@6 40
samer@12 41 voiceGrabbed=-1;
samer@12 42 }
samer@12 43
hekeus@6 44 //--------------------------------------------------------------
hekeus@6 45 void melodyTriangle::update(){
samer@22 46 bool sendStart=false;
samer@22 47
samer@22 48 while( receiver.hasWaitingMessages() ) {
hekeus@8 49 // get the next message
hekeus@8 50 ofxOscMessage m;
hekeus@8 51 receiver.getNextMessage( &m );
samer@22 52 handleMessage(m);
hekeus@8 53 }
samer@15 54
samer@22 55 constrained=false;
hekeus@6 56 if (voiceGrabbed!=-1){
samer@14 57 Voice *vg=voices[voiceGrabbed];
samer@14 58 if (mouseX!=vg->posx || mouseY!=vg->posy){
samer@15 59 int clipx=mouseX, clipy=mouseY;
samer@15 60 bool clipped=clipToTriangle(&clipx,&clipy);
samer@15 61
samer@15 62 if (vg->inTriangle) {
hekeus@6 63
samer@15 64 if (clipped) {
samer@15 65 // check how far we clipped
samer@15 66 if (ofDist(clipx, clipy, mouseX, mouseY) > BUFFER_ZONE) {
samer@15 67 // if far enough, we pop out of triangle and send
samer@23 68 sendDeath(vg->id);
samer@15 69 vg->posx=mouseX;
samer@15 70 vg->posy=mouseY;
samer@15 71 vg->inTriangle=false;
samer@23 72 vg->status=Voice::pending;
samer@15 73 } else {
samer@15 74 // otherwise, we move to clipped point
samer@15 75 constrained=true;
samer@15 76 vg->posx=clipx;
samer@15 77 vg->posy=clipy;
samer@15 78 }
samer@15 79 } else { // not clipped; normal move
samer@15 80 vg->posx=mouseX;
samer@15 81 vg->posy=mouseY;
samer@15 82 }
samer@15 83 } else { // token was outside triangle
samer@15 84 vg->posx=mouseX;
samer@15 85 vg->posy=mouseY;
samer@15 86 if (!clipped){ // ie mouse now in triangle
samer@15 87 //birth id
hekeus@7 88
samer@10 89 ofxOscMessage m;
samer@15 90 m.setAddress( "/birth" );
samer@14 91 m.addIntArg( vg->id );
samer@10 92 sender.sendMessage( m );
samer@15 93
samer@15 94 printf("sent /birth %i \n",vg->id);
samer@15 95 sendOctave(vg->id,vg->octave);
samer@15 96 sendAmplitude(vg->id,vg->amplitude);
samer@15 97 sendStart=true;
samer@15 98 vg->inTriangle=true;
hekeus@6 99 }
hekeus@6 100 }
hekeus@6 101
samer@14 102 if (vg->inTriangle){
samer@23 103 sendPosition(vg);
samer@18 104 vg->status=Voice::moved;
samer@15 105 if (sendStart && vg->isActive){
samer@15 106 ofxOscMessage m;
samer@15 107 ///track id x y left right top bottom area
samer@15 108 m.setAddress( "/start" );
samer@15 109 m.addIntArg( vg->id );
samer@15 110 sender.sendMessage( m );
samer@15 111 printf("sent /start %i \n",vg->id);
hekeus@6 112 }
hekeus@6 113 }
hekeus@6 114 }
hekeus@6 115 };
samer@22 116 }
samer@10 117
samer@22 118
samer@22 119
samer@22 120 //--------------------------------------------------------------
samer@22 121 void melodyTriangle::draw(){
samer@11 122 ofSetLineWidth(2);
samer@23 123 ofSetColor(60,60,60);
samer@10 124 ofFill();
samer@10 125 ofTriangle(x1, y1, x2, y2, x3, y3);
samer@10 126
samer@10 127 // draw smooth edge, brighter if a token is constrained
samer@23 128 if (constrained) ofSetColor(255,96,96);
samer@10 129 ofNoFill();
samer@10 130 ofTriangle(x1, y1, x2, y2, x3, y3);
samer@10 131
hekeus@6 132 for (int i=0; i<numVoices; i++){
samer@23 133 voices[i]->draw(voices[i]->isInVoice(mouseX,mouseY));
hekeus@6 134 }
hekeus@6 135
samer@22 136 // display message if any
samer@23 137 if (display_frames!=0) {
samer@23 138 ofRectangle bbox=display_font.getStringBoundingBox(display_msg,0,0);
samer@22 139 ofSetColor(220,220,220);
samer@23 140 display_font.drawString(display_msg,
samer@23 141 (ofGetWidth()-bbox.width)/2, (ofGetHeight()-bbox.height)/2);
samer@23 142 if (display_frames>0) display_frames--;
samer@22 143 }
hekeus@6 144 }
hekeus@6 145
samer@22 146 bool melodyTriangle::clipToTriangle(int *x, int *y) {
samer@22 147 bool clipped;
samer@22 148
samer@22 149 if (*y>y2) { // off the bottom
samer@22 150 clipped=true;
samer@22 151 *y=y2;
samer@22 152 if (*x<x2) *x=x2;
samer@22 153 else if (*x>x3) *x=x3;
samer@22 154 } else { // have to be a bit cleverer
samer@22 155 bool reflect=false;
samer@22 156 if (*x<x1) { // work in reflected coordinates
samer@22 157 reflect=true;
samer@22 158 *x=2*x1-*x;
samer@22 159 }
samer@22 160
samer@22 161 int dx=(*x-x1), dy=(*y-y1); // deltas from top
samer@22 162 if (dx*DY13 > dy*DX13) {
samer@22 163 // (x,y) must be somewhere right of triangle now
samer@22 164 clipped=true;
samer@22 165 int dp=dx*DX13 + dy*DY13;
samer@22 166 if (dp<0) { *x=x1; *y=y1; } // off the top
samer@22 167 else if (dp>SQLEN13) { *x=x3; *y=y3; } // off the bottom right
samer@22 168 else { // project onto right edge
samer@22 169 *x=x1+dp*DX13/SQLEN13;
samer@22 170 *y=y1+dp*DY13/SQLEN13;
samer@22 171 }
samer@22 172 } else {
samer@22 173 clipped=false;
samer@22 174 }
samer@22 175
samer@22 176 if (reflect) *x=2*x1 - *x; // reflect back if necessary
samer@22 177 }
samer@22 178 return clipped;
samer@22 179 }
samer@22 180
samer@22 181
samer@22 182
samer@22 183 //- Keyboard ----------------------------------------------------------
samer@22 184
samer@22 185 void melodyTriangle::keyReleased(int key){}
samer@23 186 void melodyTriangle::keyPressed(int key){
hekeus@6 187 //printf("key %i",key);
hekeus@6 188 if (enableKeys){
samer@12 189 switch (key) {
samer@12 190 case ' ': {
samer@12 191 ofxOscMessage m;
samer@12 192 m.setAddress( "/marker" );
samer@12 193 sender.sendMessage(m);
samer@12 194 printf("sent /marker\n");
samer@12 195 break;
samer@12 196 }
samer@12 197
samer@23 198 case '1': case '2':
samer@23 199 case '3': case '4': {
samer@12 200 int tempo=30 + 30*(key-'1');
samer@12 201 ofxOscMessage m;
samer@12 202 m.setAddress( "/tempo" );
samer@12 203 m.addIntArg(tempo);
samer@12 204 sender.sendMessage( m );
samer@12 205 printf("sent /tempo %d\n",tempo);
samer@23 206 break;
samer@12 207 }
samer@12 208
samer@22 209 case 'c': sendReplyTo(); sendCalibrate(); break;
samer@23 210 case 'F': ofToggleFullscreen(); break;
samer@23 211 case 'R': reset(); break;
samer@23 212 case 'Q': ofAppGlutWindow::exitApp();
samer@12 213
samer@23 214 default: // otherwise, send key to all voices under mouse
samer@23 215 for (int i=0; i<numVoices; i++)
samer@23 216 if (voices[i]->isInVoice(mouseX,mouseY))
samer@23 217 voiceKeypress(voices[i],key);
hekeus@6 218 }
samer@23 219 } else {
samer@23 220 ofxOscMessage m;
samer@23 221 m.setAddress( "/key" );
samer@23 222 m.addIntArg(key);
samer@23 223 sender.sendMessage(m);
samer@23 224 printf("sent /key %d\n", key);
samer@23 225 }
samer@23 226 }
samer@23 227
samer@23 228 void melodyTriangle::voiceKeypress(Voice *v, int key) {
samer@23 229 switch (key) {
samer@23 230 case 'a': {
samer@23 231 ofxOscMessage m;
samer@23 232 const char *addr = v->isActive ? "/stop" : "/start";
samer@23 233 v->isActive=!v->isActive;
samer@23 234 m.setAddress(addr);
samer@23 235 m.addIntArg(v->id );
samer@23 236 sender.sendMessage( m );
samer@23 237 printf("sent %s %i \n",addr,v->id);
samer@23 238 break;
samer@23 239 }
samer@23 240 case OF_KEY_LEFT: sendShift(v->id,-1,2); break;
samer@23 241 case OF_KEY_RIGHT: sendShift(v->id,1,2); break;
samer@23 242 case OF_KEY_UP: sendPeriod(v->id,1,2); break;
samer@23 243 case OF_KEY_DOWN: sendPeriod(v->id,2,1); break;
samer@23 244 case '.': sendPeriod(v->id,1,3); break;
samer@23 245 case ',': sendPeriod(v->id,3,1); break;
samer@23 246 case '+': sendOctave(v->id, ++v->octave); break;
samer@23 247 case '-': sendOctave(v->id, --v->octave); break;
samer@23 248 case '*': sendAmplitude(v->id, v->louder()); break;
samer@23 249 case '/': sendAmplitude(v->id, v->quieter()); break;
samer@23 250 default: printf("unrecognised key: %d.\n",key);
hekeus@6 251 }
hekeus@6 252 }
hekeus@6 253
samer@22 254 //- Mouse ------------------------------------------------------
hekeus@6 255
samer@22 256 void melodyTriangle::mouseDragged(int x, int y, int button){}
samer@23 257 void melodyTriangle::mouseMoved(int x, int y ){}
samer@23 258 void melodyTriangle::mousePressed(int x, int y, int button){
hekeus@6 259 for (int i=0; i<numVoices;i++){
samer@23 260 if (voices[i]->isInVoice(x,y)) voiceGrabbed=i;
hekeus@6 261 }
hekeus@6 262 }
hekeus@6 263
samer@22 264 void melodyTriangle::mouseReleased(int x, int y, int button){
samer@22 265 voiceGrabbed=-1;
hekeus@6 266 }
hekeus@6 267
hekeus@6 268 //--------------------------------------------------------------
samer@22 269
hekeus@6 270 void melodyTriangle::windowResized(int w, int h){
samer@22 271 fitTriangleIn(w,h);
samer@22 272 sendCalibrate();
samer@23 273 reset();
samer@22 274 }
hekeus@6 275
samer@23 276 void melodyTriangle::reset() {
samer@23 277 voiceGrabbed=-1;
samer@23 278 for (int i=0;i<numVoices;i++) {
samer@23 279 Voice *v=voices[i];
samer@23 280 v->posx=x2+RADIUS+(numVoices-1-i)*36;
samer@23 281 v->posy=48;
samer@23 282 v->status=Voice::pending;
samer@23 283 if (v->inTriangle) {
samer@23 284 sendDeath(voices[i]->id);
samer@23 285 v->inTriangle=false;
samer@23 286 }
samer@23 287 }
samer@23 288 }
samer@22 289
samer@22 290 // OSC Message handling -----------------------------------------
samer@22 291
samer@23 292 Voice *melodyTriangle::get_voice(int id) throw(bad_voice_id) {
samer@23 293 if (id<1 || id>numVoices) throw bad_voice_id(id);
samer@23 294 return voices[id-1];
samer@23 295 }
samer@23 296
samer@22 297 void melodyTriangle::handleMessage(ofxOscMessage &m) {
samer@22 298 string msg_path=m.getAddress();
samer@23 299
samer@23 300 try {
samer@23 301 if (msg_path.compare(0,8,"/notify/")==0) {
samer@23 302 Voice *v=get_voice(m.getArgAsInt32(0));
samer@23 303 if (msg_path=="/notify/status") {
samer@23 304 v->status=Voice::stringToStatus(m.getArgAsString(1));
samer@23 305 } else if (msg_path=="/notify/position") {
samer@23 306 int x=(int)m.getArgAsFloat(1);
samer@23 307 int y=(int)m.getArgAsFloat(2);
samer@23 308 v->posx=x;
samer@23 309 v->posy=y;
samer@23 310 v->inTriangle=!clipToTriangle(&x,&y);
samer@23 311 if (voiceGrabbed==v->id) voiceGrabbed=-1;
samer@23 312 } else if (msg_path=="/notify/running") {
samer@23 313 v->isActive = m.getArgAsInt32(1) ? true : false;
samer@23 314 } else if (msg_path=="/notify/params") {
samer@23 315 v->octave = m.getArgAsInt32(1);
samer@23 316 v->amplitude = m.getArgAsFloat(2);
samer@23 317 }
samer@23 318 } else if (msg_path=="/display") {
samer@23 319 display_msg=m.getArgAsString(0);
samer@23 320 display_frames=m.getArgAsInt32(1);
samer@23 321 } else if (msg_path=="/font") {
samer@23 322 display_font.loadFont(m.getArgAsString(0),m.getArgAsInt32(1));
samer@23 323 } else if (msg_path=="/keyboard") { enableKeys=m.getArgAsInt32(0); }
samer@23 324 else if (msg_path=="/reset") { reset(); }
samer@23 325 else if (msg_path=="/fullscreen") { ofSetFullscreen(m.getArgAsInt32(0)); }
samer@23 326 else if (msg_path=="/quit") { ofAppGlutWindow::exitApp(); }
samer@23 327 else {
samer@23 328 cout << m.getAddress();
samer@23 329 for (int i=0; i<m.getNumArgs(); i++) {
samer@23 330 cout << " " << m.getArgTypeName(i) << ":";
samer@23 331 switch (m.getArgType(i)) {
samer@23 332 case OFXOSC_TYPE_INT32: cout << m.getArgAsInt32(i);
samer@23 333 case OFXOSC_TYPE_FLOAT: cout << m.getArgAsFloat(i);
samer@23 334 case OFXOSC_TYPE_STRING: cout << m.getArgAsString(i);
samer@23 335 default: cout << "unknown";
samer@23 336 }
samer@23 337 }
samer@23 338 cout<< "\n";
samer@22 339 }
samer@23 340 } catch (std::exception &ex) {
samer@23 341 cout << "** Error processing OSC message: " << ex.what() << "\n";
samer@22 342 }
hekeus@6 343 }
samer@22 344
samer@22 345 // OSC Message sending -----------------------------------------
samer@22 346
samer@23 347 void melodyTriangle::sendDeath(int id) {
samer@23 348 ofxOscMessage m;
samer@23 349 m.setAddress( "/death" );
samer@23 350 m.addIntArg( id );
samer@23 351 sender.sendMessage( m );
samer@23 352 }
samer@23 353
samer@23 354 void melodyTriangle::sendPosition(Voice *v){
samer@22 355
samer@22 356 ofxOscMessage m;
samer@22 357 ///track id x y left right top bottom area
samer@22 358 m.setAddress( "/track2d" );
samer@23 359 m.addIntArg( v->id );
samer@23 360 m.addIntArg( v->posx );
samer@23 361 m.addIntArg( v->posy );
samer@22 362 sender.sendMessage( m );
samer@23 363 printf("sent - /track2d %i %i %i\n",v->id,v->posx,v->posy);
samer@22 364
samer@22 365 }
samer@22 366 void melodyTriangle::sendCalibrate(){
samer@22 367 ofxOscMessage m;
samer@22 368 m.setAddress( "/calibrate" );
samer@22 369 m.addIntArg( x1 );
samer@22 370 m.addIntArg( y1 );
samer@22 371 m.addIntArg( x2 );
samer@22 372 m.addIntArg( y2 );
samer@22 373 m.addIntArg( x3 );
samer@22 374 m.addIntArg( y3 );
samer@22 375 sender.sendMessage( m );
samer@22 376 printf("sent /calibrate %i %i %i %i %i %i\n",x1,y1,x2,y2,x3,y3);
samer@22 377 }
samer@22 378
samer@22 379 void melodyTriangle::sendReplyTo(){
samer@22 380 ofxOscMessage m;
samer@22 381 m.setAddress( "/reply_to" );
samer@22 382 m.addIntArg( receivePort );
samer@22 383 sender.sendMessage( m );
samer@22 384 printf("sent /reply_to %i\n",receivePort);
samer@22 385 }
samer@22 386
samer@22 387 void melodyTriangle::sendPeriod(int id, int num, int den){
samer@22 388 ofxOscMessage m;
samer@22 389 m.setAddress("/period");
samer@22 390 m.addIntArg(id);
samer@22 391 m.addIntArg(num);
samer@22 392 m.addIntArg(den);
samer@22 393 sender.sendMessage(m);
samer@22 394 printf("sent /period %i %i %i\n",id,num,den);
samer@22 395 }
samer@22 396
samer@22 397 void melodyTriangle::sendShift(int id, int num, int den){
samer@22 398 ofxOscMessage m;
samer@22 399 m.setAddress("/shift");
samer@22 400 m.addIntArg(id);
samer@22 401 m.addIntArg(num);
samer@22 402 m.addIntArg(den);
samer@22 403 sender.sendMessage(m);
samer@22 404 printf("sent /shift %i %i %i\n",id,num,den);
samer@22 405 }
samer@22 406
samer@22 407 void melodyTriangle::sendOctave(int id, int oct){
samer@22 408 ofxOscMessage m;
samer@22 409 m.setAddress("/octave");
samer@22 410 m.addIntArg(id);
samer@22 411 m.addIntArg(oct);
samer@22 412 sender.sendMessage(m);
samer@22 413 printf("sent /octave %i %i\n",id,oct);
samer@22 414 }
samer@22 415
samer@22 416 void melodyTriangle::sendAmplitude(int id, float amp){
samer@22 417 ofxOscMessage m;
samer@22 418 m.setAddress("/amplitude");
samer@22 419 m.addIntArg(id);
samer@22 420 m.addFloatArg(amp);
samer@22 421 sender.sendMessage(m);
samer@22 422 printf("sent /amplitude %i %1.3f\n",id,amp);
samer@22 423 }
samer@22 424
samer@22 425
samer@22 426 void melodyTriangle::fitTriangleIn(int width, int height) {
samer@22 427 int triHeight = height*0.75;
samer@22 428 int triHalfWidth = triHeight/sqrt(3);
samer@22 429
samer@22 430 x1=width/2;
samer@22 431 x2=x1-triHalfWidth;
samer@22 432 x3=x1+triHalfWidth;
samer@22 433 y1=(height-triHeight)/2;
samer@22 434 y2=y3=height - (height-triHeight)/2;
samer@22 435
samer@22 436 // used for clipping
samer@22 437 DX13=x3-x1; DY13=y3-y1;
samer@22 438 SQLEN13=DX13*DX13+DY13*DY13;
samer@22 439 }
samer@23 440
samer@23 441 const char *melodyTriangle::bad_voice_id::what() const throw() {
samer@23 442 std::stringstream out;
samer@23 443 printf("bad_voice_id(%d).\n",id);
samer@23 444 out << "Voice id " << id <<" out of range.";
samer@23 445 return out.str().c_str();
samer@23 446 }