2015-02-09 21:44:32 +01:00
/** @file
* @ author Edouard DUPIN
* @ copyright 2011 , Edouard DUPIN , all right reserved
* @ license APACHE v2 .0 ( see license file )
* @ fork from RTAudio
2014-03-11 21:46:00 +01:00
*/
2015-02-08 15:09:39 +01:00
// must run before :
2015-04-10 22:06:17 +02:00
# if defined(ORCHESTRA_BUILD_JACK)
2014-03-11 21:46:00 +01:00
# include <unistd.h>
# include <limits.h>
# include <iostream>
2016-10-02 22:06:09 +02:00
# include <audio/orchestra/Interface.hpp>
# include <audio/orchestra/debug.hpp>
2014-03-11 21:46:00 +01:00
# include <string.h>
2016-10-02 22:06:09 +02:00
# include <ethread/tools.hpp>
# include <audio/orchestra/api/Jack.hpp>
2014-03-11 21:46:00 +01:00
2016-07-19 21:43:58 +02:00
ememory : : SharedPtr < audio : : orchestra : : Api > audio : : orchestra : : api : : Jack : : create ( ) {
return ememory : : SharedPtr < audio : : orchestra : : api : : Jack > ( new audio : : orchestra : : api : : Jack ( ) ) ;
2014-03-11 22:37:22 +01:00
}
2014-03-11 21:46:00 +01:00
// JACK is a low-latency audio server, originally written for the
// GNU/Linux operating system and now also ported to OS-X. It can
// connect a number of different applications to an audio device, as
// well as allowing them to share audio between themselves.
//
// When using JACK with RtAudio, "devices" refer to JACK clients that
2015-02-08 15:09:39 +01:00
// have ports connected to the server. The JACK server is typically
2014-03-11 21:46:00 +01:00
// started in a terminal as follows:
//
// .jackd -d alsa -d hw:0
//
2015-02-08 15:09:39 +01:00
// or through an interface program such as qjackctl. Many of the
2014-03-11 21:46:00 +01:00
// parameters normally set for a stream are fixed by the JACK server
2015-02-08 15:09:39 +01:00
// and can be specified when the JACK server is started. In
2014-03-11 21:46:00 +01:00
// particular,
//
2015-02-08 15:09:39 +01:00
// jackd -d alsa -d hw:0 -r 44100 -p 512 -n 4
// jackd -r -d alsa -r 48000
2014-03-11 21:46:00 +01:00
//
// specifies a sample rate of 44100 Hz, a buffer size of 512 sample
2015-02-08 15:09:39 +01:00
// frames, and number of buffers = 4. Once the server is running, it
// is not possible to override these values. If the values are not
2014-03-11 21:46:00 +01:00
// specified in the command-line, the JACK server uses default values.
//
// The JACK server does not have to be running when an instance of
2015-04-10 22:06:17 +02:00
// audio::orchestra::Jack is created, though the function getDeviceCount() will
2015-02-08 15:09:39 +01:00
// report 0 devices found until JACK has been started. When no
2014-03-11 21:46:00 +01:00
// devices are available (i.e., the JACK server is not running), a
// stream cannot be opened.
# include <jack/jack.h>
# include <unistd.h>
# include <cstdio>
2015-04-10 22:06:17 +02:00
namespace audio {
namespace orchestra {
namespace api {
class JackPrivate {
public :
jack_client_t * client ;
jack_port_t * * ports [ 2 ] ;
std : : string deviceName [ 2 ] ;
bool xrun [ 2 ] ;
2016-03-08 21:29:34 +01:00
std : : condition_variable condition ;
2015-04-10 22:06:17 +02:00
int32_t drainCounter ; // Tracks callback counts when draining
bool internalDrain ; // Indicates if stop is initiated from callback or not.
JackPrivate ( ) :
client ( 0 ) ,
drainCounter ( 0 ) ,
internalDrain ( false ) {
ports [ 0 ] = 0 ;
ports [ 1 ] = 0 ;
xrun [ 0 ] = false ;
xrun [ 1 ] = false ;
}
} ;
}
2015-02-10 21:01:53 +01:00
}
}
2014-03-11 21:46:00 +01:00
2015-04-10 22:06:17 +02:00
audio : : orchestra : : api : : Jack : : Jack ( ) :
m_private ( new audio : : orchestra : : api : : JackPrivate ( ) ) {
2014-03-11 21:46:00 +01:00
// Nothing to do here.
}
2015-04-10 22:06:17 +02:00
audio : : orchestra : : api : : Jack : : ~ Jack ( ) {
2016-04-29 23:16:07 +02:00
if ( m_state ! = audio : : orchestra : : state : : closed ) {
2014-03-11 21:46:00 +01:00
closeStream ( ) ;
}
}
2015-04-10 22:06:17 +02:00
uint32_t audio : : orchestra : : api : : Jack : : getDeviceCount ( ) {
2014-03-11 21:46:00 +01:00
// See if we can become a jack client.
jack_options_t options = ( jack_options_t ) ( JackNoStartServer ) ; //JackNullOption;
2015-01-26 23:46:53 +01:00
jack_status_t * status = nullptr ;
2015-04-10 22:06:17 +02:00
jack_client_t * client = jack_client_open ( " orchestraJackCount " , options , status ) ;
2015-01-26 23:46:53 +01:00
if ( client = = nullptr ) {
2014-03-11 21:46:00 +01:00
return 0 ;
}
const char * * ports ;
std : : string port , previousPort ;
uint32_t nChannels = 0 , nDevices = 0 ;
2015-01-26 23:46:53 +01:00
ports = jack_get_ports ( client , nullptr , nullptr , 0 ) ;
2014-03-11 21:46:00 +01:00
if ( ports ) {
// Parse the port names up to the first colon (:).
size_t iColon = 0 ;
do {
port = ( char * ) ports [ nChannels ] ;
iColon = port . find ( " : " ) ;
if ( iColon ! = std : : string : : npos ) {
port = port . substr ( 0 , iColon + 1 ) ;
if ( port ! = previousPort ) {
nDevices + + ;
previousPort = port ;
}
}
} while ( ports [ + + nChannels ] ) ;
free ( ports ) ;
}
jack_client_close ( client ) ;
2015-06-14 18:32:08 +02:00
return nDevices * 2 ;
2014-03-11 21:46:00 +01:00
}
2015-04-10 22:06:17 +02:00
audio : : orchestra : : DeviceInfo audio : : orchestra : : api : : Jack : : getDeviceInfo ( uint32_t _device ) {
audio : : orchestra : : DeviceInfo info ;
2014-03-11 21:46:00 +01:00
jack_options_t options = ( jack_options_t ) ( JackNoStartServer ) ; //JackNullOption
2015-01-26 23:46:53 +01:00
jack_status_t * status = nullptr ;
2015-04-10 22:06:17 +02:00
jack_client_t * client = jack_client_open ( " orchestraJackInfo " , options , status ) ;
2015-01-26 23:46:53 +01:00
if ( client = = nullptr ) {
2015-02-05 23:31:22 +01:00
ATA_ERROR ( " Jack server not found or connection error! " ) ;
2015-04-10 22:06:17 +02:00
// TODO : audio::orchestra::error_warning;
2015-07-07 22:39:09 +02:00
info . clear ( ) ;
2014-03-11 21:46:00 +01:00
return info ;
}
const char * * ports ;
std : : string port , previousPort ;
uint32_t nPorts = 0 , nDevices = 0 ;
2015-01-26 23:46:53 +01:00
ports = jack_get_ports ( client , nullptr , nullptr , 0 ) ;
2015-06-14 18:32:08 +02:00
int32_t deviceID = _device / 2 ;
info . input = _device % 2 = = 0 ? true : false ; // note that jack sens are inverted
2014-03-11 21:46:00 +01:00
if ( ports ) {
// Parse the port names up to the first colon (:).
size_t iColon = 0 ;
do {
2015-06-14 18:32:08 +02:00
port = ( char * ) ports [ nPorts ] ;
2014-03-11 21:46:00 +01:00
iColon = port . find ( " : " ) ;
if ( iColon ! = std : : string : : npos ) {
port = port . substr ( 0 , iColon ) ;
if ( port ! = previousPort ) {
2015-06-14 18:32:08 +02:00
if ( nDevices = = deviceID ) {
2014-03-11 21:46:00 +01:00
info . name = port ;
}
nDevices + + ;
previousPort = port ;
}
}
} while ( ports [ + + nPorts ] ) ;
free ( ports ) ;
}
2015-06-14 18:32:08 +02:00
if ( deviceID > = nDevices ) {
2014-03-11 21:46:00 +01:00
jack_client_close ( client ) ;
2015-02-05 23:31:22 +01:00
ATA_ERROR ( " device ID is invalid! " ) ;
2015-04-10 22:06:17 +02:00
// TODO : audio::orchestra::error_invalidUse;
2014-03-11 21:46:00 +01:00
return info ;
}
// Get the current jack server sample rate.
info . sampleRates . clear ( ) ;
info . sampleRates . push_back ( jack_get_sample_rate ( client ) ) ;
2015-06-14 18:32:08 +02:00
if ( info . input = = true ) {
ports = jack_get_ports ( client , info . name . c_str ( ) , nullptr , JackPortIsOutput ) ;
if ( ports ) {
int32_t iii = 0 ;
while ( ports [ iii ] ) {
ATA_ERROR ( " ploppp=' " < < ports [ iii ] < < " ' " ) ;
info . channels . push_back ( audio : : channel_unknow ) ;
iii + + ;
}
free ( ports ) ;
2014-03-11 21:46:00 +01:00
}
2015-06-14 18:32:08 +02:00
} else {
ports = jack_get_ports ( client , info . name . c_str ( ) , nullptr , JackPortIsInput ) ;
if ( ports ) {
int32_t iii = 0 ;
while ( ports [ iii ] ) {
ATA_ERROR ( " ploppp=' " < < ports [ iii ] < < " ' " ) ;
info . channels . push_back ( audio : : channel_unknow ) ;
iii + + ;
}
free ( ports ) ;
2014-03-11 21:46:00 +01:00
}
}
2015-06-14 18:32:08 +02:00
if ( info . channels . size ( ) = = 0 ) {
2014-03-11 21:46:00 +01:00
jack_client_close ( client ) ;
2015-02-05 23:31:22 +01:00
ATA_ERROR ( " error determining Jack input/output channels! " ) ;
2015-04-10 22:06:17 +02:00
// TODO : audio::orchestra::error_warning;
2015-07-07 22:39:09 +02:00
info . clear ( ) ;
2014-03-11 21:46:00 +01:00
return info ;
}
// Jack always uses 32-bit floats.
2015-02-05 23:31:22 +01:00
info . nativeFormats . push_back ( audio : : format_float ) ;
2014-03-11 21:46:00 +01:00
// Jack doesn't provide default devices so we'll use the first available one.
2015-06-14 18:32:08 +02:00
if ( deviceID = = 0 ) {
info . isDefault = true ;
2014-03-11 21:46:00 +01:00
}
jack_client_close ( client ) ;
2015-07-07 22:39:09 +02:00
info . isCorrect = true ;
2014-03-11 21:46:00 +01:00
return info ;
}
2015-04-10 22:06:17 +02:00
int32_t audio : : orchestra : : api : : Jack : : jackCallbackHandler ( jack_nframes_t _nframes , void * _userData ) {
2015-02-09 21:44:32 +01:00
ATA_VERBOSE ( " Jack callback: [BEGIN] " < < uint64_t ( _userData ) ) ;
2015-04-10 22:06:17 +02:00
audio : : orchestra : : api : : Jack * myClass = reinterpret_cast < audio : : orchestra : : api : : Jack * > ( _userData ) ;
2015-02-09 21:44:32 +01:00
if ( myClass - > callbackEvent ( ( uint64_t ) _nframes ) = = false ) {
ATA_VERBOSE ( " Jack callback: [END] 1 " ) ;
2014-03-11 21:46:00 +01:00
return 1 ;
}
2015-02-09 21:44:32 +01:00
ATA_VERBOSE ( " Jack callback: [END] 0 " ) ;
2014-03-11 21:46:00 +01:00
return 0 ;
}
// This function will be called by a spawned thread when the Jack
// server signals that it is shutting down. It is necessary to handle
// it this way because the jackShutdown() function must return before
// the jack_deactivate() function (in closeStream()) will return.
2015-04-10 22:06:17 +02:00
void audio : : orchestra : : api : : Jack : : jackCloseStream ( void * _userData ) {
2016-03-08 21:29:34 +01:00
ethread : : setName ( " Jack_closeStream " ) ;
2015-04-10 22:06:17 +02:00
audio : : orchestra : : api : : Jack * myClass = reinterpret_cast < audio : : orchestra : : api : : Jack * > ( _userData ) ;
2015-02-09 21:44:32 +01:00
myClass - > closeStream ( ) ;
2014-03-11 21:46:00 +01:00
}
2015-04-10 22:06:17 +02:00
void audio : : orchestra : : api : : Jack : : jackShutdown ( void * _userData ) {
audio : : orchestra : : api : : Jack * myClass = reinterpret_cast < audio : : orchestra : : api : : Jack * > ( _userData ) ;
2014-03-11 21:46:00 +01:00
// Check current stream state. If stopped, then we'll assume this
2015-04-10 22:06:17 +02:00
// was called as a result of a call to audio::orchestra::api::Jack::stopStream (the
2014-03-11 21:46:00 +01:00
// deactivation of a client handle causes this function to be called).
// If not, we'll assume the Jack server is shutting down or some
// other problem occurred and we should close the stream.
2015-02-09 21:44:32 +01:00
if ( myClass - > isStreamRunning ( ) = = false ) {
2014-03-11 21:46:00 +01:00
return ;
}
2016-03-08 21:29:34 +01:00
new std : : thread ( & audio : : orchestra : : api : : Jack : : jackCloseStream , _userData ) ;
2015-02-09 21:44:32 +01:00
ATA_ERROR ( " The Jack server is shutting down this client ... stream stopped and closed!! " ) ;
2014-03-11 21:46:00 +01:00
}
2015-04-10 22:06:17 +02:00
int32_t audio : : orchestra : : api : : Jack : : jackXrun ( void * _userData ) {
audio : : orchestra : : api : : Jack * myClass = reinterpret_cast < audio : : orchestra : : api : : Jack * > ( _userData ) ;
2015-02-10 21:01:53 +01:00
if ( myClass - > m_private - > ports [ 0 ] ) {
myClass - > m_private - > xrun [ 0 ] = true ;
2014-03-11 21:46:00 +01:00
}
2015-02-10 21:01:53 +01:00
if ( myClass - > m_private - > ports [ 1 ] ) {
myClass - > m_private - > xrun [ 1 ] = true ;
2014-03-11 21:46:00 +01:00
}
return 0 ;
}
2015-07-07 22:39:09 +02:00
bool audio : : orchestra : : api : : Jack : : open ( uint32_t _device ,
audio : : orchestra : : mode _mode ,
uint32_t _channels ,
uint32_t _firstChannel ,
uint32_t _sampleRate ,
audio : : format _format ,
uint32_t * _bufferSize ,
const audio : : orchestra : : StreamOptions & _options ) {
2014-03-11 21:46:00 +01:00
// Look for jack server and try to become a client (only do once per stream).
jack_client_t * client = 0 ;
2015-04-10 22:06:17 +02:00
if ( _mode = = audio : : orchestra : : mode_output
| | ( _mode = = audio : : orchestra : : mode_input
& & m_mode ! = audio : : orchestra : : mode_output ) ) {
2014-03-11 21:46:00 +01:00
jack_options_t jackoptions = ( jack_options_t ) ( JackNoStartServer ) ; //JackNullOption;
2015-01-26 23:46:53 +01:00
jack_status_t * status = nullptr ;
2015-02-27 21:07:17 +01:00
if ( ! _options . streamName . empty ( ) ) {
client = jack_client_open ( _options . streamName . c_str ( ) , jackoptions , status ) ;
2014-03-11 21:46:00 +01:00
} else {
2015-04-10 22:06:17 +02:00
client = jack_client_open ( " orchestraJack " , jackoptions , status ) ;
2014-03-11 21:46:00 +01:00
}
if ( client = = 0 ) {
2015-02-05 23:31:22 +01:00
ATA_ERROR ( " Jack server not found or connection error! " ) ;
2014-03-13 21:16:30 +01:00
return false ;
2014-03-11 21:46:00 +01:00
}
2014-03-12 23:55:49 +01:00
} else {
2014-03-11 21:46:00 +01:00
// The handle must have been created on an earlier pass.
2015-02-10 21:01:53 +01:00
client = m_private - > client ;
2014-03-11 21:46:00 +01:00
}
const char * * ports ;
std : : string port , previousPort , deviceName ;
uint32_t nPorts = 0 , nDevices = 0 ;
2015-06-14 18:32:08 +02:00
int32_t deviceID = _device / 2 ;
bool isInput = _device % 2 = = 0 ? true : false ;
2015-01-26 23:46:53 +01:00
ports = jack_get_ports ( client , nullptr , nullptr , 0 ) ;
2014-03-11 21:46:00 +01:00
if ( ports ) {
// Parse the port names up to the first colon (:).
size_t iColon = 0 ;
do {
port = ( char * ) ports [ nPorts ] ;
iColon = port . find ( " : " ) ;
if ( iColon ! = std : : string : : npos ) {
port = port . substr ( 0 , iColon ) ;
if ( port ! = previousPort ) {
2015-06-14 18:32:08 +02:00
if ( nDevices = = deviceID ) {
2014-03-12 23:55:49 +01:00
deviceName = port ;
}
2014-03-11 21:46:00 +01:00
nDevices + + ;
previousPort = port ;
}
}
} while ( ports [ + + nPorts ] ) ;
free ( ports ) ;
}
2014-03-12 23:55:49 +01:00
if ( _device > = nDevices ) {
2015-02-05 23:31:22 +01:00
ATA_ERROR ( " device ID is invalid! " ) ;
2014-03-13 21:16:30 +01:00
return false ;
2014-03-11 21:46:00 +01:00
}
// Count the available ports containing the client name as device
// channels. Jack "input ports" equal RtAudio output channels.
uint32_t nChannels = 0 ;
uint64_t flag = JackPortIsInput ;
2015-06-14 18:32:08 +02:00
if ( _mode = = audio : : orchestra : : mode_input ) {
flag = JackPortIsOutput ;
}
2015-01-26 23:46:53 +01:00
ports = jack_get_ports ( client , deviceName . c_str ( ) , nullptr , flag ) ;
2014-03-11 21:46:00 +01:00
if ( ports ) {
2014-03-12 23:55:49 +01:00
while ( ports [ nChannels ] ) {
nChannels + + ;
}
2014-03-11 21:46:00 +01:00
free ( ports ) ;
}
// Compare the jack ports for specified client to the requested number of channels.
2014-03-12 23:55:49 +01:00
if ( nChannels < ( _channels + _firstChannel ) ) {
2015-02-05 23:31:22 +01:00
ATA_ERROR ( " requested number of channels ( " < < _channels < < " ) + offset ( " < < _firstChannel < < " ) not found for specified device ( " < < _device < < " : " < < deviceName < < " ). " ) ;
2014-03-13 21:16:30 +01:00
return false ;
2014-03-11 21:46:00 +01:00
}
// Check the jack server sample rate.
uint32_t jackRate = jack_get_sample_rate ( client ) ;
2014-03-12 23:55:49 +01:00
if ( _sampleRate ! = jackRate ) {
2014-03-11 21:46:00 +01:00
jack_client_close ( client ) ;
2015-02-05 23:31:22 +01:00
ATA_ERROR ( " the requested sample rate ( " < < _sampleRate < < " ) is different than the JACK server rate ( " < < jackRate < < " ). " ) ;
2014-03-13 21:16:30 +01:00
return false ;
2014-03-11 21:46:00 +01:00
}
2015-02-09 21:44:32 +01:00
m_sampleRate = jackRate ;
2014-03-11 21:46:00 +01:00
// Get the latency of the JACK port.
2015-01-26 23:46:53 +01:00
ports = jack_get_ports ( client , deviceName . c_str ( ) , nullptr , flag ) ;
2014-03-12 23:55:49 +01:00
if ( ports [ _firstChannel ] ) {
2014-03-11 21:46:00 +01:00
// Added by Ge Wang
2015-04-10 22:06:17 +02:00
jack_latency_callback_mode_t cbmode = ( _mode = = audio : : orchestra : : mode_input ? JackCaptureLatency : JackPlaybackLatency ) ;
2014-03-11 21:46:00 +01:00
// the range (usually the min and max are equal)
jack_latency_range_t latrange ; latrange . min = latrange . max = 0 ;
// get the latency range
2014-03-12 23:55:49 +01:00
jack_port_get_latency_range ( jack_port_by_name ( client , ports [ _firstChannel ] ) , cbmode , & latrange ) ;
2014-03-11 21:46:00 +01:00
// be optimistic, use the min!
2015-02-09 21:44:32 +01:00
m_latency [ modeToIdTable ( _mode ) ] = latrange . min ;
//m_latency[modeToIdTable(_mode)] = jack_port_get_latency(jack_port_by_name(client, ports[ _firstChannel ]));
2014-03-11 21:46:00 +01:00
}
free ( ports ) ;
// The jack server always uses 32-bit floating-point data.
2015-02-09 21:44:32 +01:00
m_deviceFormat [ modeToIdTable ( _mode ) ] = audio : : format_float ;
m_userFormat = _format ;
2014-03-11 21:46:00 +01:00
// Jack always uses non-interleaved buffers.
2015-02-09 21:44:32 +01:00
m_deviceInterleaved [ modeToIdTable ( _mode ) ] = false ;
2014-03-11 21:46:00 +01:00
// Jack always provides host byte-ordered data.
2015-02-09 21:44:32 +01:00
m_doByteSwap [ modeToIdTable ( _mode ) ] = false ;
2014-03-11 21:46:00 +01:00
// Get the buffer size. The buffer size and number of buffers
// (periods) is set when the jack server is started.
2015-02-09 21:44:32 +01:00
m_bufferSize = ( int ) jack_get_buffer_size ( client ) ;
* _bufferSize = m_bufferSize ;
m_nDeviceChannels [ modeToIdTable ( _mode ) ] = _channels ;
m_nUserChannels [ modeToIdTable ( _mode ) ] = _channels ;
2014-03-11 21:46:00 +01:00
// Set flags for buffer conversion.
2015-02-09 21:44:32 +01:00
m_doConvertBuffer [ modeToIdTable ( _mode ) ] = false ;
if ( m_userFormat ! = m_deviceFormat [ modeToIdTable ( _mode ) ] ) {
m_doConvertBuffer [ modeToIdTable ( _mode ) ] = true ;
ATA_CRITICAL ( " Can not update format ==> use RIVER lib for this ... " ) ;
2014-03-12 23:55:49 +01:00
}
2015-02-09 21:44:32 +01:00
if ( m_deviceInterleaved [ modeToIdTable ( _mode ) ] = = false
& & m_nUserChannels [ modeToIdTable ( _mode ) ] > 1 ) {
ATA_ERROR ( " Reorder channel for the interleaving properties ... " ) ;
2015-02-09 23:24:41 +01:00
m_doConvertBuffer [ modeToIdTable ( _mode ) ] = true ;
2014-03-12 23:55:49 +01:00
}
2014-03-11 21:46:00 +01:00
// Allocate our JackHandle structure for the stream.
2015-02-10 21:01:53 +01:00
m_private - > client = client ;
m_private - > deviceName [ modeToIdTable ( _mode ) ] = deviceName ;
2014-03-11 21:46:00 +01:00
// Allocate necessary internal buffers.
uint64_t bufferBytes ;
2015-02-09 21:44:32 +01:00
bufferBytes = m_nUserChannels [ modeToIdTable ( _mode ) ] * * _bufferSize * audio : : getFormatBytes ( m_deviceFormat [ modeToIdTable ( _mode ) ] ) ;
ATA_VERBOSE ( " allocate : nbChannel= " < < m_nUserChannels [ modeToIdTable ( _mode ) ] < < " bufferSize= " < < * _bufferSize < < " format= " < < m_deviceFormat [ modeToIdTable ( _mode ) ] < < " = " < < audio : : getFormatBytes ( m_deviceFormat [ modeToIdTable ( _mode ) ] ) ) ;
m_userBuffer [ modeToIdTable ( _mode ) ] . resize ( bufferBytes , 0 ) ;
if ( m_userBuffer [ modeToIdTable ( _mode ) ] . size ( ) = = 0 ) {
2015-02-05 23:31:22 +01:00
ATA_ERROR ( " error allocating user buffer memory. " ) ;
2014-03-11 21:46:00 +01:00
goto error ;
}
2015-02-09 21:44:32 +01:00
if ( m_doConvertBuffer [ modeToIdTable ( _mode ) ] ) {
2014-03-11 21:46:00 +01:00
bool makeBuffer = true ;
2015-04-10 22:06:17 +02:00
if ( _mode = = audio : : orchestra : : mode_output ) {
2015-02-09 21:44:32 +01:00
bufferBytes = m_nDeviceChannels [ 0 ] * audio : : getFormatBytes ( m_deviceFormat [ 0 ] ) ;
2015-04-10 22:06:17 +02:00
} else { // _mode == audio::orchestra::mode_input
2015-02-09 21:44:32 +01:00
bufferBytes = m_nDeviceChannels [ 1 ] * audio : : getFormatBytes ( m_deviceFormat [ 1 ] ) ;
2015-04-10 22:06:17 +02:00
if ( m_mode = = audio : : orchestra : : mode_output & & m_deviceBuffer ) {
2015-02-09 21:44:32 +01:00
uint64_t bytesOut = m_nDeviceChannels [ 0 ] * audio : : getFormatBytes ( m_deviceFormat [ 0 ] ) ;
2014-03-12 23:55:49 +01:00
if ( bufferBytes < bytesOut ) {
makeBuffer = false ;
}
2014-03-11 21:46:00 +01:00
}
}
if ( makeBuffer ) {
2014-03-12 23:55:49 +01:00
bufferBytes * = * _bufferSize ;
2015-02-09 21:44:32 +01:00
if ( m_deviceBuffer ) free ( m_deviceBuffer ) ;
m_deviceBuffer = ( char * ) calloc ( bufferBytes , 1 ) ;
if ( m_deviceBuffer = = nullptr ) {
2015-02-05 23:31:22 +01:00
ATA_ERROR ( " error allocating device buffer memory. " ) ;
2014-03-11 21:46:00 +01:00
goto error ;
}
}
}
// Allocate memory for the Jack ports (channels) identifiers.
2015-02-10 21:01:53 +01:00
m_private - > ports [ modeToIdTable ( _mode ) ] = ( jack_port_t * * ) malloc ( sizeof ( jack_port_t * ) * _channels ) ;
if ( m_private - > ports [ modeToIdTable ( _mode ) ] = = nullptr ) {
2015-02-05 23:31:22 +01:00
ATA_ERROR ( " error allocating port memory. " ) ;
2014-03-11 21:46:00 +01:00
goto error ;
}
2015-02-09 21:44:32 +01:00
m_device [ modeToIdTable ( _mode ) ] = _device ;
m_channelOffset [ modeToIdTable ( _mode ) ] = _firstChannel ;
2016-04-29 23:16:07 +02:00
m_state = audio : : orchestra : : state : : stopped ;
2015-04-10 22:06:17 +02:00
if ( m_mode = = audio : : orchestra : : mode_output
& & _mode = = audio : : orchestra : : mode_input ) {
2014-03-11 21:46:00 +01:00
// We had already set up the stream for output.
2015-04-10 22:06:17 +02:00
m_mode = audio : : orchestra : : mode_duplex ;
2014-03-12 23:55:49 +01:00
} else {
2015-02-09 21:44:32 +01:00
m_mode = _mode ;
2015-04-10 22:06:17 +02:00
jack_set_process_callback ( m_private - > client , & audio : : orchestra : : api : : Jack : : jackCallbackHandler , this ) ;
jack_set_xrun_callback ( m_private - > client , & audio : : orchestra : : api : : Jack : : jackXrun , this ) ;
jack_on_shutdown ( m_private - > client , & audio : : orchestra : : api : : Jack : : jackShutdown , this ) ;
2014-03-11 21:46:00 +01:00
}
// Register our ports.
char label [ 64 ] ;
2015-04-10 22:06:17 +02:00
if ( _mode = = audio : : orchestra : : mode_output ) {
2015-02-09 21:44:32 +01:00
for ( uint32_t i = 0 ; i < m_nUserChannels [ 0 ] ; i + + ) {
2014-03-11 21:46:00 +01:00
snprintf ( label , 64 , " outport %d " , i ) ;
2015-02-10 21:01:53 +01:00
m_private - > ports [ 0 ] [ i ] = jack_port_register ( m_private - > client ,
( const char * ) label ,
JACK_DEFAULT_AUDIO_TYPE ,
JackPortIsOutput ,
0 ) ;
2014-03-11 21:46:00 +01:00
}
} else {
2015-02-09 21:44:32 +01:00
for ( uint32_t i = 0 ; i < m_nUserChannels [ 1 ] ; i + + ) {
2014-03-11 21:46:00 +01:00
snprintf ( label , 64 , " inport %d " , i ) ;
2015-02-10 21:01:53 +01:00
m_private - > ports [ 1 ] [ i ] = jack_port_register ( m_private - > client ,
( const char * ) label ,
JACK_DEFAULT_AUDIO_TYPE ,
JackPortIsInput ,
0 ) ;
2014-03-11 21:46:00 +01:00
}
}
// Setup the buffer conversion information structure. We don't use
// buffers to do channel offsets, so we override that parameter
// here.
2015-02-09 21:44:32 +01:00
if ( m_doConvertBuffer [ modeToIdTable ( _mode ) ] ) {
2014-03-12 23:55:49 +01:00
setConvertInfo ( _mode , 0 ) ;
2014-03-11 21:46:00 +01:00
}
2014-03-13 21:16:30 +01:00
return true ;
2014-03-11 21:46:00 +01:00
error :
2015-02-10 21:01:53 +01:00
jack_client_close ( m_private - > client ) ;
if ( m_private - > ports [ 0 ] ! = nullptr ) {
free ( m_private - > ports [ 0 ] ) ;
m_private - > ports [ 0 ] = nullptr ;
}
if ( m_private - > ports [ 1 ] ! = nullptr ) {
free ( m_private - > ports [ 1 ] ) ;
m_private - > ports [ 1 ] = nullptr ;
2014-03-11 21:46:00 +01:00
}
for ( int32_t iii = 0 ; iii < 2 ; + + iii ) {
2015-02-09 21:44:32 +01:00
m_userBuffer [ iii ] . clear ( ) ;
2014-03-11 21:46:00 +01:00
}
2015-02-09 21:44:32 +01:00
if ( m_deviceBuffer ) {
free ( m_deviceBuffer ) ;
m_deviceBuffer = nullptr ;
2014-03-11 21:46:00 +01:00
}
2014-03-13 21:16:30 +01:00
return false ;
2014-03-11 21:46:00 +01:00
}
2015-04-10 22:06:17 +02:00
enum audio : : orchestra : : error audio : : orchestra : : api : : Jack : : closeStream ( ) {
2016-04-29 23:16:07 +02:00
if ( m_state = = audio : : orchestra : : state : : closed ) {
2015-02-05 23:31:22 +01:00
ATA_ERROR ( " no open stream to close! " ) ;
2015-04-10 22:06:17 +02:00
return audio : : orchestra : : error_warning ;
2014-03-11 21:46:00 +01:00
}
2015-02-10 21:01:53 +01:00
if ( m_private ! = nullptr ) {
2016-04-29 23:16:07 +02:00
if ( m_state = = audio : : orchestra : : state : : running ) {
2015-02-10 21:01:53 +01:00
jack_deactivate ( m_private - > client ) ;
2014-03-12 23:55:49 +01:00
}
2015-02-10 21:01:53 +01:00
jack_client_close ( m_private - > client ) ;
2014-03-11 21:46:00 +01:00
}
2015-02-10 21:01:53 +01:00
if ( m_private - > ports [ 0 ] ! = nullptr ) {
free ( m_private - > ports [ 0 ] ) ;
m_private - > ports [ 0 ] = nullptr ;
}
if ( m_private - > ports [ 1 ] ! = nullptr ) {
free ( m_private - > ports [ 1 ] ) ;
m_private - > ports [ 1 ] = nullptr ;
2014-03-11 21:46:00 +01:00
}
for ( int32_t i = 0 ; i < 2 ; i + + ) {
2015-02-09 21:44:32 +01:00
m_userBuffer [ i ] . clear ( ) ;
2014-03-11 21:46:00 +01:00
}
2015-02-09 21:44:32 +01:00
if ( m_deviceBuffer ) {
free ( m_deviceBuffer ) ;
m_deviceBuffer = nullptr ;
2014-03-11 21:46:00 +01:00
}
2015-04-10 22:06:17 +02:00
m_mode = audio : : orchestra : : mode_unknow ;
2016-04-29 23:16:07 +02:00
m_state = audio : : orchestra : : state : : closed ;
2015-04-10 22:06:17 +02:00
return audio : : orchestra : : error_none ;
2014-03-11 21:46:00 +01:00
}
2015-04-10 22:06:17 +02:00
enum audio : : orchestra : : error audio : : orchestra : : api : : Jack : : startStream ( ) {
2015-02-10 22:38:30 +01:00
// TODO : Check return ...
2015-04-10 22:06:17 +02:00
audio : : orchestra : : Api : : startStream ( ) ;
if ( verifyStream ( ) ! = audio : : orchestra : : error_none ) {
return audio : : orchestra : : error_fail ;
2014-03-12 23:55:49 +01:00
}
2016-04-29 23:16:07 +02:00
if ( m_state = = audio : : orchestra : : state : : running ) {
2015-02-05 23:31:22 +01:00
ATA_ERROR ( " the stream is already running! " ) ;
2015-04-10 22:06:17 +02:00
return audio : : orchestra : : error_warning ;
2014-03-11 21:46:00 +01:00
}
2015-02-10 21:01:53 +01:00
int32_t result = jack_activate ( m_private - > client ) ;
2014-03-11 21:46:00 +01:00
if ( result ) {
2015-02-05 23:31:22 +01:00
ATA_ERROR ( " unable to activate JACK client! " ) ;
2014-03-11 21:46:00 +01:00
goto unlock ;
}
const char * * ports ;
// Get the list of available ports.
2015-04-10 22:06:17 +02:00
if ( m_mode = = audio : : orchestra : : mode_output
| | m_mode = = audio : : orchestra : : mode_duplex ) {
2014-03-11 21:46:00 +01:00
result = 1 ;
2015-02-10 21:01:53 +01:00
ports = jack_get_ports ( m_private - > client , m_private - > deviceName [ 0 ] . c_str ( ) , nullptr , JackPortIsInput ) ;
2015-01-26 23:46:53 +01:00
if ( ports = = nullptr ) {
2015-02-05 23:31:22 +01:00
ATA_ERROR ( " error determining available JACK input ports! " ) ;
2014-03-11 21:46:00 +01:00
goto unlock ;
}
// Now make the port connections. Since RtAudio wasn't designed to
// allow the user to select particular channels of a device, we'll
// just open the first "nChannels" ports with offset.
2015-02-09 21:44:32 +01:00
for ( uint32_t i = 0 ; i < m_nUserChannels [ 0 ] ; i + + ) {
2014-03-11 21:46:00 +01:00
result = 1 ;
2015-02-09 21:44:32 +01:00
if ( ports [ m_channelOffset [ 0 ] + i ] )
2015-02-10 21:01:53 +01:00
result = jack_connect ( m_private - > client , jack_port_name ( m_private - > ports [ 0 ] [ i ] ) , ports [ m_channelOffset [ 0 ] + i ] ) ;
2014-03-11 21:46:00 +01:00
if ( result ) {
free ( ports ) ;
2015-02-05 23:31:22 +01:00
ATA_ERROR ( " error connecting output ports! " ) ;
2014-03-11 21:46:00 +01:00
goto unlock ;
}
}
free ( ports ) ;
}
2015-04-10 22:06:17 +02:00
if ( m_mode = = audio : : orchestra : : mode_input
| | m_mode = = audio : : orchestra : : mode_duplex ) {
2014-03-11 21:46:00 +01:00
result = 1 ;
2015-02-10 21:01:53 +01:00
ports = jack_get_ports ( m_private - > client , m_private - > deviceName [ 1 ] . c_str ( ) , nullptr , JackPortIsOutput ) ;
2015-01-26 23:46:53 +01:00
if ( ports = = nullptr ) {
2015-02-05 23:31:22 +01:00
ATA_ERROR ( " error determining available JACK output ports! " ) ;
2014-03-11 21:46:00 +01:00
goto unlock ;
}
2014-03-12 23:55:49 +01:00
// Now make the port connections. See note above.
2015-02-09 21:44:32 +01:00
for ( uint32_t i = 0 ; i < m_nUserChannels [ 1 ] ; i + + ) {
2014-03-11 21:46:00 +01:00
result = 1 ;
2015-02-09 21:44:32 +01:00
if ( ports [ m_channelOffset [ 1 ] + i ] ) {
2015-02-10 21:01:53 +01:00
result = jack_connect ( m_private - > client , ports [ m_channelOffset [ 1 ] + i ] , jack_port_name ( m_private - > ports [ 1 ] [ i ] ) ) ;
2014-03-12 23:55:49 +01:00
}
2014-03-11 21:46:00 +01:00
if ( result ) {
free ( ports ) ;
2015-02-05 23:31:22 +01:00
ATA_ERROR ( " error connecting input ports! " ) ;
2014-03-11 21:46:00 +01:00
goto unlock ;
}
}
free ( ports ) ;
}
2015-02-10 21:01:53 +01:00
m_private - > drainCounter = 0 ;
m_private - > internalDrain = false ;
2016-04-29 23:16:07 +02:00
m_state = audio : : orchestra : : state : : running ;
2014-03-12 23:55:49 +01:00
unlock :
if ( result = = 0 ) {
2015-04-10 22:06:17 +02:00
return audio : : orchestra : : error_none ;
2014-03-12 23:55:49 +01:00
}
2015-04-10 22:06:17 +02:00
return audio : : orchestra : : error_systemError ;
2014-03-11 21:46:00 +01:00
}
2015-04-10 22:06:17 +02:00
enum audio : : orchestra : : error audio : : orchestra : : api : : Jack : : stopStream ( ) {
if ( verifyStream ( ) ! = audio : : orchestra : : error_none ) {
return audio : : orchestra : : error_fail ;
2014-03-12 23:55:49 +01:00
}
2016-04-29 23:16:07 +02:00
if ( m_state = = audio : : orchestra : : state : : stopped ) {
2015-02-05 23:31:22 +01:00
ATA_ERROR ( " the stream is already stopped! " ) ;
2015-04-10 22:06:17 +02:00
return audio : : orchestra : : error_warning ;
2014-03-11 21:46:00 +01:00
}
2015-04-10 22:06:17 +02:00
if ( m_mode = = audio : : orchestra : : mode_output
| | m_mode = = audio : : orchestra : : mode_duplex ) {
2015-02-10 21:01:53 +01:00
if ( m_private - > drainCounter = = 0 ) {
m_private - > drainCounter = 2 ;
2016-03-08 21:29:34 +01:00
std : : unique_lock < std : : mutex > lck ( m_mutex ) ;
2015-02-10 21:01:53 +01:00
m_private - > condition . wait ( lck ) ;
2014-03-11 21:46:00 +01:00
}
}
2015-02-10 21:01:53 +01:00
jack_deactivate ( m_private - > client ) ;
2016-04-29 23:16:07 +02:00
m_state = audio : : orchestra : : state : : stopped ;
2015-04-10 22:06:17 +02:00
return audio : : orchestra : : error_none ;
2014-03-11 21:46:00 +01:00
}
2015-04-10 22:06:17 +02:00
enum audio : : orchestra : : error audio : : orchestra : : api : : Jack : : abortStream ( ) {
if ( verifyStream ( ) ! = audio : : orchestra : : error_none ) {
return audio : : orchestra : : error_fail ;
2014-03-12 23:55:49 +01:00
}
2016-04-29 23:16:07 +02:00
if ( m_state = = audio : : orchestra : : state : : stopped ) {
2015-02-05 23:31:22 +01:00
ATA_ERROR ( " the stream is already stopped! " ) ;
2015-04-10 22:06:17 +02:00
return audio : : orchestra : : error_warning ;
2014-03-11 21:46:00 +01:00
}
2015-02-10 21:01:53 +01:00
m_private - > drainCounter = 2 ;
2014-03-12 23:55:49 +01:00
return stopStream ( ) ;
2014-03-11 21:46:00 +01:00
}
// This function will be called by a spawned thread when the user
// callback function signals that the stream should be stopped or
// aborted. It is necessary to handle it this way because the
// callbackEvent() function must return before the jack_deactivate()
// function will return.
2015-02-09 21:44:32 +01:00
static void jackStopStream ( void * _userData ) {
2016-03-08 21:29:34 +01:00
ethread : : setName ( " Jack_stopStream " ) ;
2015-04-10 22:06:17 +02:00
audio : : orchestra : : api : : Jack * myClass = reinterpret_cast < audio : : orchestra : : api : : Jack * > ( _userData ) ;
2015-02-09 21:44:32 +01:00
myClass - > stopStream ( ) ;
2014-03-11 21:46:00 +01:00
}
2015-04-10 22:06:17 +02:00
bool audio : : orchestra : : api : : Jack : : callbackEvent ( uint64_t _nframes ) {
2016-04-29 23:16:07 +02:00
if ( m_state = = audio : : orchestra : : state : : stopped
| | m_state = = audio : : orchestra : : state : : stopping ) {
2014-03-13 21:16:30 +01:00
return true ;
2014-03-12 23:55:49 +01:00
}
2016-04-29 23:16:07 +02:00
if ( m_state = = audio : : orchestra : : state : : closed ) {
2015-02-09 21:44:32 +01:00
ATA_ERROR ( " the stream is closed ... this shouldn't happen! " ) ;
2014-03-13 21:16:30 +01:00
return false ;
2014-03-11 21:46:00 +01:00
}
2015-02-09 21:44:32 +01:00
if ( m_bufferSize ! = _nframes ) {
ATA_ERROR ( " the JACK buffer size has changed ... cannot process! " ) ;
2014-03-13 21:16:30 +01:00
return false ;
2014-03-11 21:46:00 +01:00
}
// Check if we were draining the stream and signal is finished.
2015-02-10 21:01:53 +01:00
if ( m_private - > drainCounter > 3 ) {
2016-04-29 23:16:07 +02:00
m_state = audio : : orchestra : : state : : stopping ;
2015-02-10 21:01:53 +01:00
if ( m_private - > internalDrain = = true ) {
2016-03-08 21:29:34 +01:00
new std : : thread ( jackStopStream , this ) ;
2014-03-11 21:46:00 +01:00
} else {
2015-02-10 21:01:53 +01:00
m_private - > condition . notify_one ( ) ;
2014-03-11 21:46:00 +01:00
}
2014-03-13 21:16:30 +01:00
return true ;
2014-03-11 21:46:00 +01:00
}
// Invoke user callback first, to get fresh output data.
2015-02-10 21:01:53 +01:00
if ( m_private - > drainCounter = = 0 ) {
2015-04-13 21:49:48 +02:00
audio : : Time streamTime = getStreamTime ( ) ;
2015-04-10 22:06:17 +02:00
std : : vector < enum audio : : orchestra : : status > status ;
if ( m_mode ! = audio : : orchestra : : mode_input & & m_private - > xrun [ 0 ] = = true ) {
2016-04-29 23:16:07 +02:00
status . push_back ( audio : : orchestra : : status : : underflow ) ;
2015-02-10 21:01:53 +01:00
m_private - > xrun [ 0 ] = false ;
2014-03-11 21:46:00 +01:00
}
2015-04-10 22:06:17 +02:00
if ( m_mode ! = audio : : orchestra : : mode_output & & m_private - > xrun [ 1 ] = = true ) {
2016-04-29 23:16:07 +02:00
status . push_back ( audio : : orchestra : : status : : overflow ) ;
2015-02-10 21:01:53 +01:00
m_private - > xrun [ 1 ] = false ;
2014-03-11 21:46:00 +01:00
}
2015-02-17 21:08:15 +01:00
int32_t cbReturnValue = m_callback ( & m_userBuffer [ 1 ] [ 0 ] ,
streamTime ,
& m_userBuffer [ 0 ] [ 0 ] ,
streamTime ,
m_bufferSize ,
status ) ;
2014-03-11 21:46:00 +01:00
if ( cbReturnValue = = 2 ) {
2016-04-29 23:16:07 +02:00
m_state = audio : : orchestra : : state : : stopping ;
2015-02-10 21:01:53 +01:00
m_private - > drainCounter = 2 ;
2016-03-08 21:29:34 +01:00
new std : : thread ( jackStopStream , this ) ;
2014-03-13 21:16:30 +01:00
return true ;
2014-03-11 21:46:00 +01:00
}
else if ( cbReturnValue = = 1 ) {
2015-02-10 21:01:53 +01:00
m_private - > drainCounter = 1 ;
m_private - > internalDrain = true ;
2014-03-11 21:46:00 +01:00
}
}
jack_default_audio_sample_t * jackbuffer ;
2014-03-12 23:55:49 +01:00
uint64_t bufferBytes = _nframes * sizeof ( jack_default_audio_sample_t ) ;
2015-04-10 22:06:17 +02:00
if ( m_mode = = audio : : orchestra : : mode_output
| | m_mode = = audio : : orchestra : : mode_duplex ) {
2015-02-10 21:01:53 +01:00
if ( m_private - > drainCounter > 1 ) { // write zeros to the output stream
2015-02-09 21:44:32 +01:00
for ( uint32_t i = 0 ; i < m_nDeviceChannels [ 0 ] ; i + + ) {
2015-02-10 21:01:53 +01:00
jackbuffer = ( jack_default_audio_sample_t * ) jack_port_get_buffer ( m_private - > ports [ 0 ] [ i ] , ( jack_nframes_t ) _nframes ) ;
2014-03-11 21:46:00 +01:00
memset ( jackbuffer , 0 , bufferBytes ) ;
}
2015-02-09 21:44:32 +01:00
} else if ( m_doConvertBuffer [ 0 ] ) {
convertBuffer ( m_deviceBuffer , & m_userBuffer [ 0 ] [ 0 ] , m_convertInfo [ 0 ] ) ;
for ( uint32_t i = 0 ; i < m_nDeviceChannels [ 0 ] ; i + + ) {
2015-02-10 21:01:53 +01:00
jackbuffer = ( jack_default_audio_sample_t * ) jack_port_get_buffer ( m_private - > ports [ 0 ] [ i ] , ( jack_nframes_t ) _nframes ) ;
2015-02-09 21:44:32 +01:00
memcpy ( jackbuffer , & m_deviceBuffer [ i * bufferBytes ] , bufferBytes ) ;
2014-03-11 21:46:00 +01:00
}
2014-03-12 23:55:49 +01:00
} else { // no buffer conversion
2015-02-09 21:44:32 +01:00
for ( uint32_t i = 0 ; i < m_nUserChannels [ 0 ] ; i + + ) {
2015-02-10 21:01:53 +01:00
jackbuffer = ( jack_default_audio_sample_t * ) jack_port_get_buffer ( m_private - > ports [ 0 ] [ i ] , ( jack_nframes_t ) _nframes ) ;
2015-02-09 21:44:32 +01:00
memcpy ( jackbuffer , & m_userBuffer [ 0 ] [ i * bufferBytes ] , bufferBytes ) ;
2014-03-11 21:46:00 +01:00
}
}
2015-02-10 21:01:53 +01:00
if ( m_private - > drainCounter ) {
m_private - > drainCounter + + ;
2014-03-11 21:46:00 +01:00
goto unlock ;
}
}
2015-04-10 22:06:17 +02:00
if ( m_mode = = audio : : orchestra : : mode_input
| | m_mode = = audio : : orchestra : : mode_duplex ) {
2015-02-09 21:44:32 +01:00
if ( m_doConvertBuffer [ 1 ] ) {
for ( uint32_t i = 0 ; i < m_nDeviceChannels [ 1 ] ; i + + ) {
2015-02-10 21:01:53 +01:00
jackbuffer = ( jack_default_audio_sample_t * ) jack_port_get_buffer ( m_private - > ports [ 1 ] [ i ] , ( jack_nframes_t ) _nframes ) ;
2015-02-09 21:44:32 +01:00
memcpy ( & m_deviceBuffer [ i * bufferBytes ] , jackbuffer , bufferBytes ) ;
2014-03-11 21:46:00 +01:00
}
2015-02-09 21:44:32 +01:00
convertBuffer ( & m_userBuffer [ 1 ] [ 0 ] , m_deviceBuffer , m_convertInfo [ 1 ] ) ;
2014-03-11 21:46:00 +01:00
} else {
// no buffer conversion
2015-02-09 21:44:32 +01:00
for ( uint32_t i = 0 ; i < m_nUserChannels [ 1 ] ; i + + ) {
2015-02-10 21:01:53 +01:00
jackbuffer = ( jack_default_audio_sample_t * ) jack_port_get_buffer ( m_private - > ports [ 1 ] [ i ] , ( jack_nframes_t ) _nframes ) ;
2015-02-09 21:44:32 +01:00
memcpy ( & m_userBuffer [ 1 ] [ i * bufferBytes ] , jackbuffer , bufferBytes ) ;
2014-03-11 21:46:00 +01:00
}
}
}
unlock :
2015-04-10 22:06:17 +02:00
audio : : orchestra : : Api : : tickStreamTime ( ) ;
2014-03-13 21:16:30 +01:00
return true ;
2014-03-11 21:46:00 +01:00
}
2014-03-12 23:55:49 +01:00
2014-03-11 21:46:00 +01:00
# endif
2014-03-12 23:55:49 +01:00