diff -dupPNr qtractor.0/Makefile.in qtractor.1/Makefile.in
--- qtractor.0/Makefile.in	2009-02-08 15:12:58.000000000 +0000
+++ qtractor.1/Makefile.in	2009-05-15 21:16:11.000000000 +0100
@@ -39,6 +39,7 @@ headers = config.h \
 	src/qtractorMidiBuffer.h \
 	src/qtractorMidiClip.h \
 	src/qtractorMidiConnect.h \
+	src/qtractorMidiControl.h \
 	src/qtractorMidiCursor.h \
 	src/qtractorMidiEditor.h \
 	src/qtractorMidiEditCommand.h \
@@ -133,6 +134,7 @@ sources = src/main.cpp \
 	src/qtractorMidiBuffer.cpp \
 	src/qtractorMidiClip.cpp \
 	src/qtractorMidiConnect.cpp \
+	src/qtractorMidiControl.cpp \
 	src/qtractorMidiCursor.cpp \
 	src/qtractorMidiEditor.cpp \
 	src/qtractorMidiEditCommand.cpp \
diff -dupPNr qtractor.0/qtractor.pro.in qtractor.1/qtractor.pro.in
--- qtractor.0/qtractor.pro.in	2009-02-08 15:12:58.000000000 +0000
+++ qtractor.1/qtractor.pro.in	2009-05-15 21:16:11.000000000 +0100
@@ -35,6 +35,7 @@ HEADERS += src/qtractorAbout.h \
            src/qtractorMidiBuffer.h \
            src/qtractorMidiClip.h \
            src/qtractorMidiConnect.h \
+           src/qtractorMidiControl.h \
            src/qtractorMidiCursor.h \
            src/qtractorMidiEditor.h \
            src/qtractorMidiEditCommand.h \
@@ -130,6 +131,7 @@ SOURCES += src/main.cpp \
            src/qtractorMidiBuffer.cpp \
            src/qtractorMidiClip.cpp \
            src/qtractorMidiConnect.cpp \
+           src/qtractorMidiControl.cpp \
            src/qtractorMidiCursor.cpp \
            src/qtractorMidiEditor.cpp \
            src/qtractorMidiEditCommand.cpp \
diff -dupPNr qtractor.0/src/qtractorMainForm.cpp qtractor.1/src/qtractorMainForm.cpp
--- qtractor.0/src/qtractorMainForm.cpp	2009-04-22 11:00:30.000000000 +0100
+++ qtractor.1/src/qtractorMainForm.cpp	2009-05-16 11:01:55.000000000 +0100
@@ -53,8 +53,9 @@
 
 #include "qtractorAudioMeter.h"
 #include "qtractorMidiMeter.h"
-#include "qtractorMidiMonitor.h"
 
+#include "qtractorMidiMonitor.h"
+#include "qtractorMidiControl.h"
 #include "qtractorMidiBuffer.h"
 
 #include "qtractorExportForm.h"
@@ -244,6 +245,9 @@ qtractorMainForm::qtractorMainForm (
 		pMidiEngine->setNotifySppType(QTRACTOR_SPP_EVENT);
 	}
 
+	// add the midi controller map
+	m_pMidiControl = new qtractorMidiControl();
+
 #ifdef HAVE_SIGNAL_H
 	// Set to ignore any fatal "Broken pipe" signals.
 	::signal(SIGPIPE, SIG_IGN);
@@ -774,7 +778,11 @@ qtractorMainForm::~qtractorMainForm (voi
 	// Custom tempo cursor.
 	if (m_pTempoCursor)
 		delete m_pTempoCursor;
-	
+
+	// remove midi controller
+	if (m_pMidiControl)
+		delete m_pMidiControl;
+
 	// Pseudo-singleton reference shut-down.
 	g_pMainForm = NULL;
 }
