/* * 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; } 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::progress(int t, int s, float f) { unsigned char data[3]; data[0] = 176; data[1] = 48; // record enable LED data[2] = 127 * f; jack->midiObserverWriteMIDI( _port, &data[0] ); } void GenericMIDI::trackSend(int t, int send, float v) { if ( t >= NTRACKS) { // master track return; } unsigned char data[3]; if ( send == SEND_KEY ) { data[0] = 144 + t; data[1] = 49; data[2] = v > 0.5 ? 127 : 0 ; } else if ( send == SEND_XSIDE ) { data[0] = 176 + t; data[1] = 16; data[2] = 127 * v; } else if ( send == SEND_POSTFADER ) { data[0] = 176 + t; data[1] = 17; data[2] = 127 * v; } jack->midiObserverWriteMIDI( _port, &data[0] ); } void GenericMIDI::launchScene( int s ) { unsigned char data[3]; for(int i = 0; i < 5; i++ ) { data[0] = 128; data[1] = 82 + i; // scene play data[2] = 0; jack->midiObserverWriteMIDI( _port, &data[0] ); } data[0] = 144; data[1] = 82 + s; data[2] = 127; jack->midiObserverWriteMIDI( _port, &data[0] ); } void GenericMIDI::mute(int t, bool b) { } void GenericMIDI::volume(int t, float f) { } void GenericMIDI::noteOn( int track, int note, int vel ) { printf("apc noteOn: t = %i, n = %i, vel = %i\n", track, note, vel); if ( note >= 53 && note <= 57 ) { if ( shiftPressed ) { footpedalTrack = track; footpedalScene = note - 53; } else jack->getGridLogic()->pressed( track, note - 53 ); return; } switch( note ) { case 48: { // record jack->getLogic()->trackRecordArm(track, true); } break; case 49: { // solo / cue jack->getLogic()->trackSend(track, SEND_KEY, 1); } break; case 82: // Master Scene Clips case 83: case 84: case 85: case 86: { jack->getGridLogic()->launchScene(note - 82); } break; case 98: { // Shift shiftPressed = true; break; } case 99: { // tap tempo jack->getLogic()->tapTempo(); jack->getControllerUpdater()->tapTempo( true ); } break; case 100: { // nudge + } break; case 101: { // nudge - } break; default: break; } } void GenericMIDI::noteOff( int track, int note, int vel ) { printf("apc noteOff: t = %i, n = %i, vel = %i\n", track, note, vel); if ( note >= 53 && note <= 57 ) { jack->getGridLogic()->released( track, note - 53 ); } switch( note ) { case 48: { // record jack->getLogic()->trackRecordArm(track, false); } break; case 49: { // solo / cue jack->getLogic()->trackSend(track, SEND_KEY, 0); } case 99: { // tap tempo EventTimeTempoTap e(false); writeToGuiRingbuffer( &e ); } break; case 82: // scene launch case 83: case 84: case 85: case 86: { int s = jack->getGridLogic()->getLaunchedScene(); launchScene( s ); } break ; case 98: { // Shift shiftPressed = false; break; } } } void GenericMIDI::ccChange( int track, int cc, float value ) { if ( track >= 0 && track < NTRACKS ) { switch( cc ) { /// Track faders case 7: { jack->getLogic()->trackVolume( track, value ); break; } case 14: { // master jack->getLogic()->trackVolume( -1, value ); break; } /// X-Fader case 15: { jack->getTimeManager()->setBpm( 60 + value * 180 ); break; } /// Device Control case 16: { //jack->getLogic()->trackSend( track, SEND_KEY, value ); break; } case 17: { jack->getLogic()->trackSend( track, SEND_XSIDE, value ); break; } case 18: { jack->getLogic()->trackSend( track, SEND_POSTFADER, value ); break; } case 64: { // FootSwitch 1 if ( value > 0.5 ) { printf("footpedal press %i, %i\n", footpedalTrack, footpedalScene ); jack->getGridLogic()->pressed( footpedalTrack, footpedalScene ); } else { printf("footpedal release %i, %i\n", footpedalTrack, footpedalScene ); jack->getGridLogic()->released( footpedalTrack, footpedalScene ); } break; } case 67: { // FootSwitch 2 break; } } } else // tracks outside normal track range // handle master track buttons etc here { if ( cc == 16 ) // master device control select { } } } */ 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 ); } // 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::GRID_EVENT: if ( b->active ) jack->getGridLogic()->pressed( b->track, b->scene ); else jack->getGridLogic()->released( b->track, b->scene ); break; case Event::GRID_SELECT_CLIP_EVENT: 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: // FIXME: quick-fix for "ZeroOne" type value -> BPM range jack->getLogic()->setBpm( value * 160 + 60 ); break; case Event::METRONOME_ACTIVE: jack->getLogic()->metronomeEnable( b->active ); break; case Event::MASTER_VOL: jack->getLogic()->trackVolume( -1 , value ); 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() { /* 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 ) { for( int i = 0; i < 5; i++ ) { if ( i != scene ) { unsigned char data[3]; data[0] = b->status; data[1] = b->data + i; data[2] = 0; writeMidi( data ); } } unsigned char data[3]; data[0] = b->status; data[1] = b->data + scene; data[2] = 127; //LUPPP_NOTE("this = %i GenericMIDI::launchScene()", this ); writeMidi( data ); return; } } } 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"); 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); return 0; } tmp->status = statusJson->valueint; tmp->data = dataJson->valueint; // gets the Action type from the JSON string 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; } // 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 ); } return 0; }