diff --git a/ChangeLog b/ChangeLog index 3bc65e758..3ca4e97f2 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,18 @@ 2008-07-22 Tobias Doerffel + * include/midi_alsa_seq.h: + * include/midi_client.h: + * include/midi_port.h: + * include/midi_winmm.h: + * src/core/mixer.cpp: + * src/core/midi/midi_alsa_raw.cpp: + * src/core/midi/midi_alsa_seq.cpp: + * src/core/midi/midi_client.cpp: + * src/core/midi/midi_oss.cpp: + * src/core/midi/midi_winmm.cpp: + * src/gui/setup_dialog.cpp: + added rudimentary WinMM MIDI support + * src/core/mmp.cpp: * plugins/lb302/lb302.h * plugins/CMakeLists.txt: diff --git a/include/midi_alsa_seq.h b/include/midi_alsa_seq.h index a9a185192..666298980 100644 --- a/include/midi_alsa_seq.h +++ b/include/midi_alsa_seq.h @@ -73,12 +73,12 @@ public: // list seq-ports from ALSA - inline virtual const QStringList & readablePorts( void ) const + virtual QStringList readablePorts( void ) const { return( m_readablePorts ); } - virtual const QStringList & writeablePorts( void ) const + virtual QStringList writeablePorts( void ) const { return( m_writeablePorts ); } diff --git a/include/midi_client.h b/include/midi_client.h index 27c92b5e3..57b61aa12 100644 --- a/include/midi_client.h +++ b/include/midi_client.h @@ -63,14 +63,20 @@ public: // returns whether client works with raw-MIDI, only needs to be // re-implemented by midiClientRaw for returning TRUE - inline virtual bool isRaw( void ) const + 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; + virtual QStringList readablePorts( void ) const + { + return QStringList(); + } + virtual QStringList writeablePorts( void ) const + { + return QStringList(); + } // (un)subscribe given midiPort to/from destination-port virtual void subscribeReadablePort( midiPort * _port, @@ -100,7 +106,8 @@ public: public: setupWidget( const QString & _caption, QWidget * _parent ) : tabWidget( tabWidget::tr( "Settings for %1" ).arg( - tr( _caption.toAscii() ) ).toUpper(), _parent ) + tr( _caption.toAscii() ) ).toUpper(), + _parent ) { } @@ -137,7 +144,7 @@ public: virtual ~midiClientRaw(); // we are raw-clients for sure! - inline virtual bool isRaw( void ) const + virtual bool isRaw( void ) const { return( TRUE ); } diff --git a/include/midi_port.h b/include/midi_port.h index d01dab6eb..61073b313 100644 --- a/include/midi_port.h +++ b/include/midi_port.h @@ -87,6 +87,16 @@ public: void setMode( Modes _mode ); + inline bool inputEnabled( void ) const + { + return( mode() == Input || mode() == Duplex ); + } + + inline bool outputEnabled( void ) const + { + return( mode() == Output || mode() == Duplex ); + } + inline void enableDefaultVelocityForInEvents( const bool _on ) { m_defaultVelocityInEnabledModel.setValue( _on ); @@ -171,5 +181,7 @@ private: } ; +typedef QList midiPortList; + #endif diff --git a/include/midi_winmm.h b/include/midi_winmm.h new file mode 100644 index 000000000..c8119fb0f --- /dev/null +++ b/include/midi_winmm.h @@ -0,0 +1,154 @@ +/* + * midi_winmm.h - WinMM MIDI client + * + * Copyright (c) 2008 Tobias Doerffel + * + * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net + * + * 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 (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + + +#ifndef _MIDI_WINMM_H +#define _MIDI_WINMM_H + +#include "lmmsconfig.h" + +#ifdef LMMS_BUILD_WIN32 + +#include +#include + +#include + +#include "midi_client.h" +#include "midi_port.h" + + +class QLineEdit; + + +class midiWinMM : public QObject, public midiClient +{ + Q_OBJECT +public: + midiWinMM( void ); + virtual ~midiWinMM(); + + static QString probeDevice( void ); + + + inline static QString name( void ) + { + return( QT_TRANSLATE_NOOP( "setupWidget", "WinMM MIDI" ) ); + } + + + + virtual void processOutEvent( const midiEvent & _me, + const midiTime & _time, + const midiPort * _port ); + + virtual void applyPortMode( midiPort * _port ); + virtual void removePort( midiPort * _port ); + + + // list devices as ports + virtual QStringList readablePorts( void ) const + { + return( m_inputDevices.values() ); + } + + virtual QStringList writeablePorts( void ) const + { + return( m_outputDevices.values() ); + } + + // (un)subscribe given midiPort to/from destination-port + virtual void subscribeReadablePort( midiPort * _port, + const QString & _dest, + bool _subscribe = TRUE ); + virtual void subscribeWriteablePort( midiPort * _port, + const QString & _dest, + bool _subscribe = TRUE ); + 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 ); + } + + virtual bool isRaw( void ) const + { + return( FALSE ); + } + + + class setupWidget : public midiClient::setupWidget + { + public: + setupWidget( QWidget * _parent ); + virtual ~setupWidget(); + + virtual void saveSettings( void ); + + private: + QLineEdit * m_device; + + } ; + + +private slots: + void updateDeviceList( void ); + + +private: + void openDevices( void ); + void closeDevices( void ); + static void CALLBACK inputCallback( HMIDIIN _hm, UINT _msg, + DWORD_PTR _inst, + DWORD_PTR _param1, + DWORD_PTR _param2 ); + void handleInputEvent( HMIDIIN _hm, DWORD _ev ); + + QTimer m_deviceListUpdateTimer; + QMap m_inputDevices; + QMap m_outputDevices; + + // subscriptions + typedef QMap subMap; + subMap m_inputSubs; + subMap m_outputSubs; + + +signals: + void readablePortsChanged( void ); + void writeablePortsChanged( void ); + +} ; + +#endif + +#endif + diff --git a/src/core/midi/midi_alsa_raw.cpp b/src/core/midi/midi_alsa_raw.cpp index cc00fe968..e5d286367 100644 --- a/src/core/midi/midi_alsa_raw.cpp +++ b/src/core/midi/midi_alsa_raw.cpp @@ -38,6 +38,7 @@ midiALSARaw::midiALSARaw( void ) : + midiClientRaw(), m_inputp( &m_input ), m_outputp( &m_output ), m_quit( FALSE ) diff --git a/src/core/midi/midi_alsa_seq.cpp b/src/core/midi/midi_alsa_seq.cpp index c54181f3c..384429705 100644 --- a/src/core/midi/midi_alsa_seq.cpp +++ b/src/core/midi/midi_alsa_seq.cpp @@ -42,6 +42,7 @@ midiALSASeq::midiALSASeq( void ) : + midiClient(), m_seqHandle( NULL ), m_queueID( -1 ), m_quit( FALSE ), @@ -326,9 +327,10 @@ void midiALSASeq::subscribeReadablePort( midiPort * _port, bool _subscribe ) { if( m_portIDs.contains( _port ) == FALSE || - ( _port->mode() != midiPort::Input && - _port->mode() != midiPort::Duplex ) ) + _port->inputEnabled() == FALSE ) { + printf( "port %s can't be (un)subscribed!\n", + _port->name().toAscii().constData() ); return; } snd_seq_addr_t sender; @@ -366,9 +368,10 @@ void midiALSASeq::subscribeWriteablePort( midiPort * _port, bool _subscribe ) { if( m_portIDs.contains( _port ) == FALSE || - ( _port->mode() != midiPort::Output && - _port->mode() != midiPort::Duplex ) ) + _port->outputEnabled() == FALSE ) { + printf( "port %s can't be (un)subscribed!\n", + _port->name().toAscii().constData() ); return; } snd_seq_addr_t dest; diff --git a/src/core/midi/midi_client.cpp b/src/core/midi/midi_client.cpp index 0738872c4..e97992d5d 100644 --- a/src/core/midi/midi_client.cpp +++ b/src/core/midi/midi_client.cpp @@ -86,24 +86,6 @@ 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 ) { } diff --git a/src/core/midi/midi_oss.cpp b/src/core/midi/midi_oss.cpp index 6535b228f..6e0b8b461 100644 --- a/src/core/midi/midi_oss.cpp +++ b/src/core/midi/midi_oss.cpp @@ -45,6 +45,7 @@ midiOSS::midiOSS( void ) : + midiClientRaw(), m_midiDev( probeDevice() ), m_quit( FALSE ) { diff --git a/src/core/midi/midi_winmm.cpp b/src/core/midi/midi_winmm.cpp new file mode 100644 index 000000000..da98ab53d --- /dev/null +++ b/src/core/midi/midi_winmm.cpp @@ -0,0 +1,421 @@ +/* + * midi_winmm.cpp - WinMM MIDI client + * + * Copyright (c) 2008 Tobias Doerffel + * + * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net + * + * 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 (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + + +#include +#include + + +#include "midi_winmm.h" +#include "config_mgr.h" +#include "engine.h" +#include "gui_templates.h" +#include "midi_port.h" +#include "note.h" + + +#ifdef LMMS_BUILD_WIN32 + + +midiWinMM::midiWinMM( void ) : + midiClient(), + m_deviceListUpdateTimer( this ) +{ + // initial list-update + updateDeviceList(); + + connect( &m_deviceListUpdateTimer, SIGNAL( timeout() ), + this, SLOT( updateDeviceList() ) ); + // we check for port-changes every second +// m_deviceListUpdateTimer.start( 1000 ); +} + + + + +midiWinMM::~midiWinMM() +{ + closeDevices(); +} + + + + +QString midiWinMM::probeDevice( void ) +{ + QString dev = configManager::inst()->value( "midiwinmm", "device" ); + if( dev == "" ) + { + if( getenv( "MIDIDEV" ) != NULL ) + { + return( getenv( "MIDIDEV" ) ); + } + return( "default" ); + } + return( dev ); +} + + + + +void midiWinMM::processOutEvent( const midiEvent & _me, + const midiTime & _time, + const midiPort * _port ) +{ +/* // HACK!!! - need a better solution which isn't that easy since we + // cannot store const-ptrs in our map because we need to call non-const + // methods of MIDI-port - it's a mess... + midiPort * p = const_cast( _port ); + + snd_seq_event_t ev; + snd_seq_ev_clear( &ev ); + snd_seq_ev_set_source( &ev, ( m_portIDs[p][1] != -1 ) ? + m_portIDs[p][1] : m_portIDs[p][0] ); + snd_seq_ev_set_subs( &ev ); + snd_seq_ev_schedule_tick( &ev, m_queueID, 1, + static_cast( _time ) ); + ev.queue = m_queueID; + switch( _me.m_type ) + { + case MidiNoteOn: + snd_seq_ev_set_noteon( &ev, + _port->outputChannel(), + _me.key() + KeysPerOctave, + _me.velocity() ); + break; + + case MidiNoteOff: + snd_seq_ev_set_noteoff( &ev, + _port->outputChannel(), + _me.key() + KeysPerOctave, + _me.velocity() ); + break; + + case MidiKeyPressure: + snd_seq_ev_set_keypress( &ev, + _port->outputChannel(), + _me.key() + KeysPerOctave, + _me.velocity() ); + break; + + case MidiControlChange: + snd_seq_ev_set_controller( &ev, + _port->outputChannel(), + _me.m_data.m_param[0], + _me.m_data.m_param[1] ); + break; + + case MidiProgramChange: + snd_seq_ev_set_pgmchange( &ev, + _port->outputChannel(), + _me.m_data.m_param[0] ); + break; + + case MidiChannelPressure: + snd_seq_ev_set_chanpress( &ev, + _port->outputChannel(), + _me.m_data.m_param[0] ); + break; + + case MidiPitchBend: + snd_seq_ev_set_pitchbend( &ev, + _port->outputChannel(), + _me.m_data.m_param[0] - 8192 ); + break; + + default: + printf( "ALSA-sequencer: unhandled output event %d\n", + (int) _me.m_type ); + return; + }*/ +} + + + + +void midiWinMM::applyPortMode( midiPort * _port ) +{ + // make sure no subscriptions exist which are not possible with + // current port-mode + if( !_port->inputEnabled() ) + { + for( subMap::iterator it = m_inputSubs.begin(); + it != m_inputSubs.end(); ++it ) + { + it.value().removeAll( _port ); + } + } + + if( _port->outputEnabled() ) + { + for( subMap::iterator it = m_outputSubs.begin(); + it != m_outputSubs.end(); ++it ) + { + it.value().removeAll( _port ); + } + } +} + + + + +void midiWinMM::removePort( midiPort * _port ) +{ + for( subMap::iterator it = m_inputSubs.begin(); + it != m_inputSubs.end(); ++it ) + { + it.value().removeAll( _port ); + } + for( subMap::iterator it = m_outputSubs.begin(); + it != m_outputSubs.end(); ++it ) + { + it.value().removeAll( _port ); + } + midiClient::removePort( _port ); +} + + + + +void midiWinMM::subscribeReadablePort( midiPort * _port, + const QString & _dest, + bool _subscribe ) +{ + if( _port->inputEnabled() == FALSE ) + { + printf( "port %s can't be (un)subscribed!\n", + _port->name().toAscii().constData() ); + return; + } + + if( m_inputSubs.contains( _dest ) ) + { + m_inputSubs[_dest].removeAll( _port ); + if( _subscribe ) + { + m_inputSubs[_dest].push_back( _port ); + } + } +} + + + + +void midiWinMM::subscribeWriteablePort( midiPort * _port, + const QString & _dest, + bool _subscribe ) +{ + if( _port->outputEnabled() == FALSE && _subscribe == FALSE ) + { + printf( "port %s can't be (un)subscribed!\n", + _port->name().toAscii().constData() ); + return; + } + + if( m_outputSubs.contains( _dest ) ) + { + m_outputSubs[_dest].removeAll( _port ); + if( _subscribe ) + { + m_outputSubs[_dest].push_back( _port ); + } + } +} + + + + +void CALLBACK midiWinMM::inputCallback( HMIDIIN _hm, UINT _msg, DWORD_PTR _inst, + DWORD_PTR _param1, DWORD_PTR _param2 ) +{ +printf("input callback %d\n", (int) _hm ); + if( _msg == MIM_DATA ) + { + ( (midiWinMM *) _inst )->handleInputEvent( _hm, _param1 ); + } +} + + + + +void midiWinMM::handleInputEvent( HMIDIIN _hm, DWORD _ev ) +{ + const int cmd = _ev & 0xff; + if( cmd == MidiActiveSensing ) + { + return; + } + const int par1 = ( _ev >> 8 ) & 0xff; + const int par2 = _ev >> 16; + const MidiEventTypes cmdtype = + static_cast( cmd & 0xf0 ); + const int chan = cmd & 0x0f; + + printf("%d\n", cmd ); + const QString d = m_inputDevices.value( _hm ); + if( d.isEmpty() ) + { +printf("return\n"); + return; + } + + const midiPortList & l = m_inputSubs[d]; + for( midiPortList::const_iterator it = l.begin(); it != l.end(); ++it ) + { + switch( cmdtype ) + { + case MidiNoteOn: + case MidiNoteOff: + case MidiKeyPressure: + ( *it )->processInEvent( + midiEvent( cmdtype, chan, + par1 - KeysPerOctave, + par2 & 0xff ), + midiTime() ); + break; + + case MidiControlChange: + case MidiProgramChange: + case MidiChannelPressure: + ( *it )->processInEvent( + midiEvent( cmdtype, chan, par1, + par2 & 0xff ), + midiTime() ); + + break; + + case MidiPitchBend: + ( *it )->processInEvent( + midiEvent( cmdtype, chan, + par1 + par2*128, 0 ), + midiTime() ); + default: + printf( "WinMM-MIDI: unhandled input " + "event %d\n", cmdtype ); + break; + } + } +} + + + + +void midiWinMM::updateDeviceList( void ) +{ + closeDevices(); + openDevices(); +// if( m_readablePorts != readable_ports ) + { +// m_readablePorts = readable_ports; + emit( readablePortsChanged() ); + } + +// if( m_writeablePorts != writeable_ports ) + { +// m_writeablePorts = writeable_ports; + emit( writeablePortsChanged() ); + } +} + + + +void midiWinMM::closeDevices( void ) +{ + for( QList::const_iterator it = m_inputDevices.keys().begin(); + it != m_inputDevices.keys().end(); ++it ) + { + midiInReset( *it ); + midiInClose( *it ); + } + for( QList::const_iterator it = + m_outputDevices.keys().begin(); + it != m_outputDevices.keys().end(); ++it ) + { + //midiOutStop( *it ); + midiOutClose( *it ); + } +} + + + + +void midiWinMM::openDevices( void ) +{ + m_inputDevices.clear(); + for( int i = 0; i < midiInGetNumDevs(); ++i ) + { +printf("opening %d\n", i ); + HMIDIIN hm = 0; + MMRESULT res = midiInOpen( &hm, i, (DWORD) &inputCallback, + (DWORD_PTR) this, + CALLBACK_FUNCTION ); +printf("opened %d\n", (int) hm); + if( res == MMSYSERR_NOERROR ) + { + midiInStart( hm ); +printf("started\n" ); + MIDIINCAPS c; + midiInGetDevCaps( (UINT) hm, &c, sizeof( c ) ); + m_inputDevices[hm] = c.szPname; +printf("caps done %s\n", c.szPname ); + } + } +} + + + + +midiWinMM::setupWidget::setupWidget( QWidget * _parent ) : + midiClient::setupWidget( midiWinMM::name(), _parent ) +{ + m_device = new QLineEdit( midiWinMM::probeDevice(), this ); + m_device->setGeometry( 10, 20, 160, 20 ); + + QLabel * dev_lbl = new QLabel( tr( "DEVICE" ), this ); + dev_lbl->setFont( pointSize<6>( dev_lbl->font() ) ); + dev_lbl->setGeometry( 10, 40, 160, 10 ); +} + + + + +midiWinMM::setupWidget::~setupWidget() +{ +} + + + + +void midiWinMM::setupWidget::saveSettings( void ) +{ + configManager::inst()->setValue( "midiwinmm", "device", + m_device->text() ); +} + + +#include "midi_winmm.moc" + + +#endif + diff --git a/src/core/mixer.cpp b/src/core/mixer.cpp index d0effada4..f69c357bc 100644 --- a/src/core/mixer.cpp +++ b/src/core/mixer.cpp @@ -62,6 +62,7 @@ #include "midi_alsa_raw.h" #include "midi_alsa_seq.h" #include "midi_oss.h" +#include "midi_winmm.h" #include "midi_dummy.h" @@ -1045,6 +1046,18 @@ midiClient * mixer::tryMIDIClients( void ) } #endif +#ifdef LMMS_BUILD_WIN32 + if( client_name == midiWinMM::name() || client_name == "" ) + { + midiWinMM * mwmm = new midiWinMM; +// if( moss->isRunning() ) + { + m_midiClientName = midiWinMM::name(); + return( mwmm ); + } + delete mwmm; + } +#endif printf( "Couldn't create MIDI-client, neither with ALSA nor with " "OSS. Will use dummy-MIDI-client.\n" ); diff --git a/src/gui/setup_dialog.cpp b/src/gui/setup_dialog.cpp index 847906961..6f8873bfc 100644 --- a/src/gui/setup_dialog.cpp +++ b/src/gui/setup_dialog.cpp @@ -63,6 +63,7 @@ #include "midi_alsa_raw.h" #include "midi_alsa_seq.h" #include "midi_oss.h" +#include "midi_winmm.h" #include "midi_dummy.h" @@ -537,6 +538,12 @@ setupDialog::setupDialog( ConfigTabs _tab_to_open ) : m_midiIfaceSetupWidgets[midiOSS::name()] = new midiOSS::setupWidget( msw ); #endif + +#ifdef LMMS_BUILD_WIN32 + m_midiIfaceSetupWidgets[midiWinMM::name()] = + new midiWinMM::setupWidget( msw ); +#endif + m_midiIfaceSetupWidgets[midiDummy::name()] = new midiDummy::setupWidget( msw );