view MessageOrganiser.h @ 6:92850a2b099c

set up preset slots from init file. PD synth has metronome, recieves ticks but doesn't do anything with them.
author Robert Tubb <rt300@eecs.qmul.ac.uk>
date Fri, 17 Oct 2014 14:50:50 +0100
parents 213df0baed47
children d59de9fd3496
line wrap: on
line source
//
//  MessageOrganiser.h
//  tweakathlon
//
//  Created by Robert Tubb on 10/12/2013.
//
// This object handles the mapping from GUI to params
//
// and sends their messages to PD and eventLogger
#pragma once
#include "eventLogger.h"
#include <map.h>
#include <vector>
#include <string>
#include "boost/bind.hpp"
#include "boost/function.hpp"

#include <UIElement.h>
#include <Buttron.h>
#include <ButtronSlider.h>
#include <ButtronXY.h>
#include "AppCore.h"
#include "ofxPd.h"
#include "TestController.h"
#include "timeController.h"
#include "PDSynthWrapper.h"
#include "ofxTimer.h"
#include "sliderPanel.h"
//#include "testApp.h"
#include "targetSymbol.h"
#include "3Dbox.h"
#include "TextPanel.h"
#include "CountdownText.h"
#include "buttonPanel.h"
#include "ExplorePresetManager.h"


// should be called TIMED TEST MESSAGE ORGANISER ?

// event logger needs to know
// which controls were showing in what mode
// which controls were mapped to what param
// what was the target sound params
// all the updates of control movements, submit, quit etc

// this is the bit that handles mapping from UI elements to synth i.e testApp DOESNT DO THAT

// has links to panel sliders can show hide them

// controls flow of stuff

//---------------------------------------------------------------------
//---------------------------------------------------------------------
extern TimeController timeController;

extern EventLogger eventLogger;

extern ExplorePresetManager expPresetManager;

typedef boost::function<void(void)> AppModeChangeFunction;

class MessageOrganiser {
private:
    AppCore* core;
    //testApp* theOFApp;
    PDSynthWrapper targetSynth;
    PDSynthWrapper candidateSynth;

    map<int,UIElement*> currentMapping; // could get more sophisticated if not 1-1 ?
    
    SliderPanel* panel;
    TimeController altPlaybackController;
    
    TestController* testController;
    Buttron* newTestButton;
    //Buttron* submitButton;
    
    ButtonPanel* bottomPanel; // shows during test : play buttons and submit
    Buttron* targetPlayButton; // so we can hide target in memory test. this pointer stuff is getting out of hand
    CountdownText* countdownPanel;
    TargetSymbol* targetSymbol;
    Leap3DBoxGL* box3D;
    TextPanel* scorePanel;
    TextPanel* finishPanel;
    AppModeChangeFunction testAppModeChange;
    
    //int scoreRunningTotal;
    TimerID currentSoundPlayTimer;
    
    int alternationSpeed; // ms between cand and target
    bool playingAlternating;
    
    bool okToGetLeapMidi;

    void testsFinished(){
        panel->hide();
        bottomPanel->hide();
        newTestButton->hide();
        
        vector<int> eData;
        eData.push_back(testController->getScoreRunningTotal());
        eventLogger.logEvent(ALL_TESTS_COMPLETED, eData);
        
        // TODO set final score screen txt to testController->getScoreRunningTotal()
        finishPanel->show();
        
        string user = eventLogger.getUsername();
        stringstream s;
        s << "Experiment completed"
            << endl << endl
            << "You scored: " << testController->getScoreRunningTotal() << " well done " << user << endl << endl
            << "to retake test please close " << endl << endl
             <<   "the app and restart with username<num test>";
        finishPanel->setText(s.str());
        // get test app to do something...
        
        eventLogger.saveSessionToFile();
    };
    
