Hello,
I've been working on adding JACK MIDI support to Hydrogen. To generate discussion/feedback/openness... I'm attaching the current state of my work. This code is *not* intended to be committed to SVN. This code needs a good bit of cleanup... and is known to SEGFAULT when Hydrogen exits.
The patches attached are against SVN rev 251. Below is a single patch for the whole thing. Attached is a tarball that has the patches divided up over 7 commits.
Main concepts:
* Creating a static global JACK Client object to help JackMidiDriver
and JackOutput cooperate.
* Adding hooks to MidiInput so that MIDI processing can be done
inside the audioEngine_process() callback.
* Adding timing data to MidiMessage to make sample-perfect matching
of midi messages and audio possible.
Known warts / to-do list:
* Often SEGFAULTs when exiting. The pointer to the audio driver somehow
becomes NULL while the sampler engine is still processing it.
* No LASH support.
* JackClient implemented as static global rather (my preference) rather
than a singleton (like the rest of the code). (...sorry for that.)
* Jack types (esp. jack_nframes_t), JACK_SUPPORT, JACKMIDI_SUPPORT is a
little messy.
* No JACK API checking (e.g. this code works with jack 0.103, but not 0.109.
* I'm throwing exceptions, but handling none... and all but ignoring the
Hydrogen logging system.
* My code is formatted with spaces rather than tabs.
* Restarting drivers never actually restarts the JACK client... just
the ports.
Peace,
Gabriel
diff --git a/extra/hydrogenPlayer/hydrogenPlayer.pro
b/extra/hydrogenPlayer/hydrogenPlayer.pro
index 36a1c65..33fb250 100644
--- a/extra/hydrogenPlayer/hydrogenPlayer.pro
+++ b/extra/hydrogenPlayer/hydrogenPlayer.pro
@@ -58,6 +58,10 @@ contains(H2DEFINES, JACK_SUPPORT ) {
LIBS += -ljack
}
+contains(H2DEFINES, JACKMIDI_SUPPORT ) {
+ LIBS += -ljack
+}
+
contains(H2DEFINES, LASH_SUPPORT ) {
LIBS += -llash
}
diff --git a/features.pri b/features.pri
index b395054..99db09b 100644
--- a/features.pri
+++ b/features.pri
@@ -14,6 +14,7 @@ macx-g++ {
linux-g++ {
H2DEFINES += ALSA_SUPPORT
H2DEFINES += JACK_SUPPORT
+ H2DEFINES += JACKMIDI_SUPPORT
H2DEFINES += LASH_SUPPORT
H2DEFINES += FLAC_SUPPORT
H2DEFINES += LADSPA_SUPPORT
@@ -24,6 +25,7 @@ linux-g++ {
linux-g++-64 {
H2DEFINES += ALSA_SUPPORT
H2DEFINES += JACK_SUPPORT
+ H2DEFINES += JACKMIDI_SUPPORT
H2DEFINES += LASH_SUPPORT
H2DEFINES += FLAC_SUPPORT
H2DEFINES += LADSPA_SUPPORT
diff --git a/gui/src/PreferencesDialog.cpp b/gui/src/PreferencesDialog.cpp
index a6aa261..0c2364c 100644
--- a/gui/src/PreferencesDialog.cpp
+++ b/gui/src/PreferencesDialog.cpp
@@ -95,6 +95,7 @@ PreferencesDialog::PreferencesDialog(QWidget* parent)
m_pMidiDriverComboBox->addItem( "ALSA" );
m_pMidiDriverComboBox->addItem( "PortMidi" );
m_pMidiDriverComboBox->addItem( "CoreMidi" );
+ m_pMidiDriverComboBox->addItem( "JackMidi" );
if ( pPref->m_sMidiDriver == "ALSA" ) {
m_pMidiDriverComboBox->setCurrentIndex(0);
@@ -105,6 +106,9 @@ PreferencesDialog::PreferencesDialog(QWidget* parent)
else if ( pPref->m_sMidiDriver == "CoreMidi" ) {
m_pMidiDriverComboBox->setCurrentIndex(2);
}
+ else if ( pPref->m_sMidiDriver == "JackMidi" ) {
+ m_pMidiDriverComboBox->setCurrentIndex(3);
+ }
else {
ERRORLOG( "Unknown midi input from preferences [" + pPref->m_sMidiDriver +
"]" );
}
@@ -325,6 +329,9 @@ void PreferencesDialog::on_okBtn_clicked()
else if ( m_pMidiDriverComboBox->currentText() == "CoreMidi" ) {
pPref->m_sMidiDriver = "CoreMidi";
}
+ else if ( m_pMidiDriverComboBox->currentText() == "JackMidi" ) {
+ pPref->m_sMidiDriver = "JackMidi";
+ }
pPref->m_bMidiNoteOffIgnore = m_pIgnoreNoteOffCheckBox->isChecked();
diff --git a/libs/hydrogen/hydrogen.pro b/libs/hydrogen/hydrogen.pro
index f2488bb..4cd45fa 100644
--- a/libs/hydrogen/hydrogen.pro
+++ b/libs/hydrogen/hydrogen.pro
@@ -72,6 +72,7 @@ HEADERS += \
include/hydrogen/IO/TransportInfo.h \
include/hydrogen/IO/MidiInput.h \
include/hydrogen/IO/CoreMidiDriver.h \
+ include/hydrogen/IO/JackClient.h \
include/hydrogen/IO/JackOutput.h \
include/hydrogen/IO/NullDriver.h \
\
@@ -93,6 +94,7 @@ HEADERS += \
src/IO/PortMidiDriver.h \
src/IO/PortAudioDriver.h \
src/IO/CoreAudioDriver.h \
+ src/IO/JackMidiDriver.h \
src/flac_file.h \
@@ -107,6 +109,7 @@ SOURCES += \
src/IO/alsa_midi_driver.cpp \
src/IO/disk_writer_driver.cpp \
src/IO/fake_driver.cpp \
+ src/IO/jack_client.cpp \
src/IO/jack_output.cpp \
src/IO/null_driver.cpp \
src/IO/oss_driver.cpp \
@@ -117,6 +120,7 @@ SOURCES += \
src/IO/portaudio_driver.cpp \
src/IO/coreaudio_driver.cpp \
src/IO/coremidi_driver.cpp \
+ src/IO/jack_midi_driver.cpp \
\
src/fx/effects.cpp \
src/fx/ladspa_fx.cpp \
diff --git a/libs/hydrogen/include/hydrogen/IO/JackClient.h
b/libs/hydrogen/include/hydrogen/IO/JackClient.h
new file mode 100644 index 0000000..ab189ee --- /dev/null +++ b/libs/hydrogen/include/hydrogen/IO/JackClient.h @@ -0,0 +1,72 @@ +/* + * Hydrogen + * Copyright(c) 2002-2008 by Alex >Comix< Cominu [EMAIL PROTECTED] + * + * http://www.hydrogen-music.org + * + * 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; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +/* JackClient.h + * Copyright(c) 2008 by Gabriel M. Beddingfield <[EMAIL PROTECTED]> + * + * This class manages the Jack client handle (returned by jack_client_open()). + */ + +#ifndef JACK_CLIENT_H +#define JACK_CLIENT_H + +#ifdef JACK_SUPPORT + +#include <jack/types.h> +#include <hydrogen/h2_exception.h> +#include <QtCore/QString> + +namespace H2Core +{ + class JackClient + { + public: + JackClient(void); + ~JackClient(void); + + jack_client_t* ref(void) { return m_client; } + int setAudioProcessCallback(JackProcessCallback process); + int setNonAudioProcessCallback(JackProcessCallback process); + int clearAudioProcessCallback(void); + int clearNonAudioProcessCallback(void); + std::vector<QString> getMidiOutputPortList(void); + + private: + jack_client_t* m_client; + JackProcessCallback m_audio_process; + JackProcessCallback m_nonaudio_process; + }; + + class JackClientException : public H2Exception + { + public: + JackClientException(const QString& msg ) : H2Exception(msg) {} + }; + + + JackClient& getJackClientInstance(void); + + +} + +#endif // JACK_SUPPORT + +#endif // JACK_CLIENT_Hdiff --git a/libs/hydrogen/include/hydrogen/IO/JackOutput.h b/libs/hydrogen/include/hydrogen/IO/JackOutput.h
index 4e7a1ae..4a1d211 100644
--- a/libs/hydrogen/include/hydrogen/IO/JackOutput.h
+++ b/libs/hydrogen/include/hydrogen/IO/JackOutput.h
@@ -48,8 +48,6 @@ class Instrument;
class JackOutput : public AudioOutput
{
public:
- jack_client_t *client;
-
JackOutput( JackProcessCallback processCallback );
~JackOutput();
diff --git a/libs/hydrogen/include/hydrogen/IO/MidiInput.h
b/libs/hydrogen/include/hydrogen/IO/MidiInput.h
index 60e6f71..9d8343c 100644
--- a/libs/hydrogen/include/hydrogen/IO/MidiInput.h
+++ b/libs/hydrogen/include/hydrogen/IO/MidiInput.h
@@ -26,6 +26,8 @@
#include <hydrogen/Object.h>
#include <string>
#include <vector>
+#warning "TODO: Jack corruption on the main line!"
+#include <jack/types.h>
namespace H2Core
{
@@ -56,12 +58,17 @@ public:
int m_nData2;
int m_nChannel;
std::vector<unsigned char> m_sysexData;
+ bool m_use_frame;
+ jack_nframes_t m_frame;
MidiMessage()
: m_type( UNKNOWN )
, m_nData1( -1 )
, m_nData2( -1 )
- , m_nChannel( -1 ) {}
+ , m_nChannel( -1 )
+ , m_use_frame(false)
+ , m_frame(0)
+ {}
};
@@ -82,7 +89,7 @@ class MidiInput : public Object
{
public:
MidiInput( const QString class_name );
- ~MidiInput();
+ virtual ~MidiInput();
virtual void open() = 0;
virtual void close() = 0;
@@ -94,6 +101,12 @@ public:
void handleMidiMessage( const MidiMessage& msg );
void handleSysexMessage( const MidiMessage& msg );
+ // Process callback hooks. Specifically added for JACK MIDI,
+ // but could be used by others. The default implementation
+ // does nothing.
+ virtual int processAudio(jack_nframes_t nframes); // Assumes processing in
same thread as audio
+ virtual int processNonAudio(jack_nframes_t nframes); // Assumes processing in
other thread.
+
protected:
bool m_bActive;
diff --git a/libs/hydrogen/include/hydrogen/hydrogen.h
b/libs/hydrogen/include/hydrogen/hydrogen.h
index aa340ce..cb5c0bf 100644
--- a/libs/hydrogen/include/hydrogen/hydrogen.h
+++ b/libs/hydrogen/include/hydrogen/hydrogen.h
@@ -74,7 +74,8 @@ public:
Song* getSong();
void removeSong();
- void addRealtimeNote ( int instrument, float velocity, float pan_L=1.0, float
pan_R=1.0, float pitch=0.0, bool forcePlay=false );
+ void addRealtimeNote ( int instrument, float velocity, float pan_L=1.0, float
pan_R=1.0, float pitch=0.0, bool forcePlay=false, bool use_frame = false,
uint32_t frame = 0 );
+
float getMasterPeak_L();
void setMasterPeak_L( float value );
@@ -87,7 +88,7 @@ public:
unsigned long getTickPosition();
- unsigned long getRealtimeTickPosition();
+ unsigned long getRealtimeTickPosition(unsigned long offset = 0);
unsigned long getTotalFrames();
unsigned long getRealtimeFrames();
diff --git a/libs/hydrogen/src/IO/JackMidiDriver.h
b/libs/hydrogen/src/IO/JackMidiDriver.h
new file mode 100644 index 0000000..e15490e --- /dev/null +++ b/libs/hydrogen/src/IO/JackMidiDriver.h @@ -0,0 +1,79 @@ +/* + * Hydrogen + * Copyright(c) 2002-2008 by Alex >Comix< Cominu [EMAIL PROTECTED] + * + * http://www.hydrogen-music.org + * + * 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; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +/* JackMidiDriver.h + * Copyright(c) 2008 by Gabriel M. Beddingfield <[EMAIL PROTECTED]> + * + * Note: This class implements it's own Jack Client object and process + * callback. Once working, it should be merged with the JackOutput + * class into a superclass called something like JackDriver. + */ + +#ifndef JACK_MIDI_DRIVER_H +#define JACK_MIDI_DRIVER_H + +#ifdef JACKMIDI_SUPPORT + +#include <hydrogen/IO/MidiInput.h> +#include <hydrogen/h2_exception.h> +#include <jack/jack.h> +#include <jack/midiport.h> +#include <vector> +#include <QtCore/QString> +#include <memory> + +namespace H2Core +{ + + class JackMidiDriver : public MidiInput + { + public: + JackMidiDriver(void); + ~JackMidiDriver(); + + // Reimplemented from MidiInput + void open(void); + void close(void); + virtual std::vector<QString> getOutputPortList(void); + + int processAudio(jack_nframes_t nframes); + int processNonAudio(jack_nframes_t nframes); + + private: + jack_port_t* m_port; + + int process(jack_nframes_t nframes, bool use_frame); + + }; // JackMidiDriver + + class JackMidiException : public H2Exception + { + public: + JackMidiException(const QString& msg ) : H2Exception(msg) {} + JackMidiException(const QString& msg, JackStatus /*stat*/) + : H2Exception(msg) {} + }; + +} // namespace H2Core + +#endif // JACKMIDI_SUPPORT + +#endif // JACK_MIDI_DRIVER_Hdiff --git a/libs/hydrogen/src/IO/jack_client.cpp b/libs/hydrogen/src/IO/jack_client.cpp
new file mode 100644 index 0000000..c35a44e --- /dev/null +++ b/libs/hydrogen/src/IO/jack_client.cpp @@ -0,0 +1,140 @@ +/* + * Hydrogen + * Copyright(c) 2002-2008 by Alex >Comix< Cominu [EMAIL PROTECTED] + * + * http://www.hydrogen-music.org + * + * 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; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +/* JackClient.cpp + * Copyright(c) 2008 by Gabriel M. Beddingfield <[EMAIL PROTECTED]> + */ + +#include <hydrogen/IO/JackClient.h> +#include <jack/jack.h> +#include <hydrogen/Object.h> + +#ifdef JACK_SUPPORT + +using namespace std; +namespace H2Core +{ + +JackClient& getJackClientInstance(void) +{ + static JackClient client; + return client; +} + +JackClient::JackClient() + : m_client(0), + m_audio_process(0), + m_nonaudio_process(0) +{ + m_client = jack_client_open( + "Hydrogen", + JackNullOption, + 0); + if (!m_client) throw JackClientException("Could not create JACK client"); + +} + +JackClient::~JackClient() +{ + _INFOLOG( "DESTROY" ); + if(m_client) { + jack_deactivate(m_client); // return value ignored + jack_client_close(m_client); // Ignore return value + m_client = 0; + } +} + +int JackClient::setAudioProcessCallback(JackProcessCallback process) +{ + if (jack_deactivate(m_client)) + throw JackClientException("Could not deactivate JACK MIDI client."); + int rv = jack_set_process_callback(m_client, process, 0); + if (!rv) m_audio_process = process; + if (jack_activate(m_client)) + throw JackClientException("Could not activate JACK MIDI client."); + return rv; +} + +int JackClient::setNonAudioProcessCallback(JackProcessCallback process) +{ + if (jack_deactivate(m_client)) + throw JackClientException("Could not deactivate JACK MIDI client."); + int rv = 0; + if (!m_audio_process) { + rv = jack_set_process_callback(m_client, process, 0); + } + if (!rv) m_nonaudio_process = process; + if (jack_activate(m_client)) + throw JackClientException("Could not activate JACK MIDI client."); + return rv; +} + +int JackClient::clearAudioProcessCallback(void) +{ + int rv = 0; + if (jack_deactivate(m_client)) + throw JackClientException("Could not deactivate JACK MIDI client."); + // make sure the process cycle is over before killing anything + if (m_nonaudio_process) { + rv = jack_set_process_callback(m_client, m_nonaudio_process, 0); + } + if (m_nonaudio_process && rv) { + rv = jack_set_process_callback(m_client, 0, 0); + if (jack_activate(m_client)) + throw JackClientException("Could not activate JACK MIDI client."); + } + m_audio_process = 0; + return rv; +} + +int JackClient::clearNonAudioProcessCallback(void) +{ + int rv = 0; + if (!m_audio_process) { + if (jack_deactivate(m_client)) + throw JackClientException("Could not deactivate JACK MIDI client."); + rv = jack_set_process_callback(m_client, 0, 0); + } + m_nonaudio_process = 0; + return rv; +} + +std::vector<QString> JackClient::getMidiOutputPortList(void) +{ + vector<QString> ports; + const char **port_names = 0; + port_names = jack_get_ports(m_client, + 0, + JACK_DEFAULT_MIDI_TYPE, + JackPortIsOutput); + + while (*port_names) { + ports.push_back(QString(*port_names)); + free(*port_names); + ++port_names; + } + return ports; +} + + +} // namespace H2Core + +#endif // JACKMIDI_SUPPORTdiff --git a/libs/hydrogen/src/IO/jack_midi_driver.cpp b/libs/hydrogen/src/IO/jack_midi_driver.cpp
new file mode 100644 index 0000000..ee3064f --- /dev/null +++ b/libs/hydrogen/src/IO/jack_midi_driver.cpp @@ -0,0 +1,277 @@ +/* + * Hydrogen + * Copyright(c) 2002-2008 by Alex >Comix< Cominu [EMAIL PROTECTED] + * + * http://www.hydrogen-music.org + * + * 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; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +/* JackMidiDriver.cpp + * Copyright(c) 2008 by Gabriel M. Beddingfield <[EMAIL PROTECTED]> + */ + +#include "JackMidiDriver.h" +#include <hydrogen/IO/JackClient.h> +#include <cassert> +#include <cstdlib> // free() +#include <hydrogen/Preferences.h> // For preferred auto-connection +#include <cerrno> // EEXIST for jack_connect() +#ifdef JACKMIDI_SUPPORT + +using namespace std; +using namespace H2Core; + +JackProcessCallback jackMidiFallbackProcess; // implemented in hydrogen.cpp + +JackMidiDriver::JackMidiDriver() + : MidiInput( "JackMidiDriver" ), + m_port(0) +{ +} + +JackMidiDriver::~JackMidiDriver() +{ + INFOLOG( "DESTROY" ); + close(); +} + +void JackMidiDriver::open(void) +{ + JackClient& client = getJackClientInstance(); + if (client.setNonAudioProcessCallback(jackMidiFallbackProcess)) { + throw JackMidiException("Could not set JACK process callback"); + } + m_port = jack_port_register(client.ref(), + "midi_in", + JACK_DEFAULT_MIDI_TYPE, + JackPortIsInput, + 0); + if (!m_port) throw JackMidiException("Could not create JACK MIDI input port"); + + // Autoconnect port to an Output (readable) port + QString OutPort = Preferences::getInstance()->m_sMidiPortName; + int err = jack_connect(client.ref(), + OutPort.toLatin1().constData(), + jack_port_name(m_port)); + if(err && (err != EEXIST)) { + _INFOLOG("Jack could not connect to port " + OutPort); + } + +} + +void JackMidiDriver::close(void) +{ + if(m_port) { + jack_client_t* client = getJackClientInstance().ref(); + assert(client); + jack_port_unregister(client, m_port); // Ignore return value + m_port = 0; + } +} + + +void translate_jack_midi_to_h2(H2Core::MidiMessage& msg, + const jack_midi_event_t& event, + bool use_frame) +{ + typedef enum { + UNKNOWN = 0x00, + NOTE_OFF = 0x80, + NOTE_ON = 0x90, + POLYPHONIC_KEY_PRESSURE = 0xA0, + CONTROL_CHANGE = 0xB0, + PROGRAM_CHANGE = 0xC0, + CHANNEL_PRESSURE = 0xD0, + PITCH_WHEEL = 0xE0, + SYSTEM_EXCLUSIVE = 0xF0 + } midi_commands; + typedef enum { + SYSEX_START = 0x00, + MTC_QUARTER_FRAME = 0x01, + SONG_POS = 0x02, + SONG_SELECT = 0x03, + UNDEFINED_04 = 0x04, + UNDEFINED_05 = 0x05, + TUNE_REQ = 0x06, + SYSEX_END = 0x07, + CLOCK = 0x08, + UNDEFINED_09 = 0x09, + SONG_START = 0x0A, + SONG_CONT = 0x0B, + SONG_STOP = 0x0C, + UNDEFINED_0D = 0x0D, + ACTIVE_SENSING = 0x0E, + SYSTEM_RESET = 0x0F + } sysex_messages; + + + midi_commands status; + unsigned char tmp; + msg.m_type = MidiMessage::UNKNOWN; + msg.m_nData1 = -1; + msg.m_nData2 = -1; + msg.m_nChannel = -1; + msg.m_sysexData.clear(); + + if (event.size == 0) + return; + + if (use_frame) { + msg.m_use_frame = true; + msg.m_frame = event.time; + } else { + msg.m_use_frame = false; + msg.m_frame = 0; + } + tmp = event.buffer[0] & 0xF0; + if (0x80 & tmp) { + status = (unsigned char)tmp; + } else { + status = UNKNOWN; + } + switch(status) { + case UNKNOWN: + msg = MidiMessage(); + break; + case NOTE_ON: + msg.m_type = MidiMessage::NOTE_ON; + msg.m_nData1 = event.buffer[1]; + msg.m_nData2 = event.buffer[2]; + msg.m_nChannel = 0x0F & event.buffer[0]; + break; + case NOTE_OFF: + msg.m_type = MidiMessage::NOTE_OFF; + msg.m_nData1 = event.buffer[1]; + msg.m_nData2 = event.buffer[2]; + msg.m_nChannel = 0x0F & event.buffer[0]; + break; + case POLYPHONIC_KEY_PRESSURE: + msg.m_type = MidiMessage::POLYPHONIC_KEY_PRESSURE; + msg.m_nData1 = event.buffer[1]; + msg.m_nData2 = event.buffer[2]; + msg.m_nChannel = 0x0F & event.buffer[0]; + break; + case CONTROL_CHANGE: + msg.m_type = MidiMessage::CONTROL_CHANGE; + msg.m_nData1 = event.buffer[1]; + msg.m_nData2 = event.buffer[2]; + msg.m_nChannel = 0x0F & event.buffer[0]; + break; + case PROGRAM_CHANGE: + msg.m_type = MidiMessage::PROGRAM_CHANGE; + msg.m_nData1 = event.buffer[1]; + msg.m_nData2 = event.buffer[2]; + msg.m_nChannel = 0x0F & event.buffer[0]; + break; + case CHANNEL_PRESSURE: + msg.m_type = MidiMessage::CHANNEL_PRESSURE; + msg.m_nData1 = event.buffer[1]; + msg.m_nData2 = -1; + msg.m_nChannel = 0x0F & event.buffer[0]; + break; + case PITCH_WHEEL: + msg.m_type = MidiMessage::PITCH_WHEEL; + msg.m_nData1 = event.buffer[1]; + msg.m_nData2 = event.buffer[2]; + msg.m_nChannel = 0x0F & event.buffer[0]; + break; + case SYSTEM_EXCLUSIVE: + switch (event.buffer[0] & 0x0F) { + case SYSEX_START: + msg.m_type = MidiMessage::SYSEX; + msg.m_sysexData.assign(event.buffer+1, event.buffer+event.size); + break; + case MTC_QUARTER_FRAME: + msg.m_type = MidiMessage::QUARTER_FRAME; + msg.m_nData1 = event.buffer[1]; + break; + case SONG_POS: + msg.m_type = MidiMessage::SONG_POS; + msg.m_nData1 = event.buffer[1]; + msg.m_nData2 = event.buffer[2]; + break; + case SONG_START: + msg.m_type = MidiMessage::START; + break; + case SONG_CONT: + msg.m_type = MidiMessage::CONTINUE; + break; + case SONG_STOP: + msg.m_type = MidiMessage::STOP; + break; + // Following not handled by H2Core::MidiMessage + case SYSEX_END: + case SONG_SELECT: + case TUNE_REQ: + case CLOCK: + case ACTIVE_SENSING: + case SYSTEM_RESET: + case UNDEFINED_04: + case UNDEFINED_05: + case UNDEFINED_09: + case UNDEFINED_0D: + msg.m_type = MidiMessage::UNKNOWN; + break; + default: + assert(false); // Should not reach this line + } + break; + default: + assert(false); // Should not reach this line + } +} + +int JackMidiDriver::processAudio(jack_nframes_t nframes) +{ + return process(nframes, true); +} + +int JackMidiDriver::processNonAudio(jack_nframes_t nframes) +{ + return process(nframes, false); +} + +int JackMidiDriver::process(jack_nframes_t nframes, bool use_frame) +{ + if (!m_port) return 0; + + jack_nframes_t event_ct, event_pos; + jack_midi_event_t jack_event; + H2Core::MidiMessage msg; + + void* port_buf = jack_port_get_buffer(m_port, nframes); + event_ct = jack_midi_get_event_count(port_buf, nframes); + for ( event_pos=0 ; event_pos<event_ct ; ++event_pos ) { + if (jack_midi_event_get(&jack_event, + port_buf, + event_pos, + nframes)) { + break; + } + translate_jack_midi_to_h2(msg, jack_event, use_frame); + if (msg.m_type != MidiMessage::UNKNOWN) { + handleMidiMessage(msg); + } + } + return 0; +} + +std::vector<QString> JackMidiDriver::getOutputPortList(void) +{ + return getJackClientInstance().getMidiOutputPortList(); +} + +#endif // JACKMIDI_SUPPORTdiff --git a/libs/hydrogen/src/IO/jack_output.cpp b/libs/hydrogen/src/IO/jack_output.cpp
index 90dcbb1..c7442d8 100644
--- a/libs/hydrogen/src/IO/jack_output.cpp
+++ b/libs/hydrogen/src/IO/jack_output.cpp
@@ -20,6 +20,7 @@
*
*/
+#include <hydrogen/IO/JackClient.h>
#include <hydrogen/IO/JackOutput.h>
#ifdef JACK_SUPPORT
@@ -60,7 +61,7 @@ void jackDriverShutdown( void *arg )
{
UNUSED( arg );
// jackDriverInstance->deactivate();
- jackDriverInstance->client = NULL;
+ getJackClientInstance().clearAudioProcessCallback();
Hydrogen::get_instance()->raiseError( Hydrogen::JACK_SERVER_SHUTDOWN );
}
@@ -99,8 +100,9 @@ JackOutput::~JackOutput()
int JackOutput::connect()
{
INFOLOG( "connect" );
+ jack_client_t* client = getJackClientInstance().ref();
- if ( jack_activate ( client ) ) {
+ if ( !client ) {
Hydrogen::get_instance()->raiseError(
Hydrogen::JACK_CANNOT_ACTIVATE_CLIENT );
return 1;
}
@@ -155,19 +157,18 @@ int JackOutput::connect()
void JackOutput::disconnect()
{
INFOLOG( "disconnect" );
+ jack_client_t* client = getJackClientInstance().ref();
deactivate();
- jack_client_t *oldClient = client;
- client = NULL;
- if ( oldClient ) {
- INFOLOG( "calling jack_client_close" );
- int res = jack_client_close( oldClient );
- if ( res ) {
- ERRORLOG( "Error in jack_client_close" );
- // FIXME: raise exception
- }
+
+ if (output_port_1)
+ jack_port_unregister(client, output_port_1);
+ if (output_port_2)
+ jack_port_unregister(client, output_port_2);
+ for (int j=0; j<track_port_count; ++j) {
+ jack_port_unregister(client, track_output_ports_L[j]);
+ jack_port_unregister(client, track_output_ports_R[j]);
}
- client = NULL;
}
@@ -176,13 +177,7 @@ void JackOutput::disconnect()
void JackOutput::deactivate()
{
INFOLOG( "[deactivate]" );
- if ( client ) {
- INFOLOG( "calling jack_deactivate" );
- int res = jack_deactivate( client );
- if ( res ) {
- ERRORLOG( "Error in jack_deactivate" );
- }
- }
+ getJackClientInstance().clearAudioProcessCallback();
}
@@ -270,7 +265,7 @@ void JackOutput::updateTransportInfo()
{
if ( Preferences::getInstance()->m_bJackTransportMode ==
Preferences::USE_JACK_TRANSPORT ) {
- m_JackTransportState = jack_transport_query( client, &m_JackTransportPos );+ m_JackTransportState = jack_transport_query( getJackClientInstance().ref(), &m_JackTransportPos );
// update m_transport with jack-transport data
@@ -403,18 +398,8 @@ int JackOutput::init( unsigned nBufferSize )
output_port_name_1 = Preferences::getInstance()->m_sJackPortName1;
output_port_name_2 = Preferences::getInstance()->m_sJackPortName2;
-
+ jack_client_t* client = getJackClientInstance().ref();
// test!!!
- if ( ( client = jack_client_open( "hydrogen", JackNullOption, NULL ) )
== NULL ) {
- WARNINGLOG( "Error: can't start JACK server" );
- return 3;
- }
-
- // check if another hydrogen instance is connected to jack
- if ( ( client = jack_client_new( "hydrogen-tmp" ) ) == 0 ) {
- WARNINGLOG( "Jack server not running?" );
- return 3;
- }
std::vector<QString> clientList;
const char **readports = jack_get_ports( client, NULL, NULL,
JackPortIsOutput );
@@ -435,9 +420,12 @@ int JackOutput::init( unsigned nBufferSize )
}
nPort++;
}
- jack_client_close( client );
- QString sClientName = "";
+// jack_client_close( client );
+
+ #warning "TODO: Fix this section of code for LASH support"
+ QString sClientName = "";
+#if 0
for ( int nInstance = 1; nInstance < 16; nInstance++ ) {
// sprintf( clientName, "Hydrogen-%d", nInstance );
// sprintf( clientName, "Hydrogen-%d", getpid() );
@@ -460,7 +448,7 @@ int JackOutput::init( unsigned nBufferSize )
ERRORLOG( "Jack server not running? (jack_client_new). Using " + sClientName
+ " as client name" );
return 3;
}
-
+#endif
jack_server_sampleRate = jack_get_sample_rate ( client );
jack_server_bufferSize = jack_get_buffer_size ( client );
@@ -468,7 +456,7 @@ int JackOutput::init( unsigned nBufferSize )
/* tell the JACK server to call `process()' whenever
there is work to be done.
*/
- jack_set_process_callback ( client, this->processCallback, 0 );
+ getJackClientInstance().setAudioProcessCallback(this->processCallback);
/* tell the JACK server to call `srate()' whenever
@@ -535,6 +523,7 @@ void JackOutput::makeTrackOutputs( Song * song )
setTrackOutput( n, instr );
}
// clean up unused ports
+ jack_client_t* client = getJackClientInstance().ref();
for ( int n = nInstruments; n < track_port_count; n++ ) {
jack_port_unregister( client, track_output_ports_L[n] );
jack_port_unregister( client, track_output_ports_R[n] );
@@ -553,6 +542,7 @@ void JackOutput::setTrackOutput( int n, Instrument * instr )
{
QString chName;
+ jack_client_t* client = getJackClientInstance().ref();
if ( track_port_count <= n ) { // need to create more ports
for ( int m = track_port_count; m <= n; m++ ) {
@@ -574,6 +564,7 @@ void JackOutput::setTrackOutput( int n, Instrument * instr )
void JackOutput::play()
{
+ jack_client_t* client = getJackClientInstance().ref();
if ( ( Preferences::getInstance() )->m_bJackTransportMode ==
Preferences::USE_JACK_TRANSPORT || Preferences::getInstance()->m_bJackMasterMode
== Preferences::USE_JACK_TIME_MASTER ) {
if ( client ) {
INFOLOG( "jack_transport_start()" );
@@ -588,6 +579,7 @@ void JackOutput::play()
void JackOutput::stop()
{
+ jack_client_t* client = getJackClientInstance().ref();
if ( ( Preferences::getInstance() )->m_bJackTransportMode ==
Preferences::USE_JACK_TRANSPORT || Preferences::getInstance()->m_bJackMasterMode
== Preferences::USE_JACK_TIME_MASTER ) {
if ( client ) {
INFOLOG( "jack_transport_stop()" );
@@ -602,6 +594,7 @@ void JackOutput::stop()
void JackOutput::locate( unsigned long nFrame )
{
+ jack_client_t* client = getJackClientInstance().ref();
if ( ( Preferences::getInstance() )->m_bJackTransportMode ==
Preferences::USE_JACK_TRANSPORT || Preferences::getInstance()->m_bJackMasterMode
== Preferences::USE_JACK_TIME_MASTER ) {
if ( client ) {
WARNINGLOG( QString( "Calling
jack_transport_locate(%1)" ).arg( nFrame ) );
@@ -647,6 +640,7 @@ int JackOutput::getNumTracks()
void JackOutput::initTimeMaster(void)
{
+ jack_client_t* client = getJackClientInstance().ref();
if ( client == NULL) return;
bool cond = false;
@@ -671,6 +665,7 @@ void JackOutput::initTimeMaster(void)
void JackOutput::com_release()
{
+ jack_client_t* client = getJackClientInstance().ref();
if ( client == NULL) return;
jack_release_timebase(client);
diff --git a/libs/hydrogen/src/IO/midi_input.cpp
b/libs/hydrogen/src/IO/midi_input.cpp
index acbf929..5a6a833 100644
--- a/libs/hydrogen/src/IO/midi_input.cpp
+++ b/libs/hydrogen/src/IO/midi_input.cpp
@@ -176,7 +176,7 @@ void MidiInput::handleNoteOnMessage( const MidiMessage& msg
)
nInstrument = MAX_INSTRUMENTS - 1;
}
- pEngine->addRealtimeNote( nInstrument, fVelocity,
fPan_L, fPan_R, 0.0, true );
+ pEngine->addRealtimeNote( nInstrument, fVelocity, fPan_L, fPan_R, 0.0, true,
msg.m_use_frame, msg.m_frame );
}
}
}
@@ -202,6 +202,7 @@ void MidiInput::handleNoteOffMessage( const MidiMessage&
msg )
nInstrument = MAX_INSTRUMENTS - 1;
}
Instrument *pInstr = pSong->get_instrument_list()->get( nInstrument );
+ #warning "Need some frame positional data for ms.m_use_frame... but
Sampler::note_off isn't implemented, yet... so neither are we."
const unsigned nPosition = 0;
const float fVelocity = 0.0f;
const float fPan_L = 0.5f;
@@ -322,6 +323,17 @@ void MidiInput::handleSysexMessage( const MidiMessage& msg
)
}
}
+int MidiInput::processAudio(jack_nframes_t /*nframes*/)
+{
+ return 0;
+}
+
+int MidiInput::processNonAudio(jack_nframes_t /*nframes*/)
+{
+ return 0;
+}
+
+
};
diff --git a/libs/hydrogen/src/hydrogen.cpp b/libs/hydrogen/src/hydrogen.cpp
index c0ecf46..57cdc29 100644
--- a/libs/hydrogen/src/hydrogen.cpp
+++ b/libs/hydrogen/src/hydrogen.cpp
@@ -68,7 +68,7 @@
#include "IO/AlsaMidiDriver.h"
#include "IO/PortMidiDriver.h"
#include "IO/CoreAudioDriver.h"
-
+#include "IO/JackMidiDriver.h"
namespace H2Core
{
@@ -696,6 +696,10 @@ int audioEngine_process( uint32_t nframes, void *arg )
{
UNUSED( arg );
+ // Hook for MIDI in-process callbacks. It calls its own locks
+ // on the audioengine
+ if (m_pMidiDriver) m_pMidiDriver->processAudio(nframes);
+
if ( AudioEngine::get_instance()->try_lock( "audioEngine_process" ) ==
false ) {
return 0;
}
@@ -1522,6 +1526,12 @@ void audioEngine_startAudioDrivers()
m_pMidiDriver->open();
m_pMidiDriver->setActive( true );
#endif
+ } else if ( preferencesMng->m_sMidiDriver == "JackMidi" ) {
+#ifdef JACKMIDI_SUPPORT
+ m_pMidiDriver = new JackMidiDriver();
+ m_pMidiDriver->open();
+ m_pMidiDriver->setActive( true );
+#endif
}
// change the current audio engine state
@@ -1584,6 +1594,8 @@ void audioEngine_stopAudioDrivers()
{
_INFOLOG( "[audioEngine_stopAudioDrivers]" );
+ AudioEngine::get_instance()->lock( "audioEngine_stopAudioDrivers" );
+
// check current state
if ( m_audioEngineState == STATE_PLAYING ) {
audioEngine_stop();
@@ -1611,7 +1623,6 @@ void audioEngine_stopAudioDrivers()
}
- AudioEngine::get_instance()->lock( "audioEngine_stopAudioDrivers" );
// change the current audio engine state
m_audioEngineState = STATE_INITIALIZED;
EventQueue::get_instance()->push_event( EVENT_STATE, STATE_INITIALIZED
);
@@ -1741,9 +1752,7 @@ void Hydrogen::midi_noteOff( Note *note )
audioEngine_noteOff( note );
}
-
-
-void Hydrogen::addRealtimeNote( int instrument, float velocity, float pan_L,
float pan_R, float pitch, bool forcePlay )
+void Hydrogen::addRealtimeNote( int instrument, float velocity, float pan_L,
float pan_R, float pitch, bool forcePlay, bool use_frame, uint32_t frame )
{
UNUSED( pitch );
@@ -1765,9 +1774,8 @@ void Hydrogen::addRealtimeNote( int instrument, float
velocity, float pan_L, flo
return;
}
- unsigned int column = getTickPosition();
-
- realcolumn = getRealtimeTickPosition();
+ realcolumn = (use_frame) ? getRealtimeTickPosition(frame) :
getRealtimeTickPosition();
+ unsigned int column = (use_frame) ? realcolumn : getTickPosition();
// quantize it to scale
int qcolumn = ( int )::round( column / ( double )scalar ) * scalar;
@@ -1865,10 +1873,10 @@ unsigned long Hydrogen::getTickPosition()
-unsigned long Hydrogen::getRealtimeTickPosition()
+unsigned long Hydrogen::getRealtimeTickPosition(unsigned long offset)
{
//unsigned long initTick = audioEngine_getTickPosition();
- unsigned int initTick = ( unsigned int )( m_nRealtimeFrames /
m_pAudioDriver->m_transport.m_nTickSize );
+ unsigned int initTick = ( unsigned int )( (m_nRealtimeFrames+offset) /
m_pAudioDriver->m_transport.m_nTickSize );
unsigned long retTick;
struct timeval currtime;
@@ -2706,5 +2714,13 @@ void Hydrogen::togglePlaysSelected() {
}
+#ifdef JACK_SUPPORT
+int jackMidiFallbackProcess(jack_nframes_t nframes, void* /*arg*/)
+{
+ JackMidiDriver* instance =
+ dynamic_cast<JackMidiDriver*>(m_pMidiDriver)->processNonAudio(nframes);
+}
+#endif
+
};
h2_jack_midi_support_alpha_20080721.tar.gz
Description: application/gzip
------------------------------------------------------------------------- This SF.Net email is sponsored by the Moblin Your Move Developer's challenge Build the coolest Linux based applications with Moblin SDK & win great prizes Grand prize is a trip for two to an Open Source event anywhere in the world http://moblin-contest.org/redirect.php?banner_id=100&url=/
_______________________________________________ Hydrogen-devel mailing list [email protected] https://lists.sourceforge.net/lists/listinfo/hydrogen-devel
