Files
lmms/src/core/midi/MidiAlsaSeq.cpp
T

704 lines
16 KiB
C++

/*
* MidiAlsaSeq.cpp - ALSA sequencer client
*
* Copyright (c) 2005-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
*
* This file is part of LMMS - http://lmms.io
*
* 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 "MidiAlsaSeq.h"
#include "ConfigManager.h"
#include "Engine.h"
#include "gui_templates.h"
#include "Song.h"
#include "MidiPort.h"
#include "MidiTime.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_seqMutex(),
m_seqHandle( NULL ),
m_queueID( -1 ),
m_quit( false ),
m_portListUpdateTimer( this )
{
int err;
if( ( err = snd_seq_open( &m_seqHandle,
probeDevice().toLatin1().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 );
m_seqMutex.lock();
snd_seq_stop_queue( m_seqHandle, m_queueID, NULL );
snd_seq_free_queue( m_seqHandle, m_queueID );
snd_seq_close( m_seqHandle );
m_seqMutex.unlock();
}
}
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& event, 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<MidiPort *>( 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<int>( time ) );
ev.queue = m_queueID;
switch( event.type() )
{
case MidiNoteOn:
snd_seq_ev_set_noteon( &ev,
event.channel(),
event.key() + KeysPerOctave,
event.velocity() );
break;
case MidiNoteOff:
snd_seq_ev_set_noteoff( &ev,
event.channel(),
event.key() + KeysPerOctave,
event.velocity() );
break;
case MidiKeyPressure:
snd_seq_ev_set_keypress( &ev,
event.channel(),
event.key() + KeysPerOctave,
event.velocity() );
break;
case MidiControlChange:
snd_seq_ev_set_controller( &ev,
event.channel(),
event.controllerNumber(),
event.controllerValue() );
break;
case MidiProgramChange:
snd_seq_ev_set_pgmchange( &ev,
event.channel(),
event.program() );
break;
case MidiChannelPressure:
snd_seq_ev_set_chanpress( &ev,
event.channel(),
event.channelPressure() );
break;
case MidiPitchBend:
snd_seq_ev_set_pitchbend( &ev,
event.channel(),
event.param( 0 ) - 8192 );
break;
default:
qWarning( "MidiAlsaSeq: unhandled output event %d\n", (int) event.type() );
return;
}
m_seqMutex.lock();
snd_seq_event_output( m_seqHandle, &ev );
snd_seq_drain_output( m_seqHandle );
m_seqMutex.unlock();
}
void MidiAlsaSeq::applyPortMode( MidiPort * _port )
{
m_seqMutex.lock();
// 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;
}
}
m_seqMutex.unlock();
}
void MidiAlsaSeq::applyPortName( MidiPort * _port )
{
m_seqMutex.lock();
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 );
}
m_seqMutex.unlock();
}
void MidiAlsaSeq::removePort( MidiPort * _port )
{
if( m_portIDs.contains( _port ) )
{
m_seqMutex.lock();
snd_seq_delete_simple_port( m_seqHandle, m_portIDs[_port][0] );
snd_seq_delete_simple_port( m_seqHandle, m_portIDs[_port][1] );
m_seqMutex.unlock();
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<const snd_seq_addr_t *>( _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;
}
m_seqMutex.lock();
snd_seq_addr_t sender;
if( snd_seq_parse_address( m_seqHandle, &sender,
_dest.section( ' ', 0, 0 ).toLatin1().constData() ) )
{
fprintf( stderr, "error parsing sender-address!\n" );
m_seqMutex.unlock();
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 );
m_seqMutex.unlock();
}
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;
}
m_seqMutex.lock();
snd_seq_addr_t dest;
if( snd_seq_parse_address( m_seqHandle, &dest,
_dest.section( ' ', 0, 0 ).toLatin1().constData() ) )
{
fprintf( stderr, "error parsing dest-address!\n" );
m_seqMutex.unlock();
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 );
m_seqMutex.unlock();
}
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;
}
m_seqMutex.lock();
// 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 )
{
m_seqMutex.unlock();
qCritical( "error while fetching MIDI event from sequencer" );
break;
}
m_seqMutex.unlock();
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
m_seqMutex.lock();
} // end while
m_seqMutex.unlock();
}
delete[] pollfd_set;
}
void MidiAlsaSeq::changeQueueTempo( bpm_t _bpm )
{
m_seqMutex.lock();
snd_seq_change_queue_tempo( m_seqHandle, m_queueID,
60000000 / (int) _bpm, NULL );
snd_seq_drain_output( m_seqHandle );
m_seqMutex.unlock();
}
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 );
m_seqMutex.lock();
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 ) );
}
}
}
m_seqMutex.unlock();
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();
}
}
#endif