diff --git a/CMakeLists.txt b/CMakeLists.txt index 0466f4af1..70707f3c3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -589,6 +589,7 @@ MESSAGE( "* ALSA : ${STATUS_ALSA}\n" "* OSS : ${STATUS_OSS}\n" "* Sndio : ${STATUS_SNDIO}\n" +"* JACK : ${STATUS_JACK}\n" "* WinMM : ${STATUS_WINMM}\n" "* AppleMidi : ${STATUS_APPLEMIDI}\n" ) diff --git a/include/AudioJack.h b/include/AudioJack.h index ec4fc5819..9864cf8e4 100644 --- a/include/AudioJack.h +++ b/include/AudioJack.h @@ -37,9 +37,9 @@ #include "AudioDevice.h" #include "AudioDeviceSetupWidget.h" - class QLineEdit; class LcdSpinBox; +class MidiJack; class AudioJack : public QObject, public AudioDevice @@ -49,6 +49,12 @@ public: AudioJack( bool & _success_ful, Mixer* mixer ); virtual ~AudioJack(); + // this is to allow the jack midi connection to use the same jack client connection + // the jack callback is handled here, we call the midi client so that it can read + // it's midi data during the callback + AudioJack * addMidiClient(MidiJack *midiClient); + jack_client_t * jackClient() {return m_client;}; + inline static QString name() { return QT_TRANSLATE_NOOP( "setupWidget", @@ -98,6 +104,7 @@ private: bool m_active; bool m_stopped; + MidiJack *m_midiClient; QVector m_outputPorts; jack_default_audio_sample_t * * m_tempOutBufs; surroundSampleFrame * m_outBuf; diff --git a/include/AudioPortAudio.h b/include/AudioPortAudio.h index 8b920eea6..74bb07ab1 100644 --- a/include/AudioPortAudio.h +++ b/include/AudioPortAudio.h @@ -45,11 +45,7 @@ public: #ifdef LMMS_HAVE_PORTAUDIO -#if defined(__FreeBSD__) -#include -#else #include -#endif #include diff --git a/include/MidiJack.h b/include/MidiJack.h new file mode 100644 index 000000000..0ed26019a --- /dev/null +++ b/include/MidiJack.h @@ -0,0 +1,95 @@ +/* + * MidiJack.h - MIDI client for Jack + * + * Copyright (c) 2015 Shane Ambler + * + * 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. + * + */ + +#ifndef MIDIJACK_H +#define MIDIJACK_H + +#include "lmmsconfig.h" + +#ifdef LMMS_HAVE_JACK +#include +#include + +#include +#include +#include + +#include "MidiClient.h" +#include "AudioJack.h" + +#define JACK_MIDI_BUFFER_MAX 64 /* events */ + +class QLineEdit; + +class MidiJack : public MidiClientRaw, public QThread +{ +public: + MidiJack(); + virtual ~MidiJack(); + + jack_client_t* jackClient(); + + static QString probeDevice(); + + inline static QString name() + { + return( QT_TRANSLATE_NOOP( "MidiSetupWidget", + "Jack-MIDI" ) ); + } + + void JackMidiWrite(jack_nframes_t nframes); + void JackMidiRead(jack_nframes_t nframes); + + + inline static QString configSection() + { + return "MidiJack"; + } + + +protected: + virtual void sendByte( const unsigned char c ); + virtual void run(); + + +private: + AudioJack *m_jackAudio; + jack_client_t *m_jackClient; + jack_port_t *m_input_port; + jack_port_t *m_output_port; + uint8_t m_jack_buffer[JACK_MIDI_BUFFER_MAX * 4]; + + void JackMidiOutEvent(uint8_t *buf, uint8_t len); + void lock(); + void unlock(); + + void getPortInfo( const QString& sPortName, int& nClient, int& nPort ); + + volatile bool m_quit; + +}; + +#endif // LMMS_HAVE_JACK + +#endif // MIDIJACK_H diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index ac956ec61..3ec3cb7ee 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -84,6 +84,7 @@ set(LMMS_SRCS core/midi/MidiAlsaSeq.cpp core/midi/MidiClient.cpp core/midi/MidiController.cpp + core/midi/MidiJack.cpp core/midi/MidiOss.cpp core/midi/MidiSndio.cpp core/midi/MidiApple.cpp diff --git a/src/core/Mixer.cpp b/src/core/Mixer.cpp index bc989214e..a2072fc40 100644 --- a/src/core/Mixer.cpp +++ b/src/core/Mixer.cpp @@ -50,6 +50,7 @@ // platform-specific midi-interface-classes #include "MidiAlsaRaw.h" #include "MidiAlsaSeq.h" +#include "MidiJack.h" #include "MidiOss.h" #include "MidiSndio.h" #include "MidiWinMM.h" @@ -975,6 +976,19 @@ MidiClient * Mixer::tryMidiClients() } #endif +#ifdef LMMS_HAVE_JACK + if( client_name == MidiJack::name() || client_name == "" ) + { + MidiJack * mjack = new MidiJack; + if( mjack->isRunning() ) + { + m_midiClientName = MidiJack::name(); + return mjack; + } + delete mjack; + } +#endif + #ifdef LMMS_HAVE_OSS if( client_name == MidiOss::name() || client_name == "" ) { diff --git a/src/core/audio/AudioJack.cpp b/src/core/audio/AudioJack.cpp index 481ad6ba1..238a6b9c6 100644 --- a/src/core/audio/AudioJack.cpp +++ b/src/core/audio/AudioJack.cpp @@ -41,7 +41,7 @@ #include "AudioPort.h" #include "MainWindow.h" #include "Mixer.h" - +#include "MidiJack.h" @@ -52,6 +52,7 @@ AudioJack::AudioJack( bool & _success_ful, Mixer* _mixer ) : _mixer ), m_client( NULL ), m_active( false ), + m_midiClient( NULL ), m_tempOutBufs( new jack_default_audio_sample_t *[channels()] ), m_outBuf( new surroundSampleFrame[mixer()->framesPerPeriod()] ), m_framesDoneInCurBuf( 0 ), @@ -123,7 +124,15 @@ void AudioJack::restartAfterZombified() +AudioJack* AudioJack::addMidiClient(MidiJack *midiClient) +{ + if( m_client == NULL ) + return NULL; + m_midiClient = midiClient; + + return this; +} bool AudioJack::initJackClient() { @@ -331,6 +340,14 @@ void AudioJack::renamePort( AudioPort * _port ) int AudioJack::processCallback( jack_nframes_t _nframes, void * _udata ) { + // do midi processing first so that midi input can + // add to the following sound processing + if( m_midiClient && _nframes > 0 ) + { + m_midiClient->JackMidiRead(_nframes); + m_midiClient->JackMidiWrite(_nframes); + } + for( int c = 0; c < channels(); ++c ) { m_tempOutBufs[c] = diff --git a/src/core/midi/MidiJack.cpp b/src/core/midi/MidiJack.cpp new file mode 100644 index 000000000..c3bb5a28f --- /dev/null +++ b/src/core/midi/MidiJack.cpp @@ -0,0 +1,223 @@ +/* + * MidiJack.cpp - MIDI client for Jack + * + * Copyright (c) 2015 Shane Ambler + * + * 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 "MidiJack.h" + +#ifdef LMMS_HAVE_JACK + +#include +#include +#include +#include + +#ifdef LMMS_HAVE_STDLIB_H +#include +#endif + +#include "ConfigManager.h" +#include "gui_templates.h" +#include "GuiApplication.h" +#include "Engine.h" +#include "Mixer.h" +#include "MainWindow.h" + +/* callback functions for jack */ +static int JackMidiProcessCallback(jack_nframes_t nframes, void *arg) +{ + MidiJack *jmd = (MidiJack *)arg; + + if (nframes <= 0) + return (0); + + jmd->JackMidiRead(nframes); + jmd->JackMidiWrite(nframes); + + return (0); +} + +static void JackMidiShutdown(void *arg) +{ + // TODO: support translations here + const QString mess_short = "JACK server down"; + const QString mess_long = "The JACK server seems to have been shutdown."; + QMessageBox::information( gui->mainWindow(), mess_short, mess_long ); +} + +MidiJack::MidiJack() : + MidiClientRaw(), + m_input_port( NULL ), + m_output_port( NULL ), + m_quit( false ) +{ + // if jack is used for audio then we share the connection + // AudioJack creates and maintains the jack connection + // and also handles the callback, we pass it our address + // so that we can also process during the callback + + if(Engine::mixer()->audioDevName() == AudioJack::name() ) + { + // if a jack connection has been created for audio we use that + m_jackAudio = dynamic_cast(Engine::mixer()->audioDev())->addMidiClient(this); + }else{ + m_jackAudio = NULL; + m_jackClient = jack_client_open(probeDevice().toLatin1().data(), + JackNoStartServer, NULL); + + if(m_jackClient) + { + jack_set_process_callback(m_jackClient, + JackMidiProcessCallback, this); + jack_on_shutdown(m_jackClient, + JackMidiShutdown, 0); + } + } + + if(jackClient()) + { + /* jack midi out not implemented + JackMidiWrite and sendByte needs to be functional + before enabling this + m_output_port = jack_port_register( + jackClient(), "MIDI out", JACK_DEFAULT_MIDI_TYPE, + JackPortIsOutput, 0); + */ + + m_input_port = jack_port_register( + jackClient(), "MIDI in", JACK_DEFAULT_MIDI_TYPE, + JackPortIsInput, 0); + + if(jack_activate(jackClient()) == 0 ) + { + // only start thread, if we have an active jack client. + start( QThread::LowPriority ); + } + } +} + +MidiJack::~MidiJack() +{ + if(jackClient()) + { + if( jack_port_unregister( jackClient(), m_input_port) != 0){ + printf("Failed to unregister jack midi input\n"); + } + + if( jack_port_unregister( jackClient(), m_output_port) != 0){ + printf("Failed to unregister jack midi output\n"); + } + + if(m_jackClient) + { + // an m_jackClient means we are handling the jack connection + if( jack_deactivate(m_jackClient) != 0){ + printf("Failed to deactivate jack midi client\n"); + } + + if( jack_client_close(m_jackClient) != 0){ + printf("Failed close jack midi client\n"); + } + } + } + if( isRunning() ) + { + m_quit = true; + wait( 1000 ); + terminate(); + } +} + +jack_client_t* MidiJack::jackClient() +{ + if( m_jackAudio == NULL && m_jackClient == NULL) + return NULL; + + if( m_jackAudio == NULL && m_jackClient ) + return m_jackClient; + + return m_jackAudio->jackClient(); +} + +QString MidiJack::probeDevice() +{ + QString jid = ConfigManager::inst()->value( "midijack", "lmms" ); + if( jid.isEmpty() ) + { + return "lmms"; + } + return jid; +} + +// we read data from jack +void MidiJack::JackMidiRead(jack_nframes_t nframes) +{ + unsigned int i,b; + void* port_buf = jack_port_get_buffer(m_input_port, nframes); + jack_midi_event_t in_event; + jack_nframes_t event_index = 0; + jack_nframes_t event_count = jack_midi_get_event_count(port_buf); + + jack_midi_event_get(&in_event, port_buf, 0); + for(i=0; i( msw ); #endif +#ifdef LMMS_HAVE_JACK + m_midiIfaceSetupWidgets[MidiJack::name()] = + MidiSetupWidget::create( msw ); +#endif + #ifdef LMMS_HAVE_OSS m_midiIfaceSetupWidgets[MidiOss::name()] = MidiSetupWidget::create( msw );