1254 lines
42 KiB
C++
1254 lines
42 KiB
C++
/*
|
|
Copyright (C) 2009-2013 Grame
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU Lesser General Public License as published by
|
|
the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Lesser 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.
|
|
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include <stdarg.h>
|
|
|
|
#include "JackNetInterface.h"
|
|
#include "JackAudioAdapterInterface.h"
|
|
|
|
#ifdef __cplusplus
|
|
extern "C"
|
|
{
|
|
#endif
|
|
|
|
// NetJack common API
|
|
|
|
#define MASTER_NAME_SIZE 256
|
|
|
|
enum JackNetEncoder {
|
|
|
|
JackFloatEncoder = 0,
|
|
JackIntEncoder = 1,
|
|
JackCeltEncoder = 2,
|
|
JackOpusEncoder = 3
|
|
};
|
|
|
|
typedef struct {
|
|
|
|
int audio_input;
|
|
int audio_output;
|
|
int midi_input;
|
|
int midi_output;
|
|
int mtu;
|
|
int time_out; // in millisecond, -1 means in infinite
|
|
int encoder; // one of JackNetEncoder
|
|
int kbps; // KB per second for CELT encoder
|
|
int latency; // network cycles
|
|
|
|
} jack_slave_t;
|
|
|
|
typedef struct {
|
|
|
|
int audio_input;
|
|
int audio_output;
|
|
int midi_input;
|
|
int midi_output;
|
|
jack_nframes_t buffer_size;
|
|
jack_nframes_t sample_rate;
|
|
char master_name[MASTER_NAME_SIZE];
|
|
int time_out;
|
|
int partial_cycle;
|
|
|
|
} jack_master_t;
|
|
|
|
// NetJack slave API
|
|
|
|
typedef struct _jack_net_slave jack_net_slave_t;
|
|
|
|
typedef int (* JackNetSlaveProcessCallback) (jack_nframes_t buffer_size,
|
|
int audio_input,
|
|
float** audio_input_buffer,
|
|
int midi_input,
|
|
void** midi_input_buffer,
|
|
int audio_output,
|
|
float** audio_output_buffer,
|
|
int midi_output,
|
|
void** midi_output_buffer,
|
|
void* data);
|
|
|
|
typedef int (*JackNetSlaveBufferSizeCallback) (jack_nframes_t nframes, void *arg);
|
|
typedef int (*JackNetSlaveSampleRateCallback) (jack_nframes_t nframes, void *arg);
|
|
typedef void (*JackNetSlaveShutdownCallback) (void* arg);
|
|
typedef int (*JackNetSlaveRestartCallback) (void* arg);
|
|
typedef void (*JackNetSlaveErrorCallback) (int error_code, void* arg);
|
|
|
|
LIB_EXPORT jack_net_slave_t* jack_net_slave_open(const char* ip, int port, const char* name, jack_slave_t* request, jack_master_t* result);
|
|
LIB_EXPORT int jack_net_slave_close(jack_net_slave_t* net);
|
|
|
|
LIB_EXPORT int jack_net_slave_activate(jack_net_slave_t* net);
|
|
LIB_EXPORT int jack_net_slave_deactivate(jack_net_slave_t* net);
|
|
LIB_EXPORT int jack_net_slave_is_active(jack_net_slave_t* net);
|
|
|
|
LIB_EXPORT int jack_set_net_slave_process_callback(jack_net_slave_t* net, JackNetSlaveProcessCallback net_callback, void *arg);
|
|
LIB_EXPORT int jack_set_net_slave_buffer_size_callback(jack_net_slave_t* net, JackNetSlaveBufferSizeCallback bufsize_callback, void *arg);
|
|
LIB_EXPORT int jack_set_net_slave_sample_rate_callback(jack_net_slave_t* net, JackNetSlaveSampleRateCallback samplerate_callback, void *arg);
|
|
LIB_EXPORT int jack_set_net_slave_shutdown_callback(jack_net_slave_t* net, JackNetSlaveShutdownCallback shutdown_callback, void *arg);
|
|
LIB_EXPORT int jack_set_net_slave_restart_callback(jack_net_slave_t* net, JackNetSlaveRestartCallback restart_callback, void *arg);
|
|
LIB_EXPORT int jack_set_net_slave_error_callback(jack_net_slave_t* net, JackNetSlaveErrorCallback error_callback, void *arg);
|
|
|
|
// NetJack master API
|
|
|
|
typedef struct _jack_net_master jack_net_master_t;
|
|
|
|
LIB_EXPORT jack_net_master_t* jack_net_master_open(const char* ip, int port, jack_master_t* request, jack_slave_t* result);
|
|
LIB_EXPORT int jack_net_master_close(jack_net_master_t* net);
|
|
|
|
LIB_EXPORT int jack_net_master_recv(jack_net_master_t* net, int audio_input, float** audio_input_buffer, int midi_input, void** midi_input_buffer);
|
|
LIB_EXPORT int jack_net_master_send(jack_net_master_t* net, int audio_output, float** audio_output_buffer, int midi_output, void** midi_output_buffer);
|
|
|
|
LIB_EXPORT int jack_net_master_recv_slice(jack_net_master_t* net, int audio_input, float** audio_input_buffer, int midi_input, void** midi_input_buffer, int frames);
|
|
LIB_EXPORT int jack_net_master_send_slice(jack_net_master_t* net, int audio_output, float** audio_output_buffer, int midi_output, void** midi_output_buffer, int frames);
|
|
|
|
// NetJack adapter API
|
|
|
|
typedef struct _jack_adapter jack_adapter_t;
|
|
|
|
LIB_EXPORT jack_adapter_t* jack_create_adapter(int input, int output,
|
|
jack_nframes_t host_buffer_size,
|
|
jack_nframes_t host_sample_rate,
|
|
jack_nframes_t adapted_buffer_size,
|
|
jack_nframes_t adapted_sample_rate);
|
|
LIB_EXPORT int jack_destroy_adapter(jack_adapter_t* adapter);
|
|
LIB_EXPORT void jack_flush_adapter(jack_adapter_t* adapter);
|
|
|
|
LIB_EXPORT int jack_adapter_push_and_pull(jack_adapter_t* adapter, float** input, float** output, unsigned int frames);
|
|
LIB_EXPORT int jack_adapter_pull_and_push(jack_adapter_t* adapter, float** input, float** output, unsigned int frames);
|
|
|
|
#define LOG_LEVEL_INFO 1
|
|
#define LOG_LEVEL_ERROR 2
|
|
|
|
LIB_EXPORT void jack_error(const char *fmt, ...);
|
|
LIB_EXPORT void jack_info(const char *fmt, ...);
|
|
LIB_EXPORT void jack_log(const char *fmt, ...);
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
|
|
namespace Jack
|
|
{
|
|
|
|
struct JackNetExtMaster : public JackNetMasterInterface {
|
|
|
|
jack_master_t fRequest;
|
|
|
|
JackRingBuffer** fRingBuffer;
|
|
|
|
JackNetExtMaster(const char* ip,
|
|
int port,
|
|
jack_master_t* request)
|
|
{
|
|
fRunning = true;
|
|
assert(strlen(ip) < 32);
|
|
strcpy(fMulticastIP, ip);
|
|
fSocket.SetPort(port);
|
|
fRequest.buffer_size = request->buffer_size;
|
|
fRequest.sample_rate = request->sample_rate;
|
|
fRequest.audio_input = request->audio_input;
|
|
fRequest.audio_output = request->audio_output;
|
|
fRequest.time_out = request->time_out;
|
|
fRequest.partial_cycle = request->partial_cycle;
|
|
fRingBuffer = NULL;
|
|
}
|
|
|
|
virtual ~JackNetExtMaster()
|
|
{
|
|
if (fRingBuffer) {
|
|
for (int i = 0; i < fParams.fReturnAudioChannels; i++) {
|
|
delete fRingBuffer[i];
|
|
}
|
|
delete [] fRingBuffer;
|
|
}
|
|
}
|
|
|
|
int Open(jack_slave_t* result)
|
|
{
|
|
// Check buffer_size
|
|
if (fRequest.buffer_size == 0) {
|
|
jack_error("Incorrect buffer_size...");
|
|
return -1;
|
|
}
|
|
// Check sample_rate
|
|
if (fRequest.sample_rate == 0) {
|
|
jack_error("Incorrect sample_rate...");
|
|
return -1;
|
|
}
|
|
|
|
// Init socket API (win32)
|
|
if (SocketAPIInit() < 0) {
|
|
jack_error("Can't init Socket API, exiting...");
|
|
return -1;
|
|
}
|
|
|
|
// Request socket
|
|
if (fSocket.NewSocket() == SOCKET_ERROR) {
|
|
jack_error("Can't create the network manager input socket : %s", StrError(NET_ERROR_CODE));
|
|
return -1;
|
|
}
|
|
|
|
// Bind the socket to the local port
|
|
if (fSocket.Bind() == SOCKET_ERROR) {
|
|
jack_error("Can't bind the network manager socket : %s", StrError(NET_ERROR_CODE));
|
|
fSocket.Close();
|
|
return -1;
|
|
}
|
|
|
|
// Join multicast group
|
|
if (fSocket.JoinMCastGroup(fMulticastIP) == SOCKET_ERROR) {
|
|
jack_error("Can't join multicast group : %s", StrError(NET_ERROR_CODE));
|
|
}
|
|
|
|
// Local loop
|
|
if (fSocket.SetLocalLoop() == SOCKET_ERROR) {
|
|
jack_error("Can't set local loop : %s", StrError(NET_ERROR_CODE));
|
|
}
|
|
|
|
// Set a timeout on the multicast receive (the thread can now be cancelled)
|
|
if (fSocket.SetTimeOut(MANAGER_INIT_TIMEOUT) == SOCKET_ERROR) {
|
|
jack_error("Can't set timeout : %s", StrError(NET_ERROR_CODE));
|
|
}
|
|
|
|
// Main loop, wait for data, deal with it and wait again
|
|
int attempt = 0;
|
|
int rx_bytes = 0;
|
|
int try_count = (fRequest.time_out > 0) ? int((1000000.f * float(fRequest.time_out)) / float(MANAGER_INIT_TIMEOUT)) : INT_MAX;
|
|
|
|
do
|
|
{
|
|
session_params_t net_params;
|
|
rx_bytes = fSocket.CatchHost(&net_params, sizeof(session_params_t), 0);
|
|
SessionParamsNToH(&net_params, &fParams);
|
|
|
|
if ((rx_bytes == SOCKET_ERROR) && (fSocket.GetError() != NET_NO_DATA)) {
|
|
jack_error("Error in receive : %s", StrError(NET_ERROR_CODE));
|
|
if (++attempt == 10) {
|
|
jack_error("Can't receive on the socket, exiting net manager" );
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
if (rx_bytes == sizeof(session_params_t)) {
|
|
switch (GetPacketType(&fParams)) {
|
|
|
|
case SLAVE_AVAILABLE:
|
|
if (InitMaster(result) == 0) {
|
|
SessionParamsDisplay(&fParams);
|
|
fRunning = false;
|
|
} else {
|
|
jack_error("Can't init new net master...");
|
|
goto error;
|
|
}
|
|
jack_info("Waiting for a slave...");
|
|
break;
|
|
|
|
case KILL_MASTER:
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
while (fRunning && (--try_count > 0));
|
|
|
|
if (try_count == 0) {
|
|
jack_error("Time out error in connect");
|
|
return -1;
|
|
}
|
|
|
|
// Set result parameters
|
|
result->audio_input = fParams.fSendAudioChannels;
|
|
result->audio_output = fParams.fReturnAudioChannels;
|
|
result->midi_input = fParams.fSendMidiChannels;
|
|
result->midi_output = fParams.fReturnMidiChannels;
|
|
result->mtu = fParams.fMtu;
|
|
result->latency = fParams.fNetworkLatency;
|
|
|
|
// Use ringbuffer in case of partial cycle and latency > 0
|
|
if (fRequest.partial_cycle && result->latency > 0) {
|
|
fRingBuffer = new JackRingBuffer*[fParams.fReturnAudioChannels];
|
|
for (int i = 0; i < fParams.fReturnAudioChannels; i++) {
|
|
fRingBuffer[i] = new JackRingBuffer(fRequest.buffer_size * result->latency * 2);
|
|
}
|
|
}
|
|
return 0;
|
|
|
|
error:
|
|
fSocket.Close();
|
|
return -1;
|
|
}
|
|
|
|
int InitMaster(jack_slave_t* result)
|
|
{
|
|
// Check MASTER <==> SLAVE network protocol coherency
|
|
if (fParams.fProtocolVersion != NETWORK_PROTOCOL) {
|
|
jack_error("Error : slave '%s' is running with a different protocol %d != %d", fParams.fName, fParams.fProtocolVersion, NETWORK_PROTOCOL);
|
|
return -1;
|
|
}
|
|
|
|
// Settings
|
|
fSocket.GetName(fParams.fMasterNetName);
|
|
fParams.fID = 1;
|
|
fParams.fPeriodSize = fRequest.buffer_size;
|
|
fParams.fSampleRate = fRequest.sample_rate;
|
|
|
|
if (fRequest.audio_input == -1) {
|
|
if (fParams.fSendAudioChannels == -1) {
|
|
jack_error("Error : master and slave use -1 for wanted inputs...");
|
|
return -1;
|
|
} else {
|
|
result->audio_input = fParams.fSendAudioChannels;
|
|
jack_info("Takes slave %d inputs", fParams.fSendAudioChannels);
|
|
}
|
|
} else if (fParams.fSendAudioChannels == -1) {
|
|
fParams.fSendAudioChannels = fRequest.audio_input;
|
|
jack_info("Takes master %d inputs", fRequest.audio_input);
|
|
} else if (fParams.fSendAudioChannels != fRequest.audio_input) {
|
|
jack_error("Error : master wants %d inputs and slave wants %d inputs...", fRequest.audio_input, fParams.fSendAudioChannels);
|
|
return -1;
|
|
}
|
|
|
|
if (fRequest.audio_output == -1) {
|
|
if (fParams.fReturnAudioChannels == -1) {
|
|
jack_error("Error : master and slave use -1 for wanted outputs...");
|
|
return -1;
|
|
} else {
|
|
result->audio_output = fParams.fReturnAudioChannels;
|
|
jack_info("Takes slave %d outputs", fParams.fReturnAudioChannels);
|
|
}
|
|
} else if (fParams.fReturnAudioChannels == -1) {
|
|
fParams.fReturnAudioChannels = fRequest.audio_output;
|
|
jack_info("Takes master %d outputs", fRequest.audio_output);
|
|
} else if (fParams.fReturnAudioChannels != fRequest.audio_output) {
|
|
jack_error("Error : master wants %d outputs and slave wants %d outputs...", fRequest.audio_output, fParams.fReturnAudioChannels);
|
|
return -1;
|
|
}
|
|
|
|
// Close request socket
|
|
fSocket.Close();
|
|
|
|
/// Network init
|
|
if (!JackNetMasterInterface::Init()) {
|
|
return -1;
|
|
}
|
|
|
|
// Set global parameters
|
|
if (!SetParams()) {
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int Close()
|
|
{
|
|
fSocket.Close();
|
|
return 0;
|
|
}
|
|
|
|
void UseRingBuffer(int audio_input, float** audio_input_buffer, int write, int read)
|
|
{
|
|
// Possibly use ringbuffer...
|
|
if (fRingBuffer) {
|
|
for (int i = 0; i < audio_input; i++) {
|
|
fRingBuffer[i]->Write(audio_input_buffer[i], write);
|
|
fRingBuffer[i]->Read(audio_input_buffer[i], read);
|
|
}
|
|
}
|
|
}
|
|
|
|
int Read(int audio_input, float** audio_input_buffer, int midi_input, void** midi_input_buffer, int frames)
|
|
{
|
|
try {
|
|
|
|
// frames = -1 means : entire buffer
|
|
if (frames < 0) frames = fParams.fPeriodSize;
|
|
|
|
int read_frames = 0;
|
|
assert(audio_input == fParams.fReturnAudioChannels);
|
|
|
|
for (int audio_port_index = 0; audio_port_index < audio_input; audio_port_index++) {
|
|
assert(audio_input_buffer[audio_port_index]);
|
|
fNetAudioPlaybackBuffer->SetBuffer(audio_port_index, audio_input_buffer[audio_port_index]);
|
|
}
|
|
|
|
for (int midi_port_index = 0; midi_port_index < midi_input; midi_port_index++) {
|
|
assert(((JackMidiBuffer**)midi_input_buffer)[midi_port_index]);
|
|
fNetMidiPlaybackBuffer->SetBuffer(midi_port_index, ((JackMidiBuffer**)midi_input_buffer)[midi_port_index]);
|
|
}
|
|
|
|
int res1 = SyncRecv();
|
|
switch (res1) {
|
|
|
|
case NET_SYNCHING:
|
|
// Data will not be received, so cleanup buffers...
|
|
for (int audio_port_index = 0; audio_port_index < audio_input; audio_port_index++) {
|
|
memset(audio_input_buffer[audio_port_index], 0, sizeof(float) * fParams.fPeriodSize);
|
|
}
|
|
UseRingBuffer(audio_input, audio_input_buffer, fParams.fPeriodSize, frames);
|
|
return res1;
|
|
|
|
case SOCKET_ERROR:
|
|
return res1;
|
|
|
|
case SYNC_PACKET_ERROR:
|
|
// since sync packet is incorrect, don't decode it and continue with data
|
|
break;
|
|
|
|
default:
|
|
// decode sync
|
|
DecodeSyncPacket(read_frames);
|
|
break;
|
|
}
|
|
|
|
int res2 = DataRecv();
|
|
UseRingBuffer(audio_input, audio_input_buffer, read_frames, frames);
|
|
return res2;
|
|
|
|
} catch (JackNetException& e) {
|
|
jack_error(e.what());
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
int Write(int audio_output, float** audio_output_buffer, int midi_output, void** midi_output_buffer, int frames)
|
|
{
|
|
try {
|
|
|
|
// frames = -1 means : entire buffer
|
|
if (frames < 0) frames = fParams.fPeriodSize;
|
|
|
|
assert(audio_output == fParams.fSendAudioChannels);
|
|
|
|
for (int audio_port_index = 0; audio_port_index < audio_output; audio_port_index++) {
|
|
assert(audio_output_buffer[audio_port_index]);
|
|
fNetAudioCaptureBuffer->SetBuffer(audio_port_index, audio_output_buffer[audio_port_index]);
|
|
}
|
|
|
|
for (int midi_port_index = 0; midi_port_index < midi_output; midi_port_index++) {
|
|
assert(((JackMidiBuffer**)midi_output_buffer)[midi_port_index]);
|
|
fNetMidiCaptureBuffer->SetBuffer(midi_port_index, ((JackMidiBuffer**)midi_output_buffer)[midi_port_index]);
|
|
}
|
|
|
|
EncodeSyncPacket(frames);
|
|
|
|
// send sync
|
|
if (SyncSend() == SOCKET_ERROR) {
|
|
return SOCKET_ERROR;
|
|
}
|
|
|
|
// send data
|
|
if (DataSend() == SOCKET_ERROR) {
|
|
return SOCKET_ERROR;
|
|
}
|
|
return 0;
|
|
|
|
} catch (JackNetException& e) {
|
|
jack_error(e.what());
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
// Transport
|
|
void EncodeTransportData()
|
|
{}
|
|
|
|
void DecodeTransportData()
|
|
{}
|
|
|
|
};
|
|
|
|
struct JackNetExtSlave : public JackNetSlaveInterface, public JackRunnableInterface {
|
|
|
|
// Data buffers
|
|
float** fAudioCaptureBuffer;
|
|
float** fAudioPlaybackBuffer;
|
|
|
|
JackMidiBuffer** fMidiCaptureBuffer;
|
|
JackMidiBuffer** fMidiPlaybackBuffer;
|
|
|
|
JackThread fThread;
|
|
|
|
JackNetSlaveProcessCallback fProcessCallback;
|
|
void* fProcessArg;
|
|
|
|
JackNetSlaveShutdownCallback fShutdownCallback;
|
|
void* fShutdownArg;
|
|
|
|
JackNetSlaveRestartCallback fRestartCallback;
|
|
void* fRestartArg;
|
|
|
|
JackNetSlaveErrorCallback fErrorCallback;
|
|
void* fErrorArg;
|
|
|
|
JackNetSlaveBufferSizeCallback fBufferSizeCallback;
|
|
void* fBufferSizeArg;
|
|
|
|
JackNetSlaveSampleRateCallback fSampleRateCallback;
|
|
void* fSampleRateArg;
|
|
|
|
int fConnectTimeOut;
|
|
int fFrames;
|
|
|
|
JackNetExtSlave(const char* ip,
|
|
int port,
|
|
const char* name,
|
|
jack_slave_t* request)
|
|
:fThread(this),
|
|
fProcessCallback(NULL),fProcessArg(NULL),
|
|
fShutdownCallback(NULL), fShutdownArg(NULL),
|
|
fRestartCallback(NULL), fRestartArg(NULL),
|
|
fErrorCallback(NULL), fErrorArg(NULL),
|
|
fBufferSizeCallback(NULL), fBufferSizeArg(NULL),
|
|
fSampleRateCallback(NULL), fSampleRateArg(NULL)
|
|
{
|
|
char host_name[JACK_CLIENT_NAME_SIZE + 1];
|
|
|
|
// Request parameters
|
|
assert(strlen(ip) < 32);
|
|
strcpy(fMulticastIP, ip);
|
|
fParams.fMtu = request->mtu;
|
|
fParams.fTransportSync = 0;
|
|
fParams.fSendAudioChannels = request->audio_input;
|
|
fParams.fReturnAudioChannels = request->audio_output;
|
|
fParams.fSendMidiChannels = request->midi_input;
|
|
fParams.fReturnMidiChannels = request->midi_output;
|
|
fParams.fNetworkLatency = request->latency;
|
|
fParams.fSampleEncoder = request->encoder;
|
|
fParams.fKBps = request->kbps;
|
|
fParams.fSlaveSyncMode = 1;
|
|
fConnectTimeOut = request->time_out;
|
|
|
|
// Create name with hostname and client name
|
|
GetHostName(host_name, JACK_CLIENT_NAME_SIZE);
|
|
snprintf(fParams.fName, JACK_CLIENT_NAME_SIZE, "%s_%s", host_name, name);
|
|
fSocket.GetName(fParams.fSlaveNetName);
|
|
|
|
// Set the socket parameters
|
|
fSocket.SetPort(port);
|
|
fSocket.SetAddress(fMulticastIP, port);
|
|
|
|
fAudioCaptureBuffer = NULL;
|
|
fAudioPlaybackBuffer = NULL;
|
|
fMidiCaptureBuffer = NULL;
|
|
fMidiPlaybackBuffer = NULL;
|
|
}
|
|
|
|
virtual ~JackNetExtSlave()
|
|
{}
|
|
|
|
void AllocPorts()
|
|
{
|
|
// Set buffers
|
|
if (fParams.fSendAudioChannels > 0) {
|
|
fAudioCaptureBuffer = new float*[fParams.fSendAudioChannels];
|
|
for (int audio_port_index = 0; audio_port_index < fParams.fSendAudioChannels; audio_port_index++) {
|
|
fAudioCaptureBuffer[audio_port_index] = new float[fParams.fPeriodSize];
|
|
memset(fAudioCaptureBuffer[audio_port_index], 0, sizeof(float) * fParams.fPeriodSize);
|
|
fNetAudioCaptureBuffer->SetBuffer(audio_port_index, fAudioCaptureBuffer[audio_port_index]);
|
|
}
|
|
}
|
|
|
|
if (fParams.fSendMidiChannels > 0) {
|
|
fMidiCaptureBuffer = new JackMidiBuffer*[fParams.fSendMidiChannels];
|
|
for (int midi_port_index = 0; midi_port_index < fParams.fSendMidiChannels; midi_port_index++) {
|
|
fMidiCaptureBuffer[midi_port_index] = (JackMidiBuffer*)new float[fParams.fPeriodSize];
|
|
memset(fMidiCaptureBuffer[midi_port_index], 0, sizeof(float) * fParams.fPeriodSize);
|
|
fNetMidiCaptureBuffer->SetBuffer(midi_port_index, fMidiCaptureBuffer[midi_port_index]);
|
|
}
|
|
}
|
|
|
|
if (fParams.fReturnAudioChannels > 0) {
|
|
fAudioPlaybackBuffer = new float*[fParams.fReturnAudioChannels];
|
|
for (int audio_port_index = 0; audio_port_index < fParams.fReturnAudioChannels; audio_port_index++) {
|
|
fAudioPlaybackBuffer[audio_port_index] = new float[fParams.fPeriodSize];
|
|
memset(fAudioPlaybackBuffer[audio_port_index], 0, sizeof(float) * fParams.fPeriodSize);
|
|
fNetAudioPlaybackBuffer->SetBuffer(audio_port_index, fAudioPlaybackBuffer[audio_port_index]);
|
|
}
|
|
}
|
|
|
|
if (fParams.fReturnMidiChannels > 0) {
|
|
fMidiPlaybackBuffer = new JackMidiBuffer*[fParams.fReturnMidiChannels];
|
|
for (int midi_port_index = 0; midi_port_index < fParams.fReturnMidiChannels; midi_port_index++) {
|
|
fMidiPlaybackBuffer[midi_port_index] = (JackMidiBuffer*)new float[fParams.fPeriodSize];
|
|
memset(fMidiPlaybackBuffer[midi_port_index], 0, sizeof(float) * fParams.fPeriodSize);
|
|
fNetMidiPlaybackBuffer->SetBuffer(midi_port_index, fMidiPlaybackBuffer[midi_port_index]);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FreePorts()
|
|
{
|
|
if (fAudioCaptureBuffer) {
|
|
for (int audio_port_index = 0; audio_port_index < fParams.fSendAudioChannels; audio_port_index++) {
|
|
delete[] fAudioCaptureBuffer[audio_port_index];
|
|
}
|
|
delete[] fAudioCaptureBuffer;
|
|
fAudioCaptureBuffer = NULL;
|
|
}
|
|
|
|
if (fMidiCaptureBuffer) {
|
|
for (int midi_port_index = 0; midi_port_index < fParams.fSendMidiChannels; midi_port_index++) {
|
|
delete[] fMidiCaptureBuffer[midi_port_index];
|
|
}
|
|
delete[] fMidiCaptureBuffer;
|
|
fMidiCaptureBuffer = NULL;
|
|
}
|
|
|
|
if (fAudioPlaybackBuffer) {
|
|
for (int audio_port_index = 0; audio_port_index < fParams.fReturnAudioChannels; audio_port_index++) {
|
|
delete[] fAudioPlaybackBuffer[audio_port_index];
|
|
}
|
|
delete[] fAudioPlaybackBuffer;
|
|
fAudioPlaybackBuffer = NULL;
|
|
}
|
|
|
|
if (fMidiPlaybackBuffer) {
|
|
for (int midi_port_index = 0; midi_port_index < fParams.fReturnMidiChannels; midi_port_index++) {
|
|
delete[] (fMidiPlaybackBuffer[midi_port_index]);
|
|
}
|
|
delete[] fMidiPlaybackBuffer;
|
|
fMidiPlaybackBuffer = NULL;
|
|
}
|
|
}
|
|
|
|
int Open(jack_master_t* result)
|
|
{
|
|
// Check audio/midi parameters
|
|
if (fParams.fSendAudioChannels == 0
|
|
&& fParams.fReturnAudioChannels == 0
|
|
&& fParams.fSendMidiChannels == 0
|
|
&& fParams.fReturnMidiChannels == 0) {
|
|
jack_error("Incorrect audio/midi channels number...");
|
|
return -1;
|
|
}
|
|
|
|
// Check MTU parameters
|
|
if ((fParams.fMtu < DEFAULT_MTU) && (fParams.fMtu > MAX_MTU)) {
|
|
jack_error("MTU is not in the expected range [%d ... %d]", DEFAULT_MTU, MAX_MTU);
|
|
return -1;
|
|
}
|
|
|
|
// Check CELT encoder parameters
|
|
if ((fParams.fSampleEncoder == JackCeltEncoder) && (fParams.fKBps == 0)) {
|
|
jack_error("CELT encoder with 0 for kps...");
|
|
return -1;
|
|
}
|
|
|
|
if ((fParams.fSampleEncoder == JackOpusEncoder) && (fParams.fKBps == 0)) {
|
|
jack_error("Opus encoder with 0 for kps...");
|
|
return -1;
|
|
}
|
|
|
|
// Check latency
|
|
if (fParams.fNetworkLatency > NETWORK_MAX_LATENCY) {
|
|
jack_error("Network latency is limited to %d", NETWORK_MAX_LATENCY);
|
|
return -1;
|
|
}
|
|
|
|
// Init network connection
|
|
if (!JackNetSlaveInterface::InitConnection(fConnectTimeOut)) {
|
|
jack_error("Initing network fails...");
|
|
return -1;
|
|
}
|
|
|
|
// Finish connection...
|
|
if (!JackNetSlaveInterface::InitRendering()) {
|
|
jack_error("Starting network fails...");
|
|
return -1;
|
|
}
|
|
|
|
// Then set global parameters
|
|
if (!SetParams()) {
|
|
jack_error("SetParams error...");
|
|
return -1;
|
|
}
|
|
|
|
// Set result
|
|
if (result != NULL) {
|
|
result->buffer_size = fParams.fPeriodSize;
|
|
result->sample_rate = fParams.fSampleRate;
|
|
result->audio_input = fParams.fSendAudioChannels;
|
|
result->audio_output = fParams.fReturnAudioChannels;
|
|
result->midi_input = fParams.fSendMidiChannels;
|
|
result->midi_output = fParams.fReturnMidiChannels;
|
|
strcpy(result->master_name, fParams.fMasterNetName);
|
|
}
|
|
|
|
// By default fFrames is fPeriodSize
|
|
fFrames = fParams.fPeriodSize;
|
|
|
|
SessionParamsDisplay(&fParams);
|
|
|
|
AllocPorts();
|
|
return 0;
|
|
}
|
|
|
|
int Restart()
|
|
{
|
|
// Do it until client possibly decides to stop trying to connect...
|
|
while (true) {
|
|
|
|
// If restart cb is set, then call it
|
|
if (fRestartCallback) {
|
|
if (fRestartCallback(fRestartArg) != 0) {
|
|
return -1;
|
|
}
|
|
// Otherwise if shutdown cb is set, then call it
|
|
} else if (fShutdownCallback) {
|
|
fShutdownCallback(fShutdownArg);
|
|
}
|
|
|
|
// Init network connection
|
|
if (!JackNetSlaveInterface::InitConnection(fConnectTimeOut)) {
|
|
jack_error("Initing network fails after time_out, retry...");
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Finish connection
|
|
if (!JackNetSlaveInterface::InitRendering()) {
|
|
jack_error("Starting network fails...");
|
|
return -1;
|
|
}
|
|
|
|
// Then set global parameters
|
|
if (!SetParams()) {
|
|
jack_error("SetParams error...");
|
|
return -1;
|
|
}
|
|
|
|
// We need to notify possibly new buffer size and sample rate (see Execute)
|
|
if (fBufferSizeCallback) {
|
|
if (fBufferSizeCallback(fParams.fPeriodSize, fBufferSizeArg) != 0) {
|
|
jack_error("New buffer size = %d cannot be used...", fParams.fPeriodSize);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (fSampleRateCallback) {
|
|
if (fSampleRateCallback(fParams.fSampleRate, fSampleRateArg) != 0) {
|
|
jack_error("New sample rate = %d cannot be used...", fParams.fSampleRate);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
AllocPorts();
|
|
return 0;
|
|
}
|
|
|
|
int Close()
|
|
{
|
|
fSocket.Close();
|
|
FreePorts();
|
|
return 0;
|
|
}
|
|
|
|
// Transport
|
|
void EncodeTransportData()
|
|
{}
|
|
|
|
void DecodeTransportData()
|
|
{}
|
|
|
|
bool Init()
|
|
{
|
|
// Will do "something" on OSX only...
|
|
UInt64 period, constraint;
|
|
period = constraint = UInt64(1000000000.f * (float(fParams.fPeriodSize) / float(fParams.fSampleRate)));
|
|
UInt64 computation = JackTools::ComputationMicroSec(fParams.fPeriodSize) * 1000;
|
|
fThread.SetParams(period, computation, constraint);
|
|
|
|
return (fThread.AcquireSelfRealTime(80) == 0); // TODO: get a value from the server
|
|
}
|
|
|
|
bool IsRunning()
|
|
{
|
|
return (fThread.GetStatus() == JackThread::kRunning);
|
|
}
|
|
|
|
bool Execute()
|
|
{
|
|
try {
|
|
/*
|
|
Fist cycle use an INT_MAX time out, so that connection
|
|
is considered established (with PACKET_TIMEOUT later on)
|
|
when the first cycle has been done.
|
|
*/
|
|
DummyProcess();
|
|
// keep running even in case of error
|
|
while (fThread.GetStatus() == JackThread::kRunning) {
|
|
if (Process() == SOCKET_ERROR) {
|
|
return false;
|
|
}
|
|
}
|
|
return false;
|
|
} catch (JackNetException& e) {
|
|
// otherwise just restart...
|
|
e.PrintMessage();
|
|
jack_info("NetSlave is restarted");
|
|
fThread.DropRealTime();
|
|
fThread.SetStatus(JackThread::kIniting);
|
|
FreePorts();
|
|
if (Restart() == 0 && Init()) {
|
|
fThread.SetStatus(JackThread::kRunning);
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
int Read()
|
|
{
|
|
// receive sync (launch the cycle)
|
|
switch (SyncRecv()) {
|
|
|
|
case SOCKET_ERROR:
|
|
return SOCKET_ERROR;
|
|
|
|
case SYNC_PACKET_ERROR:
|
|
// since sync packet is incorrect, don't decode it and continue with data
|
|
if (fErrorCallback) {
|
|
fErrorCallback(SYNC_PACKET_ERROR, fErrorArg);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
// decode sync
|
|
DecodeSyncPacket(fFrames);
|
|
break;
|
|
}
|
|
|
|
int res = DataRecv();
|
|
if (res == DATA_PACKET_ERROR && fErrorCallback) {
|
|
fErrorCallback(DATA_PACKET_ERROR, fErrorArg);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
int Write()
|
|
{
|
|
EncodeSyncPacket(fFrames);
|
|
|
|
if (SyncSend() == SOCKET_ERROR) {
|
|
return SOCKET_ERROR;
|
|
}
|
|
|
|
return DataSend();
|
|
}
|
|
|
|
void DummyProcess()
|
|
{
|
|
// First cycle with INT_MAX time out
|
|
SetPacketTimeOut(INT_MAX);
|
|
|
|
// One cycle
|
|
Process();
|
|
|
|
// Then use PACKET_TIMEOUT * fParams.fNetworkLatency for next cycles
|
|
SetPacketTimeOut(std::max(int(PACKET_TIMEOUT), int(PACKET_TIMEOUT * fParams.fNetworkLatency)));
|
|
}
|
|
|
|
int Process()
|
|
{
|
|
// Read data from the network, throw JackNetException in case of network error...
|
|
if (Read() == SOCKET_ERROR) {
|
|
return SOCKET_ERROR;
|
|
}
|
|
|
|
if (fFrames < 0) fFrames = fParams.fPeriodSize;
|
|
|
|
fProcessCallback(fFrames,
|
|
fParams.fSendAudioChannels,
|
|
fAudioCaptureBuffer,
|
|
fParams.fSendMidiChannels,
|
|
(void**)fMidiCaptureBuffer,
|
|
fParams.fReturnAudioChannels,
|
|
fAudioPlaybackBuffer,
|
|
fParams.fReturnMidiChannels,
|
|
(void**)fMidiPlaybackBuffer,
|
|
fProcessArg);
|
|
|
|
// Then write data to network, throw JackNetException in case of network error...
|
|
if (Write() == SOCKET_ERROR) {
|
|
return SOCKET_ERROR;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int Start()
|
|
{
|
|
return (fProcessCallback == 0) ? -1 : fThread.StartSync();
|
|
}
|
|
|
|
int Stop()
|
|
{
|
|
return (fProcessCallback == 0) ? -1 : fThread.Kill();
|
|
}
|
|
|
|
// Callback
|
|
int SetProcessCallback(JackNetSlaveProcessCallback net_callback, void *arg)
|
|
{
|
|
if (fThread.GetStatus() == JackThread::kRunning) {
|
|
return -1;
|
|
} else {
|
|
fProcessCallback = net_callback;
|
|
fProcessArg = arg;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
int SetShutdownCallback(JackNetSlaveShutdownCallback shutdown_callback, void *arg)
|
|
{
|
|
if (fThread.GetStatus() == JackThread::kRunning) {
|
|
return -1;
|
|
} else {
|
|
fShutdownCallback = shutdown_callback;
|
|
fShutdownArg = arg;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
int SetRestartCallback(JackNetSlaveRestartCallback restart_callback, void *arg)
|
|
{
|
|
if (fThread.GetStatus() == JackThread::kRunning) {
|
|
return -1;
|
|
} else {
|
|
fRestartCallback = restart_callback;
|
|
fRestartArg = arg;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
int SetErrorCallback(JackNetSlaveErrorCallback error_callback, void *arg)
|
|
{
|
|
if (fThread.GetStatus() == JackThread::kRunning) {
|
|
return -1;
|
|
} else {
|
|
fErrorCallback = error_callback;
|
|
fErrorArg = arg;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
int SetBufferSizeCallback(JackNetSlaveBufferSizeCallback bufsize_callback, void *arg)
|
|
{
|
|
if (fThread.GetStatus() == JackThread::kRunning) {
|
|
return -1;
|
|
} else {
|
|
fBufferSizeCallback = bufsize_callback;
|
|
fBufferSizeArg = arg;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
int SetSampleRateCallback(JackNetSlaveSampleRateCallback samplerate_callback, void *arg)
|
|
{
|
|
if (fThread.GetStatus() == JackThread::kRunning) {
|
|
return -1;
|
|
} else {
|
|
fSampleRateCallback = samplerate_callback;
|
|
fSampleRateArg = arg;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
};
|
|
|
|
struct JackNetAdapter : public JackAudioAdapterInterface {
|
|
|
|
JackNetAdapter(int input, int output,
|
|
jack_nframes_t host_buffer_size,
|
|
jack_nframes_t host_sample_rate,
|
|
jack_nframes_t adapted_buffer_size,
|
|
jack_nframes_t adapted_sample_rate)
|
|
:JackAudioAdapterInterface(host_buffer_size, host_sample_rate, adapted_buffer_size, adapted_sample_rate)
|
|
{
|
|
fCaptureChannels = input;
|
|
fPlaybackChannels = output;
|
|
Create();
|
|
}
|
|
|
|
void Create()
|
|
{
|
|
//ringbuffers
|
|
|
|
if (fCaptureChannels > 0) {
|
|
fCaptureRingBuffer = new JackResampler*[fCaptureChannels];
|
|
}
|
|
if (fPlaybackChannels > 0) {
|
|
fPlaybackRingBuffer = new JackResampler*[fPlaybackChannels];
|
|
}
|
|
|
|
if (fAdaptative) {
|
|
AdaptRingBufferSize();
|
|
jack_info("Ringbuffer automatic adaptative mode size = %d frames", fRingbufferCurSize);
|
|
} else {
|
|
if (fRingbufferCurSize > DEFAULT_RB_SIZE) {
|
|
fRingbufferCurSize = DEFAULT_RB_SIZE;
|
|
}
|
|
jack_info("Fixed ringbuffer size = %d frames", fRingbufferCurSize);
|
|
}
|
|
|
|
for (int i = 0; i < fCaptureChannels; i++ ) {
|
|
fCaptureRingBuffer[i] = new JackResampler();
|
|
fCaptureRingBuffer[i]->Reset(fRingbufferCurSize);
|
|
}
|
|
for (int i = 0; i < fPlaybackChannels; i++ ) {
|
|
fPlaybackRingBuffer[i] = new JackResampler();
|
|
fPlaybackRingBuffer[i]->Reset(fRingbufferCurSize);
|
|
}
|
|
|
|
if (fCaptureChannels > 0) {
|
|
jack_log("ReadSpace = %ld", fCaptureRingBuffer[0]->ReadSpace());
|
|
}
|
|
if (fPlaybackChannels > 0) {
|
|
jack_log("WriteSpace = %ld", fPlaybackRingBuffer[0]->WriteSpace());
|
|
}
|
|
}
|
|
|
|
virtual ~JackNetAdapter()
|
|
{
|
|
Destroy();
|
|
}
|
|
|
|
void Flush()
|
|
{
|
|
for (int i = 0; i < fCaptureChannels; i++ ) {
|
|
fCaptureRingBuffer[i]->Reset(fRingbufferCurSize);
|
|
}
|
|
for (int i = 0; i < fPlaybackChannels; i++ ) {
|
|
fPlaybackRingBuffer[i]->Reset(fRingbufferCurSize);
|
|
}
|
|
}
|
|
|
|
};
|
|
|
|
|
|
} // end of namespace
|
|
|
|
using namespace Jack;
|
|
|
|
LIB_EXPORT jack_net_slave_t* jack_net_slave_open(const char* ip, int port, const char* name, jack_slave_t* request, jack_master_t* result)
|
|
{
|
|
JackNetExtSlave* slave = new JackNetExtSlave(ip, port, name, request);
|
|
if (slave->Open(result) == 0) {
|
|
return (jack_net_slave_t*)slave;
|
|
} else {
|
|
delete slave;
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
LIB_EXPORT int jack_net_slave_close(jack_net_slave_t* net)
|
|
{
|
|
JackNetExtSlave* slave = (JackNetExtSlave*)net;
|
|
slave->Close();
|
|
delete slave;
|
|
return 0;
|
|
}
|
|
|
|
LIB_EXPORT int jack_set_net_slave_process_callback(jack_net_slave_t* net, JackNetSlaveProcessCallback net_callback, void *arg)
|
|
{
|
|
JackNetExtSlave* slave = (JackNetExtSlave*)net;
|
|
return slave->SetProcessCallback(net_callback, arg);
|
|
}
|
|
|
|
LIB_EXPORT int jack_net_slave_activate(jack_net_slave_t* net)
|
|
{
|
|
JackNetExtSlave* slave = (JackNetExtSlave*)net;
|
|
return slave->Start();
|
|
}
|
|
|
|
LIB_EXPORT int jack_net_slave_deactivate(jack_net_slave_t* net)
|
|
{
|
|
JackNetExtSlave* slave = (JackNetExtSlave*)net;
|
|
return slave->Stop();
|
|
}
|
|
|
|
LIB_EXPORT int jack_net_slave_is_active(jack_net_slave_t* net)
|
|
{
|
|
JackNetExtSlave* slave = (JackNetExtSlave*)net;
|
|
return slave->IsRunning();
|
|
}
|
|
|
|
LIB_EXPORT int jack_set_net_slave_buffer_size_callback(jack_net_slave_t *net, JackNetSlaveBufferSizeCallback bufsize_callback, void *arg)
|
|
{
|
|
JackNetExtSlave* slave = (JackNetExtSlave*)net;
|
|
return slave->SetBufferSizeCallback(bufsize_callback, arg);
|
|
}
|
|
|
|
LIB_EXPORT int jack_set_net_slave_sample_rate_callback(jack_net_slave_t *net, JackNetSlaveSampleRateCallback samplerate_callback, void *arg)
|
|
{
|
|
JackNetExtSlave* slave = (JackNetExtSlave*)net;
|
|
return slave->SetSampleRateCallback(samplerate_callback, arg);
|
|
}
|
|
|
|
LIB_EXPORT int jack_set_net_slave_shutdown_callback(jack_net_slave_t *net, JackNetSlaveShutdownCallback shutdown_callback, void *arg)
|
|
{
|
|
JackNetExtSlave* slave = (JackNetExtSlave*)net;
|
|
return slave->SetShutdownCallback(shutdown_callback, arg);
|
|
}
|
|
|
|
LIB_EXPORT int jack_set_net_slave_restart_callback(jack_net_slave_t *net, JackNetSlaveRestartCallback restart_callback, void *arg)
|
|
{
|
|
JackNetExtSlave* slave = (JackNetExtSlave*)net;
|
|
return slave->SetRestartCallback(restart_callback, arg);
|
|
}
|
|
|
|
LIB_EXPORT int jack_set_net_slave_error_callback(jack_net_slave_t *net, JackNetSlaveErrorCallback error_callback, void *arg)
|
|
{
|
|
JackNetExtSlave* slave = (JackNetExtSlave*)net;
|
|
return slave->SetErrorCallback(error_callback, arg);
|
|
}
|
|
|
|
// Master API
|
|
|
|
LIB_EXPORT jack_net_master_t* jack_net_master_open(const char* ip, int port, jack_master_t* request, jack_slave_t* result)
|
|
{
|
|
JackNetExtMaster* master = new JackNetExtMaster(ip, port, request);
|
|
if (master->Open(result) == 0) {
|
|
return (jack_net_master_t*)master;
|
|
} else {
|
|
delete master;
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
LIB_EXPORT int jack_net_master_close(jack_net_master_t* net)
|
|
{
|
|
JackNetExtMaster* master = (JackNetExtMaster*)net;
|
|
master->Close();
|
|
delete master;
|
|
return 0;
|
|
}
|
|
|
|
LIB_EXPORT int jack_net_master_recv(jack_net_master_t* net, int audio_input, float** audio_input_buffer, int midi_input, void** midi_input_buffer)
|
|
{
|
|
JackNetExtMaster* master = (JackNetExtMaster*)net;
|
|
return master->Read(audio_input, audio_input_buffer, midi_input, midi_input_buffer, -1);
|
|
}
|
|
|
|
LIB_EXPORT int jack_net_master_send(jack_net_master_t* net, int audio_output, float** audio_output_buffer, int midi_output, void** midi_output_buffer)
|
|
{
|
|
JackNetExtMaster* master = (JackNetExtMaster*)net;
|
|
return master->Write(audio_output, audio_output_buffer, midi_output, midi_output_buffer, -1);
|
|
}
|
|
|
|
LIB_EXPORT int jack_net_master_recv_slice(jack_net_master_t* net, int audio_input, float** audio_input_buffer, int midi_input, void** midi_input_buffer, int frames)
|
|
{
|
|
JackNetExtMaster* master = (JackNetExtMaster*)net;
|
|
return master->Read(audio_input, audio_input_buffer, midi_input, midi_input_buffer, frames);
|
|
}
|
|
|
|
LIB_EXPORT int jack_net_master_send_slice(jack_net_master_t* net, int audio_output, float** audio_output_buffer, int midi_output, void** midi_output_buffer, int frames)
|
|
{
|
|
JackNetExtMaster* master = (JackNetExtMaster*)net;
|
|
return master->Write(audio_output, audio_output_buffer, midi_output, midi_output_buffer, frames);
|
|
}
|
|
|
|
// Adapter API
|
|
|
|
LIB_EXPORT jack_adapter_t* jack_create_adapter(int input, int output,
|
|
jack_nframes_t host_buffer_size,
|
|
jack_nframes_t host_sample_rate,
|
|
jack_nframes_t adapted_buffer_size,
|
|
jack_nframes_t adapted_sample_rate)
|
|
{
|
|
try {
|
|
return (jack_adapter_t*)new JackNetAdapter(input, output, host_buffer_size, host_sample_rate, adapted_buffer_size, adapted_sample_rate);
|
|
} catch (...) {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
LIB_EXPORT int jack_destroy_adapter(jack_adapter_t* adapter)
|
|
{
|
|
delete((JackNetAdapter*)adapter);
|
|
return 0;
|
|
}
|
|
|
|
LIB_EXPORT void jack_flush_adapter(jack_adapter_t* adapter)
|
|
{
|
|
JackNetAdapter* slave = (JackNetAdapter*)adapter;
|
|
slave->Flush();
|
|
}
|
|
|
|
LIB_EXPORT int jack_adapter_push_and_pull(jack_adapter_t* adapter, float** input, float** output, unsigned int frames)
|
|
{
|
|
JackNetAdapter* slave = (JackNetAdapter*)adapter;
|
|
return slave->PushAndPull(input, output, frames);
|
|
}
|
|
|
|
LIB_EXPORT int jack_adapter_pull_and_push(jack_adapter_t* adapter, float** input, float** output, unsigned int frames)
|
|
{
|
|
JackNetAdapter* slave = (JackNetAdapter*)adapter;
|
|
return slave->PullAndPush(input, output, frames);
|
|
}
|
|
|
|
static void jack_format_and_log(int level, const char *prefix, const char *fmt, va_list ap)
|
|
{
|
|
static const char* netjack_log = getenv("JACK_NETJACK_LOG");
|
|
static bool is_netjack_log = (netjack_log) ? atoi(netjack_log) : 0;
|
|
|
|
if (is_netjack_log) {
|
|
char buffer[300];
|
|
size_t len;
|
|
|
|
if (prefix != NULL) {
|
|
len = strlen(prefix);
|
|
memcpy(buffer, prefix, len);
|
|
} else {
|
|
len = 0;
|
|
}
|
|
|
|
vsnprintf(buffer + len, sizeof(buffer) - len, fmt, ap);
|
|
printf("%s", buffer);
|
|
printf("\n");
|
|
}
|
|
}
|
|
|
|
LIB_EXPORT void jack_error(const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
jack_format_and_log(LOG_LEVEL_INFO, "Jack: ", fmt, ap);
|
|
va_end(ap);
|
|
}
|
|
|
|
LIB_EXPORT void jack_info(const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
jack_format_and_log(LOG_LEVEL_INFO, "Jack: ", fmt, ap);
|
|
va_end(ap);
|
|
}
|
|
|
|
LIB_EXPORT void jack_log(const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
jack_format_and_log(LOG_LEVEL_INFO, "Jack: ", fmt, ap);
|
|
va_end(ap);
|
|
}
|