@@ -1187,29 +1195,29 @@ void qtractorMainForm::customEvent ( QEv
 // Custom MMC event handler.
 void qtractorMainForm::mmcEvent ( qtractorMmcEvent *pMmcEvent )
 {
-	QString sMmcText;
+	QString sMmcText("MMC: ");
 	switch (pMmcEvent->cmd()) {
 	case qtractorMmcEvent::STOP:
 	case qtractorMmcEvent::PAUSE:
-		sMmcText = tr("STOP");
+		sMmcText += tr("STOP");
 		setPlaying(false);
 		break;
 	case qtractorMmcEvent::PLAY:
 	case qtractorMmcEvent::DEFERRED_PLAY:
-		sMmcText = tr("PLAY");
+		sMmcText += tr("PLAY");
 		setPlaying(true);
 		break;
 	case qtractorMmcEvent::FAST_FORWARD:
-		sMmcText = tr("FFWD");
+		sMmcText += tr("FFWD");
 		setRolling(+1);
 		break;
 	case qtractorMmcEvent::REWIND:
-		sMmcText = tr("REW");
+		sMmcText += tr("REW");
 		setRolling(-1);
 		break;
 	case qtractorMmcEvent::RECORD_STROBE:
 	case qtractorMmcEvent::RECORD_PAUSE:
-		sMmcText = tr("REC ON");
+		sMmcText += tr("REC ON");
 		if (!setRecording(true)) {
 			// Send MMC RECORD_EXIT command immediate reply...
 			m_pSession->midiEngine()->sendMmcCommand(
@@ -1217,54 +1225,54 @@ void qtractorMainForm::mmcEvent ( qtract
 		}
 		break;
 	case qtractorMmcEvent::RECORD_EXIT:
-		sMmcText = tr("REC OFF");
+		sMmcText += tr("REC OFF");
 		setRecording(false);
 		break;
 	case qtractorMmcEvent::MMC_RESET:
-		sMmcText = tr("RESET");
+		sMmcText += tr("RESET");
 		setRolling(0);
 		break;
 	case qtractorMmcEvent::LOCATE:
-		sMmcText = tr("LOCATE %1").arg(pMmcEvent->locate());
+		sMmcText += tr("LOCATE %1").arg(pMmcEvent->locate());
 		setLocate(pMmcEvent->locate());
 		break;
 	case qtractorMmcEvent::SHUTTLE:
-		sMmcText = tr("SHUTTLE %1").arg(pMmcEvent->shuttle());
+		sMmcText += tr("SHUTTLE %1").arg(pMmcEvent->shuttle());
 		setShuttle(pMmcEvent->shuttle());
 		break;
 	case qtractorMmcEvent::STEP:
-		sMmcText = tr("STEP %1").arg(pMmcEvent->step());
+		sMmcText += tr("STEP %1").arg(pMmcEvent->step());
 		setStep(pMmcEvent->step());
 		break;
 	case qtractorMmcEvent::MASKED_WRITE:
 		switch (pMmcEvent->scmd()) {
 		case qtractorMmcEvent::TRACK_RECORD:
-			sMmcText = tr("TRACK RECORD %1 %2")
+			sMmcText += tr("TRACK RECORD %1 %2")
 				.arg(pMmcEvent->track())
 				.arg(pMmcEvent->isOn());
 			break;
 		case qtractorMmcEvent::TRACK_MUTE:
-			sMmcText = tr("TRACK MUTE %1 %2")
+			sMmcText += tr("TRACK MUTE %1 %2")
 				.arg(pMmcEvent->track())
 				.arg(pMmcEvent->isOn());
 			break;
 		case qtractorMmcEvent::TRACK_SOLO:
-			sMmcText = tr("TRACK SOLO %1 %2")
+			sMmcText += tr("TRACK SOLO %1 %2")
 				.arg(pMmcEvent->track())
 				.arg(pMmcEvent->isOn());
 			break;
 		default:
-			sMmcText = tr("Unknown sub-command");
+			sMmcText += tr("Unknown sub-command");
 			break;
 		}
 		setTrack(pMmcEvent->scmd(), pMmcEvent->track(), pMmcEvent->isOn());
 		break;
 	default:
-		sMmcText = tr("Not implemented");
+		sMmcText += tr("Not implemented");
 		break;
 	}
 
-	appendMessages("MMC: " + sMmcText);
+	appendMessages(sMmcText);
 	stabilizeForm();
 }
 
@@ -1272,12 +1280,19 @@ void qtractorMainForm::mmcEvent ( qtract
 // Custom controller event handler.
 void qtractorMainForm::midiControlEvent ( qtractorMidiControlEvent *pCtlEvent )
 {
-	QString sCtlText = tr("MIDI channel %1, Controller %2, Value %3")
+	QString sCtlText("CTL: ");
+	sCtlText += tr("MIDI channel %1, Controller %2, Value %3")
 		.arg(pCtlEvent->channel())
 		.arg(pCtlEvent->controller())
 		.arg(pCtlEvent->value());
 
-	// FIXME: JCooper faders (as from US-224)...
+	// TODO: check if controller is used as MIDI controller...
+	if (m_pMidiControl->processEvent(pCtlEvent)) {
+		appendMessages(sCtlText);
+		return;
+	}
+
+	/* FIXME: JLCooper faders (as from US-224)...
 	if (pCtlEvent->channel() == 15) {
 		// Event translation...
 		int   iTrack = int(pCtlEvent->controller()) & 0x3f;
@@ -1286,31 +1301,22 @@ void qtractorMainForm::midiControlEvent
 		qtractorTrack *pTrack = m_pSession->tracks().at(iTrack);
 		if (pTrack) {
 			m_pSession->execute(
-				new qtractorTrackGainCommand(pTrack, fGain));
-			sCtlText += tr("(track %1, gain %2)").arg(iTrack).arg(fGain);
+				new qtractorTrackGainCommand(pTrack, fGain, true));
+			sCtlText += tr("(track %1, gain %2)")
+				.arg(iTrack).arg(fGain);
 		}
 	}
-	else
+	else */
 	// Handle volume controls...
 	if (pCtlEvent->controller() == 7) {
 		int iTrack = 0;
+		float fGain = float(pCtlEvent->value()) / 127.0f;
 		for (qtractorTrack *pTrack = m_pSession->tracks().first();
 				pTrack; pTrack = pTrack->next()) {
 			if (pTrack->trackType() == qtractorTrack::Midi &&
 				pTrack->midiChannel() == pCtlEvent->channel()) {
-				float fGain = float(pCtlEvent->value()) / 127.0f;
-				// Set track gain/volume directly,
-				// bypassing the undo/redo system
-				// to avoid feedback issues with
-				// motorized external controllers...
-				pTrack->setGain(fGain);
-				qtractorMixer *pMixer = mixer();
-				if (pMixer) {
-					qtractorMixerStrip *pStrip
-						= pMixer->trackRack()->findStrip(pTrack->monitor());
-					if (pStrip && pStrip->meter())
-						pStrip->meter()->updateGain();
-				}
+				m_pSession->execute(
+					new qtractorTrackGainCommand(pTrack, fGain, true));
 				sCtlText += tr("(track %1, gain %2)")
 					.arg(iTrack).arg(fGain);
 			}
@@ -1321,22 +1327,13 @@ void qtractorMainForm::midiControlEvent
 	// Handle pan controls...
 	if (pCtlEvent->controller() == 10) {
 		int iTrack = 0;
+		float fPanning = (float(pCtlEvent->value()) - 63.0f) / 64.0f;
 		for (qtractorTrack *pTrack = m_pSession->tracks().first();
 				pTrack; pTrack = pTrack->next()) {
 			if (pTrack->trackType() == qtractorTrack::Midi &&
 				pTrack->midiChannel() == pCtlEvent->channel()) {
-				// Set track panning directly,
-				// bypassing the undo/redo system
-				// to avoid feedback issues with
-				// motorized external controllers...
-				float fPanning = (float(pCtlEvent->value()) - 63.0f) / 64.0f;
-				qtractorMixer *pMixer = mixer();
-				if (pMixer) {
-					qtractorMixerStrip *pStrip
-						= pMixer->trackRack()->findStrip(pTrack->monitor());
-					if (pStrip && pStrip->meter())
-						pStrip->meter()->updatePanning();
-				}
+				m_pSession->execute(
+					new qtractorTrackPanningCommand(pTrack, fPanning, true));
 				sCtlText += tr("(track %1, panning %2)")
 					.arg(iTrack).arg(fPanning);
 			}
@@ -1344,38 +1341,38 @@ void qtractorMainForm::midiControlEvent
 		}
 	}
 
-	appendMessages("CTL: " + sCtlText);
+	appendMessages(sCtlText);
 }
 
 
 // Custom MIDI SPP  event handler.
 void qtractorMainForm::midiSppEvent ( qtractorMidiSppEvent *pSppEvent )
 {
-	QString sSppText;
+	QString sSppText("SPP: ");
 	switch (pSppEvent->cmdType()) {
 	case SND_SEQ_EVENT_START:
-		sSppText = tr("START");
+		sSppText += tr("START");
 		setSongPos(0);
 		setPlaying(true);
 		break;
 	case SND_SEQ_EVENT_STOP:
-		sSppText = tr("STOP");
+		sSppText += tr("STOP");
 		setPlaying(false);
 		break;
 	case SND_SEQ_EVENT_CONTINUE:
-		sSppText = tr("CONTINUE");
+		sSppText += tr("CONTINUE");
 		setPlaying(true);
 		break;
 	case SND_SEQ_EVENT_SONGPOS:
-		sSppText = tr("SONGPOS %1").arg(pSppEvent->songPos());
+		sSppText += tr("SONGPOS %1").arg(pSppEvent->songPos());
 		setSongPos(pSppEvent->songPos());
 		break;
 	default:
-		sSppText = tr("Not implemented");
+		sSppText += tr("Not implemented");
 		break;
 	}
 
-	appendMessages("SPP: " + sSppText);
+	appendMessages(sSppText);
 	stabilizeForm();
 }
 
@@ -1684,6 +1681,7 @@ bool qtractorMainForm::closeSession (voi
 		// Reset all dependables to default.
 		m_pMixer->clear();
 		m_pFiles->clear();
+		m_pMidiControl->clear();
 		// Close session engines.
 		m_pSession->close();
 		m_pSession->clear();
@@ -3962,6 +3960,8 @@ void qtractorMainForm::updateSession (vo
 		m_pSession->setPlayHead(0);
 		// (Re)initialize MIDI instrument patching...
 		m_pSession->setMidiPatch();
+		// Re-send all mapped MIDI controllers...
+		m_pMidiControl->sendAllControllers();
 		// Get on with the special ALSA sequencer notifier...
 		if (m_pSession->midiEngine()->alsaNotifier()) {
 			QObject::connect(m_pSession->midiEngine()->alsaNotifier(),
diff -dupPNr qtractor.0/src/qtractorMainForm.h qtractor.1/src/qtractorMainForm.h
--- qtractor.0/src/qtractorMainForm.h	2009-04-16 22:06:22.000000000 +0100
+++ qtractor.1/src/qtractorMainForm.h	2009-05-15 21:16:11.000000000 +0100
@@ -39,6 +39,7 @@ class qtractorConnections;
 class qtractorMixer;
 class qtractorMmcEvent;
 class qtractorMidiControlEvent;
+class qtractorMidiControl;
 class qtractorMidiSppEvent;
 class qtractorTimeSpinBox;
 class qtractorTempoSpinBox;
@@ -330,6 +331,8 @@ private:
 	int m_iMidiRefreshTimer;
 	int m_iPlayerTimer;
 
+	qtractorMidiControl* m_pMidiControl;
+
 	// Status bar item indexes
 	enum {
 		StatusName    = 0,   // Active session track caption.
diff -dupPNr qtractor.0/src/qtractorMidiControl.cpp qtractor.1/src/qtractorMidiControl.cpp
--- qtractor.0/src/qtractorMidiControl.cpp	1970-01-01 01:00:00.000000000 +0100
+++ qtractor.1/src/qtractorMidiControl.cpp	2009-05-16 11:49:20.000000000 +0100
@@ -0,0 +1,494 @@
+// qtractorMidiControl.cpp
+//
+/****************************************************************************
+   Copyright (C) 2005-2009, rncbc aka Rui Nuno Capela. All rights reserved.
+
+   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 2
+   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, write to the Free Software Foundation, Inc.,
+   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+*****************************************************************************/
+
+#include "qtractorAbout.h"
+#include "qtractorMidiControl.h"
+#include "qtractorMidiEngine.h"
+
+#include "qtractorMainForm.h"
+
+#include "qtractorTracks.h"
+#include "qtractorTrackList.h"
+#include "qtractorTrackCommand.h"
+
+
+//----------------------------------------------------------------------
+// qtractorMidiControl::MapKey -- MIDI control map key.
+//
+
+class qtractorMidiControl::MapKey
+{
+public:
+
+	// Constructor.
+	MapKey(unsigned short iChannel = 0, unsigned short iController = 0)
+		: m_iChannel(iChannel), m_iController(iController) {}
+
+	// Channel accessors.
+	void setChannel(unsigned short iChannel)
+		{ m_iChannel = iChannel; }
+	unsigned short channel() const
+		{ return (m_iChannel & MaskParam); }
+
+	bool isChannel() const
+		{ return ((m_iChannel & MaskParam) == m_iChannel); }
+	bool isChannelParam() const
+		{ return (m_iChannel & TrackParam); }
+
+	// Controller accessors.
+	void setController (unsigned short iController)
+		{ m_iController = iController; }
+	unsigned short controller() const
+		{ return (m_iController & MaskParam); }
+
+	bool isController() const
+		{ return ((m_iController & MaskParam ) == m_iController); }
+	bool isControllerParam() const
+		{ return (m_iController & TrackParam); }
+
+	// Generic key matcher.
+	bool match (unsigned short iChannel, unsigned iController) const
+	{
+		return (isChannelParam() || channel() == iChannel)
+			&& (isControllerParam() || controller() == iController);
+	}
+
+	// Raw hash key.
+	unsigned short hash() const
+		{ return (m_iChannel ^ m_iController); }
+
+	// Hash key comparator.
+	bool operator== ( const MapKey& key ) const
+	{
+		return (key.m_iChannel == m_iChannel)
+			&& (key.m_iController == m_iController);
+	}
+
+private:
+
+	// Instance (key) member variables.
+	unsigned short m_iChannel;
+	unsigned short m_iController;
+};
+
+
+// Hash key function
+inline uint qHash ( const qtractorMidiControl::MapKey& key )
+{
+	return qHash(key.hash());
+}
+
+
+//----------------------------------------------------------------------
+// qtractorMidiControl::MapVal -- MIDI control map data value.
+//
+
+class qtractorMidiControl::MapVal
+{
+public:
+
+	// Constructor.
+	MapVal(Command command = TrackNone, int iParam = 0, bool bFeedback = false)
+		: m_command(command), m_iParam(iParam), m_bFeedback(bFeedback) {}
+
+	// Command accessors
+	void setCommand(Command command)
+		{ m_command = command; }
+	Command command() const
+		{ return m_command; }
+
+	// Parameter accessor (eg. track delta-index)
+	void setParam(int iParam)
+		{ m_iParam = iParam; }
+	int param() const
+		{ return m_iParam; }
+
+	// Feedback flag accessor.
+	void setFeedback(bool bFeedback)
+		{ m_bFeedback = bFeedback; }
+	int isFeedback() const
+		{ return m_bFeedback; }
+
+private:
+
+	// Instance (value) member variables.
+	Command m_command;
+	int     m_iParam;
+	bool    m_bFeedback;
+};
+
+
+//----------------------------------------------------------------------
+// qtractorMidiControl -- MIDI control map (singleton).
+//
+
+// Kind of singleton reference.
+qtractorMidiControl* qtractorMidiControl::g_pMidiControl = NULL;
+
+// Constructor.
+qtractorMidiControl::qtractorMidiControl (void)
+{
+	// Pseudo-singleton reference setup.
+	g_pMidiControl = this;
+
+	// Default controller mapping...
+	clear();
+}
+
+
+// Destructor.
+qtractorMidiControl::~qtractorMidiControl (void)
+{
+	// Pseudo-singleton reference shut-down.
+	g_pMidiControl = NULL;
+}
+
+
+// Kind of singleton reference.
+qtractorMidiControl *qtractorMidiControl::getInstance (void)
+{
+	return g_pMidiControl;
+}
+
+// Clear control map (reset to default).
+void qtractorMidiControl::clear (void)
+{
+	m_controlMap.clear();
+
+	// TEST: (Re)generate the default controller map...
+	//
+	// JLCooper faders (as in US-224)...
+	mapChannelControllerParam(15, TrackGain, 0x40); // No feedback.
+	// Generic track feedback controllers (eg. Behringer BCx2000)...
+	mapChannelParamController(7, TrackGain, 0, true);
+	mapChannelParamController(10, TrackPanning, 0, true);
+	mapChannelParamController(20, TrackMute, 0, true);
+}
+
+
+// Insert new controller mappings.
+void qtractorMidiControl::mapChannelController (
+	unsigned short iChannel, unsigned short iController,
+	Command command, int iParam, bool bFeedback )
+{
+	m_controlMap.insert(
+		MapKey(iChannel, iController),
+		MapVal(command, iParam, bFeedback));
+}
+
+void qtractorMidiControl::mapChannelParamController (
+	unsigned short iController,
+	Command command, int iParam, bool bFeedback )
+{
+	mapChannelController(
+		TrackParam, iController, command, iParam, bFeedback);
+}
+
+void qtractorMidiControl::mapChannelControllerParam (
+	unsigned short iChannel,
+	Command command, int iParam, bool bFeedback )
+{
+	mapChannelController(
+		iChannel, TrackParam, command, iParam, bFeedback);
+}
+
+
+// Resend all the controllers
+// (as output bus changed, or session initialized)
+void qtractorMidiControl::sendAllControllers (void) const
+{
+	qtractorSession *pSession = qtractorSession::getInstance();
+	if (pSession == NULL)
+		return;
+
+#ifdef CONFIG_DEBUG
+	qDebug("qtractorMidiControl::sendAllControllers()");
+#endif
+
+	// Walk through midi controller map...
+	// FIXME: should only be sent if command ask for feedback.
+	ControlMap::ConstIterator it = m_controlMap.constBegin();
+	for ( ; it != m_controlMap.constEnd(); ++it) {
+		const MapVal& val = it.value();
+		if (val.isFeedback()) {
+			const MapKey& key = it.key();
+			unsigned short iChannel = key.channel();
+			unsigned short iController = key.controller();
+			int iParam = val.param();
+			int iTrack = 0;
+			for (qtractorTrack *pTrack = pSession->tracks().first();
+					pTrack; pTrack = pTrack->next()) {
+				if (key.isChannelParam())
+					sendTrackController(pTrack,
+						val.command(), iParam + iTrack, iController);
+				else if (key.isControllerParam())
+					sendTrackController(pTrack,
+						val.command(), iChannel, iParam + iTrack);
+				else if (val.param() == iTrack) {
+					sendTrackController(pTrack,
+						val.command(), iChannel, iController);
+					break; // Bail out from inner track loop.
+				}
+				++iTrack;
+			}
+		}
+	}
+}
+
+
+// Find incoming controller event map.
+qtractorMidiControl::ControlMap::ConstIterator
+qtractorMidiControl::findEvent (
+	qtractorMidiControlEvent *pEvent ) const
+{
+	// Check if controller map includes this event...
+	ControlMap::ConstIterator it = m_controlMap.constBegin();
+	for ( ; it != m_controlMap.constEnd(); ++it) {
+		const MapKey& key = it.key();
+		if (key.match(pEvent->channel(), pEvent->controller()))
+			break;
+	}
+	return it;
+}
+
+
+// Process incoming controller event.
+bool qtractorMidiControl::processEvent (
+	qtractorMidiControlEvent *pEvent ) const
+{
+
+	// Find incoming controller event map tuple.
+	ControlMap::ConstIterator it = findEvent(pEvent);
+	// Is there one mapped, indeed?
+	if (it == m_controlMap.end())
+		return false;
+
+	// Find the track by number...
+	const MapKey& key = it.key();
+	const MapVal& val = it.value();
+
+	int iTrack = -1;	
+	if (key.isChannelParam()) {
+		if (int(pEvent->channel()) >= val.param())
+			iTrack = int(pEvent->channel()) - val.param();
+	}
+	else
+	if (key.isControllerParam()) {
+		if (int(pEvent->controller()) >= val.param())
+			iTrack = int(pEvent->controller()) - val.param();
+	}
+	else iTrack = val.param();
+
+	if (iTrack < 0) return false;
+
+	qtractorMainForm *pMainForm = qtractorMainForm::getInstance();
+	if (pMainForm == NULL)
+		return false;
+
+	qtractorSession *pSession = qtractorSession::getInstance();
+	if (pSession == NULL)
+		return false;
+
+	qtractorTracks *pTracks = pMainForm->tracks();
+	if (pTracks == NULL)
+		return false;
+
+	qtractorTrackList *pTrackList = pTracks->trackList();
+	if (pTrackList == NULL)
+		return false;
+
+	qtractorTrack *pTrack = pTrackList->track(iTrack);
+	if (pTrack == NULL)
+		return false;
+
+	qtractorTrackItemWidget *pTrackWidget
+		= pTrackList->trackWidget(iTrack);
+	if (pTrackWidget == NULL)
+		return false;
+
+	switch (val.command()) {
+	case TrackGain:
+		pSession->execute(
+			new qtractorTrackGainCommand(pTrack,
+				float(pEvent->value()) / 127.0f,
+				true));
+		break;
+	case TrackPanning:
+		pSession->execute(
+			new qtractorTrackPanningCommand(pTrack,
+				(float(pEvent->value()) - 63.0f) / 64.0f,
+				true));
+		break;
+	case TrackMonitor:
+		pSession->execute(
+			new qtractorTrackMonitorCommand(pTrack,
+				bool(pEvent->value() > 0),
+				true));
+		break;
+	case TrackRecord:
+		pSession->execute(
+			new qtractorTrackButtonCommand(
+				pTrackWidget->recordButton(),
+				bool(pEvent->value() > 0),
+				true));
+		break;
+	case TrackMute:
+		pSession->execute(
+			new qtractorTrackButtonCommand(
+				pTrackWidget->muteButton(),
+				bool(pEvent->value() > 0),
+				true));
+		break;
+	case TrackSolo:
+		pSession->execute(
+			new qtractorTrackButtonCommand(
+				pTrackWidget->soloButton(),
+				bool(pEvent->value() > 0),
+				true));
+		break;
+	default:
+		break;
+	}
+
+	return true;
+}
+
+
+// Process incoming command.
+void qtractorMidiControl::processCommand (
+	Command command, int iParam, float fValue ) const
+{
+	switch (command) {
+	case TrackGain:
+		sendParamController(command, iParam, int(127.0f * fValue));
+		break;
+	case TrackPanning:
+		sendParamController(command, iParam, int((64.0f * fValue) + 63.0f));
+		break;
+	default:
+		break;
+	}
+}
+
+
+void qtractorMidiControl::processCommand (
+	Command command, int iParam, bool bValue ) const
+{
+	switch (command) {
+	case TrackMonitor:
+	case TrackRecord:
+	case TrackMute:
+	case TrackSolo:
+		sendParamController(command, iParam, (bValue ? 127 : 0));
+		break;
+	default:
+		break;
+	}
+}
+
+
+// Further processing of outgoing midi controller messages
+void qtractorMidiControl::sendParamController (
+	Command command, int iParam, int iValue ) const
+{
+	// Search for the command and parameter in controller map...
+	ControlMap::ConstIterator it = m_controlMap.constBegin();
+	for ( ; it != m_controlMap.constEnd(); ++it) {
+		const MapKey& key = it.key();
+		const MapVal& val = it.value();
+		if (val.command() == command && val.isFeedback()) {
+			// Now send the message out...
+			if (key.isChannelParam())
+				sendController(val.param() + iParam, key.controller(), iValue);
+			else
+			if (key.isControllerParam())
+				sendController(key.channel(), val.param() + iParam, iValue);
+			else
+			if (val.param() == iParam)
+				sendController(key.channel(), key.controller(), iValue);
+		}
+	}
+}
+
+
+void qtractorMidiControl::sendTrackController ( qtractorTrack *pTrack,
+	Command command, unsigned short iChannel, unsigned short iController ) const
+{
+	int iValue = 0;
+
+	switch (command) {
+	case TrackGain:
+		iValue = int(127.0f * pTrack->gain());
+		break;
+	case TrackPanning:
+		iValue = int((64.0f * pTrack->panning()) + 63.0f);
+		break;
+	case TrackMonitor:
+		iValue = (pTrack->isMonitor() ? 127 : 0);
+		break;
+	case TrackRecord:
+		iValue = (pTrack->isRecord() ? 127 : 0);
+		break;
+	case TrackMute:
+		iValue = (pTrack->isMute() ? 127 : 0);
+		break;
+	case TrackSolo:
+		iValue = (pTrack->isSolo() ? 127 : 0);
+		break;
+	default:
+		break;
+	}
+
+	if (iValue < 0)
+		iValue = 0;
+	else if (iValue > 127)
+		iValue = 127;
+
+	sendController(iChannel, iController, iValue);
+}
+
+
+// Send this value out to midi bus.
+void qtractorMidiControl::sendController (
+	unsigned short iChannel, unsigned short iController, int iValue ) const
+{
+	qtractorSession *pSession = qtractorSession::getInstance();
+	if (pSession == NULL)
+		return;
+
+	qtractorMidiEngine *pMidiEngine = pSession->midiEngine();
+	if (pMidiEngine == NULL)
+		return;
+
+	qtractorMidiBus *pMidiBus = pMidiEngine->controlBus_out();
+	if (pMidiBus == NULL)
+		return;
+
+#ifdef CONFIG_DEBUG
+	qDebug("qtractorMidiControl::sendController(%u, %u, %d)",
+		iChannel, iController, iValue);
+#endif
+
+	pMidiBus->setController(iChannel, iController, iValue);
+}
+
+
+// end of qtractorMidiControl.cpp
diff -dupPNr qtractor.0/src/qtractorMidiControl.h qtractor.1/src/qtractorMidiControl.h
--- qtractor.0/src/qtractorMidiControl.h	1970-01-01 01:00:00.000000000 +0100
+++ qtractor.1/src/qtractorMidiControl.h	2009-05-16 11:21:50.000000000 +0100
@@ -0,0 +1,142 @@
+// qtractorMidiControl.h
+//
+/****************************************************************************
+   Copyright (C) 2005-2009, rncbc aka Rui Nuno Capela. All rights reserved.
+
+   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 2
+   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, write to the Free Software Foundation, Inc.,
+   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+*****************************************************************************/
+
+#ifndef __qtractorMidiControl_h
+#define __qtractorMidiControl_h
+
+#include <QEvent>
+#include <QHash>
+
+// Forward declarations.
+class qtractorTrack;
+
+
+//----------------------------------------------------------------------
+// qtractorMidiControlEvent - MIDI Control custom event.
+//
+
+class qtractorMidiControlEvent : public QEvent
+{
+public:
+
+	// Contructor.
+	qtractorMidiControlEvent(QEvent::Type eType, unsigned short iChannel,
+		unsigned char controller, unsigned char value)
+		: QEvent(eType), m_channel(iChannel),
+			m_controller(controller), m_value(value) {}
+
+	// Accessors.
+	unsigned short channel()    const { return m_channel; }
+	unsigned short controller() const { return m_controller; }
+	unsigned short value()      const { return m_value; }
+
+private:
+
+	// Instance variables.
+	unsigned short m_channel;
+	unsigned short m_controller;
+	unsigned short m_value;
+};
+
+
+//----------------------------------------------------------------------
+// qtractorMidiControl -- MIDI control map (singleton).
+//
+
+class qtractorMidiControl
+{
+public:
+
+	// Forward declarations
+	class MapKey;
+	class MapVal;
+
+	typedef QHash<MapKey, MapVal> ControlMap;
+
+	// Controller command types.
+	enum Command {
+		TrackNone    = 0,
+		TrackGain    = 1,
+		TrackPanning = 2,
+		TrackMonitor = 3,
+		TrackRecord  = 4,
+		TrackMute    = 5,
+		TrackSolo    = 6
+	};
+
+	// Key param masks (wildcard flags).
+	enum { TrackParam = 0x4000, MaskParam = 0x3fff };
+
+	// Constructor.
+	qtractorMidiControl();
+
+	// Destructor.
+	~qtractorMidiControl();
+
+	static qtractorMidiControl *getInstance();
+
+	// Clear control map (reset to default).
+	void clear();
+
+	// Insert new contrller mappings.
+	void mapChannelController(
+		unsigned short iChannel, unsigned short iController,
+		Command command, int iParam = 0, bool bFeedback = false);
+	void mapChannelParamController (unsigned short iController,
+		Command command, int iParam = 0, bool bFeedback = false);
+	void mapChannelControllerParam(unsigned short iChannel,
+		Command command, int iParam = 0, bool bFeedback = false);
+
+	// Re-send all controllers.
+	void sendAllControllers() const;
+
+	// Process incoming controller messages.
+	bool processEvent(qtractorMidiControlEvent *pEvent) const;
+
+	// Process incoming command.
+	void processCommand(Command command, int iParam, float fValue) const;
+	void processCommand(Command command, int iParam, bool bValue) const;
+
+protected:
+
+	// Find incoming controller event map.
+	ControlMap::ConstIterator findEvent(qtractorMidiControlEvent *pEvent) const;
+
+	// Overloaded controller value senders.
+	void sendParamController(Command command, int iParam, int iValue) const;
+	void sendTrackController(qtractorTrack *pTrack, Command command,
+		unsigned short iChannel, unsigned short iController) const;
+	void sendController(
+		unsigned short iChannel, unsigned short iController, int iValue) const;
+
+private:
+
+	// MIDI control map.
+	ControlMap m_controlMap;
+
+	// Pseudo-singleton instance.
+	static qtractorMidiControl *g_pMidiControl;
+};
+
+
+#endif  // __qtractorMidiControl_h
+
+// end of qtractorMidiControl.h
diff -dupPNr qtractor.0/src/qtractorMidiEngine.cpp qtractor.1/src/qtractorMidiEngine.cpp
--- qtractor.0/src/qtractorMidiEngine.cpp	2009-04-21 15:39:22.000000000 +0100
+++ qtractor.1/src/qtractorMidiEngine.cpp	2009-05-16 10:50:25.000000000 +0100
@@ -31,6 +31,7 @@
 #include "qtractorMidiSequence.h"
 #include "qtractorMidiClip.h"
 #include "qtractorMidiBuffer.h"
+#include "qtractorMidiControl.h"
 
 #include "qtractorPlugin.h"
 
@@ -2311,19 +2312,19 @@ int qtractorMidiEngine::updateConnects (
 	// Control bus inputs...
 	if (m_bControlBus && m_pIControlBus) {
 		iUpdate += m_pIControlBus->updateConnects(
-				qtractorBus::Input, m_pIControlBus->inputs(), true);
+			qtractorBus::Input, m_pIControlBus->inputs(), true);
 	}
 
 	// Control bus outputs...
 	if (m_bControlBus && m_pOControlBus) {
 		iUpdate += m_pOControlBus->updateConnects(
-				qtractorBus::Output, m_pOControlBus->outputs(), true);
+			qtractorBus::Output, m_pOControlBus->outputs(), true);
 	}
 
 	// Metronome bus outputs...
 	if (m_bMetroBus && m_pMetroBus) {
 		iUpdate += m_pMetroBus->updateConnects(
-				qtractorBus::Output, m_pMetroBus->outputs(), true);
+			qtractorBus::Output, m_pMetroBus->outputs(), true);
 	}
 
 	// Done.
@@ -2639,6 +2640,12 @@ void qtractorMidiBus::setController ( qt
 	setControllerEx(pTrack->midiChannel(), iController, iValue, pTrack);
 }
 
+void qtractorMidiBus::setController ( unsigned short iChannel,
+	int iController, int iValue ) const
+{
+	setControllerEx(iChannel, iController, iValue, NULL);
+}
+
 
 // Direct MIDI controller common helper.
 void qtractorMidiBus::setControllerEx ( unsigned short iChannel,
diff -dupPNr qtractor.0/src/qtractorMidiEngine.h qtractor.1/src/qtractorMidiEngine.h
--- qtractor.0/src/qtractorMidiEngine.h	2009-02-25 19:58:56.000000000 +0000
+++ qtractor.1/src/qtractorMidiEngine.h	2009-05-15 21:16:11.000000000 +0100
@@ -307,6 +307,8 @@ public:
 	// Direct MIDI controller helpers.
 	void setController(qtractorTrack *pTrack,
 		int iController, int iValue = 0) const;
+	void setController(unsigned short iChannel,
+		int iController, int iValue = 0) const;
 
 	// Direct MIDI note helper.
 	void sendNote(qtractorTrack *pTrack,
@@ -387,34 +389,6 @@ private:
 };
 
 
-//----------------------------------------------------------------------
-// qtractorMidiControlEvent - MIDI Control custom event.
-//
-
-class qtractorMidiControlEvent : public QEvent
-{
-public:
-
-	// Contructor.
-	qtractorMidiControlEvent(QEvent::Type eType, unsigned short iChannel,
-		unsigned char controller, unsigned char value)
-		: QEvent(eType), m_channel(iChannel),
-			m_controller(controller), m_value(value) {}
-
-	// Accessors.
-	unsigned short channel()    const { return m_channel; }
-	unsigned short controller() const { return m_controller; }
-	unsigned short value()      const { return m_value; }
-
-private:
-
-	// Instance variables.
-	unsigned short m_channel;
-	unsigned short m_controller;
-	unsigned short m_value;
-};
-
-
 //----------------------------------------------------------------------
 // qtractorMidiSppEvent - MIDI SPP custom event.
 //
diff -dupPNr qtractor.0/src/qtractorMixer.cpp qtractor.1/src/qtractorMixer.cpp
--- qtractor.0/src/qtractorMixer.cpp	2009-02-25 11:03:14.000000000 +0000
+++ qtractor.1/src/qtractorMixer.cpp	2009-05-15 21:16:11.000000000 +0100
@@ -1,7 +1,7 @@
 // qtractorMixer.cpp
 //
 /****************************************************************************
-   Copyright (C) 2005-2008, rncbc aka Rui Nuno Capela. All rights reserved.
+   Copyright (C) 2005-2009, rncbc aka Rui Nuno Capela. All rights reserved.
 
    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
@@ -806,7 +806,7 @@ void qtractorMixerStrip::gainChangedSlot
 		return;
 
 #ifdef CONFIG_DEBUG
-	qDebug("qtractorMixerStrip[%p]::gainChangedSlot(%.3g)\n", this, fGain);
+	qDebug("qtractorMixerStrip[%p]::gainChangedSlot(%.3g)", this, fGain);
 #endif
 
 	qtractorSession *pSession = qtractorSession::getInstance();
diff -dupPNr qtractor.0/src/qtractorMixer.h qtractor.1/src/qtractorMixer.h
--- qtractor.0/src/qtractorMixer.h	2008-10-21 00:34:40.000000000 +0100
+++ qtractor.1/src/qtractorMixer.h	2009-05-15 21:16:11.000000000 +0100
@@ -1,7 +1,7 @@
 // qtractorMixer.h
 //
 /****************************************************************************
-   Copyright (C) 2005-2008, rncbc aka Rui Nuno Capela. All rights reserved.
+   Copyright (C) 2005-2009, rncbc aka Rui Nuno Capela. All rights reserved.
 
    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
diff -dupPNr qtractor.0/src/qtractorTrackCommand.cpp qtractor.1/src/qtractorTrackCommand.cpp
--- qtractor.0/src/qtractorTrackCommand.cpp	2008-12-07 15:59:21.000000000 +0000
+++ qtractor.1/src/qtractorTrackCommand.cpp	2009-05-16 00:31:56.000000000 +0100
@@ -1,7 +1,7 @@
 // qtractorTrackCommand.cpp
 //
 /****************************************************************************
-   Copyright (C) 2005-2008, rncbc aka Rui Nuno Capela. All rights reserved.
+   Copyright (C) 2005-2009, rncbc aka Rui Nuno Capela. All rights reserved.
 
    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
@@ -30,6 +30,7 @@
 #include "qtractorTrackView.h"
 #include "qtractorTrackButton.h"
 #include "qtractorMidiEngine.h"
+#include "qtractorMidiControl.h"
 #include "qtractorMixer.h"
 #include "qtractorMeter.h"
 
@@ -435,13 +436,34 @@ bool qtractorEditTrackCommand::undo (voi
 
 
 //----------------------------------------------------------------------
+// class qtractorTrackControlCommand - implementation.
+//
+
+// Constructor.
+qtractorTrackControlCommand::qtractorTrackControlCommand (
+	const QString& sName, qtractorTrack *pTrack, bool bMidiControl )
+	: qtractorTrackCommand(sName, pTrack),
+		m_bMidiControl(bMidiControl), m_iMidiControlFeedback(0)
+{
+}
+
+
+// Primitive control predicate.
+bool qtractorTrackControlCommand::midiControlFeedback (void)
+{
+	return (m_bMidiControl ? (++m_iMidiControlFeedback > 1) : true);
+}
+
+
+//----------------------------------------------------------------------
 // class qtractorTrackButtonCommand - implementation.
 //
 
 // Constructor.
 qtractorTrackButtonCommand::qtractorTrackButtonCommand (
-	qtractorTrackButton *pTrackButton, bool bOn )
-	: qtractorTrackCommand(QString::null, pTrackButton->track())
+	qtractorTrackButton *pTrackButton, bool bOn, bool bMidiControl )
+	: qtractorTrackControlCommand(
+		QString(), pTrackButton->track(), bMidiControl)
 {
 	m_toolType = pTrackButton->toolType();
 	m_bOn = bOn;
@@ -507,10 +529,12 @@ bool qtractorTrackButtonCommand::redo (v
 
 	bool bOn = false;
 	qtractorMmcEvent::SubCommand scmd = qtractorMmcEvent::TRACK_NONE;
+	qtractorMidiControl::Command ccmd = qtractorMidiControl::TrackNone;
 
 	switch (m_toolType) {
 	case qtractorTrack::Record:
 		scmd = qtractorMmcEvent::TRACK_RECORD;
+		ccmd = qtractorMidiControl::TrackRecord;
 		// Special stuffing if currently recording at first place...
 		bOn = pTrack->isRecord();
 		if (bOn && !m_bOn
@@ -535,23 +559,35 @@ bool qtractorTrackButtonCommand::redo (v
 		break;
 	case qtractorTrack::Mute:
 		scmd = qtractorMmcEvent::TRACK_MUTE;
+		ccmd = qtractorMidiControl::TrackMute;
 		bOn = pTrack->isMute();
 		pTrack->setMute(m_bOn);
 		break;
 	case qtractorTrack::Solo:
 		scmd = qtractorMmcEvent::TRACK_SOLO;
+		ccmd = qtractorMidiControl::TrackSolo;
 		bOn = pTrack->isSolo();
 		pTrack->setSolo(m_bOn);
 		break;
+	default:
+		// Whaa?
+		return false;
 	}
 
-	// Track-list and mixer update...
+	// Send MMC MASKED_WRITE command...
+	qtractorTrackList *pTrackList = pMainForm->tracks()->trackList();
 	qtractorMidiEngine *pMidiEngine = pSession->midiEngine();
-	qtractorTrackList  *pTrackList  = pMainForm->tracks()->trackList();
+	int iTrack = pTrackList->trackRow(pTrack);
+	if (pMidiEngine)
+		pMidiEngine->sendMmcMaskedWrite(scmd, iTrack, m_bOn);
+
+	// Send MIDI controller command...
+	if (midiControlFeedback()) {
+		qtractorMidiControl *pMidiControl = qtractorMidiControl::getInstance();
+		if (pMidiControl)
+			pMidiControl->processCommand(ccmd, iTrack, m_bOn);
+	}
 
-	// Send MMC MASKED_WRITE command...
-	pMidiEngine->sendMmcMaskedWrite(scmd,
-		pTrackList->trackRow(pTrack), m_bOn);
 	// Update track list item...
 	pTrackList->updateTrack(pTrack);
 
@@ -569,6 +605,9 @@ bool qtractorTrackButtonCommand::redo (v
 		// Done with single mode.
 	} else {
 		// Exclusive mode.
+		qtractorTrackList *pTrackList = pMainForm->tracks()->trackList();
+		qtractorMidiEngine *pMidiEngine = pSession->midiEngine();
+		qtractorMidiControl *pMidiControl = qtractorMidiControl::getInstance();
 		QListIterator<TrackItem *> iter(m_tracks);
 		while (iter.hasNext()) {
 			TrackItem *pTrackItem = iter.next();
@@ -584,8 +623,12 @@ bool qtractorTrackButtonCommand::redo (v
 				pTrack->setSolo(pTrackItem->on);
 			}
 			// Send MMC MASKED_WRITE command...
-			pMidiEngine->sendMmcMaskedWrite(scmd,
-				pTrackList->trackRow(pTrack), pTrackItem->on);
+			int iTrack = pTrackList->trackRow(pTrack);
+			if (pMidiEngine)
+				pMidiEngine->sendMmcMaskedWrite(scmd, iTrack, pTrackItem->on);
+			// Send MIDI controller command...
+			if (pMidiControl)
+				pMidiControl->processCommand(ccmd, iTrack, pTrackItem->on);
 			// Update track list item...
 			pTrackList->updateTrack(pTrack);
 			// Swap for undo...
@@ -614,8 +657,9 @@ bool qtractorTrackButtonCommand::undo (v
 
 // Constructor.
 qtractorTrackMonitorCommand::qtractorTrackMonitorCommand (
-	qtractorTrack *pTrack, bool bMonitor )
-	: qtractorTrackCommand(QObject::tr("track monitor"), pTrack)
+	qtractorTrack *pTrack, bool bMonitor, bool bMidiControl )
+	: qtractorTrackControlCommand(
+		QObject::tr("track monitor"), pTrack, bMidiControl)
 {
 	m_bMonitor = bMonitor;
 
@@ -662,8 +706,21 @@ bool qtractorTrackMonitorCommand::redo (
 
 	// Save undo value...
 	bool bMonitor = pTrack->isMonitor();
+
 	// Set track monitoring...
 	pTrack->setMonitor(m_bMonitor);
+
+	// Send MIDI controller command(s)...
+	if (midiControlFeedback()) {
+		qtractorMidiControl *pMidiControl = qtractorMidiControl::getInstance();
+		if (pMidiControl) {
+			qtractorTrackList *pTrackList = pMainForm->tracks()->trackList();
+			int iTrack = pTrackList->trackRow(pTrack);
+			pMidiControl->processCommand(
+				qtractorMidiControl::TrackMonitor, iTrack, m_bMonitor);
+		}
+	}
+
 	// Set undo value...
 	m_bMonitor = bMonitor;
 
@@ -678,12 +735,20 @@ bool qtractorTrackMonitorCommand::redo (
 		// Done with single mode.
 	} else {
 		// Exclusive mode.
+		qtractorTrackList *pTrackList = pMainForm->tracks()->trackList();
+		qtractorMidiControl *pMidiControl = qtractorMidiControl::getInstance();
 		QListIterator<TrackItem *> iter(m_tracks);
 		while (iter.hasNext()) {
 			TrackItem *pTrackItem = iter.next();
 			pTrack = pTrackItem->track;
 			bMonitor = pTrack->isMonitor();
 			pTrack->setMonitor(pTrackItem->on);
+			// Send MIDI controller command...
+			if (pMidiControl) {
+				int iTrack = pTrackList->trackRow(pTrack);
+				pMidiControl->processCommand(
+					qtractorMidiControl::TrackMonitor, iTrack, pTrackItem->on);
+			}
 			// Swap for undo...
 			pTrackItem->on = bMonitor;
 		}
@@ -701,8 +766,9 @@ bool qtractorTrackMonitorCommand::redo (
 //
 // Constructor.
 qtractorTrackGainCommand::qtractorTrackGainCommand (
-	qtractorTrack *pTrack, float fGain )
-	: qtractorTrackCommand(QObject::tr("track gain"), pTrack)
+	qtractorTrack *pTrack, float fGain, bool bMidiControl )
+	: qtractorTrackControlCommand(
+		QObject::tr("track gain"), pTrack, bMidiControl)
 {
 	m_fGain = fGain;
 	m_fPrevGain = 1.0f;
@@ -743,37 +809,52 @@ qtractorTrackGainCommand::qtractorTrackG
 // Track-gain command method.
 bool qtractorTrackGainCommand::redo (void)
 {
+	qtractorMainForm *pMainForm = qtractorMainForm::getInstance();
+	if (pMainForm == NULL)
+		return false;
+
 	qtractorTrack *pTrack = track();
 	if (pTrack == NULL)
 		return false;
 
-	// Set track gain (repective monitor gets set too...)
+	// Set undo value...
 	float fGain = (m_bPrevGain ? m_fPrevGain : pTrack->gain());	
+
+	// Set track gain (respective monitor gets set too...)
 	pTrack->setGain(m_fGain);
+
 	// MIDI tracks are special...
 	if (pTrack->trackType() == qtractorTrack::Midi) {
-		// Now we gotta make sure of proper MIDI bus...
+		// Gotta make sure we've a proper MIDI bus...
 		qtractorMidiBus *pMidiBus
 			= static_cast<qtractorMidiBus *> (pTrack->outputBus());
 		if (pMidiBus)
 			pMidiBus->setVolume(pTrack, m_fGain);
 	}
 
+	// Send MIDI controller command(s)...
+	if (midiControlFeedback()) {
+		qtractorMidiControl *pMidiControl = qtractorMidiControl::getInstance();
+		if (pMidiControl) {
+			qtractorTrackList *pTrackList = pMainForm->tracks()->trackList();
+			int iTrack = pTrackList->trackRow(pTrack);
+			pMidiControl->processCommand(
+				qtractorMidiControl::TrackGain, iTrack, m_fGain);
+		}
+	}
+
 	// Set undo value...
 	m_bPrevGain = false;
 	m_fPrevGain = m_fGain;
 	m_fGain     = fGain;
 
 	// Mixer/Meter turn...
-	qtractorMainForm *pMainForm = qtractorMainForm::getInstance();
-	if (pMainForm) {
-		qtractorMixer *pMixer = pMainForm->mixer();
-		if (pMixer) {
-			qtractorMixerStrip *pStrip
-				= pMixer->trackRack()->findStrip(pTrack->monitor());
-			if (pStrip && pStrip->meter())
-				pStrip->meter()->updateGain();
-		}
+	qtractorMixer *pMixer = pMainForm->mixer();
+	if (pMixer) {
+		qtractorMixerStrip *pStrip
+			= pMixer->trackRack()->findStrip(pTrack->monitor());
+		if (pStrip && pStrip->meter())
+			pStrip->meter()->updateGain();
 	}
 
 	return true;
@@ -786,8 +867,9 @@ bool qtractorTrackGainCommand::redo (voi
 
 // Constructor.
 qtractorTrackPanningCommand::qtractorTrackPanningCommand (
-	qtractorTrack *pTrack, float fPanning )
-	: qtractorTrackCommand(QObject::tr("track pan"), pTrack)
+	qtractorTrack *pTrack, float fPanning, bool bMidiControl )
+	: qtractorTrackControlCommand(
+		QObject::tr("track pan"), pTrack, bMidiControl)
 {
 	m_fPanning = fPanning;
 	m_fPrevPanning = 0.0f;
@@ -828,37 +910,52 @@ qtractorTrackPanningCommand::qtractorTra
 // Track-panning command method.
 bool qtractorTrackPanningCommand::redo (void)
 {
+	qtractorMainForm *pMainForm = qtractorMainForm::getInstance();
+	if (pMainForm == NULL)
+		return false;
+
 	qtractorTrack *pTrack = track();
 	if (pTrack == NULL)
 		return false;
 
-	// Set track panning (repective monitor gets set too...)
+	// Set undo value...
 	float fPanning = (m_bPrevPanning ? m_fPrevPanning : pTrack->panning());	
+
+	// Set track panning (respective monitor gets set too...)
 	pTrack->setPanning(m_fPanning);
+
 	// MIDI tracks are special...
 	if (pTrack->trackType() == qtractorTrack::Midi) {
-		// Now we gotta make sure of proper MIDI bus...
+		// Gotta make sure we've a proper MIDI bus...
 		qtractorMidiBus *pMidiBus
 			= static_cast<qtractorMidiBus *> (pTrack->outputBus());
 		if (pMidiBus)
 			pMidiBus->setPanning(pTrack, m_fPanning);
 	}
-	
+
+	// Send MIDI controller command(s)...
+	if (midiControlFeedback()) {
+		qtractorMidiControl *pMidiControl = qtractorMidiControl::getInstance();
+		if (pMidiControl) {
+			qtractorTrackList *pTrackList = pMainForm->tracks()->trackList();
+			int iTrack = pTrackList->trackRow(pTrack);
+			pMidiControl->processCommand(
+				qtractorMidiControl::TrackPanning, iTrack, m_fPanning);
+		}
+	}
+
 	// Set undo value...
 	m_bPrevPanning = false;
 	m_fPrevPanning = m_fPanning;
 	m_fPanning     = fPanning;
 
 	// Mixer/Meter turn...
-	qtractorMainForm *pMainForm = qtractorMainForm::getInstance();
-	if (pMainForm) {
-		qtractorMixer *pMixer = pMainForm->mixer();
-		if (pMixer) {
-			qtractorMixerStrip *pStrip
-				= pMixer->trackRack()->findStrip(pTrack->monitor());
-			if (pStrip && pStrip->meter())
-				pStrip->meter()->updatePanning();
-		}
+	qtractorMixer *pMixer = pMainForm->mixer();
+	if (pMixer) {
+		qtractorMixerStrip *pStrip
+			= pMixer->trackRack()->findStrip(pTrack->monitor());
+		if (pStrip && pStrip->meter())
+			pStrip->meter()->updatePanning();
 	}
 
 	return true;
diff -dupPNr qtractor.0/src/qtractorTrackCommand.h qtractor.1/src/qtractorTrackCommand.h
--- qtractor.0/src/qtractorTrackCommand.h	2008-09-14 17:44:37.000000000 +0100
+++ qtractor.1/src/qtractorTrackCommand.h	2009-05-16 00:30:47.000000000 +0100
@@ -1,7 +1,7 @@
 // qtractorTrackCommand.h
 //
 /****************************************************************************
-   Copyright (C) 2005-2008, rncbc aka Rui Nuno Capela. All rights reserved.
+   Copyright (C) 2005-2009, rncbc aka Rui Nuno Capela. All rights reserved.
 
    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
@@ -212,15 +212,41 @@ private:
 
 
 //----------------------------------------------------------------------
+// class qtractorTrackControlCommand - declaration.
+//
+
+class qtractorTrackControlCommand : public qtractorTrackCommand
+{
+public:
+
+	// Constructor.
+	qtractorTrackControlCommand(const QString& sName,
+		qtractorTrack *pTrack, bool bMidiControl = false);
+
+protected:
+
+	// Primitive control predicates.
+	bool midiControlFeedback();
+
+private:
+
+	// Instance variables.
+	bool m_bMidiControl;
+	int  m_iMidiControlFeedback; 
+};
+
+
+//----------------------------------------------------------------------
 // class qtractorTrackButtonCommand - declaration.
 //
 
-class qtractorTrackButtonCommand : public qtractorTrackCommand
+class qtractorTrackButtonCommand : public qtractorTrackControlCommand
 {
 public:
 
 	// Constructor.
-	qtractorTrackButtonCommand(qtractorTrackButton *pTrackButton, bool bOn);
+	qtractorTrackButtonCommand(qtractorTrackButton *pTrackButton,
+		bool bOn, bool bMidiControl = false);
 
 	// Destructor.
 	~qtractorTrackButtonCommand();
@@ -248,12 +274,13 @@ private:
 // class qtractorTrackMonitorCommand - declaration.
 //
 
-class qtractorTrackMonitorCommand : public qtractorTrackCommand
+class qtractorTrackMonitorCommand : public qtractorTrackControlCommand
 {
 public:
 
 	// Constructor.
-	qtractorTrackMonitorCommand(qtractorTrack *pTrack, bool bMonitor);
+	qtractorTrackMonitorCommand(qtractorTrack *pTrack,
+		bool bMonitor, bool bMidiControl = false);
 
 	// Destructor.
 	~qtractorTrackMonitorCommand();
@@ -265,7 +292,7 @@ public:
 private:
 
 	// Instance variables.
-	bool  m_bMonitor;
+	bool m_bMonitor;
 
 	// Extra track list.
 	QList<TrackItem *> m_tracks;
@@ -276,12 +303,13 @@ private:
 // class qtractorTrackGainCommand - declaration.
 //
 
-class qtractorTrackGainCommand : public qtractorTrackCommand
+class qtractorTrackGainCommand : public qtractorTrackControlCommand
 {
 public:
 
 	// Constructor.
-	qtractorTrackGainCommand(qtractorTrack *pTrack, float fGain);
+	qtractorTrackGainCommand(qtractorTrack *pTrack,
+		float fGain, bool bMidiControl = false);
 
 	// Track-gain command methods.
 	bool redo();
@@ -306,12 +334,13 @@ private:
 // class qtractorTrackPanningCommand - declaration.
 //
 
-class qtractorTrackPanningCommand : public qtractorTrackCommand
+class qtractorTrackPanningCommand : public qtractorTrackControlCommand
 {
 public:
 
 	// Constructor.
-	qtractorTrackPanningCommand(qtractorTrack *pTrack, float fPanning);
+	qtractorTrackPanningCommand(qtractorTrack *pTrack,
+		float fPanning, bool bMidiControl = false);
 
 	// Track-panning command methods.
 	bool redo();
@@ -335,4 +364,3 @@ private:
 #endif	// __qtractorTrackCommand_h
 
 // end of qtractorTrackCommand.h
-
diff -dupPNr qtractor.0/src/qtractorTrackList.cpp qtractor.1/src/qtractorTrackList.cpp
--- qtractor.0/src/qtractorTrackList.cpp	2009-04-08 11:35:35.000000000 +0100
+++ qtractor.1/src/qtractorTrackList.cpp	2009-05-15 21:16:11.000000000 +0100
@@ -459,6 +459,15 @@ qtractorTrack *qtractorTrackList::track
 }
 
 
+qtractorTrackItemWidget *qtractorTrackList::trackWidget ( int iTrack ) const
+{
+	if (iTrack < 0 || iTrack >= m_items.count())
+		return NULL;
+
+	return m_items.at(iTrack)->widget;
+}
+
+
 // Retrive the given track row rectangular (in viewport coordinates).
 QRect qtractorTrackList::trackRect ( int iTrack ) const
 {
diff -dupPNr qtractor.0/src/qtractorTrackList.h qtractor.1/src/qtractorTrackList.h
--- qtractor.0/src/qtractorTrackList.h	2009-01-15 14:15:36.000000000 +0000
+++ qtractor.1/src/qtractorTrackList.h	2009-05-15 21:16:11.000000000 +0100
@@ -114,6 +114,7 @@ public:
 
 	// Find the track pointer reference from list view item row.
 	qtractorTrack *track(int iTrack) const;
+	qtractorTrackItemWidget *trackWidget(int iTrack) const;
 
 	// Insert/remove a track item; return actual track of incident.
 	int insertTrack(int iTrack, qtractorTrack *pTrack);
diff -dupPNr qtractor.0/win32/qtractor.pro qtractor.1/win32/qtractor.pro
--- qtractor.0/win32/qtractor.pro	2009-02-08 15:12:58.000000000 +0000
+++ qtractor.1/win32/qtractor.pro	2009-05-15 21:16:11.000000000 +0100
@@ -35,6 +35,7 @@ HEADERS += ../src/qtractorAbout.h \
            ../src/qtractorMidiBuffer.h \
            ../src/qtractorMidiClip.h \
            ../src/qtractorMidiConnect.h \
+           ../src/qtractorMidiControl.h \
            ../src/qtractorMidiCursor.h \
            ../src/qtractorMidiEditor.h \
            ../src/qtractorMidiEditCommand.h \
@@ -130,6 +131,7 @@ SOURCES += ../src/main.cpp \
            ../src/qtractorMidiBuffer.cpp \
            ../src/qtractorMidiClip.cpp \
            ../src/qtractorMidiConnect.cpp \
+           ../src/qtractorMidiControl.cpp \
            ../src/qtractorMidiCursor.cpp \
            ../src/qtractorMidiEditor.cpp \
            ../src/qtractorMidiEditCommand.cpp \
