rt300@8: // rt300@8: // SearchMessageOrganiser.h rt300@8: // riftathon rt300@8: // rt300@8: // Created by Robert Tubb on 17/10/2014. rt300@8: // rt300@8: // rt300@8: rt300@8: #ifndef __riftathon__SearchMessageOrganiser__ rt300@8: #define __riftathon__SearchMessageOrganiser__ rt300@8: rt300@8: #include rt300@8: #include "MessageOrganiser.h" rt300@8: rt300@8: class SearchMessageOrganiser : public MessageOrganiser { rt300@8: rt300@8: public: rt300@8: void init( PDSynthWrapper& cs, PDSynthWrapper& ts){ rt300@8: rt300@8: testController = new TestController; rt300@8: rt300@8: MessageOrganiser::init(cs,ts); rt300@9: rt300@8: currentSoundPlayTimer = -1; rt300@8: okToGetLeapMidi = false; rt300@8: rt300@8: alternationSpeed = 200; rt300@8: rt300@8: candidateSynth.setNoteLength(alternationSpeed); rt300@8: targetSynth.setNoteLength(alternationSpeed); rt300@8: rt300@8: playingAlternating = false; rt300@8: rt300@8: rt300@8: verdBig.loadFont("verdana.ttf", 18, true, true); rt300@8: verdBig.setLineHeight(18.0f); rt300@8: verdBig.setLetterSpacing(1.037); rt300@8: }; rt300@8: rt300@8: //------------------------------------------------------------------------ rt300@8: void drawScore(){ rt300@8: ofColor txtCol = ofColor(150,235,200,255); rt300@8: rt300@8: int score = getScore(); rt300@8: stringstream msg; rt300@8: rt300@8: msg << "Test: " << testController->getCurrentTestLetter(); rt300@8: ofSetColor(txtCol); rt300@8: verdBig.drawString(msg.str(), 40, 140); rt300@8: msg.str(std::string()); rt300@8: rt300@8: msg << "Score: " << score; rt300@8: verdBig.drawString(msg.str(), 240, 140); rt300@8: msg.str(std::string()); rt300@8: rt300@8: pair time; rt300@8: time = getTime(); rt300@8: msg << "Time taken: " << time.first << ":" << time.second << endl; rt300@8: verdBig.drawString(msg.str(), 600, 140); rt300@8: rt300@8: } rt300@8: rt300@8: void setNewTestButton(Buttron * ntb){ rt300@8: newTestButton = ntb; rt300@8: }; rt300@8: void set3Dbox(Leap3DBoxGL* box){ rt300@8: box3D = box; rt300@8: }; rt300@9: rt300@9: rt300@8: void setCountdownPanel(CountdownText* cd){ rt300@8: countdownPanel = cd; rt300@8: }; rt300@8: void setTargetSymbol(TargetSymbol* ts){ rt300@8: targetSymbol = ts; rt300@8: }; rt300@8: void setScorePanel(TextPanel* tp){ rt300@8: scorePanel = tp; rt300@8: }; rt300@8: void setFinishPanel(TextPanel* fp){ rt300@8: finishPanel = fp; rt300@8: } rt300@8: void setTargetButton(Buttron* tb){ rt300@8: targetPlayButton = tb; rt300@8: } rt300@8: int getScore(){ rt300@8: return testController->getScoreRunningTotal(); rt300@8: }; rt300@8: rt300@8: pair getTime(){ rt300@8: TimerMillisec tms = timeController.getStopwatchElapsedTime(); rt300@8: int s = int(tms/1000); rt300@8: int hs = int((tms%1000)/10); rt300@8: pair p(s,hs); rt300@8: return p; rt300@8: }; rt300@8: void countdownToNewTest(){ rt300@8: rt300@27: controlPanel->hide(); rt300@27: controlPanel->setActive(false); rt300@8: scorePanel->hide(); rt300@8: bottomPanel->hide(); rt300@8: newTestButton->hide(); rt300@8: rt300@8: // set up stuff rt300@8: setupNewTest(); rt300@8: eventLogger.logEvent(COUNTDOWN_INITIATED); rt300@8: rt300@8: countdownPanel->showAndStart(3); rt300@8: rt300@8: timeController.scheduleEvent(boost::bind(&SearchMessageOrganiser::startNewTest, this), 3000); rt300@8: rt300@8: }; rt300@9: rt300@8: void playTargetButtonPressed(){ rt300@8: rt300@8: static int numPlays = 3; rt300@8: rt300@8: Test* t = testController->getCurrentTestPtr(); rt300@8: if (!t->checkTargetPlaysRemaining()){ rt300@8: cout << t->getTargetPlaysLeft() << endl; rt300@8: rt300@8: sendSynthValuesAgain(); rt300@8: targetSynth.trigger(); rt300@8: eventLogger.logEvent(TARGET_PLAYED); rt300@8: targetPlayButton->hide(); rt300@8: return; rt300@8: rt300@8: } rt300@8: cout << t->getTargetPlaysLeft() << endl; rt300@8: rt300@8: sendSynthValuesAgain(); rt300@8: targetSynth.trigger(); rt300@8: eventLogger.logEvent(TARGET_PLAYED); rt300@8: rt300@8: return; rt300@8: } rt300@9: rt300@8: void buttonPressCallback(int mappingID, int value){ rt300@8: if(mappingID == VOLUME_CHANGE_ID){ rt300@8: targetSynth.sendVolume(value); rt300@8: candidateSynth.sendVolume(value); rt300@8: rt300@8: } rt300@8: if(mappingID == SPEED_CHANGE_ID){ rt300@8: alternationSpeed = 2*(140 - value); rt300@8: vector eData; rt300@8: eData.push_back(alternationSpeed); rt300@8: eventLogger.logEvent(SPEED_CHANGED, eData); rt300@8: } rt300@8: if(mappingID == NEW_TEST_ID){ rt300@8: countdownToNewTest(); rt300@8: return; rt300@8: } rt300@8: if (mappingID == START_ALTERNATE_ID){ rt300@8: if(!playingAlternating){ rt300@8: startAlternatingPlayback(); rt300@8: rt300@8: }else{ rt300@8: stopAlternatingPlayback(); rt300@8: } rt300@8: return; rt300@8: } rt300@9: rt300@8: if (mappingID == RANDOMISE_TARGET_ID){ // bleyeueurrrr rt300@8: targetSynth.randomiseParams(); rt300@8: return; rt300@8: } rt300@8: if (mappingID == TRIGGER_TARGET_ID){ rt300@8: playTargetButtonPressed(); rt300@8: rt300@8: } rt300@8: if (mappingID == TRIGGER_CANDIDATE_ID){ rt300@8: // log event rt300@8: sendSynthValuesAgain(); rt300@8: candidateSynth.trigger(); rt300@8: eventLogger.logEvent(CANDIDATE_PLAYED); rt300@8: // flash panel? rt300@27: controlPanel->flash(); rt300@8: return; rt300@8: } rt300@8: if (mappingID == SUBMIT_CANDIDATE){ rt300@8: // log event rt300@8: submitPressed(); rt300@8: rt300@8: return; rt300@8: } rt300@8: if (mappingID == CRAP_TEST_ID){ rt300@8: // this is rubbish! send a log of target values, and mapping ids rt300@8: vector data; rt300@8: vector tvals = targetSynth.getAllParamValues(); rt300@8: vector pidx = testController->getCurrentChangeableParams(); rt300@8: data.insert(data.end(), tvals.begin(), tvals.end()); rt300@8: data.insert(data.end(), pidx.begin(), pidx.end()); rt300@8: rt300@8: eventLogger.logEvent(CRAP_TEST, data); rt300@8: } rt300@8: if(mappingID == SHOW_HIDE_PANEL){ rt300@8: static bool showing; rt300@8: rt300@8: if(showing){ rt300@8: cout << " showing"<show(); rt300@8: rt300@8: }else{ rt300@8: cout << " hiding"<hide(); rt300@8: } rt300@8: showing = !showing; rt300@8: } rt300@8: if(mappingID == SHOW_HIDE_HINT){ rt300@8: static bool showingHint; rt300@8: if(showingHint){ rt300@27: controlPanel->showHint(false); rt300@8: showingHint = false; rt300@8: }else{ rt300@27: controlPanel->showHint(true); rt300@8: showingHint = true; rt300@8: } rt300@8: } rt300@10: rt300@8: } rt300@10: rt300@8: // called from UI rt300@8: rt300@8: rt300@8: void midiFromLeap(int ctl_num, int ctl_val){ rt300@8: rt300@8: if (!okToGetLeapMidi){ rt300@8: return; rt300@8: } rt300@8: rt300@8: Test *theTest = testController->getCurrentTestPtr(); rt300@8: if (theTest == NULL) return; rt300@8: rt300@8: vector ci = theTest->getChangeableIndices(); rt300@8: vector mids = candidateSynth.getMappingIDForIndices(ci); rt300@8: if (ctl_num >= mids.size() || ctl_num < 0) return; rt300@8: rt300@8: candidateSynth.paramChangeCallback(mids[ctl_num], ctl_val); rt300@8: rt300@8: setUIToParam(ctl_num, ctl_val); rt300@8: rt300@8: vector evtData; rt300@8: evtData.push_back(mids[ctl_num]); // or just index? rt300@8: evtData.push_back(ctl_val); rt300@8: rt300@8: eventLogger.logEvent(CANDIDATE_PARAM_ADJUSTED, evtData); rt300@9: rt300@8: } rt300@9: rt300@8: rt300@8: ofTrueTypeFont verdBig; rt300@8: rt300@9: rt300@9: protected: rt300@9: rt300@9: rt300@9: rt300@9: // protected methods rt300@9: void testsFinished(){ rt300@27: controlPanel->hide(); rt300@9: bottomPanel->hide(); rt300@9: newTestButton->hide(); rt300@9: rt300@9: vector eData; rt300@9: eData.push_back(testController->getScoreRunningTotal()); rt300@9: eventLogger.logEvent(ALL_TESTS_COMPLETED, eData); rt300@9: rt300@9: // TODO set final score screen txt to testController->getScoreRunningTotal() rt300@9: finishPanel->show(); rt300@9: rt300@9: string user = eventLogger.getUsername(); rt300@9: stringstream s; rt300@9: s << "Experiment completed" rt300@9: << endl << endl rt300@9: << "You scored: " << testController->getScoreRunningTotal() << " well done " << user << endl << endl rt300@9: << "to retake test please close " << endl << endl rt300@9: << "the app and restart with username"; rt300@9: finishPanel->setText(s.str()); rt300@9: // get test app to do something... rt300@9: rt300@9: eventLogger.saveSessionToFile(); rt300@9: }; rt300@9: rt300@9: void setupNewTest(){ rt300@9: // get mapping for new test and make sure we have right controls and stuff rt300@9: rt300@9: rt300@9: Test newTest = testController->goToNextTest(); rt300@9: rt300@9: // V0.2 put details about what kind of test it is rt300@9: vector eData; rt300@9: eData.push_back(newTest.isPractice()); rt300@9: eData.push_back(newTest.isWithHint()); rt300@9: eData.push_back(newTest.isMemoryTest()); rt300@9: eventLogger.logEvent(NEW_TEST, eData); rt300@9: rt300@9: rt300@9: vector mappingIDsForChangeableParams = setSynthsUpForNewTest(newTest); rt300@9: rt300@27: vector UIElemHandles = controlPanel->generateControls(testController->getCurrentListOfControls(), testController->getCurrentPanelType()); rt300@9: rt300@9: mapUIToNewTestParams(UIElemHandles, mappingIDsForChangeableParams); rt300@9: rt300@9: countdownPanel->setTestTypeString(newTest.getTestTypeAdvanceWarning()); rt300@9: rt300@9: rt300@9: }; rt300@9: void startNewTest(){ rt300@9: Test t = testController->getCurrentTest(); rt300@9: rt300@9: countdownPanel->hide(); rt300@27: controlPanel->show(); rt300@27: controlPanel->setActive(true); rt300@9: if(t.isWithHint()){ rt300@27: controlPanel->showHint(true); rt300@9: } rt300@9: rt300@9: bottomPanel->show(); rt300@9: targetPlayButton->show(); // incase it was memory test rt300@9: if (t.isMemoryTest()){ rt300@9: targetPlayButton->setLabel("Memorise!"); rt300@9: }else{ rt300@9: targetPlayButton->setLabel("Target"); rt300@9: } rt300@9: rt300@9: timeController.startStopwatch(); rt300@9: eventLogger.logEvent(TEST_TIMER_STARTED); rt300@9: rt300@9: rt300@9: if(t.getListOfControlTypes()[0] == LEAP3D){ rt300@9: okToGetLeapMidi = true; rt300@9: } rt300@9: }; rt300@9: rt300@9: vector setSynthsUpForNewTest(Test newTest){ rt300@9: targetSynth.setAllParams(newTest.getTargetValues()); rt300@9: eventLogger.logEvent(TARGET_PARAM_SET, newTest.getTargetValues()); // unless something goes wrong in setAllParams rt300@9: rt300@9: candidateSynth.setAllParams(newTest.getStartingCandidateValues()); rt300@9: eventLogger.logEvent(CANDIDATE_PARAM_SET,newTest.getStartingCandidateValues()); rt300@9: rt300@9: vector mids = candidateSynth.getMappingIDForIndices(newTest.getChangeableIndices()); rt300@9: rt300@9: eventLogger.logEvent(CANDIDATE_CHANGEABLE_IDX, newTest.getChangeableIndices()); rt300@9: eventLogger.logEvent(CANDIDATE_MAPPING_IDS, mids); rt300@9: rt300@9: return mids; rt300@9: }; rt300@9: rt300@9: rt300@9: // could have been cleverer. takes forever due to searching ??? rt300@9: void mapUIToNewTestParams(vector elems, vector mids){ rt300@9: rt300@9: vector::iterator elit; rt300@9: vector typeListLog; rt300@9: int i = 0; rt300@9: for(elit=elems.begin(); elitgetType() == XYPAD){ rt300@9: if(i+1 >= mids.size()){ rt300@9: cout << "ERROR ERROR: too many controls for mapping IDs" << endl; rt300@9: } rt300@9: rt300@9: ButtronXY* theXY = (ButtronXY*)(*elit); rt300@9: mapXYToParams(theXY, mids[i], mids[i+1]); rt300@9: theXY->setValueAndScale(candidateSynth.getParamValueForID(mids[i]), candidateSynth.getParamValueForID(mids[i+1])); rt300@9: theXY->setHintValue(targetSynth.getParamValueFromName(candidateSynth.getNameForMappingID(mids[i])) rt300@9: ,targetSynth.getParamValueFromName(candidateSynth.getNameForMappingID(mids[i+1]))); rt300@9: i+=2; rt300@9: typeListLog.push_back(int(XYPAD)); rt300@9: }else if ( (*elit)->getType() == SLIDER){ rt300@9: if(i >= mids.size()){ rt300@9: rt300@9: cout << "ERROR ERROR: too many controls for mapping IDs: " << mids.size() << endl; rt300@9: } rt300@9: rt300@9: ButtronSlider* theSlider = (ButtronSlider*)(*elit); rt300@9: mapControlToParam((*elit), mids[i]); rt300@9: theSlider->setValueAndScale(candidateSynth.getParamValueForID(mids[i])); rt300@9: cout << "Hint Value " << targetSynth.getParamValueFromName(candidateSynth.getNameForMappingID(mids[i])) << endl; rt300@9: theSlider->setHintValue(targetSynth.getParamValueFromName(candidateSynth.getNameForMappingID(mids[i]))); rt300@9: i++; rt300@9: typeListLog.push_back(int(SLIDER)); rt300@9: }else if ( (*elit)->getType() == LEAP3D ){ rt300@9: set3Dbox((Leap3DBoxGL*)(*elit)); rt300@9: // UH rt300@9: string nameX = candidateSynth.getNameForMappingID(mids[i]); rt300@9: box3D->setHintValue(0,targetSynth.getParamValueFromName(nameX)); rt300@9: box3D->setValueAndScale(0, candidateSynth.getParamValueForID(mids[i])); rt300@9: i++; rt300@9: rt300@9: string nameY = candidateSynth.getNameForMappingID(mids[i]); rt300@9: box3D->setHintValue(1,targetSynth.getParamValueFromName(nameY)); rt300@9: box3D->setValueAndScale(1, candidateSynth.getParamValueForID(mids[i])); rt300@9: i++; rt300@9: rt300@9: string nameZ = candidateSynth.getNameForMappingID(mids[i]); rt300@9: box3D->setHintValue(2,targetSynth.getParamValueFromName(nameZ)); rt300@9: box3D->setValueAndScale(2, candidateSynth.getParamValueForID(mids[i])); rt300@9: i++; rt300@9: rt300@9: rt300@9: box3D->setLabels(nameX,nameY,nameZ); rt300@9: typeListLog.push_back(int(LEAP3D)); rt300@9: rt300@9: }else{ rt300@28: cout << "ERROR ERROR SEARCHMSGORG: ui type not handled my mapping function !" << endl; rt300@9: } rt300@9: } rt300@9: rt300@9: eventLogger.logEvent(CONTROL_LIST,typeListLog); rt300@9: }; rt300@9: rt300@9: // TODO - no, triggering playback needs to be logged rt300@9: void startAlternatingPlayback(){ rt300@9: rt300@9: cout << "start alt playback" << endl; rt300@9: // use our special timer to fire off play to pd rt300@9: // sets off timed alternating playback rt300@9: rt300@9: playAlternating(); rt300@9: playingAlternating = true; rt300@9: rt300@9: }; rt300@9: void stopAlternatingPlayback(){ rt300@9: cout << "stop alt playback" << endl; rt300@9: // kill the alternation rt300@9: timeController.cancelEvent(currentSoundPlayTimer); rt300@9: playingAlternating = false; rt300@9: }; rt300@9: rt300@9: void playAlternating(){ rt300@9: rt300@9: static bool alt; rt300@9: int nextTime; rt300@9: if (alt){ rt300@9: targetSynth.trigger(); rt300@9: // flash the target thingy rt300@9: targetSymbol->flash(); rt300@9: nextTime = alternationSpeed*1.503; // gap after target rt300@9: }else{ rt300@9: sendSynthValuesAgain(); // and again and again rt300@9: candidateSynth.trigger(); rt300@27: controlPanel->flash(); rt300@9: nextTime = alternationSpeed; rt300@9: } rt300@9: alt = !alt; rt300@9: candidateSynth.setNoteLength(alternationSpeed); rt300@9: targetSynth.setNoteLength(alternationSpeed); // could be user alterable rt300@9: currentSoundPlayTimer = timeController.scheduleEvent(boost::bind(&SearchMessageOrganiser::playAlternating,this), nextTime); rt300@9: rt300@9: rt300@9: rt300@9: }; rt300@9: rt300@9: void delayedShowNewTest(){ rt300@9: newTestButton->show(); rt300@9: // JUST IN CASE IT CRASHES near end... rt300@9: //eventLogger.saveSessionToFile(); rt300@9: }; rt300@9: void submitSingleControl(){ rt300@9: // if last one rt300@9: // submitPressed() rt300@9: rt300@9: // else rt300@9: rt300@9: // grey out that slider, rt300@9: // activate next slider and show it's button (same button but moved!???) rt300@9: rt300@9: }; rt300@9: rt300@9: rt300@9: void submitPressed(){ rt300@9: rt300@9: // depending on mode go to next control rt300@9: // if(testController->getCurrentPanelType() == SEQUENTIAL){ rt300@9: // submitSingleControl(); rt300@9: // return; rt300@9: // } rt300@9: // otherwise do this other - or call rt300@9: rt300@9: okToGetLeapMidi = false; rt300@9: rt300@9: TimerMillisec timeTaken = timeController.stopStopwatch(); rt300@9: vector answer = candidateSynth.getAllParamValues(); rt300@9: rt300@9: eventLogger.logEvent(SUBMIT_PRESSED, answer); //, answer, scoreRunningTotal, time taken (why not?)); rt300@9: rt300@9: TestResult result = testController->submitAnswer(answer, timeTaken); // TODO returns all the results rt300@9: rt300@9: vector logResult; rt300@9: logResult.push_back(result.realDistanceToTarget*1000); // measured in milliCC !??! rt300@9: logResult.push_back(result.timeTaken); // milliseconds rt300@9: logResult.push_back(result.score); rt300@9: logResult.push_back(result.targetBandHit); rt300@9: logResult.push_back(result.timeWindowHit); rt300@9: rt300@9: eventLogger.logEvent(DISTANCE_TIME_SCORE, logResult); rt300@9: rt300@9: rt300@9: // gui stuff - different controller? rt300@27: controlPanel->setActive(false); rt300@27: controlPanel->showHint(true); // add some encouraging feedback to hint rt300@9: bottomPanel->hide(); rt300@9: rt300@9: showScoreForTest(result); rt300@9: rt300@9: stopAlternatingPlayback(); rt300@9: rt300@9: // was it the final sumbit? rt300@9: if(testController->isLastTest()){ rt300@9: // thats it - show a final score screen etc rt300@9: timeController.scheduleEvent(boost::bind(&SearchMessageOrganiser::testsFinished, this), 500); rt300@9: return; rt300@9: }else{ rt300@9: timeController.scheduleEvent(boost::bind(&SearchMessageOrganiser::delayedShowNewTest, this), 300); rt300@9: } rt300@9: rt300@9: rt300@9: }; rt300@9: rt300@9: void showScoreForTest(TestResult result){ rt300@9: scorePanel->setText(result.displayText); rt300@9: scorePanel->show(); rt300@9: rt300@9: ofColor c; rt300@9: if(result.targetBandHit == 1){ rt300@9: // yellow red blue rt300@9: c = ofColor(255,255,0,255); rt300@9: }else if(result.targetBandHit == 2){ rt300@9: c = ofColor(255,0,0,255); rt300@9: }else if(result.targetBandHit == 3){ rt300@9: c = ofColor(45,45,255,255); rt300@9: }else if(result.targetBandHit == 4){ rt300@9: c = ofColor(0,255,0,255); rt300@9: }else{ rt300@9: c = ofColor(150,235,200,255); rt300@9: } rt300@9: scorePanel->setColor(c); rt300@27: controlPanel->setHintColor(c); rt300@9: }; rt300@9: rt300@24: rt300@24: rt300@24: TimeController altPlaybackController; rt300@24: rt300@24: TestController* testController; rt300@24: Buttron* newTestButton; rt300@24: //Buttron* submitButton; rt300@24: rt300@24: rt300@24: Buttron* targetPlayButton; // so we can hide target in memory test. this pointer stuff is getting out of hand rt300@24: CountdownText* countdownPanel; rt300@24: TargetSymbol* targetSymbol; rt300@24: Leap3DBoxGL* box3D; rt300@24: TextPanel* scorePanel; rt300@24: TextPanel* finishPanel; rt300@24: rt300@24: //int scoreRunningTotal; rt300@24: TimerID currentSoundPlayTimer; rt300@24: rt300@24: int alternationSpeed; // ms between cand and target rt300@24: bool playingAlternating; rt300@24: rt300@24: bool okToGetLeapMidi; rt300@9: rt300@8: }; rt300@8: rt300@8: #endif /* defined(__riftathon__SearchMessageOrganiser__) */