    void setupNewTest(){
        // get mapping for new test and make sure we have right controls and stuff

        
        Test newTest = testController->goToNextTest();
        
        // V0.2 put details about what kind of test it is
        vector<int> eData;
        eData.push_back(newTest.isPractice());
        eData.push_back(newTest.isWithHint());
        eData.push_back(newTest.isMemoryTest());
        eventLogger.logEvent(NEW_TEST, eData);
        
        
        vector<int> mappingIDsForChangeableParams = setSynthsUpForNewTest(newTest);
        
        vector<UIElement*> UIElemHandles = panel->generateControls(testController->getCurrentListOfControls(), testController->getCurrentPanelType());
        
        mapUIToNewTestParams(UIElemHandles, mappingIDsForChangeableParams);
        
        countdownPanel->setTestTypeString(newTest.getTestTypeAdvanceWarning());
        

    };
    void startNewTest(){
        Test t = testController->getCurrentTest();
        
        countdownPanel->hide();
        panel->show();
        panel->setActive(true);
        if(t.isWithHint()){
            panel->showHint(true);
        }
        
        bottomPanel->show();
        targetPlayButton->show(); // incase it was memory test
        if (t.isMemoryTest()){
            targetPlayButton->setLabel("Memorise!");
        }else{
            targetPlayButton->setLabel("Target");
        }
        //startAlternatingPlayback();
        //timeController.scheduleEvent(boost::bind(&MessageOrganiser::sendSynthValuesAgain, this), 200);
        timeController.startStopwatch();
        eventLogger.logEvent(TEST_TIMER_STARTED);
        
        
        if(t.getListOfControlTypes()[0] == LEAP3D){
            okToGetLeapMidi = true;
        }
    };
    
    vector<int> setSynthsUpForNewTest(Test newTest){
        targetSynth.setAllParams(newTest.getTargetValues());
        eventLogger.logEvent(TARGET_PARAM_SET, newTest.getTargetValues()); // unless something goes wrong in setAllParams
        
        candidateSynth.setAllParams(newTest.getStartingCandidateValues());
        eventLogger.logEvent(CANDIDATE_PARAM_SET,newTest.getStartingCandidateValues());
        
        // eventLogger.logEvent(NEW_TARGET_PARAMS, vector<int> );
        // eventLogger.logEvent(NEW_CANDIDATE_PARAMS, vector<int> );
        
        vector<int> mids = candidateSynth.getMappingIDForIndices(newTest.getChangeableIndices());
        
        eventLogger.logEvent(CANDIDATE_CHANGEABLE_IDX, newTest.getChangeableIndices());
        eventLogger.logEvent(CANDIDATE_MAPPING_IDS, mids);
        
        return mids;
    };
    void sendSynthValuesAgain(){
        candidateSynth.sendAllParams();
        targetSynth.sendAllParams();
    };
    
    // could have been cleverer. takes forever due to searching ???
    void mapUIToNewTestParams(vector<UIElement*> elems, vector<int> mids){
        
        vector<UIElement*>::iterator elit;
        vector<int> typeListLog;
        int i = 0;
        for(elit=elems.begin(); elit<elems.end();elit++){
            if ( (*elit)->getType() == XYPAD){
                if(i+1 >= mids.size()){
                    cout << "ERROR ERROR: too many controls for mapping IDs" << endl;
                }
                
                ButtronXY* theXY = (ButtronXY*)(*elit);
                mapXYToParams(theXY, mids[i], mids[i+1]);
                theXY->setValueAndScale(candidateSynth.getParamValueForID(mids[i]), candidateSynth.getParamValueForID(mids[i+1]));
                theXY->setHintValue(targetSynth.getParamValueFromName(candidateSynth.getNameForMappingID(mids[i]))
                                    ,targetSynth.getParamValueFromName(candidateSynth.getNameForMappingID(mids[i+1])));
                i+=2;
                typeListLog.push_back(int(XYPAD));
            }else if ( (*elit)->getType() == SLIDER){
                if(i >= mids.size()){
                    
                    cout << "ERROR ERROR: too many controls for mapping IDs: " << mids.size() << endl;
                }
                
                ButtronSlider* theSlider = (ButtronSlider*)(*elit);
                mapControlToParam((*elit), mids[i]);
                theSlider->setValueAndScale(candidateSynth.getParamValueForID(mids[i]));
                cout << "Hint Value " << targetSynth.getParamValueFromName(candidateSynth.getNameForMappingID(mids[i])) << endl;
                theSlider->setHintValue(targetSynth.getParamValueFromName(candidateSynth.getNameForMappingID(mids[i])));
                i++;
                typeListLog.push_back(int(SLIDER));
            }else if ( (*elit)->getType() == LEAP3D ){
                set3Dbox((Leap3DBoxGL*)(*elit));
                // UH
                string nameX = candidateSynth.getNameForMappingID(mids[i]);
                box3D->setHintValue(0,targetSynth.getParamValueFromName(nameX));
                box3D->setValueAndScale(0, candidateSynth.getParamValueForID(mids[i]));
                i++;
                
                string nameY = candidateSynth.getNameForMappingID(mids[i]);
                box3D->setHintValue(1,targetSynth.getParamValueFromName(nameY));
                box3D->setValueAndScale(1, candidateSynth.getParamValueForID(mids[i]));
                i++;
                
                string nameZ = candidateSynth.getNameForMappingID(mids[i]);
                box3D->setHintValue(2,targetSynth.getParamValueFromName(nameZ));
                box3D->setValueAndScale(2, candidateSynth.getParamValueForID(mids[i]));
                i++;
                

                box3D->setLabels(nameX,nameY,nameZ);
                typeListLog.push_back(int(LEAP3D));
                
            }else{
                cout << "ERROR ERROR: ui type not handled my mapping function !" << endl;
            }
        }
        
        eventLogger.logEvent(CONTROL_LIST,typeListLog);
    };
    
