/* * Author: Harry van Haaren 2013 * harryhaaren@gmail.com * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "genericmidi.hxx" #include #include #include #include #include "../jack.hxx" #include "../logic.hxx" #include "../gridlogic.hxx" #include "../eventhandler.hxx" extern Jack* jack; int Binding::privateID = 0; GenericMIDI::GenericMIDI(int waste, std::string n) : Controller(), MidiIO() { LUPPP_NOTE("Adding GenericMIDI %s", n.c_str() ); name = n; registerMidiPorts( name ); stat = CONTROLLER_OK; setFootswitchToNextScene( 0 ); } void GenericMIDI::setFootswitchToNextScene(int v) { LUPPP_NOTE("Set Footswitch to %i", v ); if ( v == 0 ) { footswitchNextScene = false; footswitchPrevScene = false; } else if ( v > 0 ) { footswitchNextScene = true; footswitchPrevScene = false; } else { footswitchNextScene = false; footswitchPrevScene = true; } } void GenericMIDI::setName(std::string n) { name = n; } GenericMIDI::GenericMIDI(std::string file) : Controller(), MidiIO() { // default name for each controller name = "generic"; // load the JSON config file int result = loadController( file ); if ( result == LUPPP_RETURN_OK ) { LUPPP_NOTE("GenericMIDI registering ports: %s", name.c_str() ); registerMidiPorts( name ); stat = CONTROLLER_OK; } else { LUPPP_ERROR("Error in loading controller map!" ); stat = CONTROLLER_ERROR; } } const std::vector& GenericMIDI::getMidiToAction() { return midiToAction; } Controller::STATUS GenericMIDI::status() { return stat; } int GenericMIDI::registerComponents() { // makes JACK add this controller to the midiObservers list: // note the static_cast<>() is *needed* here for multiple-inheritance MidiIO* m = static_cast(this); jack->registerMidiIO( m ); return LUPPP_RETURN_OK; } std::string GenericMIDI::getName() { return name; } std::string GenericMIDI::getAuthor() { return author; } std::string GenericMIDI::getEmail() { return email; } void GenericMIDI::volume(int t, float f) { } void GenericMIDI::recordArm(int t, bool enabled) { for(unsigned int i = 0; i < actionToMidi.size(); i++) { Binding* b = actionToMidi.at(i); if ( b->action == TRACK_RECORD_ARM && b->track == t ) { unsigned char data[3]; data[0] = b->status; data[1] = b->data; data[2] = enabled ? 127 : 0; writeMidi( data ); return; } } } void GenericMIDI::metronomeEnable(bool enabled) { for(unsigned int i = 0; i < actionToMidi.size(); i++) { Binding* b = actionToMidi.at(i); if ( b->action == METRONOME_ACTIVE ) { unsigned char data[3]; data[0] = b->status; data[1] = b->data; data[2] = enabled ? 127 : 0; writeMidi( data ); return; } } } void GenericMIDI::trackSend(int t, int send, float v) { for(unsigned int i = 0; i < actionToMidi.size(); i++) { Binding* b = actionToMidi.at(i); if ( b->action == TRACK_SEND && b->track == t && b->send == send ) { unsigned char data[3]; data[0] = b->status; data[1] = b->data; data[2] = v * 127; writeMidi( data ); return; } } } void GenericMIDI::trackSendActive(int t, int send, bool a) { for(unsigned int i = 0; i < actionToMidi.size(); i++) { Binding* b = actionToMidi.at(i); if ( b->action == TRACK_SEND_ACTIVE && b->track == t && b->send == send ) { unsigned char data[3]; data[0] = b->status; data[1] = b->data; data[2] = a ? 127 : 0; writeMidi( data ); return; } } } void GenericMIDI::trackJackSend(int t, float v) { } void GenericMIDI::trackJackSendActivate(int t, bool a) { } void GenericMIDI::midi(unsigned char* midi) { int status = midi[0]; int data = midi[1]; float value = midi[2] / 127.f; // create new MIDI binding? if ( jack->bindingEventRecordEnable ) { //LUPPP_NOTE("making binding from: %i %i %f", status, data, value ); setupBinding( jack->bindingEventType, status, data, jack->bindingTrack, jack->bindingScene, jack->bindingSend, jack->bindingActive ); // reset jack state, so bindings defaults are setup after each creation jack->resetMidiBindingState(); // binding is now created, so disable GUI binding enable button EventControllerBindingEnable e( getID(), false ); writeToGuiRingbuffer( &e ); // update GUI of new binding EventControllerBindingMade e2( getID(), (void*)midiToAction.back() ); writeToGuiRingbuffer( &e2 ); } if ( status == 0x90 && data == 0x64 ) { // nudge + // footpedal to scene++ setFootswitchToNextScene( 1 ); } else if ( status == 0x90 && data == 0x65 ) { // nudge // footpedal to scene-- setFootswitchToNextScene( -1 ); } else if ( status == 0x90 && data == 0x60 ) { // > on bank select: clear scene // footpedal to special clip setFootswitchToNextScene( 0 ); } // iterate over bindings, execute binding action if matches for(unsigned int i = 0; i < midiToAction.size(); i++) { Binding* b = midiToAction.at(i); if ( b->status == status && b->data == data ) { //LUPPP_NOTE("Executing action %s, send %i value %f, b->active %i", Event::getPrettyName(b->action), b->send, value, int(b->active) ); switch( b->action ) { case Event::TRACK_VOLUME: jack->getLogic()->trackVolume( b->track, value ); break; case Event::TRACK_SEND: jack->getLogic()->trackSend( b->track, b->send, value ); break; case Event::TRACK_SEND_ACTIVE: jack->getLogic()->trackSendActive( b->track, b->send, b->active ); break; case Event::TRACK_RECORD_ARM: jack->getLogic()->trackRecordArm( b->track, b->active ); break; case Event::TRACK_JACKSEND: jack->getLogic()->trackJackSend(b->track,value); break; case Event::TRACK_JACKSEND_ACTIVATE: jack->getLogic()->trackJackSendActivate(b->track,b->active); break; case Event::GRID_EVENT: if ( b->active == 1 ) { /* press */ jack->getGridLogic()->pressed( b->track, b->scene ); } else if ( b->active == -1 ) { /* clear */ jack->getGridLogic()->clear( b->track, b->scene ); } else { /* release */ jack->getGridLogic()->released( b->track, b->scene ); } break; case Event::GRID_SELECT_CLIP_EVENT: // hack to do scene ++ / -- with footswitch if ( footswitchNextScene && value > 0.5 ) { // avoid note offs cout << "footswitch next scene *now*" << endl; jack->getGridLogic()->launchScene( jack->getGridLogic()->getCurrentScene() + 1 ); } else if ( footswitchPrevScene && value > 0.5 ) { cout << "footswitch prev scene *now*" << endl; jack->getGridLogic()->launchScene( jack->getGridLogic()->getCurrentScene() - 1 ); } else { cout << "footswitch special clip action now" << endl; jack->getGridLogic()->selectedTrackSceneEvent( value ); } break; case Event::GRID_SELECT_CLIP_ENABLE: jack->getGridLogic()->setSelectTrackScene( b->active ); break; case Event::GRID_LAUNCH_SCENE: jack->getGridLogic()->launchScene( b->scene ); break; case Event::MASTER_RETURN: jack->getLogic()->masterReturn( RETURN_MAIN, value ); break; case Event::MASTER_INPUT_VOL: jack->getLogic()->masterInputVol( value ); break; case Event::MASTER_INPUT_TO: //LUPPP_NOTE("GenMidi event INPUT_TO %i", b->send ); jack->getLogic()->masterInputTo( b->send, value ); break; case Event::MASTER_INPUT_TO_ACTIVE: //LUPPP_NOTE("GenMidi event INPUT_TO_ACTIVE %i", b->send ); jack->getLogic()->masterInputToActive( b->send, b->active ); break; case Event::TIME_TEMPO_TAP: jack->getLogic()->tapTempo(); break; case Event::TIME_BPM: jack->getLogic()->setBpmZeroOne( value ); break; case Event::METRONOME_ACTIVE: jack->getLogic()->metronomeEnable( b->active ); break; case Event::MASTER_VOL: jack->getLogic()->trackVolume( -1 , value ); break; case Event::AUTO_STOP_REC_CLIP_LENGTH: jack->getLogic()->setClipLength(value); break; case Event::AUTO_STOP_REC_CLIP_LENGTH_UP: jack->getLogic()->setClipLengthUp(); break; case Event::AUTO_STOP_REC_CLIP_LENGTH_DOWN: jack->getLogic()->setClipLengthDown(); break; } } } } void GenericMIDI::setSceneState(int t, int scene, GridLogic::State s) { for(unsigned int i = 0; i < actionToMidi.size(); i++) { Binding* b = actionToMidi.at(i); if ( b->action == GRID_STATE && b->track == t && b->scene == scene ) { for( map::iterator it = b->clipStateMap.begin(); it != b->clipStateMap.end(); ++it) { // check if its the right clip state if ( it->first == int(s) ) { unsigned char data[3]; data[0] = b->status; data[1] = b->data; data[2] = it->second; //LUPPP_NOTE("GenericMIDI::sceneState() writing event %i, %i, %i", data[0],data[1],data[2] ); writeMidi( data ); } } } } } void GenericMIDI::reset() { //TODO needed? /* unsigned char data[3]; // setup "volume" style rotary display for(int i = 0; i < NTRACKS; i++) { data[0] = 176 + i; data[1] = 0x19; data[2] = 2; jack->midiObserverWriteMIDI( _port, &data[0] ); } */ } void GenericMIDI::launchScene( int scene ) { for(unsigned int i = 0; i < actionToMidi.size(); i++) { Binding* b = actionToMidi.at(i); if ( b->action == GRID_LAUNCH_SCENE ) { unsigned char data[3]; data[0] = b->status; data[1] = b->data; data[2] = (i == scene) * 127; //LUPPP_NOTE("this = %i GenericMIDI::launchScene()", this ); writeMidi( data ); } } } int GenericMIDI::loadController( std::string file ) { /// open and read whole file #ifdef DEBUG_CONTROLLER LUPPP_NOTE("%s%s","Loading controller : ", file.c_str() ); #endif std::ifstream sampleFile( file.c_str(), std::ios_base::in|std::ios_base::ate); long file_length = sampleFile.tellg(); if ( file_length > 0 ) { sampleFile.seekg(0, std::ios_base::beg); sampleFile.clear(); char *sampleString = new char[file_length]; sampleFile.read(sampleString, file_length); cJSON* controllerJson = cJSON_Parse( sampleString ); if (!controllerJson) { std::string error = cJSON_GetErrorPtr(); std::istringstream ss( error ); std::string line; std::getline( ss, line ); LUPPP_ERROR("%s %s","Error in JSON *before*: ", line.c_str() ); for(int i = 0; i < 5; i++) { std::getline( ss, line ); LUPPP_ERROR("%s %s","Error in JSON : ", line.c_str() ); } return LUPPP_RETURN_ERROR; } cJSON* nameJson = cJSON_GetObjectItem( controllerJson, "name" ); if ( nameJson ) { name = nameJson->valuestring; LUPPP_NOTE("Device %s", name.c_str() ); } else { LUPPP_NOTE("Has no name field"); } cJSON* authorJson = cJSON_GetObjectItem( controllerJson, "author" ); if ( authorJson ) { author = authorJson->valuestring; LUPPP_NOTE("Author %s", author.c_str() ); } else { LUPPP_NOTE("Has no author field"); } cJSON* linkJson = cJSON_GetObjectItem( controllerJson, "link" ); if ( linkJson ) { email = linkJson->valuestring; LUPPP_NOTE("Link %s", email.c_str() ); } else { LUPPP_NOTE("Has no link field"); } int nInputBindings = 0; cJSON* inputBindings = cJSON_GetObjectItem( controllerJson, "inputBindings"); if ( inputBindings ) { int nBindings = cJSON_GetArraySize( inputBindings ); for(int i = 0; i < nBindings; i++ ) { cJSON* bindingJson = cJSON_GetArrayItem( inputBindings, i ); Binding* tmp = setupBinding( bindingJson ); if ( tmp ) midiToAction.push_back( tmp ); nInputBindings++; } } else { LUPPP_WARN("No input bindings array in .ctlr map." ); nInputBindings++; // hack to avoid 2 prints } if ( nInputBindings == 0 ) { LUPPP_NOTE("Zero input bindings present in .ctlr map."); } int nOutputBindings = 0; cJSON* outputBindings = cJSON_GetObjectItem( controllerJson, "outputBindings"); if ( outputBindings ) { int nBindings = cJSON_GetArraySize( outputBindings ); for(int i = 0; i < nBindings; i++ ) { cJSON* bindingJson = cJSON_GetArrayItem( outputBindings, i ); Binding* tmp = setupBinding( bindingJson ); if ( tmp ) actionToMidi.push_back( tmp ); nOutputBindings++; //LUPPP_NOTE("Binding from %s to %i %i", actionJ->valuestring, statusJson->valueint, dataJson->valueint ); } } else { LUPPP_NOTE("No output bindings array in .ctlr map." ); nOutputBindings++; // hack to avoid 2 prints } if ( nOutputBindings == 0 ) { LUPPP_NOTE("Zero output bindings present in .ctlr map." ); } cJSON_Delete( controllerJson ); delete[] sampleString; } else { LUPPP_WARN("%s %s","No controller file found at ", file.c_str() ); return LUPPP_RETURN_WARNING; } LUPPP_NOTE("Controller loading complete." ); return LUPPP_RETURN_OK; } void GenericMIDI::removeBinding( int bindingID ) { for(unsigned int i = 0; i < midiToAction.size(); i++) { if ( midiToAction.at(i)->ID == bindingID ) { //LUPPP_NOTE("MIDI binding REMOVED with bindingID %i\n", bindingID ); Binding* tmp = midiToAction.at(i); midiToAction.erase( midiToAction.begin()+i ); if ( tmp ) { delete tmp; } return; } } } void GenericMIDI::setupBinding( LupppAction eventType, int midiStatus, int midiData, int track, int scene, int send, int active ) { LUPPP_NOTE("MIDI binding, track %d, send %d from eventType %d to %d, %d", track, send, eventType, midiStatus, midiData ); // FIXME: NON-RT Have stack of Bindings() available, or push in GUI thread? Binding* tmp = new Binding(); tmp->action = eventType; tmp->status = midiStatus; tmp->data = midiData; tmp->track = track; tmp->scene = scene; tmp->send = send; tmp->active = active; // FIXME: Could allocate memory! Issue? Shouldn't be binding during performance? midiToAction.push_back( tmp ); } Binding* GenericMIDI::setupBinding( cJSON* binding ) { // include Event::getPrettyName, and avoid Event::TRACK_VOLUME etc each time using namespace Event; // create binding, then fill in data as from JSON. Binding* tmp = new Binding(); cJSON* actionJson = cJSON_GetObjectItem( binding, "action" ); if ( !actionJson ) { LUPPP_WARN("Binding doesn't have action field: fix .ctlr file"); delete tmp; return 0; } cJSON* statusJson = cJSON_GetObjectItem( binding, "status" ); cJSON* dataJson = cJSON_GetObjectItem( binding, "data" ); if ( !statusJson || !dataJson ) { LUPPP_WARN("Binding w Action:%s doesn't have status / data field: fix .ctlr file", actionJson->valuestring); delete tmp; return 0; } tmp->status = statusJson->valueint; tmp->data = dataJson->valueint; // gets the Action type from the JSON string cJSON* activeJson = cJSON_GetObjectItem( binding, "active" ); if ( activeJson ) { tmp->active = activeJson->valueint; } // gets the active bool from the JSON tmp->action = Event::getTypeFromName( actionJson->valuestring ); // check what our send value should be: cJSON* sendJson = cJSON_GetObjectItem( binding, "send" ); if ( sendJson ) { tmp->send = sendJson->valueint; } if ( strcmp( actionJson->valuestring, "grid:clip_state" ) == 0 ) { tmp->action = Event::GRID_STATE; // read "state", to bind multiple values depending on clip state cJSON* stateBindings = cJSON_GetObjectItem( binding, "state"); if ( stateBindings ) { cJSON* empty = cJSON_GetObjectItem( stateBindings, "empty"); cJSON* stopped = cJSON_GetObjectItem( stateBindings, "stopped"); cJSON* playing = cJSON_GetObjectItem( stateBindings, "playing"); cJSON* recording = cJSON_GetObjectItem( stateBindings, "recording"); cJSON* qPlaying = cJSON_GetObjectItem( stateBindings, "queuePlaying"); cJSON* qStopped = cJSON_GetObjectItem( stateBindings, "queueStopped"); cJSON* qRecording = cJSON_GetObjectItem( stateBindings, "queueRecording"); // keep a map of GridLogic::STATE to the MIDI byte 3 output from .ctlr file if ( empty ) tmp->clipStateMap.insert( std::pair( GridLogic::STATE_EMPTY, empty->valueint ) ); if ( stopped ) tmp->clipStateMap.insert( std::pair( GridLogic::STATE_STOPPED, stopped->valueint ) ); if ( playing ) tmp->clipStateMap.insert( std::pair( GridLogic::STATE_PLAYING, playing->valueint ) ); if ( recording ) tmp->clipStateMap.insert( std::pair( GridLogic::STATE_RECORDING, recording->valueint ) ); if ( qPlaying ) tmp->clipStateMap.insert( std::pair( GridLogic::STATE_PLAY_QUEUED, qPlaying->valueint ) ); if ( qStopped ) tmp->clipStateMap.insert( std::pair( GridLogic::STATE_STOP_QUEUED, qStopped->valueint ) ); if ( qRecording ) tmp->clipStateMap.insert( std::pair( GridLogic::STATE_RECORD_QUEUED, qRecording->valueint ) ); } /* // Extreme debugging for( map::iterator it = tmp->clipStateMap.begin(); it != tmp->clipStateMap.end(); ++it) { std::co ut << it->first << " " << it->second << "\n"; } */ } else if ( strcmp( actionJson->valuestring, "grid:launch_scene" ) == 0 ) { tmp->action = Event::GRID_LAUNCH_SCENE; } else if ( strcmp( actionJson->valuestring, "grid:special_clip_event" ) == 0 ) { tmp->action = Event::GRID_SELECT_CLIP_EVENT; } else if ( strcmp( actionJson->valuestring, "grid:select_clip_enable" ) == 0 ) { tmp->action = Event::GRID_SELECT_CLIP_ENABLE; } else if ( strcmp( actionJson->valuestring, "master:volume" ) == 0 ) { tmp->action = Event::MASTER_VOL; } else if ( strcmp( actionJson->valuestring, "metronome:active" ) == 0 ) { tmp->action = Event::METRONOME_ACTIVE; LUPPP_NOTE("binding metro active event, tmp->active == %i", tmp->active ); } // check for valid event: otherwise pass if ( tmp->action != Event::EVENT_NULL ) { //LUPPP_NOTE("Binding from %i %i %s", statusJson->valueint, dataJson->valueint, actionJson->valuestring); cJSON* track = cJSON_GetObjectItem( binding, "track" ); cJSON* scene = cJSON_GetObjectItem( binding, "scene" ); cJSON* activeJson = cJSON_GetObjectItem( binding, "active" ); if ( track ) tmp->track = track->valueint; if ( scene ) tmp->scene = scene->valueint; if ( activeJson ) tmp->active = activeJson->valueint; return tmp; } else { LUPPP_WARN("Binding action not recognized: %s", actionJson->valuestring ); } delete tmp; return 0; }