added support for easy MIDI-port-subscription inside LMMS

git-svn-id: https://lmms.svn.sf.net/svnroot/lmms/trunk/lmms@29 0778d3d1-df1d-0410-868b-ea421aaaa00d
This commit is contained in:
Tobias Doerffel
2005-12-08 10:31:29 +00:00
parent 9c899fe33f
commit 457c39ccdf
9 changed files with 472 additions and 15 deletions

View File

@@ -1,3 +1,15 @@
2005-12-07 Tobias Doerffel <tobydox/at/users.sourceforge.net>
* include/midi_alsa_seq.h:
* include/midi_client.h:
* include/midi_tab_widget.h:
* src/core/midi_tab_widget.cpp:
* src/midi/midi_alsa_seq.cpp:
* src/midi/midi_client.cpp:
added support for easy MIDI-port-subscription inside LMMS by just
selecting according source-/destination-port from a menu in
MIDI-setup-tab in each channel-window
2005-12-06 Tobias Doerffel <tobydox/at/users.sourceforge.net>
* src/core/note_play_handle.cpp:

4
TODO
View File

@@ -1,11 +1,9 @@
to be done as soon as possible:
- fix midi-channel-selection stuff and add possibility to select destination-MIDI-port inside LMMS
- save connections in midi-tab-widget and debug it (when disabling input/output, automatically unsubscribe all ports!)
- add note-len- and note-alignment-selectbox to piano-roll
- make it possible in bb-editor to add single beats to beat-patterns
- select connected midi-device in midi-setup-tabwidget
- fix audio/midi-settings stuff/translation
- arpeggio: send midi-out-events via channel-track
- tooltips for controls in MIDI-tab
- sample-track: sane bg and wave-color
- dnd everywhere: presets, samples (afp/sample-track), TCO's, knob-values

View File

@@ -2,8 +2,8 @@
# Process this file with autoconf to produce a configure script.
AC_PREREQ(2.50)
AC_INIT(lmms, 0.1.1-cvs20051206, tobydox/at/users.sourceforge.net)
AM_INIT_AUTOMAKE(lmms, 0.1.1-cvs20051206)
AC_INIT(lmms, 0.1.1-cvs20051207, tobydox/at/users.sourceforge.net)
AM_INIT_AUTOMAKE(lmms, 0.1.1-cvs20051207)
AM_CONFIG_HEADER(config.h)

View File

@@ -43,11 +43,13 @@
#ifdef QT4
#include <QThread>
#include <QTimer>
#else
#include <qobject.h>
#include <qthread.h>
#include <qtimer.h>
#endif
@@ -91,6 +93,38 @@ public:
virtual void FASTCALL removePort( midiPort * _port );
// list seq-ports from ALSA
inline virtual const QStringList & readablePorts( void ) const
{
return( m_readablePorts );
}
virtual const QStringList & writeablePorts( void ) const
{
return( m_writeablePorts );
}
// (un)subscribe given midiPort to/from destination-port
virtual void subscribeReadablePort( midiPort * _port,
const QString & _dest,
bool _unsubscribe = FALSE );
virtual void subscribeWriteablePort( midiPort * _port,
const QString & _dest,
bool _unsubscribe = FALSE );
virtual void connectRPChanged( QObject * _receiver,
const char * _member )
{
connect( this, SIGNAL( readablePortsChanged() ),
_receiver, _member );
}
virtual void connectWPChanged( QObject * _receiver,
const char * _member )
{
connect( this, SIGNAL( writeablePortsChanged() ),
_receiver, _member );
}
class setupWidget : public midiClient::setupWidget
{
@@ -108,6 +142,7 @@ public:
private slots:
void changeQueueTempo( int _bpm );
void updatePortList( void );
private:
@@ -127,6 +162,16 @@ private:
volatile bool m_quit;
QTimer m_portListUpdateTimer;
QStringList m_readablePorts;
QStringList m_writeablePorts;
signals:
void readablePortsChanged( void );
void writeablePortsChanged( void );
} ;
#endif

View File