    // TODO - no, triggering playback needs to be logged
    void startAlternatingPlayback(){
        
        cout << "start alt playback" << endl;
        // use our special timer to fire off play to pd
        // sets off timed alternating playback
        
        playAlternating();
        playingAlternating = true;

    };
    void stopAlternatingPlayback(){
        cout << "stop alt playback" << endl;
        // kill the alternation
        timeController.cancelEvent(currentSoundPlayTimer);
        playingAlternating = false;
    };
    
    void playAlternating(){
        
        static bool alt;
        int nextTime;
        if (alt){
            targetSynth.trigger();
            // flash the target thingy
            targetSymbol->flash();
            nextTime = alternationSpeed*1.503; // gap after target
        }else{
            sendSynthValuesAgain(); // and again and again
            candidateSynth.trigger();
            panel->flash();
            nextTime = alternationSpeed;
        }
        alt = !alt;
        candidateSynth.setNoteLength(alternationSpeed);
        targetSynth.setNoteLength(alternationSpeed); // could be user alterable
        currentSoundPlayTimer = timeController.scheduleEvent(boost::bind(&MessageOrganiser::playAlternating,this), nextTime);
        
        
        
    };
    
    void delayedShowNewTest(){
        newTestButton->show();
        // JUST IN CASE IT CRASHES near end...
        //eventLogger.saveSessionToFile();
    };
    void submitSingleControl(){
        // if last one
        // submitPressed()
        
        // else
        
        // grey out that slider,
        // activate next slider and show it's button (same button but moved!???)
        
    };
    
    
    void submitPressed(){
        
        // depending on mode go to next control
        //        if(testController->getCurrentPanelType() == SEQUENTIAL){
        //            submitSingleControl();
        //            return;
        //        }
        // otherwise do this other  - or call
        
        okToGetLeapMidi = false;
        
        TimerMillisec timeTaken = timeController.stopStopwatch();
        vector<int> answer = candidateSynth.getAllParamValues();
        
        eventLogger.logEvent(SUBMIT_PRESSED, answer); //, answer, scoreRunningTotal, time taken (why not?));
        
        TestResult result = testController->submitAnswer(answer, timeTaken); // TODO returns all the results
        
        vector<int> logResult;
        logResult.push_back(result.realDistanceToTarget*1000); // measured in milliCC !??!
        logResult.push_back(result.timeTaken); // milliseconds
        logResult.push_back(result.score);
        logResult.push_back(result.targetBandHit);
        logResult.push_back(result.timeWindowHit);
        
        eventLogger.logEvent(DISTANCE_TIME_SCORE, logResult);
        
        
        // gui stuff - different controller?
        panel->setActive(false);
        panel->showHint(true); // add some encouraging feedback to hint
        bottomPanel->hide();
        
        showScoreForTest(result);
        
        stopAlternatingPlayback();
        
        // was it the final sumbit?
        if(testController->isLastTest()){
            // thats it - show a final score screen etc
            timeController.scheduleEvent(boost::bind(&MessageOrganiser::testsFinished, this), 500);
            return;
        }else{
            timeController.scheduleEvent(boost::bind(&MessageOrganiser::delayedShowNewTest, this), 300);
        }
        

    };
    
