From 7587d7c1f63e09cdc068b3f603bc73873e6fb472 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Tue, 8 Nov 2005 13:58:38 +0000 Subject: [PATCH] bugfixes in ALSA-sequencer-client git-svn-id: https://lmms.svn.sf.net/svnroot/lmms/trunk/lmms@24 0778d3d1-df1d-0410-868b-ea421aaaa00d --- ChangeLog | 6 + configure.in | 4 +- include/midi_alsa_seq.h | 133 ++++++++++++ include/midi_client.h | 1 - src/midi/midi_alsa_seq.cpp | 427 +++++++++++++++++++++++++++++++++++++ 5 files changed, 568 insertions(+), 3 deletions(-) create mode 100644 include/midi_alsa_seq.h create mode 100644 src/midi/midi_alsa_seq.cpp diff --git a/ChangeLog b/ChangeLog index 611fbcbc6..eb20ee8d2 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,9 @@ +2005-11-07 Tobias Doerffel + + * src/midi/midi_alsa_seq.cpp: + use relative instead of absolute scheduling for events which makes + MIDI-out working + 2005-10-31 Tobias Doerffel * include/midi_alsa_seq.h: diff --git a/configure.in b/configure.in index 9f4579983..faefd28dd 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-cvs20051106, tobydox/at/users.sourceforge.net) -AM_INIT_AUTOMAKE(lmms, 0.1.1-cvs20051106) +AC_INIT(lmms, 0.1.1-cvs20051107, tobydox/at/users.sourceforge.net) +AM_INIT_AUTOMAKE(lmms, 0.1.1-cvs20051107) AM_CONFIG_HEADER(config.h) diff --git a/include/midi_alsa_seq.h b/include/midi_alsa_seq.h new file mode 100644 index 000000000..9a19fa8d8 --- /dev/null +++ b/include/midi_alsa_seq.h @@ -0,0 +1,133 @@ +/* + * midi_alsa_seq.h - ALSA-sequencer-client + * + * Linux MultiMedia Studio + * Copyright (c) 2004-2005 Tobias Doerffel + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + */ + + +#ifndef _MIDI_ALSA_SEQ_H +#define _MIDI_ALSA_SEQ_H + +#ifdef HAVE_CONFIG_H +#include +#endif + +#ifdef HAVE_ALSA_ASOUNDLIB_H + +#ifndef ALSA_SUPPORT +#define ALSA_SUPPORT +#endif + +#include + +#include "qt3support.h" + +#ifdef QT4 + +#include + +#else + +#include +#include + +#endif + + +#include "midi_client.h" + + +struct pollfd; +class QLineEdit; + + +class midiALSASeq : +#ifndef QT4 + public QObject, +#endif + public midiClient, public QThread +{ + Q_OBJECT +public: + midiALSASeq( void ); + ~midiALSASeq(); + + static QString probeDevice( void ); + + + inline static QString name( void ) + { + return( setupWidget::tr( "ALSA-Sequencer (Advanced Linux Sound " + "Architecture)" ) ); + } + + + + virtual void FASTCALL processOutEvent( const midiEvent & _me, + const midiTime & _time, + const midiPort * _port ); + + virtual void FASTCALL applyPortMode( midiPort * _port ); + virtual void FASTCALL applyPortName( midiPort * _port ); + + virtual void FASTCALL removePort( midiPort * _port ); + + + + class setupWidget : public midiClient::setupWidget + { + public: + setupWidget( QWidget * _parent ); + virtual ~setupWidget(); + + virtual void saveSettings( void ); + + private: + QLineEdit * m_device; + + } ; + + +private slots: + void changeQueueTempo( int _bpm ); + + +private: + virtual void run( void ); + + + snd_seq_t * m_seqHandle; + struct ports + { + ports() { p[0] = -1; p[1] = -1; } + int & operator[]( const int _i ) { return( p[_i] ); } + private: int p[2]; + } ; + QMap m_portIDs; + + int m_queueID; + + volatile bool m_quit; + +} ; + +#endif + +#endif diff --git a/include/midi_client.h b/include/midi_client.h index 4d564c914..735ae46f5 100644 --- a/include/midi_client.h +++ b/include/midi_client.h @@ -144,6 +144,5 @@ private: } m_midiParseData; } ; -*/ #endif diff --git a/src/midi/midi_alsa_seq.cpp b/src/midi/midi_alsa_seq.cpp new file mode 100644 index 000000000..50f824ff6 --- /dev/null +++ b/src/midi/midi_alsa_seq.cpp @@ -0,0 +1,427 @@ +/* + * midi_alsa_seq.cpp - ALSA-sequencer-client + * + * Linux MultiMedia Studio + * Copyright (c) 2004-2005 Tobias Doerffel + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + */ + + +#include "qt3support.h" + +#ifdef QT4 + +#include +#include + +#else + +#include +#include +#include + +#endif + + +#include "midi_alsa_seq.h" +#include "config_mgr.h" +#include "gui_templates.h" +#include "song_editor.h" +#include "midi_port.h" +#include "note.h" + + +#ifdef ALSA_SUPPORT + + +midiALSASeq::midiALSASeq( void ) : + midiClient(), + QThread(), + m_seqHandle( NULL ), + m_queueID( -1 ), + m_quit( FALSE ) +{ + int err; + if( ( err = snd_seq_open( &m_seqHandle, +#ifdef QT4 + probeDevice().toAscii().constData(), +#else + probeDevice().ascii(), +#endif + SND_SEQ_OPEN_DUPLEX, 0 ) ) < 0 ) + { + printf( "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_alloca( &tempo ); + snd_seq_queue_tempo_set_tempo( tempo, 6000000 / songEditor::inst()->getBPM() ); + snd_seq_queue_tempo_set_ppq( tempo, 16 ); + snd_seq_set_queue_tempo( m_seqHandle, m_queueID, tempo ); + + snd_seq_start_queue( m_seqHandle, m_queueID, NULL ); + changeQueueTempo( songEditor::inst()->getBPM() ); + connect( songEditor::inst(), SIGNAL( bpmChanged( int ) ), + this, SLOT( changeQueueTempo( int ) ) ); + + start( +#if QT_VERSION >= 0x030200 + QThread::LowPriority +#endif + ); +} + + + + +midiALSASeq::~midiALSASeq() +{ + if( running() ) + { + m_quit = TRUE; + wait( 500 ); + terminate(); + + 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( void ) +{ + QString dev = configManager::inst()->value( "midialsaseq", "device" ); + if( dev == "" ) + { + 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 NOTE_ON: + snd_seq_ev_set_noteon( &ev, + _port->outputChannel(), + _me.key() + NOTES_PER_OCTAVE, + _me.velocity() ); + break; + + case NOTE_OFF: + snd_seq_ev_set_note( &ev, + _port->outputChannel(), + _me.key() + NOTES_PER_OCTAVE, + _me.velocity(), 500 ); + break; + + case KEY_PRESSURE: + snd_seq_ev_set_keypress( &ev, + _port->outputChannel(), + _me.key() + NOTES_PER_OCTAVE, + _me.velocity() ); + break; + + case PITCH_BEND: + snd_seq_ev_set_pitchbend( &ev, + _port->outputChannel(), + _me.m_data.m_param[0] - 8192 ); + break; + + case PROGRAM_CHANGE: + snd_seq_ev_set_pgmchange( &ev, + _port->outputChannel(), + _me.m_data.m_param[0] ); + break; + + case CHANNEL_PRESSURE: + snd_seq_ev_set_chanpress( &ev, + _port->outputChannel(), + _me.m_data.m_param[0] ); + break; + + default: + printf( "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[0] |= 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->name().ascii(), + caps[i], + SND_SEQ_PORT_TYPE_MIDI_GENERIC | + SND_SEQ_PORT_TYPE_APPLICATION ); + continue; + } + // this C-API sucks!! normally we at least could create + // a local snd_seq_port_info_t variable but the type- + // info for this is hidden and we have to mess with + // pointers... + 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_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; + } + // this C-API sucks!! normally we at least could create a local + // snd_seq_port_info_t variable but the type-info for this is + // hidden and we have to mess with pointers... + 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->name().ascii() ); + snd_seq_set_port_info( m_seqHandle, m_portIDs[_port][i], + port_info ); + snd_seq_port_info_free( port_info ); + } + // this small workaround would make qjackctl refresh it's MIDI- + // connection-window since it doesn't update it automatically if only + // the name of a client-port changes +/* snd_seq_delete_simple_port( m_seqHandle, + snd_seq_create_simple_port( m_seqHandle, "", 0, + SND_SEQ_PORT_TYPE_APPLICATION ) );*/ +} + + + + +void midiALSASeq::removePort( midiPort * _port ) +{ + if( m_portIDs.contains( _port ) ) + { + snd_seq_delete_port( m_seqHandle, m_portIDs[_port][0] ); + snd_seq_delete_port( m_seqHandle, m_portIDs[_port][1] ); + m_portIDs.remove( _port ); + } + midiClient::removePort( _port ); +} + + + + +void midiALSASeq::run( void ) +{ + while( m_quit == FALSE ) + { + snd_seq_event_t * ev; + snd_seq_event_input( m_seqHandle, &ev ); + + midiPort * dest = NULL; + for( csize i = 0; i < m_portIDs.values().size(); ++i ) + { + if( m_portIDs.values()[i][0] == ev->dest.port || + m_portIDs.values()[i][0] == ev->dest.port ) + { + dest = m_portIDs.keys()[i]; + } + } + if( dest == NULL ) + { + continue; + } + switch( ev->type ) + { + case SND_SEQ_EVENT_NOTEON: + dest->processInEvent( midiEvent( NOTE_ON, + ev->data.note.channel, + ev->data.note.note - + NOTES_PER_OCTAVE, + ev->data.note.velocity + ), midiTime( ev->time.tick) ); + break; + + case SND_SEQ_EVENT_NOTEOFF: + dest->processInEvent( midiEvent( NOTE_OFF, + ev->data.note.channel, + ev->data.note.note - + NOTES_PER_OCTAVE, + ev->data.note.velocity + ), midiTime( ev->time.tick) ); + break; + + case SND_SEQ_EVENT_KEYPRESS: + dest->processInEvent( midiEvent( KEY_PRESSURE, + ev->data.note.channel, + ev->data.note.note - + NOTES_PER_OCTAVE, + ev->data.note.velocity + ), midiTime() ); + break; + + case SND_SEQ_EVENT_SENSING: + case SND_SEQ_EVENT_CLOCK: + break; + + default: + printf( "ALSA-sequencer: unhandled input " + "event %d\n", ev->type ); + break; + } + } + +} + + + + +void midiALSASeq::changeQueueTempo( int _bpm ) +{ + snd_seq_change_queue_tempo( m_seqHandle, m_queueID, 60000000 / _bpm, + NULL ); + snd_seq_drain_output( m_seqHandle ); +} + + + + + + + + +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( void ) +{ + configManager::inst()->setValue( "midialsaseq", "device", + m_device->text() ); +} + + +#include "midi_alsa_seq.moc" + + +#endif +