/* * MidiAlsaSeq.cpp - ALSA sequencer client * * Copyright (c) 2005-2009 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 "MidiAlsaSeq.h" #include "config_mgr.h" #include "engine.h" #include "gui_templates.h" #include "song.h" #include "MidiPort.h" #include "note.h" #ifdef LMMS_HAVE_ALSA const int EventPollTimeOut = 250; // static helper functions static QString __portName( snd_seq_client_info_t * _cinfo, snd_seq_port_info_t * _pinfo ) { return 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 ) ); } static QString __portName( snd_seq_t * _seq, const snd_seq_addr_t * _addr ) { snd_seq_client_info_t * cinfo; snd_seq_port_info_t * pinfo; snd_seq_client_info_malloc( &cinfo ); snd_seq_port_info_malloc( &pinfo ); snd_seq_get_any_port_info( _seq, _addr->client, _addr->port, pinfo ); snd_seq_get_any_client_info( _seq, _addr->client, cinfo ); const QString name = __portName( cinfo, pinfo ); snd_seq_client_info_free( cinfo ); snd_seq_port_info_free( pinfo ); return name; } MidiAlsaSeq::MidiAlsaSeq() : MidiClient(), m_seqHandle( NULL ), m_queueID( -1 ), m_quit( false ), m_portListUpdateTimer( this ) { int err; if( ( err = snd_seq_open( &m_seqHandle, probeDevice().toAscii().constData(), SND_SEQ_OPEN_DUPLEX, 0 ) ) < 0 ) { fprintf( stderr, "cannot open sequencer: %s\n", snd_strerror( err ) ); return; } 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_malloc( &tempo ); snd_seq_queue_tempo_set_tempo( tempo, 6000000 / engine::getSong()->getTempo() ); snd_seq_queue_tempo_set_ppq( tempo, 16 ); snd_seq_set_queue_tempo( m_seqHandle, m_queueID, tempo ); snd_seq_queue_tempo_free( tempo ); snd_seq_start_queue( m_seqHandle, m_queueID, NULL ); changeQueueTempo( engine::getSong()->getTempo() ); connect( engine::getSong(), SIGNAL( tempoChanged( bpm_t ) ), this, SLOT( changeQueueTempo( bpm_t ) ) ); // initial list-update updatePortList(); connect( &m_portListUpdateTimer, SIGNAL( timeout() ), this, SLOT( updatePortList() ) ); // we check for port-changes every second m_portListUpdateTimer.start( 1000 ); // use a pipe to detect shutdown if( pipe( m_pipe ) == -1 ) { perror( __FILE__ ": pipe" ); } start( QThread::IdlePriority ); } MidiAlsaSeq::~MidiAlsaSeq() { if( isRunning() ) { m_quit = true; wait( EventPollTimeOut*2 ); snd_seq_stop_queue( m_seqHandle, m_queueID, NULL ); snd_seq_free_queue( m_seqHandle, m_queueID ); snd_seq_close( m_seqHandle ); } } QString MidiAlsaSeq::probeDevice() { QString dev = configManager::inst()->value( "Midialsaseq", "device" ); if( dev.isEmpty() ) { if( getenv( "MIDIDEV" ) != NULL ) { return getenv( "MIDIDEV" ); } return "default"; } return dev; } void MidiAlsaSeq::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, _me.channel(), _me.key() + KeysPerOctave, _me.velocity() ); break; case MidiNoteOff: snd_seq_ev_set_noteoff( &ev, _me.channel(), _me.key() + KeysPerOctave, _me.velocity() ); break; case MidiKeyPressure: snd_seq_ev_set_keypress( &ev, _me.channel(), _me.key() + KeysPerOctave, _me.velocity() ); break; case MidiControlChange: snd_seq_ev_set_controller( &ev, _me.channel(), _me.m_data.m_param[0], _me.m_data.m_param[1] ); break; case MidiProgramChange: snd_seq_ev_set_pgmchange( &ev, _me.channel(), _me.m_data.m_param[0] ); break; case MidiChannelPressure: snd_seq_ev_set_chanpress( &ev, _me.channel(), _me.m_data.m_param[0] ); break; case MidiPitchBend: snd_seq_ev_set_pitchbend( &ev, _me.channel(), _me.m_data.m_param[0] - 8192 ); break; default: fprintf( stderr, "ALSA-sequencer: unhandled output " "event %d\n", (int) _me.m_type ); return; } snd_seq_event_output( m_seqHandle, &ev ); snd_seq_drain_output( m_seqHandle ); } void MidiAlsaSeq::applyPortMode( MidiPort * _port ) { // determine port-capabilities unsigned int caps[2] = { 0, 0 }; switch( _port->mode() ) { case MidiPort::Duplex: caps[1] |= SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ; case MidiPort::Input: caps[0] |= SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE; break; case MidiPort::Output: caps[1] |= SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ; break; default: break; } for( int i = 0; i < 2; ++i ) { if( caps[i] != 0 ) { // no port there yet? if( m_portIDs[_port][i] == -1 ) { // then create one; m_portIDs[_port][i] = snd_seq_create_simple_port( m_seqHandle, _port->displayName().toUtf8().constData(), caps[i], SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION ); continue; } snd_seq_port_info_t * port_info; snd_seq_port_info_malloc( &port_info ); snd_seq_get_port_info( m_seqHandle, m_portIDs[_port][i], port_info ); snd_seq_port_info_set_capability( port_info, caps[i] ); snd_seq_set_port_info( m_seqHandle, m_portIDs[_port][i], port_info ); snd_seq_port_info_free( port_info ); } // still a port there although no caps? ( = dummy port) else if( m_portIDs[_port][i] != -1 ) { // then remove this port snd_seq_delete_simple_port( m_seqHandle, m_portIDs[_port][i] ); m_portIDs[_port][i] = -1; } } } void MidiAlsaSeq::applyPortName( MidiPort * _port ) { for( int i = 0; i < 2; ++i ) { if( m_portIDs[_port][i] == -1 ) { continue; } snd_seq_port_info_t * port_info; snd_seq_port_info_malloc( &port_info ); snd_seq_get_port_info( m_seqHandle, m_portIDs[_port][i], port_info ); snd_seq_port_info_set_name( port_info, _port->displayName().toUtf8().constData() ); snd_seq_set_port_info( m_seqHandle, m_portIDs[_port][i], port_info ); snd_seq_port_info_free( port_info ); } } void MidiAlsaSeq::removePort( MidiPort * _port ) { if( m_portIDs.contains( _port ) ) { snd_seq_delete_simple_port( m_seqHandle, m_portIDs[_port][0] ); snd_seq_delete_simple_port( m_seqHandle, m_portIDs[_port][1] ); m_portIDs.remove( _port ); } MidiClient::removePort( _port ); } QString MidiAlsaSeq::sourcePortName( const midiEvent & _event ) const { if( _event.sourcePort() ) { const snd_seq_addr_t * addr = static_cast( _event.sourcePort() ); return __portName( m_seqHandle, addr ); } return MidiClient::sourcePortName( _event ); } void MidiAlsaSeq::subscribeReadablePort( MidiPort * _port, const QString & _dest, bool _subscribe ) { if( !m_portIDs.contains( _port ) || m_portIDs[_port][0] < 0 ) { return; } snd_seq_addr_t sender; if( snd_seq_parse_address( m_seqHandle, &sender, _dest.section( ' ', 0, 0 ).toAscii().constData() ) ) { fprintf( stderr, "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_malloc( &subs ); snd_seq_port_subscribe_set_sender( subs, &sender ); snd_seq_port_subscribe_set_dest( subs, dest ); if( _subscribe ) { snd_seq_subscribe_port( m_seqHandle, subs ); } else { snd_seq_unsubscribe_port( m_seqHandle, subs ); } snd_seq_port_subscribe_free( subs ); snd_seq_port_info_free( port_info ); } void MidiAlsaSeq::subscribeWritablePort( MidiPort * _port, const QString & _dest, bool _subscribe ) { if( !m_portIDs.contains( _port ) ) { return; } const int pid = m_portIDs[_port][1] < 0 ? m_portIDs[_port][0] : m_portIDs[_port][1]; if( pid < 0 ) { return; } snd_seq_addr_t dest; if( snd_seq_parse_address( m_seqHandle, &dest, _dest.section( ' ', 0, 0 ).toAscii().constData() ) ) { fprintf( stderr, "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, pid, 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_malloc( &subs ); snd_seq_port_subscribe_set_sender( subs, sender ); snd_seq_port_subscribe_set_dest( subs, &dest ); if( _subscribe ) { snd_seq_subscribe_port( m_seqHandle, subs ); } else { snd_seq_unsubscribe_port( m_seqHandle, subs ); } snd_seq_port_subscribe_free( subs ); snd_seq_port_info_free( port_info ); } void MidiAlsaSeq::run() { // watch the pipe and sequencer input events int pollfd_count = snd_seq_poll_descriptors_count( m_seqHandle, POLLIN ); struct pollfd * pollfd_set = new struct pollfd[pollfd_count + 1]; snd_seq_poll_descriptors( m_seqHandle, pollfd_set + 1, pollfd_count, POLLIN ); pollfd_set[0].fd = m_pipe[0]; pollfd_set[0].events = POLLIN; ++pollfd_count; while( m_quit == false ) { int pollRet = poll( pollfd_set, pollfd_count, EventPollTimeOut ); if( pollRet == 0 ) { continue; } else if( pollRet == -1 ) { // gdb may interrupt the poll if( errno == EINTR ) { continue; } qCritical( "error while polling ALSA sequencer handle" ); break; } // shutdown? if( m_quit ) { break; } // while event queue is not empty while( snd_seq_event_input_pending( m_seqHandle, true ) > 0 ) { snd_seq_event_t * ev; if( snd_seq_event_input( m_seqHandle, &ev ) < 0 ) { qCritical( "error while fetching MIDI event from sequencer" ); break; } snd_seq_addr_t * source = NULL; MidiPort * dest = NULL; for( int i = 0; i < m_portIDs.size(); ++i ) { if( m_portIDs.values()[i][0] == ev->dest.port ) { dest = m_portIDs.keys()[i]; } if( ( m_portIDs.values()[i][1] != -1 && m_portIDs.values()[i][1] == ev->source.port ) || m_portIDs.values()[i][0] == ev->source.port ) { source = &ev->source; } } if( dest == NULL ) { continue; } switch( ev->type ) { case SND_SEQ_EVENT_NOTEON: dest->processInEvent( midiEvent( MidiNoteOn, ev->data.note.channel, ev->data.note.note - KeysPerOctave, ev->data.note.velocity, source ), midiTime( ev->time.tick ) ); break; case SND_SEQ_EVENT_NOTEOFF: dest->processInEvent( midiEvent( MidiNoteOff, ev->data.note.channel, ev->data.note.note - KeysPerOctave, ev->data.note.velocity, source ), midiTime( ev->time.tick) ); break; case SND_SEQ_EVENT_KEYPRESS: dest->processInEvent( midiEvent( MidiKeyPressure, ev->data.note.channel, ev->data.note.note - KeysPerOctave, ev->data.note.velocity, source ), midiTime() ); break; case SND_SEQ_EVENT_CONTROLLER: dest->processInEvent( midiEvent( MidiControlChange, ev->data.control.channel, ev->data.control.param, ev->data.control.value, source ), midiTime() ); break; case SND_SEQ_EVENT_PGMCHANGE: dest->processInEvent( midiEvent( MidiProgramChange, ev->data.control.channel, ev->data.control.param, ev->data.control.value, source ), midiTime() ); break; case SND_SEQ_EVENT_CHANPRESS: dest->processInEvent( midiEvent( MidiChannelPressure, ev->data.control.channel, ev->data.control.param, ev->data.control.value, source ), midiTime() ); break; case SND_SEQ_EVENT_PITCHBEND: dest->processInEvent( midiEvent( MidiPitchBend, ev->data.control.channel, ev->data.control.value + 8192, 0, source ), midiTime() ); break; case SND_SEQ_EVENT_SENSING: case SND_SEQ_EVENT_CLOCK: break; default: fprintf( stderr, "ALSA-sequencer: unhandled input " "event %d\n", ev->type ); break; } // end switch } // end while } delete[] pollfd_set; } void MidiAlsaSeq::changeQueueTempo( bpm_t _bpm ) { snd_seq_change_queue_tempo( m_seqHandle, m_queueID, 60000000 / (int) _bpm, NULL ); snd_seq_drain_output( m_seqHandle ); } void MidiAlsaSeq::updatePortList() { QStringList readablePorts; QStringList writablePorts; // get input- and output-ports snd_seq_client_info_t * cinfo; snd_seq_port_info_t * pinfo; snd_seq_client_info_malloc( &cinfo ); snd_seq_port_info_malloc( &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 ) ) { readablePorts.push_back( __portName( cinfo, 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 ) ) { writablePorts.push_back( __portName( cinfo, pinfo ) ); } } } snd_seq_client_info_free( cinfo ); snd_seq_port_info_free( pinfo ); if( m_readablePorts != readablePorts ) { m_readablePorts = readablePorts; emit readablePortsChanged(); } if( m_writablePorts != writablePorts ) { m_writablePorts = writablePorts; emit writablePortsChanged(); } } MidiAlsaSeq::setupWidget::setupWidget( QWidget * _parent ) : MidiClient::setupWidget( MidiAlsaSeq::name(), _parent ) { m_device = new QLineEdit( MidiAlsaSeq::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 ); } MidiAlsaSeq::setupWidget::~setupWidget() { } void MidiAlsaSeq::setupWidget::saveSettings() { configManager::inst()->setValue( "Midialsaseq", "device", m_device->text() ); } #include "moc_MidiAlsaSeq.cxx" #endif