Save and load MIDI control listener settings

Signed-off-by: Tobias Doerffel <tobias.doerffel@gmail.com>
This commit is contained in:
Achim Settelmeier
2009-05-21 01:00:22 +02:00
committed by Tobias Doerffel
parent 8395c9428c
commit 57d4024c2a
5 changed files with 414 additions and 86 deletions

View File

@@ -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;

View File

@@ -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;
} ;

View File

@@ -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 );

View File

@@ -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

View File

@@ -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];
}