@@ -31,10 +31,12 @@
#ifdef QT4
#include <QVector>
#include <QStringList>
#else
#include <qvaluevector.h>
#include <qstringlist.h>
#endif
@@ -74,6 +76,38 @@ public:
// re-implemented methods HAVE to call removePort() of base-class!!
virtual void FASTCALL removePort( midiPort * _port );
// returns whether client works with raw-MIDI, only needs to be
// re-implemented by midiClientRaw for returning TRUE
inline virtual bool isRaw( void ) const
{
return( FALSE );
}
// if not raw-client, return all readable/writeable ports
virtual const QStringList & readablePorts( void ) const;
virtual const QStringList & writeablePorts( void ) const;
// (un)subscribe given midiPort to/from destination-port
virtual void subscribeReadablePort( midiPort * _port,
const QString & _dest,
bool _unsubscribe = FALSE );
virtual void subscribeWriteablePort( midiPort * _port,
const QString & _dest,
bool _unsubscribe = FALSE );
// qobject-derived classes can use this for make a slot being
// connected to signal of non-raw-MIDI-client if port-lists change
virtual void connectRPChanged( QObject *, const char * )
{
}
virtual void connectWPChanged( QObject *, const char * )
{
}
// tries to open either MIDI-driver from config-file or (if it fails)
// any other working
static midiClient * openMidiClient( void );
@@ -102,6 +136,7 @@ protected:
const Uint8 RAW_MIDI_PARSE_BUF_SIZE = 16;
@@ -111,22 +146,34 @@ public:
midiClientRaw( void );
virtual ~midiClientRaw();
// we are raw-clients for sure!
inline virtual bool isRaw( void ) const
{
return( TRUE );
}
protected:
// generic raw-MIDI-parser which generates appropriate MIDI-events
void FASTCALL parseData( const Uint8 _c );
// to be implemented by actual client-implementation
virtual void FASTCALL sendByte( const Uint8 _c ) = 0;
private:
// this does MIDI-event-process
void processParsedEvent();
virtual void FASTCALL processOutEvent( const midiEvent & _me,
const midiTime & _time,
const midiPort * _port );
// small helper function returning length of a certain event - this
// is neccessary for parsing raw-MIDI-data
static Uint8 FASTCALL eventLength( const Uint8 _event );
// data being used for parsing
struct midiParserData
{
Uint8 m_status; // identifies the type of event, that
@@ -142,7 +189,9 @@ private:
// buffer for incoming data
midiEvent m_midiEvent; // midi-event
} m_midiParseData;
} ;
#endif

View File

@@ -46,7 +46,7 @@
#include "settings.h"
class QComboBox;
class QMenu;
class QPixmap;
class channelTrack;
@@ -78,7 +78,10 @@ protected slots:
void inputChannelChanged( int );
void outputChannelChanged( int );
void midiPortModeToggled( bool );
void readablePortsChanged( void );
void writeablePortsChanged( void );
void activatedReadablePort( int _id );
void activatedWriteablePort( int _id );
private:
channelTrack * m_channelTrack;
@@ -90,6 +93,8 @@ private:
ledCheckBox * m_receiveCheckBox;
ledCheckBox * m_sendCheckBox;
ledCheckBox * m_routeCheckBox;
QMenu * m_readablePorts;
QMenu * m_writeablePorts;
} ;

View File

