Save and load MIDI control listener settings
Signed-off-by: Tobias Doerffel <tobias.doerffel@gmail.com>
This commit is contained in:
committed by
Tobias Doerffel
parent
8395c9428c
commit
57d4024c2a
@@ -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;
|
||||
|
||||
@@ -30,6 +30,11 @@
|
||||
#include "midi_event_processor.h"
|
||||
#include "midi_port.h"
|
||||
#include "note.h"
|
||||
#include <QString>
|
||||
#include <QPair>
|
||||
#include <QDomElement>
|
||||
|
||||
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<int, EventAction> 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;
|
||||
} ;
|
||||
|
||||
@@ -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 = "<?xml version=\"1.0\"?>\n" + doc.toString( 2 );
|
||||
|
||||
QFile outfile( m_lmmsRcFile );
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
*/
|
||||
|
||||
#include <QtCore/QDir>
|
||||
#include <QDomDocument>
|
||||
|
||||
#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
|
||||
|
||||
@@ -24,6 +24,12 @@
|
||||
*/
|
||||
|
||||
|
||||
#include <stdio.h>
|
||||
#include <QString>
|
||||
#include <QDomDocument>
|
||||
#include <QDomElement>
|
||||
#include <QDomNodeList>
|
||||
|
||||
#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];
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user