    void showScoreForTest(TestResult result){
        scorePanel->setText(result.displayText);
        scorePanel->show();
        
        ofColor c;
        if(result.targetBandHit == 1){
            // yellow red blue
            c = ofColor(255,255,0,255);
        }else if(result.targetBandHit == 2){
            c = ofColor(255,0,0,255);
        }else if(result.targetBandHit == 3){
            c = ofColor(45,45,255,255);
        }else if(result.targetBandHit == 4){
            c = ofColor(0,255,0,255);
        }else{
            c = ofColor(150,235,200,255);
        }
        scorePanel->setColor(c);
        panel->setHintColor(c);
    };
    
    void setAllSlidersToValues(vector<int> values){
        for(int i = 0; i < values.size(); i++){
            setUIToParam(i, values[i]);
        }
    }
    // we want to set UI object
    void setUIToParam(int index, int value){ // e.g. from MIDI incoming, will handle both box and sliders...
        // theXY->setValueAndScale(candidateSynth.getParamValueForID(mids[i]), candidateSynth.getParamValueForID(mids[i+1]));
        UIElement* elem;
        // get the element
        if(panel->subElements.size() <= index){
            cout << "ERROR: index out of range for num sliders" << endl;
            return;
        }
        elem = panel->subElements[index];
        if ( elem->getType() == SLIDER){
            ButtronSlider* theSlider = (ButtronSlider*)elem;
            theSlider->setValueAndScale(value);
            
        }else{
            cout << "ERROR ERROR: ui type not handled by setUIToParam!" << endl;
        }
        
    };
    
    
    void mapControlToParam(UIElement* control, int mappingID){
        
        UICallbackFunction callbackF;
        callbackF = boost::bind(&MessageOrganiser::paramChangeCallback, this, _1,_2);
        control->addHandler(callbackF, mappingID);
        // put in our map so we can send param values to gui
        currentMapping.insert(std::pair<int,UIElement*>(mappingID,control));
        cout << " Mapped control to ID: " << mappingID << "Name: " << candidateSynth.getNameForMappingID(mappingID) << endl;
        control->setLabel(candidateSynth.getNameForMappingID(mappingID));
    };
    
    void mapXYToParams(ButtronXY* control, int mappingIDX, int mappingIDY){
        UICallbackFunction callback;
        
        callback = boost::bind(&MessageOrganiser::paramChangeCallback, this, _1,_2);
        
        control->addHandler(callback, mappingIDX, mappingIDY);
        
        // put in our map so we can send param values to gui
        //currentMapping.insert(std::pair<int,UIElement*>(mappingID,control));
        
        
        cout << " Mapped control to XID: " << mappingIDX << "Name: " << candidateSynth.getNameForMappingID(mappingIDX) << endl;
        cout << " Mapped control to YID: " << mappingIDY << "Name: " << candidateSynth.getNameForMappingID(mappingIDY) << endl;
        control->setLabel(candidateSynth.getNameForMappingID(mappingIDX), candidateSynth.getNameForMappingID(mappingIDY));
        
    };
    
    void mapLeapToParams(ButtronXY* control, int mappingIDX, int mappingIDY, int mappingIDZ){
//        UICallbackFunction callbackX;
//        UICallbackFunction callbackY;
//        UICallbackFunction callbackZ;
//        
//        callbackX = boost::bind(&MessageOrganiser::paramChangeCallback, this, _1,_2);
//        callbackY = boost::bind(&MessageOrganiser::paramChangeCallback, this, _1,_2);
//        callbackZ = boost::bind(&MessageOrganiser::paramChangeCallback, this, _1,_2);
//        
//        control->addHandler(callbackX, mappingIDX);
//        control->addHandler(callbackY, mappingIDY);
//        
//        // put in our map so we can send param values to gui
//        //currentMapping.insert(std::pair<int,UIElement*>(mappingID,control));
//        
//        
//        cout << " Mapped control to XID: " << mappingIDX << "Name: " << candidateSynth.getNameForMappingID(mappingIDX) << endl;
//        cout << " Mapped control to YID: " << mappingIDY << "Name: " << candidateSynth.getNameForMappingID(mappingIDY) << endl;
//        control->setLabel(candidateSynth.getNameForMappingID(mappingIDX), candidateSynth.getNameForMappingID(mappingIDY));
        
    };
    
