From 57d4024c2a101c29a8c3d41b6b2d48016d6d05e4 Mon Sep 17 00:00:00 2001 From: Achim Settelmeier Date: Thu, 21 May 2009 01:00:22 +0200 Subject: [PATCH] Save and load MIDI control listener settings Signed-off-by: Tobias Doerffel --- include/engine.h | 10 + include/midi_control_listener.h | 95 +++++- src/core/config_mgr.cpp | 8 +- src/core/engine.cpp | 20 ++ src/core/midi/midi_control_listener.cpp | 367 +++++++++++++++++++----- 5 files changed, 414 insertions(+), 86 deletions(-) diff --git a/include/engine.h b/include/engine.h index 0817da100..56361ac6e 100644 --- a/include/engine.h +++ b/include/engine.h @@ -51,6 +51,7 @@ class songEditor; class ladspa2LMMS; class controllerRackView; class MidiControlListener; +class QDomDocument; class EXPORT engine @@ -181,6 +182,15 @@ public: { return s_lmmsStyle; } + + static void saveConfiguration( QDomDocument & doc ); + + static void loadConfiguration( QDomDocument & doc ); + + static MidiControlListener * getMidiControlListener( void ) + { + return s_midiControlListener; + } private: static bool s_hasGUI; diff --git a/include/midi_control_listener.h b/include/midi_control_listener.h index 7154ba708..c54a87e63 100644 --- a/include/midi_control_listener.h +++ b/include/midi_control_listener.h @@ -30,6 +30,11 @@ #include "midi_event_processor.h" #include "midi_port.h" #include "note.h" +#include +#include +#include + +class QDomDocument; class MidiControlListener : public MidiEventProcessor { @@ -37,13 +42,31 @@ public: typedef enum { ActionNone = 0, + ActionControl, ActionPlay, ActionStop } EventAction; + static const int numActions = 4; + typedef QMap ActionMap; + + typedef struct + { + EventAction action; + QString name; + QString nameShort; + } ActionNameMap; + + static const ActionNameMap actionNames[]; + + static ActionNameMap action2ActionNameMap( EventAction _action ); + static ActionNameMap actionName2ActionNameMap( QString _actionName ); + + static void rememberConfiguration( QDomDocument &doc ); - MidiControlListener(); - virtual ~MidiControlListener(); +public: + MidiControlListener( void ); + virtual ~MidiControlListener( void ); virtual void processInEvent( const midiEvent & _me, const midiTime & _time ); @@ -52,18 +75,80 @@ public: { } + void saveConfiguration( QDomDocument &doc ); + + void readConfiguration( void ); + + inline bool getEnabled( void ) + { + return m_listenerEnabled; + } + + inline void setEnabled( bool _enabled ) + { + m_listenerEnabled = _enabled; + m_controlKeyCount = 0; + } + + inline bool getUseControlKey( void ) + { + return m_useControlKey; + } + + inline void setUseControlKey( bool _useControlKey ) + { + m_useControlKey = _useControlKey; + m_controlKeyCount = 0; + } + + inline int getChannel( void ) + { + return m_channel; + } + + inline void setChannel( int _Channel ) + { + m_channel = _Channel; + m_controlKeyCount = 0; + } + + inline ActionMap getActionMapKeys( void ) + { + return m_actionMapKeys; + } + + inline void setActionMapKeys( ActionMap _keys ) + { + m_actionMapKeys = _keys; + m_controlKeyCount = 0; + } + + inline ActionMap getActionMapControllers( void ) + { + return m_actionMapControllers; + } + + inline void setActionMapControllers( ActionMap _controllers ) + { + m_actionMapControllers = _controllers; + m_controlKeyCount = 0; + } + private: + static const QString configClass; + static QDomElement s_configTree; + void act( EventAction _action ); midiPort m_port; - bool m_controlKeyPressed; // flag, whether the control key is pressed + int m_controlKeyCount; // 0: no control key(s) pressed, 1-n: control keys pressed // configuration + bool m_listenerEnabled; // turns feature on/off + int m_channel; // number of channel (0 - 15) or -1 for all channels bool m_useControlKey; // true: use control key (two key sequence); false: single key sequence - int m_controlKey; // number of the control key (0 - NumKeys) - int m_controlChannel; // number of channel (0 - 15) or -1 for all channels ActionMap m_actionMapKeys; ActionMap m_actionMapControllers; } ; diff --git a/src/core/config_mgr.cpp b/src/core/config_mgr.cpp index dfb58dd5f..f99a08604 100644 --- a/src/core/config_mgr.cpp +++ b/src/core/config_mgr.cpp @@ -34,7 +34,7 @@ #include "lmmsversion.h" #include "config_mgr.h" #include "main_window.h" - +#include "engine.h" static inline QString ensureTrailingSlash( const QString & _s ) { @@ -227,6 +227,8 @@ void configManager::loadConfigFile( void ) QDomNode node = root.firstChild(); + engine::loadConfiguration( dom_tree ); + // create the settings-map out of the DOM while( !node.isNull() ) { @@ -395,7 +397,9 @@ void configManager::saveConfigFile( void ) recent_files.appendChild( n ); } lmms_config.appendChild( recent_files ); - + + engine::saveConfiguration( doc ); + QString xml = "\n" + doc.toString( 2 ); QFile outfile( m_lmmsRcFile ); diff --git a/src/core/engine.cpp b/src/core/engine.cpp index 0ac6108bf..e6c354cb1 100644 --- a/src/core/engine.cpp +++ b/src/core/engine.cpp @@ -25,6 +25,7 @@ */ #include +#include #include "engine.h" #include "automation_editor.h" @@ -234,4 +235,23 @@ void engine::initPluginFileHandling( void ) +void engine::loadConfiguration( QDomDocument & doc ) +{ + // must be a call to a static method as the engine + // is not yet created and initialized and + // s_midiControlListener is still NULL. + MidiControlListener::rememberConfiguration( doc ); +} + + + + +void engine::saveConfiguration( QDomDocument & doc ) +{ + s_midiControlListener->saveConfiguration( doc ); +} + + + + #endif diff --git a/src/core/midi/midi_control_listener.cpp b/src/core/midi/midi_control_listener.cpp index bcd41bb66..3a47a70db 100644 --- a/src/core/midi/midi_control_listener.cpp +++ b/src/core/midi/midi_control_listener.cpp @@ -24,6 +24,12 @@ */ +#include +#include +#include +#include +#include + #include "midi_control_listener.h" #include "mixer.h" #include "midi_client.h" @@ -31,26 +37,32 @@ #include "engine.h" #include "note.h" #include "song.h" +#include "config_mgr.h" + +const QString MidiControlListener::configClass = "midicontrollistener"; +QDomElement MidiControlListener::s_configTree; + +const MidiControlListener::ActionNameMap MidiControlListener::actionNames[] = +{ + { MidiControlListener::ActionNone, "", "" }, + { MidiControlListener::ActionControl, "Control key", "control" }, + { MidiControlListener::ActionPlay, "Play", "play" }, + { MidiControlListener::ActionStop, "Stop", "stop" } +}; + + MidiControlListener::MidiControlListener() : m_port( "unnamed_midi_controller", engine::getMixer()->getMidiClient(), this, NULL, midiPort::Input ), - m_controlKeyPressed( false ) + m_controlKeyCount( 0 ), + m_listenerEnabled( false ), + m_channel( -1 ), + m_useControlKey( false ) { - // default settings - m_useControlKey = true; // use control key - m_controlKey = 60; // C5 - m_controlChannel = -1; // listen on all channels - -#warning TODO replace hard-coded defaults - // test config - m_port.subscribeReadablePort( "24:0", true ); - m_actionMapKeys[57] = ActionPlay; - m_actionMapKeys[59] = ActionStop; - m_actionMapControllers[24] = ActionPlay; - m_actionMapControllers[23] = ActionStop; + readConfiguration(); // reads previously remembered configuration } @@ -66,77 +78,68 @@ MidiControlListener::~MidiControlListener() void MidiControlListener::processInEvent( const midiEvent & _me, const midiTime & _time ) { + // don't do anything unless the listener is enabled + if( ! m_listenerEnabled ) + { + return; + } + // pre-check whether this MIDI packet suits our configuration switch( _me.m_type ) { - case MidiNoteOn: - case MidiNoteOff: - case MidiControlChange: - // ignore commands for other channels - if( m_controlChannel != -1 && - m_controlChannel != _me.channel() ) - { - return; - } - break; - default: - // ignore commands other than note on/off and - // control change - return; - } - - // check MIDI packet type and act upon - switch( _me.m_type ) - { - case MidiNoteOn: - if( _me.key() == m_controlKey) - { - if( _me.velocity() == 0 ) - { - // special case: key press with velocity 0 - // means key release - m_controlKeyPressed = false; - break; - } - m_controlKeyPressed = true; - break; - } - else if( !m_useControlKey || m_controlKeyPressed ) - { - if( _me.velocity() > 0 && - m_actionMapKeys.contains( _me.key() ) ) - { - act( m_actionMapKeys.value( _me.key(), - ActionNone ) ); - } - } - break; - + case MidiNoteOn: case MidiNoteOff: - if( _me.key() == m_controlKey ) + case MidiControlChange: + // ignore commands for other channels + if( m_channel != -1 && m_channel != _me.channel() ) + return; + break; + + default: + // ignore commands other than note on/off and control change + return; + } + + // check MIDI packet type and act upon + switch( _me.m_type ) + { + case MidiNoteOn: + if( m_actionMapKeys.contains( _me.key() ) ) { - m_controlKeyPressed = false; + if( m_actionMapKeys.value( _me.key(), ActionNone ) == ActionControl ) + { + if( _me.velocity() > 0 ) + { + m_controlKeyCount++; + } + else + { + m_controlKeyCount--; + if( m_controlKeyCount < 0 ) + { + m_controlKeyCount = 0; + } + } + } + else if( _me.velocity() > 0 && + ( !m_useControlKey || m_controlKeyCount > 0) ) + { + act( m_actionMapKeys.value( _me.key(), ActionNone ) ); + } } break; - + + case MidiNoteOff: + break; + case MidiControlChange: // controller changed to a value other than zero if( _me.m_data.m_param[1] > 0 ) { - switch( m_actionMapControllers.value( - _me.m_data.m_param[0], ActionNone ) ) - { - case ActionNone: - break; - case ActionPlay: - engine::getSong()->play(); - break; - case ActionStop: - engine::getSong()->stop(); - break; - } + act( m_actionMapControllers.value( _me.m_data.m_param[0], ActionNone ) ); } break; + default: // nop break; @@ -150,14 +153,220 @@ void MidiControlListener::act( EventAction _action ) { switch( _action ) { - case ActionNone: - break; - case ActionPlay: - engine::getSong()->play(); - break; - case ActionStop: - engine::getSong()->stop(); - break; + case ActionNone: + case ActionControl: + break; + case ActionPlay: + engine::getSong()->play(); + break; + case ActionStop: + engine::getSong()->stop(); + break; } } + + + +/** + * add DOM nodes to the configuration + */ +void MidiControlListener::saveConfiguration( QDomDocument & doc ) +{ + // The root node must not have any attributes, otherwise it would + // conflict with the configManager. Instead, attributes are moved + // to a subnode. + QDomElement confRoot = doc.createElement( configClass ); + QDomElement conf = doc.createElement( "config" ); + confRoot.appendChild( conf ); + + // add basic configuration variables + conf.setAttribute( "enabled", m_listenerEnabled ); + conf.setAttribute( "useControlKey", m_useControlKey ); + conf.setAttribute( "channel", m_channel + 1 ); + + // get subscribed MIDI device + midiPort::map map = m_port.readablePorts(); + for( midiPort::map::iterator it = map.begin(); + it != map.end(); ++it ) + { + if( it.value() ) + { + QDomElement device = doc.createElement( "device" ); + device.appendChild( doc.createTextNode( it.key() ) ); + conf.appendChild( device ); + } + } + + // add key actions + for( ActionMap::const_iterator it = m_actionMapKeys.begin(); + it != m_actionMapKeys.end(); ++it ) + { + QDomElement actionNode = doc.createElement( "action" ); + actionNode.setAttribute( "type", "key" ); + actionNode.setAttribute( "key", it.key() ); + actionNode.setAttribute( "actionName", + action2ActionNameMap( it.value() ).nameShort ); + confRoot.appendChild( actionNode ); + } + + // add controller actions + for( ActionMap::const_iterator it = m_actionMapControllers.begin(); + it != m_actionMapControllers.end(); ++it ) + { + QDomElement actionNode = doc.createElement( "action" ); + actionNode.setAttribute( "type", "controller" ); + actionNode.setAttribute( "controller", it.key() ); + actionNode.setAttribute( "actionName", + action2ActionNameMap( it.value() ).nameShort ); + confRoot.appendChild( actionNode ); + } + + QDomElement lmms_config = doc.documentElement(); + lmms_config.appendChild( confRoot ); +} + + + + +/** + * Remember configuration for later retrieval. + * This is necessary because at the time the configManager loads the + * configuration, the engine is not yet initialized and there's no + * MidiControlListener object. + */ +void MidiControlListener::rememberConfiguration( QDomDocument & doc ) +{ + QDomNodeList list = doc.elementsByTagName( configClass ); + if( list.isEmpty() ) + { + return; + } + + s_configTree = list.item(0).cloneNode( true ).toElement(); +} + + + + +/** + * reads the configuration from the previously stored configuration subtree. + */ +void MidiControlListener::readConfiguration() +{ + // default settings + m_listenerEnabled = false; // turn off by default + m_useControlKey = true; // use control key + m_channel = -1; // listen on all channels + m_actionMapKeys.clear(); // empty action lists + m_actionMapControllers.clear(); + + // unsubscribe all ports + midiPort::map map = m_port.readablePorts(); + for( midiPort::map::iterator it = map.begin(); + it != map.end(); ++it ) + { + if( it.value() ) + { + m_port.subscribeReadablePort( it.key(), false ); + } + } + + + // check whether there's a configuration tree at all + if( s_configTree.isNull() || ! s_configTree.hasChildNodes() ) + { + return; + } + + QDomElement conf = s_configTree.firstChildElement( "config" ); + // check whether config tag is present + if( ! conf.isNull() ) + { + QString val; + + // read config parameters + val = conf.attribute( "enabled" ); + if( val.toInt() ) // default: false + { + m_listenerEnabled = true; + } + + val = conf.attribute( "channel" ); + if( val != "" ) + { + m_channel = val.toInt() - 1; + } + + val = conf.attribute( "useControlKey" ); + if( val.toInt() == 0 ) // default: true + { + m_useControlKey = false; + } + + QDomNodeList deviceNodes = s_configTree.elementsByTagName( "device" ); + for( uint i = 0; i < deviceNodes.length(); ++i ) + { + QDomElement deviceNode = deviceNodes.at( i ).toElement(); + if( deviceNode.text() != "" ) + { + m_port.subscribeReadablePort( deviceNode.text(), true ); + } + } + } + + // now read action tags + QDomNodeList actionNodes = s_configTree.elementsByTagName( "action" ); + for( uint i = 0; i < actionNodes.length(); ++i ) + { + QDomElement actionNode = actionNodes.at( i ).toElement(); + + EventAction action = actionName2ActionNameMap( actionNode.attribute( "actionName" ) ).action; + + if( actionNode.attribute( "type" ) == "key" ) + { + int key = actionNode.attribute( "key" ).toInt(); + m_actionMapKeys[key] = action; + } + else if( actionNode.attribute( "type" ) == "controller" ) + { + int controller = actionNode.attribute( "controller" ).toInt(); + m_actionMapControllers[controller] = action; + } + } +} + + + + +MidiControlListener::ActionNameMap MidiControlListener::action2ActionNameMap( EventAction _action ) +{ + for( int i = 0; i < numActions; ++i ) + { + if( actionNames[i].action == _action ) + { + return actionNames[i]; + } + } + return actionNames[0]; +} + + + + +MidiControlListener::ActionNameMap MidiControlListener::actionName2ActionNameMap( QString _actionName ) +{ + for( int i = 0; i < numActions; ++i ) + { + if( actionNames[i].name == _actionName || + actionNames[i].nameShort == _actionName ) + { + return actionNames[i]; + } + } + return actionNames[0]; +} + + + +