Mercurial > hg > screen-ui
view src/melodyTriangle.cpp @ 38:330f2746fedd tip
Added keys help in a smaller font; changed loadness keyboard controls.
author | samer |
---|---|
date | Mon, 27 Feb 2012 20:09:10 +0000 |
parents | 260cc4f4d70a |
children |
line wrap: on
line source
#include "melodyTriangle.h" #include <GLUT/GLUT.h> #define BUFFER_ZONE 64 // have to drag this far to snap out of triange. #define NUM_TEMPI 9 #define NUMEL(a) sizeof(a)/sizeof(a[0]) static int tempi[]={20,30,45,60,90,120,150,180,240}; static ofxOscBundle& operator<<(ofxOscBundle &bundle, ofxOscMessage msg) { bundle.addMessage(msg); return bundle; } static ofxOscSender& operator<<(ofxOscSender &sender, ofxOscMessage msg) { sender.sendMessage(msg); return sender; } static ofxOscSender& operator<<(ofxOscSender &sender, ofxOscBundle bundle) { sender.sendBundle(bundle); return sender; } melodyTriangle::melodyTriangle(const char *host, int port, int numVoices, bool enableKeys,int voiceIdOffset,int receivePort): numVoices(numVoices), receivePort(receivePort), snapTruePos(enableKeys), enableKeys(enableKeys), randInit(false),allowExit(true), ratio(2), tempoIndex(4), display_msg(""), display_frames(0), display_font(NULL) { for (int i=0;i<numVoices;i++) voices[i]=new Voice(i+1+voiceIdOffset); sender.setup( host,port ); receiver.setup( receivePort ); main_font.loadFont("/System/Library/Fonts/LucidaGrande.ttc",24); help_font.loadFont("/System/Library/Fonts/Menlo.ttc",16); //help_font.loadFont("/System/Library/Fonts/Courier.dfont",16); } melodyTriangle::~melodyTriangle() { printf("Deleting voice objects...\n"); for (int i=0;i<numVoices;i++) delete voices[i]; } //-------------------------------------------------------------- void melodyTriangle::setup(){ ofSetCircleResolution(64); ofBackground(0,0,0); ofSetWindowTitle("Melody Triangle"); ofSetFrameRate(40); // caps framerate if vertical sync is off. ofEnableSmoothing(); // Set up triange coordinates. // NB. whatever happens here, the triangle must be // isosceles and left-right symmetric around x=x1. // Otherwise the clipping won't work fitTriangleIn(ofGetWidth(),ofGetHeight()); ofxOscBundle bundle; sender << ( bundle << msg("/tempo",tempi[tempoIndex]) << msgCalibrate() << msgReplyTo()); voiceGrabbed=NULL; } //-------------------------------------------------------------- void melodyTriangle::update(){ bool sendStart=false; while( receiver.hasWaitingMessages() ) { // get the next message ofxOscMessage m; receiver.getNextMessage( &m ); handleMessage(m); } constrained=false; if (voiceGrabbed!=NULL){ Voice *vg=voiceGrabbed; if (mouseX!=vg->posx || mouseY!=vg->posy){ int clipx=mouseX, clipy=mouseY; bool clipped=clipToTriangle(&clipx,&clipy); ofxOscBundle bundle; if (vg->inTriangle) { if (clipped) { // check how far we clipped if (ofDist(clipx, clipy, mouseX, mouseY)>BUFFER_ZONE && allowExit) { // if far enough, we pop out of triangle and send bundle << msg("/death",vg->id); sender << bundle; vg->posx=mouseX; vg->posy=mouseY; vg->inTriangle=false; vg->status=Voice::pending; } else { // otherwise, we move to clipped point constrained=true; vg->posx=clipx; vg->posy=clipy; } } else { // not clipped; normal move vg->posx=mouseX; vg->posy=mouseY; } } else { // token was outside triangle vg->posx=mouseX; vg->posy=mouseY; if (!clipped){ // ie mouse now in triangle bundle << msg("/birth",vg->id) << msgAmplitude(vg->id,vg->amplitude) << ( randInit ? msg("/randinit",vg->id) : msgOctave(vg->id,vg->octave) ); sendStart=true; vg->inTriangle=true; vg->truex=vg->truey=-1; // ie not known yet. } } if (vg->inTriangle){ bundle << msgPosition(vg); vg->status=Voice::moved; if (sendStart && vg->isActive) bundle << msg("/start",vg->id); sender << bundle; } } }; } //-------------------------------------------------------------- void melodyTriangle::draw(){ ofSetLineWidth(2); ofSetColor(60,60,60); ofFill(); ofTriangle(x1, y1, x2, y2, x3, y3); // draw smooth edge, brighter if a token is constrained if (constrained) ofSetColor(255,96,96); ofNoFill(); ofTriangle(x1, y1, x2, y2, x3, y3); for (int i=numVoices-1; i>=0; i--){ voices[i]->draw(enableKeys && voices[i]->isInVoice(mouseX,mouseY)); } // display message if any if (display_frames!=0) { ofRectangle bbox=display_font->getStringBoundingBox(display_msg,0,0); ofSetColor(220,220,220); display_font->drawString(display_msg, (ofGetWidth()-bbox.width)/2, (ofGetHeight()-bbox.height)/2); if (display_frames>0) display_frames--; } if (ratio!=2) { ofSetColor(160,160,160); display_font->drawString(ofToString(ratio),16,ofGetHeight()-16); } } bool melodyTriangle::clipToTriangle(int *x, int *y) { bool clipped; if (*y>y2) { // off the bottom clipped=true; *y=y2; if (*x<x2) *x=x2; else if (*x>x3) *x=x3; } else { // have to be a bit cleverer bool reflect=false; if (*x<x1) { // work in reflected coordinates reflect=true; *x=2*x1-*x; } int dx=(*x-x1), dy=(*y-y1); // deltas from top if (dx*DY13 > dy*DX13) { // (x,y) must be somewhere right of triangle now clipped=true; int dp=dx*DX13 + dy*DY13; if (dp<0) { *x=x1; *y=y1; } // off the top else if (dp>SQLEN13) { *x=x3; *y=y3; } // off the bottom right else { // project onto right edge *x=x1+dp*DX13/SQLEN13; *y=y1+dp*DY13/SQLEN13; } } else { clipped=false; } if (reflect) *x=2*x1 - *x; // reflect back if necessary } return clipped; } //- Keyboard ---------------------------------------------------------- void melodyTriangle::keyReleased(int key){ if (enableKeys && key>='2' && key<='9') { ratio=2; } } struct command { const char *key; const char *desc; }; struct command voice_commands[] = { // { "←", "fractional delay" }, // { "→", "fractional advance" }, // { "↑", "multiply note rate" }, // { "↓", "divide note rate" }, { "[right]", "fractional delay" }, { "[left]", "fractional advance" }, { "[up]", "multiply note rate" }, { "[down]", "divide note rate" }, { "[", "transpose octave down" }, { "]", "transpose octave up" }, { "{", "quieter" }, { "}", "louder" }, { "c", "change to nearby pattern" }, { "s", "shuffle notes" }, { "h", "this help screen" }, { "H", "global keys help screen" }, }; struct command global_commands[] = { { "<", "reduce tempo" }, { ">", "increase tempo" }, { "I", "toggle randomise on birth" }, { "S", "save current pattern" }, { "C", "calibrate triangle" }, { "F", "toggle full screen" }, { "R", "reset token positions" }, { "Q", "quit" }, { "H", "this help screen" }, { "h", "token keys help screen" } }; void melodyTriangle::keyPressed(int key){ if (enableKeys){ if (key>='2' && key<='9') { ratio=key-'0'; } else { printf("got key: %d.\n",key); switch (key) { case '<': if (tempoIndex>0) tempoIndex--; sender << msg("/tempo",tempi[tempoIndex]); break; case '>': if (tempoIndex<NUM_TEMPI-1) tempoIndex++; sender << msg("/tempo",tempi[tempoIndex]); break; case ' ': sender << msg("/marker"); break; case 'S': sender << msg("/save"); break; case 'r': sender << msg("/report"); break; case 'I': randInit ^= true; display_msg = (randInit?"randomise on birth":"no randomise on birth"); display_font = &main_font; display_frames = 40; break; case 'C': { ofxOscBundle bundle; sender << (bundle << msgReplyTo() << msgCalibrate()); break; } case 'F': ofToggleFullscreen(); break; case 'R': reset(); break; case 'Q': ofAppGlutWindow::exitApp(); case '/': display_frames=0; break; case 'h': case 'H': case '?': display_frames = -1; display_msg = help_string(key!='h'); display_font = &help_font; break; case OF_KEY_ESC: setKeyboardEnable(false); break; default: // otherwise, send key to all voices under mouse for (int i=0; i<numVoices; i++) if (voices[i]->isInVoice(mouseX,mouseY)) voiceKeypress(voices[i],key); } } } else { if (key==OF_KEY_ESC) setKeyboardEnable(true); else sender << msg("/key",key); } } void melodyTriangle::voiceKeypress(Voice *v, int key) { switch (key) { case 'a': if (v->inTriangle) { sender << msg(v->isActive ? "/stop" : "/start", v->id); } v->isActive=!v->isActive; break; case OF_KEY_LEFT: sender << msgShift(v->id,-1,ratio); break; case OF_KEY_RIGHT: sender << msgShift(v->id,1,ratio); break; case OF_KEY_UP: sender << msgPeriod(v->id,1,ratio); break; case OF_KEY_DOWN: sender << msgPeriod(v->id,ratio,1); break; case ']': sender << msgOctave(v->id, ++v->octave); break; case '[': sender << msgOctave(v->id, --v->octave); break; case '}': sender << msgAmplitude(v->id, v->louder()); break; case '{': sender << msgAmplitude(v->id, v->quieter()); break; case 'c': sender << msg("/change",v->id); v->status=Voice::pending; break; case 's': sender << msg("/shuffle",v->id); break; default: printf("unrecognised key: %d.\n",key); } } inline int max(int x, int y) { return y>x ? y : x; } string melodyTriangle::help_string(bool global) { stringstream buf; struct command *cmds; int n, maxw=0; buf << " ( " << (global ? "GLOBAL" : "TOKEN") << " KEYBOARD CONTROLS ) \n\n"; if (global) { cmds=global_commands; n=NUMEL(global_commands); } else { cmds=voice_commands; n=NUMEL(voice_commands); } for (int i=0; i<n; i++) maxw=max(maxw,strlen(cmds[i].key)); for (int i=0; i<n; i++) buf << setw(maxw) << cmds[i].key << " - " << cmds[i].desc << "\n"; buf << "\nPress '/' to dismiss."; return buf.str(); } //- Mouse ------------------------------------------------------ void melodyTriangle::mouseDragged(int x, int y, int button){} void melodyTriangle::mouseMoved(int x, int y ){} void melodyTriangle::mousePressed(int x, int y, int button){ // pick up first token under mouse for (int i=0; i<numVoices;i++){ if (voices[i]->isInVoice(x,y)) { voiceGrabbed=voices[i]; break; } } } void melodyTriangle::mouseReleased(int x, int y, int button){ if (voiceGrabbed!=NULL) { Voice *v=voiceGrabbed; if (v->status==Voice::clear && v->truex>=0) { v->posx=v->truex; v->posy=v->truey; v->truex=v->truey=-1; } voiceGrabbed=NULL; } } //-------------------------------------------------------------- void melodyTriangle::windowResized(int w, int h){ fitTriangleIn(w,h); sender << msgCalibrate(); reset(); } void melodyTriangle::reset() { voiceGrabbed=NULL; for (int i=0;i<numVoices;i++) { Voice *v=voices[i]; v->posx=x2+RADIUS+(numVoices-1-i)*36; v->posy=48; v->status=Voice::pending; v->isActive=true; if (v->inTriangle) { sender << msg("/death",v->id); v->inTriangle=false; } } } // OSC Message handling ----------------------------------------- Voice *melodyTriangle::get_voice(int id) throw(bad_voice_id) { for (int i=0; i<numVoices; i++) { if (voices[i]->id==id) return voices[i]; } throw bad_voice_id(id); } void melodyTriangle::handleMessage(ofxOscMessage &m) { string msg_path=m.getAddress(); try { if (msg_path.compare(0,8,"/notify/")==0) { string msg=msg_path.substr(8); Voice *v=get_voice(m.getArgAsInt32(0)); cout << "Received " << msg_path << "\n"; if (msg=="requested") v->status=Voice::waiting; else if (msg=="pending") v->status=Voice::pending; else if (msg=="received") { float x=m.getArgAsFloat(1); float y=m.getArgAsFloat(2); v->status=Voice::clear; if (snapTruePos) { if (voiceGrabbed==v) { v->truex=x; v->truey=y; } else { v->posx=x; v->posy=y; v->truex=v->truey=-1; } } printf("True position of %d: %4.1f, %4.1f\n",v->id,x,y); } else if (msg=="position") { int x=(int)m.getArgAsFloat(1); int y=(int)m.getArgAsFloat(2); v->posx=x; v->posy=y; v->inTriangle=!clipToTriangle(&x,&y); v->status=Voice::clear; if (voiceGrabbed==v) voiceGrabbed=NULL; } else if (msg=="running") { v->isActive = m.getArgAsInt32(1) ? true : false; } else if (msg=="params") { v->octave = m.getArgAsInt32(1); v->amplitude = m.getArgAsFloat(2); } else if (msg=="visible") { v->isVisible = m.getArgAsInt32(1); if (voiceGrabbed==v && !v->isVisible) voiceGrabbed=NULL; } } else if (msg_path=="/display") { display_msg=m.getArgAsString(0); display_frames=m.getArgAsInt32(1); display_font=&main_font; } else if (msg_path=="/font") { main_font.loadFont(m.getArgAsString(0),m.getArgAsInt32(1)); } else if (msg_path=="/keyboard") { setKeyboardEnable(m.getArgAsInt32(0)); } else if (msg_path=="/reset") { reset(); } else if (msg_path=="/fullscreen") { ofSetFullscreen(m.getArgAsInt32(0)); } else if (msg_path=="/quit") { ofAppGlutWindow::exitApp(); } else if (msg_path=="/allowExit") { allowExit=m.getArgAsInt32(0); } else { cout << m.getAddress(); for (int i=0; i<m.getNumArgs(); i++) { cout << " " << m.getArgTypeName(i) << ":"; switch (m.getArgType(i)) { case OFXOSC_TYPE_INT32: cout << m.getArgAsInt32(i); case OFXOSC_TYPE_FLOAT: cout << m.getArgAsFloat(i); case OFXOSC_TYPE_STRING: cout << m.getArgAsString(i); default: cout << "unknown"; } } cout<< "\n"; } } catch (std::exception &ex) { cout << "** Error processing OSC message: " << ex.what() << "\n"; } } void melodyTriangle::setKeyboardEnable(bool en) { enableKeys=en; snapTruePos=en; display_msg=en ? "Keyboard enabled" : "Keybard disabled"; display_frames=40; display_font=&main_font; } // OSC Message sending ----------------------------------------- ofxOscMessage melodyTriangle::msgPosition(Voice *v){ ofxOscMessage m; ///track id x y left right top bottom area m.setAddress( "/track2d" ); m.addIntArg( v->id ); m.addIntArg( v->posx ); m.addIntArg( v->posy ); // printf("/track2d %i %i %i\n",v->id,v->posx,v->posy); return m; } ofxOscMessage melodyTriangle::msgCalibrate(){ ofxOscMessage m; m.setAddress( "/calibrate" ); m.addIntArg( x1 ); m.addIntArg( y1 ); m.addIntArg( x2 ); m.addIntArg( y2 ); m.addIntArg( x3 ); m.addIntArg( y3 ); printf("/calibrate %i %i %i %i %i %i\n",x1,y1,x2,y2,x3,y3); return m; } ofxOscMessage melodyTriangle::msgReplyTo(){ ofxOscMessage m; m.setAddress( "/reply_to" ); m.addIntArg( receivePort ); printf("sent /reply_to %i\n",receivePort); return m; } ofxOscMessage melodyTriangle::msgPeriod(int id, int num, int den) { return msg("/period",id,num,den); } ofxOscMessage melodyTriangle::msgShift(int id, int num, int den) { return msg("/shift",id,num,den); } ofxOscMessage melodyTriangle::msgOctave(int id, int oct) { return msg("/octave",id,oct); } ofxOscMessage melodyTriangle::msgAmplitude(int id, float amp){ ofxOscMessage m; m.setAddress("/amplitude"); m.addIntArg(id); m.addFloatArg(amp); printf("/amplitude %i %1.3f\n",id,amp); return m; } ofxOscMessage melodyTriangle::msg(const char *addr, int a, int b, int c) { ofxOscMessage m; m.setAddress(addr); m.addIntArg(a); m.addIntArg(b); m.addIntArg(c); printf("%s %i %i %i\n",addr,a,b,c); return m; } ofxOscMessage melodyTriangle::msg(const char *addr, int a, int b) { ofxOscMessage m; m.setAddress(addr); m.addIntArg(a); m.addIntArg(b); printf("%s %i %i\n",addr,a,b); return m; } ofxOscMessage melodyTriangle::msg(const char *addr, int a) { ofxOscMessage m; m.setAddress(addr); m.addIntArg(a); printf("%s %i\n",addr,a); return m; } ofxOscMessage melodyTriangle::msg(const char *addr) { ofxOscMessage m; m.setAddress(addr); printf("%s\n",addr); return m; } void melodyTriangle::fitTriangleIn(int width, int height) { int triHeight = height*0.75; int triHalfWidth = triHeight/sqrt(3); x1=width/2; x2=x1-triHalfWidth; x3=x1+triHalfWidth; y1=(height-triHeight)/2; y2=y3=height - (height-triHeight)/2; // used for clipping DX13=x3-x1; DY13=y3-y1; SQLEN13=DX13*DX13+DY13*DY13; } const char *melodyTriangle::bad_voice_id::what() const throw() { std::stringstream out; printf("bad_voice_id(%d).\n",id); out << "Voice id " << id <<" not present."; return out.str().c_str(); }