    void mapControlToParam(UIElement* control, string paramName){
        // get mapping ID from synth
        int mappingID = candidateSynth.getMappingIDForName(paramName);
        mapControlToParam(control, mappingID);
        control->setLabel(paramName);
    };
public:
    void init(AppCore* aCore, TestController* tc){
        // set PD core...

        core = aCore;
        targetSynth.init(aCore,"targetSynth");
        candidateSynth.init(aCore,"candidateSynth");
        
        testController = tc;
        currentSoundPlayTimer = -1;
        okToGetLeapMidi = false;
        
        alternationSpeed = 200;
        
        candidateSynth.setNoteLength(alternationSpeed);
        targetSynth.setNoteLength(alternationSpeed); 
        
        playingAlternating = false;
    };
    void setNewTestButton(Buttron * ntb){
        newTestButton = ntb;
    };
    void set3Dbox(Leap3DBoxGL* box){
        box3D = box;
    };
    void setBottomPanel(ButtonPanel * ntb){
        bottomPanel = ntb;
    };
    void setControlPanel(SliderPanel* p){
        panel = p;
        
    };
    void setCountdownPanel(CountdownText* cd){
        countdownPanel = cd;
    };
    void setTargetSymbol(TargetSymbol* ts){
        targetSymbol = ts;
    };
    void setScorePanel(TextPanel* tp){
        scorePanel = tp;
    };
    void setFinishPanel(TextPanel* fp){
        finishPanel = fp;
    }
    void setTargetButton(Buttron* tb){
        targetPlayButton = tb;
    }
    int getScore(){
        return testController->getScoreRunningTotal();
    };
    
    pair<int,int> getTime(){
        TimerMillisec tms = timeController.getStopwatchElapsedTime();
        int s = int(tms/1000);
        int hs = int((tms%1000)/10);
        pair<int,int> p(s,hs);
        return p;
    };
    void countdownToNewTest(){
        
        panel->hide();
        panel->setActive(false);
        scorePanel->hide();
        bottomPanel->hide();
        newTestButton->hide();
        
        // set up stuff
        setupNewTest();
        eventLogger.logEvent(COUNTDOWN_INITIATED);
        
        countdownPanel->showAndStart(3);

        timeController.scheduleEvent(boost::bind(&MessageOrganiser::startNewTest, this), 3000);

    };
    void sendToGUI(vector<int> paramsToMap){
        // look up these ids in mapping table
    };
    void saveGoodTest(Test t){
        
    };
    void playTargetButtonPressed(){

        static int numPlays = 3;
        
        Test* t = testController->getCurrentTestPtr();
        if (!t->checkTargetPlaysRemaining()){
                cout << t->getTargetPlaysLeft() << endl;
            
                sendSynthValuesAgain();
                targetSynth.trigger();
                eventLogger.logEvent(TARGET_PLAYED);
                targetPlayButton->hide();
                return;

        }
        cout << t->getTargetPlaysLeft() << endl;
        
        sendSynthValuesAgain();
        targetSynth.trigger();
        eventLogger.logEvent(TARGET_PLAYED);
        
        return;
    }
    void playCandidateButtonPressed(){
        //
    }
    void buttonPressCallback(int mappingID, int value){
        if(mappingID == VOLUME_CHANGE_ID){
            targetSynth.sendVolume(value);
            candidateSynth.sendVolume(value);
            
        }
        if(mappingID == SPEED_CHANGE_ID){
            alternationSpeed = 2*(140 - value);
            vector<int> eData;
            eData.push_back(alternationSpeed);
            eventLogger.logEvent(SPEED_CHANGED, eData);
        }
        if(mappingID == NEW_TEST_ID){
            countdownToNewTest();
            return;
        }
        if (mappingID == START_ALTERNATE_ID){
            if(!playingAlternating){
                startAlternatingPlayback();
                
            }else{
                stopAlternatingPlayback();
            }
            return;
        }
        if(mappingID == GOOD_TEST_ID){
            Test t = testController->getCurrentTest();
            saveGoodTest(t);
        }
        if (mappingID == RANDOMISE_TARGET_ID){ // bleyeueurrrr
            targetSynth.randomiseParams();
            return;
        }
        if (mappingID == TRIGGER_TARGET_ID){
            playTargetButtonPressed();
            
        }
        if (mappingID == TRIGGER_CANDIDATE_ID){
            // log event
            sendSynthValuesAgain();
            candidateSynth.trigger();
            eventLogger.logEvent(CANDIDATE_PLAYED);
            // flash panel?
            panel->flash();
            return;
        }
        if (mappingID == SUBMIT_CANDIDATE){
            // log event
            submitPressed();
            
            return;
        }
        if (mappingID == CRAP_TEST_ID){
            // this is rubbish! send a log of target values, and mapping ids
            vector<int> data;
            vector<int> tvals = targetSynth.getAllParamValues();
            vector<int> pidx = testController->getCurrentChangeableParams();
            data.insert(data.end(), tvals.begin(), tvals.end());
            data.insert(data.end(), pidx.begin(), pidx.end());

            eventLogger.logEvent(CRAP_TEST, data);
        }
        if(mappingID == SHOW_HIDE_PANEL){
            static bool showing;
            
            if(showing){
                cout << " showing"<<endl;
                
                panel->show();
                
            }else{
                cout << " hiding"<<endl;
                panel->hide();
            }
            showing = !showing;
        }
        if(mappingID == SHOW_HIDE_HINT){
            static bool showingHint;
            if(showingHint){
                panel->showHint(false);
                showingHint = false;
            }else{
                panel->showHint(true);
                showingHint = true;
            }
        }
        if(mappingID == SAVE_PRESET_HIT){
            expPresetManager.savePreset("blah", candidateSynth.getAllParamValues());
            
        }
        if(mappingID == RECALL_PRESET_HIT){
            
            loadPreset("blah");
            //candidateSynth.startMetronome();
            
        }
    }
    void loadPreset(string pname){
        
        vector<int> values = expPresetManager.recallPreset(pname);
        if (values.size()){
        candidateSynth.setAllParams(values);
        setAllSlidersToValues(values);
        }else{
            cout << "ERROR, no preset found" << endl;
        }
    }
    // called from UI
    void paramChangeCallback(int mappingID, int value){
        candidateSynth.paramChangeCallback(mappingID, value);
        vector<int> evtData;
        evtData.push_back(mappingID); // or just index?
        evtData.push_back(value);
        
        eventLogger.logEvent(CANDIDATE_PARAM_ADJUSTED, evtData);
    };

