void chladniReceiver::onAlsNoteEvent( alsNoteEventArgs &args ){
	
	ofxOscMessage m;
	m.setAddress("/km/chladni/noteEvent");
	m.addStringArg(args.clipName);
	m.addIntArg(args.note.key);
	
	sendOscMessage(m);
}
// ALS Parser
void chladniReceiver::onAlsTrackEvent( alsTrackEventArgs &args ){
	//sendOscMessage("/km/chladni/trackChange", args.trackName );
	ofxOscMessage m;
	m.setAddress("/km/chladni/trackChange");
	m.addStringArg(args.trackName);
	m.addFloatArg(args.audioClip.duration);
	//cout << args.audioClip.time << " - " << args.trackName << endl;
	sendOscMessage(m);
}
bool chladniReceiver::sendOscMessage(const string& _addr, const float& _value){
	
	if( bSenderIsConnected && !_addr.empty() ){
		ofxOscMessage m;
		m.setAddress(_addr);
		m.addFloatArg(_value);
		
		return sendOscMessage(m);
	}
	
	return false;
}
// (addr starts with a slash)
bool durationReceiver::sendOscMessage(const string& _addr, const string& _value){
	if( bSenderIsConnected && !_addr.empty() ){
		ofxOscMessage m;
		m.setAddress(_addr);
		
		if(_value.empty()){
			m.addTriggerArg();
		}
		else {
			m.addSymbolArg(_addr);
		}
		
		return sendOscMessage(m);
	}
	
	return false;
}
void chladniReceiver::showGuiWindow(){
	if(!bShowGuiWindow) return;
	
	ImGui::SetNextWindowSize(ImVec2(400,ofGetHeight()*0.8), ImGuiSetCond_Once);
	ImGui::Begin( ((string)"Module: ").append(karmaModule::getName()).append("###module-").append( ofToString(this) ).c_str() , &bShowGuiWindow );
	ImGui::TextWrapped("This module receives OSC messages trough the OSCRouter module and forwards them to effects and other components.");
	ImGui::TextWrapped("Works together with a chladni plate / cymatics wave generator in PureData.");
	
	ImGui::Separator();
	OSCRouter::ImGuiShowOSCRouterConnectionTester();
	ImGui::Separator();
	
	if( ImGui::CollapsingHeader( "OSC Setup / Config (Pd)", "chladniReceiverOSC", true, true ) ){
		
		ImGui::TextWrapped("Pd OSC connection");
		ImGui::Indent();
		ImGui::TextWrapped("Configure how you connect to PureData patch /utilities/chladni-plate-generator.pd");
		if(ImGui::InputInt("chladni sending port", &oscSendParams.port)){
			connectOSCSender();
		}
		static char addrBuffer[64];
		for(int i=0; i<64; ++i){
			if(i < oscSendParams.host.size()){
				addrBuffer[i]=oscSendParams.host[i];
			}
			else {
				addrBuffer[i]=0;
			}
		}
		if(ImGui::InputText("chladni remote host", &addrBuffer[0], 64, ImGuiInputTextFlags_EnterReturnsTrue)){
			oscSendParams.host = ofToString(addrBuffer);
			connectOSCSender();
		}
		
		if( chladniRC::getInstance().pureDataIsConnected()){
			ImGui::TextWrapped("Status: Connected");
		}
		else {
			ImGui::TextWrapped("Status: Not Connected");
		}
		
		if(ImGui::Button("Reconnect")){
			connectOSCSender();
		}
		
		// todo: launch pd from OF ?
		// see: https://forum.openframeworks.cc/t/using-shell-script-to-launch-external-apps-from-of/3853
		// see: http://stackoverflow.com/questions/646217/how-to-run-a-bash-script-from-c-program
		
		ImGui::Unindent();
		
		ImGui::Separator();
		
		if( chladniRC::getInstance().pureDataIsConnected() && ImGui::CollapsingHeader( "Chladni-plate Pd Controls", "chladniReceiverPdCommands", true, true ) ){
			
			ImGui::TextWrapped("Pd OSC Controls");
			ImGui::Indent();
			ImGui::TextWrapped("Control the chladni Pd-patch !");
			
			
			if(ImGui::Button("Trigger change")){
				sendOscMessage("/km/chladni/randomChange", "1");
			}
			
			static float frequency = 64.05f;
			if( ImGui::SliderFloat("Frequency", &frequency, 30.f, 140.f) ){
				sendOscMessage( "/km/chladni/setFreq", frequency );
			}
			
			static int delay = 600;
			if( ImGui::SliderInt("Delay (ms)", &delay, 0, 1000) ){
				sendOscMessage( "/km/chladni/setDelay", delay );
			}
			
//			static broadcast
//			if(ImGui::Checkbox("react to notes")){
//				
//			}
			
			ImGui::Unindent();
		}
	}
	ImGui::Separator();
	
	static bool wasOpen = false;
	if( ImGui::CollapsingHeader( "Arduino Setup (water control)", "chladniReceiverArduino", true, true ) ){
		
		static std::vector<ofx::IO::SerialDeviceInfo> devicesInfo = ofx::IO::SerialDeviceUtils::listDevices();
			
		// refresh list ?
		if(!wasOpen){
			devicesInfo = ofx::IO::SerialDeviceUtils::listDevices();
			
			wasOpen = true;
		}
		
		ImGui::Indent();
		
		ImGui::TextWrapped("Communicates with a USB Arduino device. Needs patch: /src/modules/serialController_v1/karmaMapperSerial_v1/karmaMapperSerial_v1.ino");
		
		if(ImGui::Button("Connect to first Arduino device")){
			chladniRC::getInstance().setupArduinoSerial();
		}
		
		if(ImGui::ListBoxHeader("Connect to device...")){
			
			if (!devicesInfo.empty()){
				for (std::size_t i = 0; i < devicesInfo.size(); ++i) {
					if(ImGui::Selectable( ( devicesInfo[i].getDescription() + " (" + devicesInfo[i].getPort() + ")" +"###"+devicesInfo[i].getDescription()).c_str(), devicesInfo[i].getPort().compare( chladniRC::getInstance().getArduinoDevicePort() )==0 )){
						
						if( chladniRC::getInstance().connectToArduino(devicesInfo[i]) ){
							// todo: remember device ?
						}
						else {
							// todo: notify failure
							
						}
					}
				}
			}
			else {
				ImGui::TextWrapped("[None Available]");
			}
			ImGui::ListBoxFooter();
		}
		
		if( chladniRC::getInstance().arduinoIsConnected()){
			ImGui::TextWrapped("Status: Connected to: %s", chladniRC::getInstance().getArduinoDevicePort().c_str() );
		}
		else {
			ImGui::TextWrapped("Status: Not Connected");
		}
		
		ImGui::Separator();
		
		// ping
		if(chladniRC::getInstance().arduinoIsConnected()){
			if(ImGui::Button("Ping Device")){
				chladniRC::getInstance().pingArduino();
			}
			ImGui::TextWrapped("Last ping return: %s", chladniRC::getInstance().getLastArduinoPingMessage().c_str() );
			
			ImGui::Separator();
			
			// Water flow
			ImGui::TextWrapped("Water Flow Control:");
			ImGui::Indent();
			for(int ev=0; ev<KM_CHLADNI_NUM_ELECTROVALVES; ++ev){
				float flow = chladniRC::getInstance().getSolenoidFlow(ev);
				if( ImGui::SliderFloat((ofToString("EV")+ofToString(ev)).c_str(), &flow, 0.f, 1.f) ){
					chladniRC::getInstance().setSolenoidFlow(ev, flow);
				}
			}
			ImGui::Unindent();
			
			ImGui::Separator();
			
			// Led strips
			ImGui::TextWrapped("LED strips:");
			ImGui::Indent();
			for(int ls=0; ls<KM_CHLADNI_NUM_LED_STRIPS; ++ls){
				ImGui::TextWrapped("LED strip #%d", ls);
				ImGui::Indent();
				float intensityManu = chladniRC::getInstance().getLEDStripIntensityManu(ls);
				ImGui::SliderFloat((ofToString("LED strip ")+ofToString(ls)+" manu value").c_str(), &intensityManu, 0.f, 1.f);
				float intensityAuto = chladniRC::getInstance().getLEDStripIntensityAuto(ls);
				if(ImGui::SliderFloat((ofToString("LED strip ")+ofToString(ls)+" auto value").c_str(), &intensityAuto, 0.f, 1.f)){
					chladniRC::getInstance().setLEDStripIntensityAuto(ls, intensityAuto);
				}
				ImGui::Unindent();
			}
			ImGui::Unindent();
			
			// Flowmeters
			ImGui::TextWrapped("Flow meters:");
			ImGui::Indent();
			for(int fm=0; fm<KM_CHLADNI_NUM_LED_STRIPS; ++fm){
				ImGui::TextWrapped("Flow Meter %d", fm);
				float value = chladniRC::getInstance().getFlowMeterRate(fm);
				ImGui::SliderFloat("Flow rate", &value, 0.f, 1.f);
			}
		}
		
		ImGui::Unindent();
	}
	
	ImGui::End();
}
//--------------------------------------------------------------
void ofApp::update(){
    
    activePoses.clear();
    bodies.clear();

    kinect.update();

    unsigned short * depthPixels = this->kinect.getDepthSource()->getPixels();

    HRESULT hr = m_pCoordinateMapper->MapColorFrameToDepthSpace(
        512 * 424,
        (const UINT16*)depthPixels,
        1920 * 1080,
        m_pDepthCoordinates);

    // 1. create tracked bodies/joints array
    // THERE IS A MAXIMUM OF 6 BODIES TRACKED BY KINECT
    for (int i = 0; i<6; i++){

        // IF THE BODY IS BEING TRACKED...
        if (this->kinect.getBodySource()->getBodies()[i].tracked){

            auto b = this->kinect.getBodySource()->getBodies()[i];
            
            map<JointType, ofxKFW2::Data::Joint>::iterator it;
            map<int, ofxKFW2::Data::Joint> jointsData;

            if (b.joints.find(JointType_SpineBase) != b.joints.end()) {
                if (b.joints.find(JointType_SpineBase)->second.getPosition().z <= maxDistance) {
                                // ITERATE THROUGH ALL JOINTS IN THE TRACKED BODY...
                    for (it = b.joints.begin(); it != b.joints.end(); ++it) {
                
                        jointsData.insert( pair<int, ofxKFW2::Data::Joint>(it->first, it->second));
                
                    }
                    bodies.push_back(jointsData);
                }
            }
        }
    }

    // 2. calculate angles
    for (vector< map<int, ofxKFW2::Data::Joint> >::iterator i = bodies.begin(); i != bodies.end(); i++) {

        int index = i - bodies.begin();

        map<string, float> jointAngles;
        for (map<string, CalcParams>::iterator paramsIterator = jointCalcParams.begin(); paramsIterator != jointCalcParams.end(); paramsIterator++) {

            map<int, ofxKFW2::Data::Joint> b = *i;
            auto j1 = b.find(paramsIterator->second.j[0]);
            auto j2 = b.find(paramsIterator->second.j[1]);
            auto j3 = b.find(paramsIterator->second.j[2]);

            if ( checkTracking(j1, j2, j3, b) ) {
                float angle = calcAngle( j1, j2, j3 );
                jointAngles.insert( pair<string, float>(paramsIterator->first, angle) );
            }
        }

        jointAngleArray.push_back(jointAngles);     

        // 3. check for poses
        // check for angles matching poses
        int activePose = -1;
        for (map<int, Pose>::iterator j = poses.begin(); j != poses.end(); j++) {
            Pose pose = j->second;
            float elTar = pose.ElbowLeft;
            float erTar = pose.ElbowRight;
            float zslTar = pose.zShoulderLeft;
            float zsrTar = pose.zShoulderRight;

            // get angles or if not found to -1
            float elA = (jointAngles.find("elbowLeft") != jointAngles.end()) ? jointAngles.find("elbowLeft")->second : -1.0;
            float erA = (jointAngles.find("elbowRight") != jointAngles.end()) ? jointAngles.find("elbowRight")->second : -1.0;
            float zslA = (jointAngles.find("zShoulderLeft") != jointAngles.end()) ? jointAngles.find("zShoulderLeft")->second : -1.0;
            float zsrA = (jointAngles.find("zShoulderRight") != jointAngles.end()) ? jointAngles.find("zShoulderRight")->second : -1.0;

            float elDif = abs( elTar - elA );
            float erDif = abs( erTar - erA );
            float zslDif = abs( zslTar - zslA );
            float zsrDif = abs( zsrTar - zsrA );

            // tolerance value

            if ( elDif < tol && elA != -1.0 && erDif < tol && erA != -1.0 &&
                 zslDif < tol && zslA != -1.0 && zsrDif < 10.0 && zsrA != -1.0 ) {
                // sendOscMessage(97 + i->first);
                // cout << "pose " << j->first << endl;
                activePose = j->first;
            } else {
                // activePoses.push_back(-1.0);
                // cout << "pose -1" << endl;
            }
        }
        activePoses.push_back(activePose);
    }

    scanLineX = ofGetFrameNum() % ofGetWidth();

    for (int i = 0; i < bodies.size(); i++) {
        if (bodies[i].find(JointType_SpineBase) != bodies[i].end() ) {
            ofVec2f pos = bodies[i].find(JointType_SpineBase)->second.getProjected(m_pCoordinateMapper);

            if (pos.x > scanLineX && pos.x < scanLineX + 1 ) {
                cout << "hitting body " << pos.x << endl;
                // cout << "active pose " << activePoses[i] << endl;
                if ( activePoses[i] > -1 ) {
                    cout << "triggering: " << 97 + activePoses[i] << endl;
                    sendOscMessage(97 + activePoses[i]);
                } else {
                    // play 'fallback sound'
                }
            }
        }
    }
    // mesh = kinect.getDepthSource()->getMesh(
    //  false,
    //  ofxKinectForWindows2::Source::Depth::PointCloudOptions::TextureCoordinates::ColorCamera);

}
//--------------------------------------------------------------
void ofApp::keyPressed(int key){
    if (key = 'h') {
        showGUI = !showGUI;
    }
    sendOscMessage(key);
}