From 457c39ccdf4bbd042aad27c50a33e84222bd4c52 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Thu, 8 Dec 2005 10:31:29 +0000 Subject: [PATCH] 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 --- ChangeLog | 12 +++ TODO | 4 +- configure.in | 4 +- include/midi_alsa_seq.h | 45 +++++++++ include/midi_client.h | 49 ++++++++++ include/midi_tab_widget.h | 9 +- src/core/midi_tab_widget.cpp | 148 +++++++++++++++++++++++++++- src/midi/midi_alsa_seq.cpp | 184 ++++++++++++++++++++++++++++++++++- src/midi/midi_client.cpp | 32 ++++++ 9 files changed, 472 insertions(+), 15 deletions(-) diff --git a/ChangeLog b/ChangeLog index efc62a186..1170b9a7e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,15 @@ +2005-12-07 Tobias Doerffel + + * 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 * src/core/note_play_handle.cpp: diff --git a/TODO b/TODO index dca5ad360..2f1fb8627 100644 --- a/TODO +++ b/TODO @@ -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 diff --git a/configure.in b/configure.in index 10cd208db..3cab35ce2 100644 --- a/configure.in +++ b/configure.in @@ -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) diff --git a/include/midi_alsa_seq.h b/include/midi_alsa_seq.h index 1b3aeb994..a0bdd7db5 100644 --- a/include/midi_alsa_seq.h +++ b/include/midi_alsa_seq.h @@ -43,11 +43,13 @@ #ifdef QT4 #include +#include #else #include #include +#include #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 diff --git a/include/midi_client.h b/include/midi_client.h index f15859b25..2a5f670ad 100644 --- a/include/midi_client.h +++ b/include/midi_client.h @@ -31,10 +31,12 @@ #ifdef QT4 #include +#include #else #include +#include #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 diff --git a/include/midi_tab_widget.h b/include/midi_tab_widget.h index 655932118..8bf3e5eb0 100644 --- a/include/midi_tab_widget.h +++ b/include/midi_tab_widget.h @@ -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; } ; diff --git a/src/core/midi_tab_widget.cpp b/src/core/midi_tab_widget.cpp index 87ee48d46..2ff3aa84b 100644 --- a/src/core/midi_tab_widget.cpp +++ b/src/core/midi_tab_widget.cpp @@ -29,10 +29,16 @@ #ifdef QT4 #include +#include +#include +#include #else #include +#include +#include +#include #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" diff --git a/src/midi/midi_alsa_seq.cpp b/src/midi/midi_alsa_seq.cpp index bd05cd2e5..c46d84952 100644 --- a/src/midi/midi_alsa_seq.cpp +++ b/src/midi/midi_alsa_seq.cpp @@ -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() ); + } +} + + + diff --git a/src/midi/midi_client.cpp b/src/midi/midi_client.cpp index d7075335e..c4bfdcac1 100644 --- a/src/midi/midi_client.cpp +++ b/src/midi/midi_client.cpp @@ -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) +{ +} + + + +