/*
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
This file is part of the JUCE library .
Copyright ( c ) 2015 - ROLI Ltd .
Permission is granted to use this software under the terms of either :
a ) the GPL v2 ( or any later version )
b ) the Affero GPL v3
Details of these licenses can be found at : www . gnu . org / licenses
JUCE 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 .
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
To release a closed - source product which uses JUCE , commercial licenses are
available : visit www . juce . com for more information .
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
*/
# if JUCE_ALSA
// You can define these strings in your app if you want to override the default names:
# ifndef JUCE_ALSA_MIDI_INPUT_NAME
# define JUCE_ALSA_MIDI_INPUT_NAME "Juce Midi Input"
# endif
# ifndef JUCE_ALSA_MIDI_OUTPUT_NAME
# define JUCE_ALSA_MIDI_OUTPUT_NAME "Juce Midi Output"
# endif
//==============================================================================
namespace
{
class AlsaPortAndCallback ;
//==============================================================================
class AlsaClient : public ReferenceCountedObject
{
public :
typedef ReferenceCountedObjectPtr < AlsaClient > Ptr ;
AlsaClient ( bool forInput )
: input ( forInput ) , handle ( nullptr )
{
snd_seq_open ( & handle , " default " , forInput ? SND_SEQ_OPEN_INPUT
: SND_SEQ_OPEN_OUTPUT , 0 ) ;
}
~ AlsaClient ( )
{
if ( handle ! = nullptr )
{
snd_seq_close ( handle ) ;
handle = nullptr ;
}
jassert ( activeCallbacks . size ( ) = = 0 ) ;
if ( inputThread )
{
inputThread - > stopThread ( 3000 ) ;
inputThread = nullptr ;
}
}
bool isInput ( ) const noexcept { return input ; }
void setName ( const String & name )
{
snd_seq_set_client_name ( handle , name . toUTF8 ( ) ) ;
}
void registerCallback ( AlsaPortAndCallback * cb )
{
if ( cb ! = nullptr )
{
{
const ScopedLock sl ( callbackLock ) ;
activeCallbacks . add ( cb ) ;
if ( inputThread = = nullptr )
inputThread = new MidiInputThread ( * this ) ;
}
inputThread - > startThread ( ) ;
}
}
void unregisterCallback ( AlsaPortAndCallback * cb )
{
const ScopedLock sl ( callbackLock ) ;
jassert ( activeCallbacks . contains ( cb ) ) ;
activeCallbacks . removeAllInstancesOf ( cb ) ;
if ( activeCallbacks . size ( ) = = 0 & & inputThread - > isThreadRunning ( ) )
inputThread - > signalThreadShouldExit ( ) ;
}
void handleIncomingMidiMessage ( const MidiMessage & message , int port ) ;
snd_seq_t * get ( ) const noexcept { return handle ; }
private :
bool input ;
snd_seq_t * handle ;
Array < AlsaPortAndCallback * > activeCallbacks ;
CriticalSection callbackLock ;
//==============================================================================
class MidiInputThread : public Thread
{
public :
MidiInputThread ( AlsaClient & c )
: Thread ( " Juce MIDI Input " ) , client ( c )
{
jassert ( client . input & & client . get ( ) ! = nullptr ) ;
}
void run ( ) override
{
const int maxEventSize = 16 * 1024 ;
snd_midi_event_t * midiParser ;
snd_seq_t * seqHandle = client . get ( ) ;
if ( snd_midi_event_new ( maxEventSize , & midiParser ) > = 0 )
{
const int numPfds = snd_seq_poll_descriptors_count ( seqHandle , POLLIN ) ;
HeapBlock < pollfd > pfd ( ( size_t ) numPfds ) ;
snd_seq_poll_descriptors ( seqHandle , pfd , ( unsigned int ) numPfds , POLLIN ) ;
HeapBlock < uint8 > buffer ( maxEventSize ) ;
while ( ! threadShouldExit ( ) )
{
if ( poll ( pfd , ( nfds_t ) numPfds , 100 ) > 0 ) // there was a "500" here which is a bit long when we exit the program and have to wait for a timeout on this poll call
{
if ( threadShouldExit ( ) )
break ;
snd_seq_nonblock ( seqHandle , 1 ) ;
do
{
snd_seq_event_t * inputEvent = nullptr ;
if ( snd_seq_event_input ( seqHandle , & inputEvent ) > = 0 )
{
// xxx what about SYSEXes that are too big for the buffer?
const long numBytes = snd_midi_event_decode ( midiParser , buffer ,
maxEventSize , inputEvent ) ;
snd_midi_event_reset_decode ( midiParser ) ;
if ( numBytes > 0 )
{
const MidiMessage message ( ( const uint8 * ) buffer , ( int ) numBytes ,
Time : : getMillisecondCounter ( ) * 0.001 ) ;
client . handleIncomingMidiMessage ( message , inputEvent - > dest . port ) ;
}
snd_seq_free_event ( inputEvent ) ;
}
}
while ( snd_seq_event_input_pending ( seqHandle , 0 ) > 0 ) ;
}
}
snd_midi_event_free ( midiParser ) ;
}
} ;
private :
AlsaClient & client ;
} ;
ScopedPointer < MidiInputThread > inputThread ;
} ;
static AlsaClient : : Ptr globalAlsaSequencerIn ( )
{
static AlsaClient : : Ptr global ( new AlsaClient ( true ) ) ;
return global ;
}
static AlsaClient : : Ptr globalAlsaSequencerOut ( )
{
static AlsaClient : : Ptr global ( new AlsaClient ( false ) ) ;
return global ;
}
static AlsaClient : : Ptr globalAlsaSequencer ( bool input )
{
return input ? globalAlsaSequencerIn ( )
: globalAlsaSequencerOut ( ) ;
}
//==============================================================================
// represents an input or output port of the supplied AlsaClient
class AlsaPort
{
public :
AlsaPort ( ) noexcept : portId ( - 1 ) { }
AlsaPort ( const AlsaClient : : Ptr & c , int port ) noexcept : client ( c ) , portId ( port ) { }
void createPort ( const AlsaClient : : Ptr & c , const String & name , bool forInput )
{
client = c ;
if ( snd_seq_t * handle = client - > get ( ) )
portId = snd_seq_create_simple_port ( handle , name . toUTF8 ( ) ,
forInput ? ( SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE )
: ( SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ ) ,
SND_SEQ_PORT_TYPE_MIDI_GENERIC ) ;
}
void deletePort ( )
{
if ( isValid ( ) )
{
snd_seq_delete_simple_port ( client - > get ( ) , portId ) ;
portId = - 1 ;
}
}
void connectWith ( int sourceClient , int sourcePort )
{
if ( client - > isInput ( ) )
snd_seq_connect_from ( client - > get ( ) , portId , sourceClient , sourcePort ) ;
else
snd_seq_connect_to ( client - > get ( ) , portId , sourceClient , sourcePort ) ;
}
bool isValid ( ) const noexcept
{
return client ! = nullptr & & client - > get ( ) ! = nullptr & & portId > = 0 ;
}
AlsaClient : : Ptr client ;
int portId ;
} ;
//==============================================================================
class AlsaPortAndCallback
{
public :
AlsaPortAndCallback ( AlsaPort p , MidiInput * in , MidiInputCallback * cb )
: port ( p ) , midiInput ( in ) , callback ( cb ) , callbackEnabled ( false )
{
}
~ AlsaPortAndCallback ( )
{
enableCallback ( false ) ;
port . deletePort ( ) ;
}
void enableCallback ( bool enable )
{
if ( callbackEnabled ! = enable )
{
callbackEnabled = enable ;
if ( enable )
port . client - > registerCallback ( this ) ;
else
port . client - > unregisterCallback ( this ) ;
}
}
void handleIncomingMidiMessage ( const MidiMessage & message ) const
{
callback - > handleIncomingMidiMessage ( midiInput , message ) ;
}
private :
AlsaPort port ;
MidiInput * midiInput ;
MidiInputCallback * callback ;
bool callbackEnabled ;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR ( AlsaPortAndCallback )
} ;
void AlsaClient : : handleIncomingMidiMessage ( const MidiMessage & message , int port )
{
const ScopedLock sl ( callbackLock ) ;
if ( AlsaPortAndCallback * const cb = activeCallbacks [ port ] )
cb - > handleIncomingMidiMessage ( message ) ;
}
//==============================================================================
static AlsaPort iterateMidiClient ( const AlsaClient : : Ptr & seq ,
snd_seq_client_info_t * clientInfo ,
const bool forInput ,
StringArray & deviceNamesFound ,
const int deviceIndexToOpen )
{
AlsaPort port ;
snd_seq_t * seqHandle = seq - > get ( ) ;
snd_seq_port_info_t * portInfo = nullptr ;
if ( snd_seq_port_info_malloc ( & portInfo ) = = 0 )
{
int numPorts = snd_seq_client_info_get_num_ports ( clientInfo ) ;
const int client = snd_seq_client_info_get_client ( clientInfo ) ;
snd_seq_port_info_set_client ( portInfo , client ) ;
snd_seq_port_info_set_port ( portInfo , - 1 ) ;
while ( - - numPorts > = 0 )
{
if ( snd_seq_query_next_port ( seqHandle , portInfo ) = = 0
& & ( snd_seq_port_info_get_capability ( portInfo ) & ( forInput ? SND_SEQ_PORT_CAP_READ
: SND_SEQ_PORT_CAP_WRITE ) ) ! = 0 )
{
deviceNamesFound . add ( snd_seq_client_info_get_name ( clientInfo ) ) ;
if ( deviceNamesFound . size ( ) = = deviceIndexToOpen + 1 )
{
const int sourcePort = snd_seq_port_info_get_port ( portInfo ) ;
const int sourceClient = snd_seq_client_info_get_client ( clientInfo ) ;
if ( sourcePort ! = - 1 )
{
const String name ( forInput ? JUCE_ALSA_MIDI_INPUT_NAME
: JUCE_ALSA_MIDI_OUTPUT_NAME ) ;
seq - > setName ( name ) ;
port . createPort ( seq , name , forInput ) ;
port . connectWith ( sourceClient , sourcePort ) ;
}
}
}
}
snd_seq_port_info_free ( portInfo ) ;
}
return port ;
}
static AlsaPort iterateMidiDevices ( const bool forInput ,
StringArray & deviceNamesFound ,
const int deviceIndexToOpen )
{
AlsaPort port ;
const AlsaClient : : Ptr client ( globalAlsaSequencer ( forInput ) ) ;
if ( snd_seq_t * const seqHandle = client - > get ( ) )
{
snd_seq_system_info_t * systemInfo = nullptr ;
snd_seq_client_info_t * clientInfo = nullptr ;
if ( snd_seq_system_info_malloc ( & systemInfo ) = = 0 )
{
if ( snd_seq_system_info ( seqHandle , systemInfo ) = = 0
& & snd_seq_client_info_malloc ( & clientInfo ) = = 0 )
{
int numClients = snd_seq_system_info_get_cur_clients ( systemInfo ) ;
while ( - - numClients > = 0 & & ! port . isValid ( ) )
if ( snd_seq_query_next_client ( seqHandle , clientInfo ) = = 0 )
port = iterateMidiClient ( client , clientInfo , forInput ,
deviceNamesFound , deviceIndexToOpen ) ;
snd_seq_client_info_free ( clientInfo ) ;
}
snd_seq_system_info_free ( systemInfo ) ;
}
}
deviceNamesFound . appendNumbersToDuplicates ( true , true ) ;
return port ;
}
AlsaPort createMidiDevice ( const bool forInput , const String & deviceNameToOpen )
{
AlsaPort port ;
AlsaClient : : Ptr client ( new AlsaClient ( forInput ) ) ;
if ( client - > get ( ) )
{
client - > setName ( deviceNameToOpen + ( forInput ? " Input " : " Output " ) ) ;
port . createPort ( client , forInput ? " in " : " out " , forInput ) ;
}
return port ;
}
//==============================================================================
class MidiOutputDevice
{
public :
MidiOutputDevice ( MidiOutput * const output , const AlsaPort & p )
: midiOutput ( output ) , port ( p ) ,
maxEventSize ( 16 * 1024 )
{
jassert ( port . isValid ( ) & & midiOutput ! = nullptr ) ;
snd_midi_event_new ( ( size_t ) maxEventSize , & midiParser ) ;
}
~ MidiOutputDevice ( )
{
snd_midi_event_free ( midiParser ) ;
port . deletePort ( ) ;
}
bool sendMessageNow ( const MidiMessage & message )
{
if ( message . getRawDataSize ( ) > maxEventSize )
{
maxEventSize = message . getRawDataSize ( ) ;
snd_midi_event_free ( midiParser ) ;
snd_midi_event_new ( ( size_t ) maxEventSize , & midiParser ) ;
}
snd_seq_event_t event ;
snd_seq_ev_clear ( & event ) ;
long numBytes = ( long ) message . getRawDataSize ( ) ;
const uint8 * data = message . getRawData ( ) ;
snd_seq_t * seqHandle = port . client - > get ( ) ;
bool success = true ;
while ( numBytes > 0 )
{
const long numSent = snd_midi_event_encode ( midiParser , data , numBytes , & event ) ;
if ( numSent < = 0 )
{
success = numSent = = 0 ;
break ;
}
numBytes - = numSent ;
data + = numSent ;
snd_seq_ev_set_source ( & event , 0 ) ;
snd_seq_ev_set_subs ( & event ) ;
snd_seq_ev_set_direct ( & event ) ;
if ( snd_seq_event_output_direct ( seqHandle , & event ) < 0 )
{
success = false ;
break ;
}
}
snd_midi_event_reset_encode ( midiParser ) ;
return success ;
}
private :
MidiOutput * const midiOutput ;
AlsaPort port ;
snd_midi_event_t * midiParser ;
int maxEventSize ;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR ( MidiOutputDevice ) ;
} ;
} // namespace
StringArray MidiOutput : : getDevices ( )
{
StringArray devices ;
iterateMidiDevices ( false , devices , - 1 ) ;
return devices ;
}
int MidiOutput : : getDefaultDeviceIndex ( )
{
return 0 ;
}
MidiOutput * MidiOutput : : openDevice ( int deviceIndex )
{
MidiOutput * newDevice = nullptr ;
StringArray devices ;
AlsaPort port ( iterateMidiDevices ( false , devices , deviceIndex ) ) ;
if ( port . isValid ( ) )
{
newDevice = new MidiOutput ( ) ;
newDevice - > internal = new MidiOutputDevice ( newDevice , port ) ;
}
return newDevice ;
}
MidiOutput * MidiOutput : : createNewDevice ( const String & deviceName )
{
MidiOutput * newDevice = nullptr ;
AlsaPort port ( createMidiDevice ( false , deviceName ) ) ;
if ( port . isValid ( ) )
{
newDevice = new MidiOutput ( ) ;
newDevice - > internal = new MidiOutputDevice ( newDevice , port ) ;
}
return newDevice ;
}
MidiOutput : : ~ MidiOutput ( )
{
stopBackgroundThread ( ) ;
delete static_cast < MidiOutputDevice * > ( internal ) ;
}
void MidiOutput : : sendMessageNow ( const MidiMessage & message )
{
static_cast < MidiOutputDevice * > ( internal ) - > sendMessageNow ( message ) ;
}
//==============================================================================
MidiInput : : MidiInput ( const String & nm )
: name ( nm ) , internal ( nullptr )
{
}
MidiInput : : ~ MidiInput ( )
{
stop ( ) ;
delete static_cast < AlsaPortAndCallback * > ( internal ) ;
}
void MidiInput : : start ( )
{
static_cast < AlsaPortAndCallback * > ( internal ) - > enableCallback ( true ) ;
}
void MidiInput : : stop ( )
{
static_cast < AlsaPortAndCallback * > ( internal ) - > enableCallback ( false ) ;
}
int MidiInput : : getDefaultDeviceIndex ( )
{
return 0 ;
}
StringArray MidiInput : : getDevices ( )
{
StringArray devices ;
iterateMidiDevices ( true , devices , - 1 ) ;
return devices ;
}
MidiInput * MidiInput : : openDevice ( int deviceIndex , MidiInputCallback * callback )
{
MidiInput * newDevice = nullptr ;
StringArray devices ;
AlsaPort port ( iterateMidiDevices ( true , devices , deviceIndex ) ) ;
if ( port . isValid ( ) )
{
newDevice = new MidiInput ( devices [ deviceIndex ] ) ;
newDevice - > internal = new AlsaPortAndCallback ( port , newDevice , callback ) ;
}
return newDevice ;
}
MidiInput * MidiInput : : createNewDevice ( const String & deviceName , MidiInputCallback * callback )
{
MidiInput * newDevice = nullptr ;
AlsaPort port ( createMidiDevice ( true , deviceName ) ) ;
if ( port . isValid ( ) )
{
newDevice = new MidiInput ( deviceName ) ;
newDevice - > internal = new AlsaPortAndCallback ( port , newDevice , callback ) ;
}
return newDevice ;
}
//==============================================================================
# else
// (These are just stub functions if ALSA is unavailable...)
StringArray MidiOutput : : getDevices ( ) { return StringArray ( ) ; }
int MidiOutput : : getDefaultDeviceIndex ( ) { return 0 ; }
MidiOutput * MidiOutput : : openDevice ( int ) { return nullptr ; }
MidiOutput * MidiOutput : : createNewDevice ( const String & ) { return nullptr ; }
MidiOutput : : ~ MidiOutput ( ) { }
void MidiOutput : : sendMessageNow ( const MidiMessage & ) { }
MidiInput : : MidiInput ( const String & nm ) : name ( nm ) , internal ( nullptr ) { }
MidiInput : : ~ MidiInput ( ) { }
void MidiInput : : start ( ) { }
void MidiInput : : stop ( ) { }
int MidiInput : : getDefaultDeviceIndex ( ) { return 0 ; }
StringArray MidiInput : : getDevices ( ) { return StringArray ( ) ; }
MidiInput * MidiInput : : openDevice ( int , MidiInputCallback * ) { return nullptr ; }
MidiInput * MidiInput : : createNewDevice ( const String & , MidiInputCallback * ) { return nullptr ; }
# endif