    // could template for ui element type??
    void mapButtonToAction(UIElement* control, int mappingID){
        UICallbackFunction callbackF;
        callbackF = boost::bind(&MessageOrganiser::buttonPressCallback, this, _1,_2);
        control->addHandler(callbackF, mappingID);
        currentMapping.insert(std::pair<int,UIElement*>(mappingID,control));
    }

    
    void midiFromLeap(int ctl_num, int ctl_val){
        
  
        if (!okToGetLeapMidi){
            return;
        }


        Test *theTest = testController->getCurrentTestPtr();
        if (theTest == NULL) return;
        
    

        
        
        vector<int> ci = theTest->getChangeableIndices();
        vector<int> mids = candidateSynth.getMappingIDForIndices(ci);
        if (ctl_num >= mids.size() || ctl_num < 0) return;
        
        candidateSynth.paramChangeCallback(mids[ctl_num], ctl_val);
        
        setUIToParam(ctl_num, ctl_val);
        
        vector<int> evtData;
        evtData.push_back(mids[ctl_num]); // or just index?
        evtData.push_back(ctl_val);
        
        eventLogger.logEvent(CANDIDATE_PARAM_ADJUSTED, evtData);
        // also call UI object
        // get mapping ID for
        // setUIToParam(ctl_num, ctl_val);
    }
//    void setSlidersFromLeap(int i, int val){
//        ButtronSlider* theSlider = (ButtronSlider*)panel->getSlider(i);
//        theSlider->setValueAndScale(val);
//    }
    

    
    void setSlidersToTarget(){
        // this will actually show sliders with target vals - for "memorisation" purposes mwa heh heh
        // get target values
        // set ui
        vector<int> vals = targetSynth.getAllParamValues();
        for(int i=1; i < vals.size(); i++){
            setUIToParam(i, vals[i]);
        }
    }

};