@@ -29,10 +29,16 @@
#ifdef QT4
#include <Qt/QtXml>
#include <QListBox>
#include <QMenu>
#include <QToolButton>
#else
#include <qdom.h>
#include <qlistbox.h>
#include <qpopupmenu.h>
#include <qtoolbutton.h>
#endif
@@ -45,7 +51,7 @@
#include "lcd_spinbox.h"
#include "tooltip.h"
#include "song_editor.h"
#include "midi_client.h"
@@ -58,7 +64,7 @@ midiTabWidget::midiTabWidget( channelTrack * _channel_track,
{
m_setupTabWidget = new tabWidget( tr( "MIDI-SETUP FOR THIS CHANNEL" ),
this );
m_setupTabWidget->setGeometry( 4, 5, 238, 130 );
m_setupTabWidget->setGeometry( 4, 5, 238, 180 );
m_inputChannelSpinBox = new lcdSpinBox( 0, MIDI_CHANNEL_COUNT, 3,
@@ -75,7 +81,7 @@ midiTabWidget::midiTabWidget( channelTrack * _channel_track,
m_outputChannelSpinBox->addTextForValue( 0, "---" );
m_outputChannelSpinBox->setValue( m_midiPort->outputChannel() + 1 );
m_outputChannelSpinBox->setLabel( tr( "CHANNEL" ) );
m_outputChannelSpinBox->move( 190, 60 );
m_outputChannelSpinBox->move( 190, 90 );
connect( m_outputChannelSpinBox, SIGNAL( valueChanged( int ) ),
this, SLOT( outputChannelChanged( int ) ) );
@@ -91,7 +97,7 @@ midiTabWidget::midiTabWidget( channelTrack * _channel_track,
m_sendCheckBox = new ledCheckBox( tr( "SEND MIDI-EVENTS" ),
m_setupTabWidget );
m_sendCheckBox->move( 10, 64 );
m_sendCheckBox->move( 10, 94 );
connect( m_sendCheckBox, SIGNAL( toggled( bool ) ),
this, SLOT( midiPortModeToggled( bool ) ) );
connect( m_sendCheckBox, SIGNAL( toggled( bool ) ),
@@ -102,7 +108,7 @@ midiTabWidget::midiTabWidget( channelTrack * _channel_track,
m_setupTabWidget );
m_routeCheckBox->setChecked(
m_channelTrack->midiEventRoutingEnabled() );
m_routeCheckBox->move( 10, 100 );
m_routeCheckBox->move( 10, 150 );
connect( m_sendCheckBox, SIGNAL( toggled( bool ) ),
m_channelTrack, SLOT( toggleMidiEventRouting( bool ) ) );
@@ -112,6 +118,41 @@ midiTabWidget::midiTabWidget( channelTrack * _channel_track,
m_sendCheckBox->setChecked( m == midiPort::OUTPUT ||
m == midiPort::DUPLEX );
midiClient * mc = mixer::inst()->getMIDIClient();
// non-raw-clients have ports we can subscribe to!
if( mc->isRaw() == FALSE )
{
m_readablePorts = new QMenu( m_setupTabWidget );
m_readablePorts->setCheckable( TRUE );
connect( m_readablePorts, SIGNAL( activated( int ) ),
this, SLOT( activatedReadablePort( int ) ) );
m_writeablePorts = new QMenu( m_setupTabWidget );
m_writeablePorts->setCheckable( TRUE );
connect( m_writeablePorts, SIGNAL( activated( int ) ),
this, SLOT( activatedWriteablePort( int ) ) );
// fill menus
readablePortsChanged();
writeablePortsChanged();
QToolButton * rp_btn = new QToolButton( m_setupTabWidget );
rp_btn->setText( tr( "RECEIVE FROM" ) );
rp_btn->setGeometry( 24, 52, 140, 24 );
rp_btn->setPopup( m_readablePorts );
rp_btn->setPopupDelay( 1 );
QToolButton * wp_btn = new QToolButton( m_setupTabWidget );
wp_btn->setText( tr( "SEND TO" ) );
wp_btn->setGeometry( 24, 112, 140, 24 );
wp_btn->setPopup( m_writeablePorts );
wp_btn->setPopupDelay( 1 );
// we want to get informed about port-changes!
mc->connectRPChanged( this, SLOT( readablePortsChanged() ) );
mc->connectWPChanged( this, SLOT( writeablePortsChanged() ) );
}
}
@@ -193,5 +234,102 @@ void midiTabWidget::midiPortModeToggled( bool )
void midiTabWidget::readablePortsChanged( void )
{
// first save all selected ports
QStringList selected_ports;
for( csize i = 0; i < m_readablePorts->count(); ++i )
{
int id = m_readablePorts->idAt( i );
if( m_readablePorts->isItemChecked( id ) )
{
selected_ports.push_back( m_readablePorts->text( id ) );
}
}
m_readablePorts->clear();
const QStringList & rp = mixer::inst()->getMIDIClient()->
readablePorts();
// now insert new ports and restore selections
for( QStringList::const_iterator it = rp.begin(); it != rp.end(); ++it )
{
int id = m_readablePorts->insertItem( *it );
if( selected_ports.find( *it ) != selected_ports.end() )
{
m_readablePorts->setItemChecked( id, TRUE );
}
}
}
void midiTabWidget::writeablePortsChanged( void )
{
// first save all selected ports
QStringList selected_ports;
for( csize i = 0; i < m_writeablePorts->count(); ++i )
{
int id = m_writeablePorts->idAt( i );
if( m_writeablePorts->isItemChecked( id ) )
{
selected_ports.push_back( m_writeablePorts->text(
id ) );
}
}
m_writeablePorts->clear();
const QStringList & wp = mixer::inst()->getMIDIClient()->
writeablePorts();
// now insert new ports and restore selections
for( QStringList::const_iterator it = wp.begin(); it != wp.end(); ++it )
{
int id = m_writeablePorts->insertItem( *it );
if( selected_ports.find( *it ) != selected_ports.end() )
{
m_writeablePorts->setItemChecked( id, TRUE );
}
}
}
void midiTabWidget::activatedReadablePort( int _id )
{
// make sure, MIDI-port is configured for input
if( m_readablePorts->isItemChecked( _id ) == FALSE &&
m_midiPort->mode() != midiPort::INPUT &&
m_midiPort->mode() != midiPort::DUPLEX )
{
m_receiveCheckBox->setChecked( TRUE );
}
m_readablePorts->setItemChecked( _id,
!m_readablePorts->isItemChecked( _id ) );
mixer::inst()->getMIDIClient()->subscribeReadablePort(
m_midiPort, m_readablePorts->text( _id ),
!m_readablePorts->isItemChecked( _id ) );
}
void midiTabWidget::activatedWriteablePort( int _id )
{
// make sure, MIDI-port is configured for output
if( m_writeablePorts->isItemChecked( _id ) == FALSE &&
m_midiPort->mode() != midiPort::OUTPUT &&
m_midiPort->mode() != midiPort::DUPLEX )
{
m_sendCheckBox->setChecked( TRUE );
}
m_writeablePorts->setItemChecked( _id,
!m_writeablePorts->isItemChecked( _id ) );
mixer::inst()->getMIDIClient()->subscribeWriteablePort(
m_midiPort, m_writeablePorts->text( _id ),
!m_writeablePorts->isItemChecked( _id ) );
}
#include "midi_tab_widget.moc"

View File

@@ -51,11 +51,17 @@
midiALSASeq::midiALSASeq( void ) :
#ifndef QT4
QObject(),
#endif
midiClient(),
QThread(),
m_seqHandle( NULL ),
m_queueID( -1 ),
m_quit( FALSE )
m_quit( FALSE ),
m_portListUpdateTimer( this ),
m_readablePorts(),
m_writeablePorts()
{
int err;
if( ( err = snd_seq_open( &m_seqHandle,
@@ -71,6 +77,7 @@ midiALSASeq::midiALSASeq( void ) :
}
snd_seq_set_client_name( m_seqHandle, "LMMS" );
m_queueID = snd_seq_alloc_queue( m_seqHandle );
snd_seq_queue_tempo_t * tempo;
snd_seq_queue_tempo_alloca( &tempo );
@@ -84,6 +91,14 @@ midiALSASeq::midiALSASeq( void ) :
connect( songEditor::inst(), SIGNAL( bpmChanged( int ) ),
this, SLOT( changeQueueTempo( int ) ) );
// initial list-update
updatePortList();
connect( &m_portListUpdateTimer, SIGNAL( timeout() ),
this, SLOT( updatePortList() ) );
// we check for port-changes every second
m_portListUpdateTimer.start( 1000 );
start(
#if QT_VERSION >= 0x030200
QThread::LowPriority
@@ -315,6 +330,87 @@ void midiALSASeq::removePort( midiPort * _port )
void midiALSASeq::subscribeReadablePort( midiPort * _port,
const QString & _dest,
bool _unsubscribe )
{
if( m_portIDs.contains( _port ) == FALSE ||
( _port->mode() != midiPort::INPUT &&
_port->mode() != midiPort::DUPLEX ) )
{
return;
}
snd_seq_addr_t sender;
if( snd_seq_parse_address( m_seqHandle, &sender,
_dest.section( ' ', 0, 0 ).ascii() ) )
{
printf( "error parsing sender-address!!\n" );
return;
}
snd_seq_port_info_t * port_info;
snd_seq_port_info_malloc( &port_info );
snd_seq_get_port_info( m_seqHandle, m_portIDs[_port][0], port_info );
const snd_seq_addr_t * dest = snd_seq_port_info_get_addr( port_info );
snd_seq_port_subscribe_t * subs;
snd_seq_port_subscribe_alloca( &subs );
snd_seq_port_subscribe_set_sender( subs, &sender );
snd_seq_port_subscribe_set_dest( subs, dest );
if( _unsubscribe )
{
snd_seq_unsubscribe_port( m_seqHandle, subs );
}
else
{
snd_seq_subscribe_port( m_seqHandle, subs );
}
snd_seq_port_info_free( port_info );
}
void midiALSASeq::subscribeWriteablePort( midiPort * _port,
const QString & _dest,
bool _unsubscribe )
{
if( m_portIDs.contains( _port ) == FALSE ||
( _port->mode() != midiPort::OUTPUT &&
_port->mode() != midiPort::DUPLEX ) )
{
return;
}
snd_seq_addr_t dest;
if( snd_seq_parse_address( m_seqHandle, &dest,
_dest.section( ' ', 0, 0 ).ascii() ) )
{
printf( "error parsing dest-address!!\n" );
return;
}
snd_seq_port_info_t * port_info;
snd_seq_port_info_malloc( &port_info );
snd_seq_get_port_info( m_seqHandle, ( m_portIDs[_port][1] == -1 ) ?
m_portIDs[_port][0] :
m_portIDs[_port][1],
port_info );
const snd_seq_addr_t * sender = snd_seq_port_info_get_addr( port_info );
snd_seq_port_subscribe_t * subs;
snd_seq_port_subscribe_alloca( &subs );
snd_seq_port_subscribe_set_sender( subs, sender );
snd_seq_port_subscribe_set_dest( subs, &dest );
if( _unsubscribe )
{
snd_seq_unsubscribe_port( m_seqHandle, subs );
}
else
{
snd_seq_subscribe_port( m_seqHandle, subs );
}
snd_seq_port_info_free( port_info );
}
void midiALSASeq::run( void )
{
while( m_quit == FALSE )
@@ -331,10 +427,12 @@ void midiALSASeq::run( void )
dest = m_portIDs.keys()[i];
}
}
if( dest == NULL )
{
continue;
}
switch( ev->type )
{
case SND_SEQ_EVENT_NOTEON:
@@ -343,7 +441,8 @@ void midiALSASeq::run( void )
ev->data.note.note -
NOTES_PER_OCTAVE,
ev->data.note.velocity
), midiTime( ev->time.tick) );
),
midiTime( ev->time.tick ) );
break;
case SND_SEQ_EVENT_NOTEOFF:
@@ -352,7 +451,8 @@ void midiALSASeq::run( void )
ev->data.note.note -
NOTES_PER_OCTAVE,
ev->data.note.velocity
), midiTime( ev->time.tick) );
),
midiTime( ev->time.tick) );
break;
case SND_SEQ_EVENT_KEYPRESS:
@@ -373,6 +473,7 @@ void midiALSASeq::run( void )
"event %d\n", ev->type );
break;
}
}
}
@@ -390,6 +491,83 @@ void midiALSASeq::changeQueueTempo( int _bpm )
void midiALSASeq::updatePortList( void )
{
QStringList readable_ports;
QStringList writeable_ports;
// get input- and output-ports
snd_seq_client_info_t * cinfo;
snd_seq_port_info_t * pinfo;
snd_seq_client_info_alloca( &cinfo );
snd_seq_port_info_alloca( &pinfo );
snd_seq_client_info_set_client( cinfo, -1 );
while( snd_seq_query_next_client( m_seqHandle, cinfo ) >= 0 )
{
int client = snd_seq_client_info_get_client( cinfo );
snd_seq_port_info_set_client( pinfo, client );
snd_seq_port_info_set_port( pinfo, -1 );
while( snd_seq_query_next_port( m_seqHandle, pinfo ) >= 0 )
{
// we need both READ and SUBS_READ
if( ( snd_seq_port_info_get_capability( pinfo )
& ( SND_SEQ_PORT_CAP_READ |
SND_SEQ_PORT_CAP_SUBS_READ ) ) ==
( SND_SEQ_PORT_CAP_READ |
SND_SEQ_PORT_CAP_SUBS_READ ) )
{
readable_ports.push_back(
QString( "%1:%2 %3:%4" ).
arg( snd_seq_port_info_get_client(
pinfo ) ).
arg( snd_seq_port_info_get_port(
pinfo ) ).
arg( snd_seq_client_info_get_name(
cinfo ) ).
arg( snd_seq_port_info_get_name(
pinfo ) ) );
}
if( ( snd_seq_port_info_get_capability( pinfo )
& ( SND_SEQ_PORT_CAP_WRITE |
SND_SEQ_PORT_CAP_SUBS_WRITE ) ) ==
( SND_SEQ_PORT_CAP_WRITE |
SND_SEQ_PORT_CAP_SUBS_WRITE ) )
{
writeable_ports.push_back(
QString( "%1:%2 %3:%4" ).
arg( snd_seq_port_info_get_client(
pinfo ) ).
arg( snd_seq_port_info_get_port(
pinfo ) ).
arg( snd_seq_client_info_get_name(
cinfo ) ).
arg( snd_seq_port_info_get_name(
pinfo ) ) );
}
}
}
/* snd_seq_client_info_free( cinfo );
snd_seq_port_info_free( pinfo );*/
if( m_readablePorts != readable_ports )
{
m_readablePorts = readable_ports;
emit( readablePortsChanged() );
}
if( m_writeablePorts != writeable_ports )
{
m_writeablePorts = writeable_ports;
emit( writeablePortsChanged() );
}
}

View File

@@ -91,6 +91,38 @@ void midiClient::removePort( midiPort * _port )
const QStringList & midiClient::readablePorts( void ) const
{
static QStringList sl;
return( sl );
}
const QStringList & midiClient::writeablePorts( void ) const
{
static QStringList sl;
return( sl );
}
void midiClient::subscribeReadablePort( midiPort *, const QString & , bool )
{
}
void midiClient::subscribeWriteablePort( midiPort * , const QString & , bool)
{
}