annotate src/melodyTriangle.cpp @ 31:417deb31dd4e

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