Support for using jack midi input (#2038)

* Support jack midi input

* If jack is used for audio then use the same connection for midi as well

* Remove old FreeBSD adjustment for portaudio as multiple version support has been dropped.

* Disable jack midi out port until it is functional
This commit is contained in:
Shane Ambler
2016-11-10 09:33:35 +10:30
committed by Oskar Wallgren
parent c9618961d6
commit 05ace7e348
9 changed files with 366 additions and 6 deletions

View File

@@ -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"
)

View File

@@ -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<jack_port_t *> m_outputPorts;
jack_default_audio_sample_t * * m_tempOutBufs;
surroundSampleFrame * m_outBuf;

View File

@@ -45,11 +45,7 @@ public:
#ifdef LMMS_HAVE_PORTAUDIO
#if defined(__FreeBSD__)
#include <portaudio2/portaudio.h>
#else
#include <portaudio.h>
#endif
#include <QtCore/QSemaphore>

95
include/MidiJack.h Normal file
View File

@@ -0,0 +1,95 @@
/*
* MidiJack.h - MIDI client for Jack
*
* Copyright (c) 2015 Shane Ambler <develop/at/shaneware.biz>
*
* 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 <jack/jack.h>
#include <jack/midiport.h>
#include <QtCore/QThread>
#include <QMutex>
#include <QtCore/QFile>
#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

View File

@@ -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

View File

@@ -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 == "" )
{

View File

@@ -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] =

223
src/core/midi/MidiJack.cpp Normal file
View File

@@ -0,0 +1,223 @@
/*
* MidiJack.cpp - MIDI client for Jack
*
* Copyright (c) 2015 Shane Ambler <develop/at/shaneware.biz>
*
* 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 <QCompleter>
#include <QDirModel>
#include <QMessageBox>
#include <QTranslator>
#ifdef LMMS_HAVE_STDLIB_H
#include <stdlib.h>
#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<AudioJack*>(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<nframes; i++)
{
if((in_event.time == i) && (event_index < event_count))
{
// lmms is setup to parse bytes coming from a device
// parse it byte by byte as it expects
for(b=0;b<in_event.size;b++)
parseData( *(in_event.buffer + b) );
event_index++;
if(event_index < event_count)
jack_midi_event_get(&in_event, port_buf, event_index);
}
}
}
/* jack midi out is not implemented
sending plain bytes to jack midi outputs doesn't work
once working the output port needs to be enabled in the constructor
*/
void MidiJack::sendByte( const unsigned char c )
{
//m_midiDev.putChar( c );
}
// we write data to jack
void MidiJack::JackMidiWrite(jack_nframes_t nframes)
{
// TODO: write midi data to jack port
}
void MidiJack::run()
{
while( m_quit == false )
{
// we sleep the thread to keep it alive
// midi processing is handled by jack server callbacks
sleep(1);
}
}
#endif // LMMS_HAVE_JACK

View File

@@ -65,6 +65,7 @@
// platform-specific midi-interface-classes
#include "MidiAlsaRaw.h"
#include "MidiAlsaSeq.h"
#include "MidiJack.h"
#include "MidiOss.h"
#include "MidiSndio.h"
#include "MidiWinMM.h"
@@ -862,6 +863,11 @@ SetupDialog::SetupDialog( ConfigTabs _tab_to_open ) :
MidiSetupWidget::create<MidiAlsaRaw>( msw );
#endif
#ifdef LMMS_HAVE_JACK
m_midiIfaceSetupWidgets[MidiJack::name()] =
MidiSetupWidget::create<MidiJack>( msw );
#endif
#ifdef LMMS_HAVE_OSS
m_midiIfaceSetupWidgets[MidiOss::name()] =
MidiSetupWidget::create<MidiOss>( msw );