summaryrefslogtreecommitdiff
path: root/common/JackClient.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'common/JackClient.cpp')
-rw-r--r--common/JackClient.cpp767
1 files changed, 767 insertions, 0 deletions
diff --git a/common/JackClient.cpp b/common/JackClient.cpp
new file mode 100644
index 00000000..ec1e0468
--- /dev/null
+++ b/common/JackClient.cpp
@@ -0,0 +1,767 @@
+/*
+Copyright (C) 2001 Paul Davis
+Copyright (C) 2004-2006 Grame
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+
+#include "JackClient.h"
+#include "JackGraphManager.h"
+#include "JackClientControl.h"
+#include "JackEngineControl.h"
+#include "JackGlobals.h"
+#include "JackChannel.h"
+#include "JackTransportEngine.h"
+#include <math.h>
+#include <string>
+
+int verbose = 0;
+
+using namespace std;
+
+namespace Jack
+{
+
+JackClient::JackClient()
+{}
+
+JackClient::JackClient(JackSynchro** table)
+{
+ fThread = JackGlobals::MakeThread(this);
+ fSynchroTable = table;
+ fProcess = NULL;
+ fGraphOrder = NULL;
+ fXrun = NULL;
+ fShutdown = NULL;
+ fInit = NULL;
+ fBufferSize = NULL;
+ fFreewheel = NULL;
+ fPortRegistration = NULL;
+ fSync = NULL;
+ fProcessArg = NULL;
+ fGraphOrderArg = NULL;
+ fXrunArg = NULL;
+ fShutdownArg = NULL;
+ fInitArg = NULL;
+ fBufferSizeArg = NULL;
+ fFreewheelArg = NULL;
+ fPortRegistrationArg = NULL;
+ fSyncArg = NULL;
+ fConditionnal = 0; // Temporary??
+}
+
+JackClient::~JackClient()
+{
+ delete fThread;
+}
+
+int JackClient::Close()
+{
+ JackLog("JackClient::Close ref = %ld\n", GetClientControl()->fRefNum);
+ Deactivate();
+ int result = -1;
+ fChannel->ClientClose(GetClientControl()->fRefNum, &result);
+ fChannel->Stop();
+ fChannel->Close();
+ fSynchroTable[GetClientControl()->fRefNum]->Disconnect();
+ return result;
+}
+
+bool JackClient::IsActive()
+{
+ return (GetClientControl()) ? GetClientControl()->fActive : false;
+}
+
+pthread_t JackClient::GetThreadID()
+{
+ return fThread->GetThreadID();
+}
+
+/*!
+\brief
+ In ASYNC mode, the server does not synchronize itself on the output drivers, thus it would never "consume" the activations.
+ The synchronization primitives for drivers are setup in "flush" mode that to not keep unneeded activations.
+ Drivers synchro are setup in "flush" mode if server is ASYNC and NOT freewheel.
+*/
+void JackClient::SetupDriverSync(bool freewheel)
+{
+ if (!freewheel && !GetEngineControl()->fSyncMode) {
+ JackLog("JackClient::SetupDriverSync driver sem in flush mode\n");
+ fSynchroTable[AUDIO_DRIVER_REFNUM]->SetFlush(true);
+ fSynchroTable[FREEWHEEL_DRIVER_REFNUM]->SetFlush(true);
+ fSynchroTable[LOOPBACK_DRIVER_REFNUM]->SetFlush(true);
+ } else {
+ JackLog("JackClient::SetupDriverSync driver sem in normal mode\n");
+ fSynchroTable[AUDIO_DRIVER_REFNUM]->SetFlush(false);
+ fSynchroTable[FREEWHEEL_DRIVER_REFNUM]->SetFlush(false);
+ fSynchroTable[LOOPBACK_DRIVER_REFNUM]->SetFlush(false);
+ }
+}
+
+/*!
+\brief Notification received from the server.
+*/
+
+int JackClient::ClientNotifyImp(int refnum, const char* name, int notify, int sync, int value)
+{
+ return 0;
+}
+
+int JackClient::ClientNotify(int refnum, const char* name, int notify, int sync, int value)
+{
+ int res = 0;
+
+ // Done all time: redirected on subclass implementation JackLibClient and JackInternalClient
+ switch (notify) {
+
+ case JackNotifyChannelInterface::kAddClient:
+ case JackNotifyChannelInterface::kRemoveClient:
+ res = ClientNotifyImp(refnum, name, notify, sync, value);
+ break;
+ }
+
+ /*
+ The current semantic is that notifications can only be received when the client has been activated,
+ although is this implementation, one could imagine calling notifications as soon as the client has be opened.
+ */
+ if (IsActive()) {
+
+ switch (notify) {
+
+ case JackNotifyChannelInterface::kBufferSizeCallback:
+ JackLog("JackClient::kBufferSizeCallback buffer_size = %ld\n", value);
+ if (fBufferSize)
+ res = fBufferSize(value, fBufferSizeArg);
+ break;
+
+ case JackNotifyChannelInterface::kGraphOrderCallback:
+ JackLog("JackClient::kGraphOrderCallback\n");
+ if (fGraphOrder)
+ res = fGraphOrder(fGraphOrderArg);
+ break;
+
+ case JackNotifyChannelInterface::kStartFreewheel:
+ JackLog("JackClient::kStartFreewheel\n");
+ SetupDriverSync(true);
+ fThread->DropRealTime();
+ if (fFreewheel)
+ fFreewheel(1, fFreewheelArg);
+ break;
+
+ case JackNotifyChannelInterface::kStopFreewheel:
+ JackLog("JackClient::kStopFreewheel\n");
+ SetupDriverSync(false);
+ if (fFreewheel)
+ fFreewheel(0, fFreewheelArg);
+ fThread->AcquireRealTime();
+ break;
+
+ case JackNotifyChannelInterface::kPortRegistrationOn:
+ JackLog("JackClient::kPortRegistrationOn port_index = %ld\n", value);
+ if (fPortRegistration)
+ fPortRegistration(value, 1, fPortRegistrationArg);
+ break;
+
+ case JackNotifyChannelInterface::kPortRegistrationOff:
+ JackLog("JackClient::kPortRegistrationOff port_index = %ld \n", value);
+ if (fPortRegistration)
+ fPortRegistration(value, 0, fPortRegistrationArg);
+ break;
+
+ case JackNotifyChannelInterface::kXRunCallback:
+ JackLog("JackClient::kXRunCallback\n");
+ if (fXrun)
+ res = fXrun(fXrunArg);
+ break;
+
+ case JackNotifyChannelInterface::kZombifyClient:
+ res = fThread->Kill();
+ JackLog("JackClient::kZombifyClient name = %s ref = %ld \n", name, refnum);
+ ShutDown();
+ break;
+ }
+ }
+
+ return res;
+}
+
+/*!
+\brief We need to start thread before activating in the server, otherwise the FW driver
+ connected to the client may not be activated.
+*/
+int JackClient::Activate()
+{
+ JackLog("JackClient::Activate \n");
+ if (IsActive())
+ return 0;
+
+ if (StartThread() < 0)
+ return -1;
+
+ int result = -1;
+ fChannel->ClientActivate(GetClientControl()->fRefNum, &result);
+ if (result < 0)
+ return result;
+
+ if (fSync != NULL) /* If a SyncCallback is pending... */
+ SetSyncCallback(fSync, fSyncArg);
+
+ if (fTimebase != NULL) /* If a TimebaseCallback is pending... */
+ SetTimebaseCallback(fConditionnal, fTimebase, fTimebaseArg);
+
+ GetClientControl()->fActive = true;
+ return 0;
+}
+
+/*!
+\brief Need to stop thread after deactivating in the server.
+*/
+int JackClient::Deactivate()
+{
+ JackLog("JackClient::Deactivate \n");
+ if (!IsActive())
+ return 0;
+
+ GetClientControl()->fActive = false;
+ int result = -1;
+ fChannel->ClientDeactivate(GetClientControl()->fRefNum, &result);
+
+ JackLog("JackClient::Deactivate res = %ld \n", result);
+ // We need to wait for the new engine cycle before stopping the RT thread, but this is done by ClientDeactivate
+ fThread->Kill();
+ return result;
+}
+
+//----------------------
+// RT thread management
+//----------------------
+
+bool JackClient::CallProcessCallback()
+{
+ return (fProcess == NULL) ? true : (fProcess(GetEngineControl()->fBufferSize, fProcessArg) == 0);
+}
+
+/*!
+\brief Called once when the thread starts.
+*/
+bool JackClient::Init()
+{
+ if (fInit) {
+ JackLog("JackClient::Init calling client thread init callback\n");
+ fInit(fInitArg);
+ }
+ return true;
+}
+
+int JackClient::StartThread()
+{
+ JackLog("JackClient::StartThread : period = %ld computation = %ld constraint = %ld\n",
+ long(int64_t(GetEngineControl()->fPeriod) / 1000.0f),
+ long(int64_t(GetEngineControl()->fComputation) / 1000.0f),
+ long(int64_t(GetEngineControl()->fConstraint) / 1000.0f));
+
+ // Will do "something" on OSX only...
+ fThread->SetParams(GetEngineControl()->fPeriod, GetEngineControl()->fComputation, GetEngineControl()->fConstraint);
+
+ if (fThread->Start() < 0) {
+ jack_error("Start thread error");
+ return -1;
+ }
+
+ if (GetEngineControl()->fRealTime) {
+ if (fThread->AcquireRealTime(GetEngineControl()->fPriority - 1) < 0) {
+ jack_error("AcquireRealTime error");
+ }
+ }
+
+ return 0;
+}
+
+/*!
+\brief RT thread.
+*/
+bool JackClient::Execute()
+{
+ // Suspend itself: wait on the input synchro
+ if (GetGraphManager()->SuspendRefNum(GetClientControl(), fSynchroTable, 0x7FFFFFFF) < 0) {
+ jack_error("SuspendRefNum error");
+ goto error;
+ }
+
+ // Process call
+ if (IsActive()) {
+ CallSyncCallback();
+ bool res = CallProcessCallback();
+ CallTimebaseCallback();
+ if (!res)
+ goto end;
+ } else {
+ JackLog("Process called for an inactive client\n");
+ // Happens if client is still not activated (connected to the FW)
+ // or still runs while being desactivated by the server
+ }
+
+ // Resume: signal output clients connected to the running client
+ if (GetGraphManager()->ResumeRefNum(GetClientControl(), fSynchroTable) < 0) {
+ jack_error("ResumeRefNum error");
+ }
+
+ return true;
+
+end:
+ JackLog("JackClient::Execute end name = %s\n", GetClientControl()->fName);
+
+ // Continue graph execution for this cycle
+ if (GetGraphManager()->ResumeRefNum(GetClientControl(), fSynchroTable) < 0) {
+ jack_error("ResumeRefNum error");
+ }
+
+ // Hum... not sure about this, the following "close" code is called in the RT thread...
+ int result;
+ fThread->DropRealTime();
+ fChannel->ClientDeactivate(GetClientControl()->fRefNum, &result);
+ Close(); // Not sure...
+ return false;
+
+error:
+ jack_error("JackClient::Execute error name = %s", GetClientControl()->fName);
+ // Hum... not sure about this, the following "close" code is called in the RT thread...
+ fThread->DropRealTime();
+ ShutDown();
+ return false;
+}
+
+//-----------------
+// Port management
+//-----------------
+
+int JackClient::PortRegister(const char* port_name, const char* port_type, unsigned long flags, unsigned long buffer_size)
+{
+ // Check port name length
+ string port_name_str = string(port_name);
+ if (port_name_str.size() == 0) {
+ jack_error("port_name is empty.");
+ return 0; // Means failure here...
+ }
+
+ string name = string(GetClientControl()->fName) + string(":") + port_name_str;
+ if (name.size() >= JACK_PORT_NAME_SIZE) {
+ jack_error("\"%s:%s\" is too long to be used as a JACK port name.\n"
+ "Please use %lu characters or less.",
+ GetClientControl()->fName,
+ port_name,
+ JACK_PORT_NAME_SIZE - 1);
+ return 0; // Means failure here...
+ }
+
+ // Check if port name already exists
+ if (GetGraphManager()->GetPort(name.c_str()) != NO_PORT) {
+ jack_error("port_name \"%s\" already exists.", port_name);
+ return 0; // Means failure here...
+ }
+
+ JackLog("JackClient::PortRegister ref = %ld name = %s \n", GetClientControl()->fRefNum, name.c_str());
+
+ int result = -1;
+ jack_port_id_t port_index = NO_PORT;
+ fChannel->PortRegister(GetClientControl()->fRefNum, name.c_str(), flags, buffer_size, &port_index, &result);
+ JackLog("JackClient::PortRegister port_index = %ld \n", port_index);
+
+ if (result == 0) {
+ fPortList.push_back(port_index);
+ return port_index;
+ } else {
+ return 0;
+ }
+}
+
+int JackClient::PortUnRegister(jack_port_id_t port_index)
+{
+ JackLog("JackClient::PortUnRegister port_index = %ld\n", port_index);
+ list<jack_port_id_t>::iterator it;
+ for (it = fPortList.begin(); it != fPortList.end() && *it != port_index; it++)
+ ;
+
+ if (it != fPortList.end()) {
+ fPortList.erase(it);
+ int result = -1;
+ fChannel->PortUnRegister(GetClientControl()->fRefNum, port_index, &result);
+ return result;
+ } else {
+ jack_error("unregistering a port %ld that is not own by the client", port_index);
+ return -1;
+ }
+}
+
+int JackClient::PortConnect(const char* src, const char* dst)
+{
+ JackLog("JackClient::Connect src = %s dst = %s\n", src, dst);
+ int result = -1;
+ fChannel->PortConnect(GetClientControl()->fRefNum, src, dst, &result);
+ return result;
+}
+
+int JackClient::PortDisconnect(const char* src, const char* dst)
+{
+ JackLog("JackClient::Disconnect src = %s dst = %s\n", src, dst);
+ int result = -1;
+ fChannel->PortDisconnect(GetClientControl()->fRefNum, src, dst, &result);
+ return result;
+}
+
+int JackClient::PortConnect(jack_port_id_t src, jack_port_id_t dst)
+{
+ JackLog("JackClient::PortConnect src = %ld dst = %ld\n", src, dst);
+ int result = -1;
+ fChannel->PortConnect(GetClientControl()->fRefNum, src, dst, &result);
+ return result;
+}
+
+int JackClient::PortDisconnect(jack_port_id_t src)
+{
+ JackLog("JackClient::PortDisconnect src = %ld\n", src);
+ int result = -1;
+ fChannel->PortDisconnect(GetClientControl()->fRefNum, src, ALL_PORTS, &result);
+ return result;
+}
+
+int JackClient::PortIsMine(jack_port_id_t port_index)
+{
+ JackPort* port = GetGraphManager()->GetPort(port_index);
+ return GetClientControl()->fRefNum == port->GetRefNum();
+}
+
+//--------------------
+// Context management
+//--------------------
+
+int JackClient::SetBufferSize(jack_nframes_t nframes)
+{
+ int result = -1;
+ fChannel->SetBufferSize(nframes, &result);
+ return result;
+}
+
+int JackClient::SetFreeWheel(int onoff)
+{
+ int result = -1;
+ fChannel->SetFreewheel(onoff, &result);
+ return result;
+}
+
+/*
+ShutDown is called:
+- from the RT thread when Execute method fails
+- possibly from a "closed" notification channel
+(Not needed since the synch object used (Sema of Fifo will fails when server quits... see ShutDown))
+*/
+
+void JackClient::ShutDown()
+{
+ JackLog("ShutDown\n");
+ if (fShutdown) {
+ GetClientControl()->fActive = false;
+ fShutdown(fShutdownArg);
+ fShutdown = NULL;
+ }
+}
+
+//----------------------
+// Transport management
+//----------------------
+
+int JackClient::ReleaseTimebase()
+{
+ int result = -1;
+ fChannel->ReleaseTimebase(GetClientControl()->fRefNum, &result);
+ if (result == 0) {
+ fTimebase = NULL;
+ fTimebaseArg = NULL;
+ }
+ return result;
+}
+
+/* Call the server if the client is active, otherwise keeps the arguments */
+int JackClient::SetSyncCallback(JackSyncCallback sync_callback, void* arg)
+{
+ if (IsActive())
+ GetClientControl()->fTransportState = (sync_callback == NULL) ? JackTransportStopped : JackTransportSynching;
+ fSync = sync_callback;
+ fSyncArg = arg;
+ return 0;
+}
+
+int JackClient::SetSyncTimeout(jack_time_t timeout)
+{
+ GetEngineControl()->fTransport.SetSyncTimeout(timeout);
+ return 0;
+}
+
+/* Call the server if the client is active, otherwise keeps the arguments */
+int JackClient::SetTimebaseCallback(int conditional, JackTimebaseCallback timebase_callback, void* arg)
+{
+ if (IsActive()) {
+ int result = -1;
+ fChannel->SetTimebaseCallback(GetClientControl()->fRefNum, conditional, &result);
+ JackLog("SetTimebaseCallback result = %ld\n", result);
+ if (result == 0) {
+ fTimebase = timebase_callback;
+ fTimebaseArg = arg;
+ } else {
+ fTimebase = NULL;
+ fTimebaseArg = NULL;
+ }
+ JackLog("SetTimebaseCallback OK result = %ld\n", result);
+ return result;
+ } else {
+ fTimebase = timebase_callback;
+ fTimebaseArg = arg;
+ fConditionnal = conditional;
+ return 0;
+ }
+}
+
+// Must be RT safe
+int JackClient::RequestNewPos(jack_position_t* pos)
+{
+ JackTransportEngine& transport = GetEngineControl()->fTransport;
+ jack_position_t* request = transport.WriteNextStateStart(2);
+ pos->unique_1 = pos->unique_2 = transport.GenerateUniqueID();
+ JackTransportEngine::TransportCopyPosition(pos, request);
+ JackLog("RequestNewPos pos = %ld\n", pos->frame);
+ transport.WriteNextStateStop(2);
+ return 0;
+}
+
+int JackClient::TransportLocate(jack_nframes_t frame)
+{
+ jack_position_t pos;
+ pos.frame = frame;
+ pos.valid = (jack_position_bits_t)0;
+ JackLog("TransportLocate pos = %ld\n", pos.frame);
+ return RequestNewPos(&pos);
+}
+
+int JackClient::TransportReposition(jack_position_t* pos)
+{
+ jack_position_t tmp = *pos;
+ JackLog("TransportReposition pos = %ld\n", pos->frame);
+ return (tmp.valid & ~JACK_POSITION_MASK) ? EINVAL : RequestNewPos(&tmp);
+}
+
+jack_transport_state_t JackClient::TransportQuery(jack_position_t* pos)
+{
+ if (pos)
+ GetEngineControl()->fTransport.ReadCurrentPos(pos);
+ return GetEngineControl()->fTransport.GetState();
+}
+
+jack_nframes_t JackClient::GetCurrentTransportFrame()
+{
+ jack_position_t pos;
+ jack_transport_state_t state = TransportQuery(&pos);
+
+ if (state == JackTransportRolling) {
+ float usecs = GetMicroSeconds() - pos.usecs;
+ jack_nframes_t elapsed = (jack_nframes_t)floor((((float) pos.frame_rate) / 1000000.0f) * usecs);
+ return pos.frame + elapsed;
+ } else {
+ return pos.frame;
+ }
+}
+
+// Must be RT safe: directly write in the transport shared mem
+void JackClient::TransportStart()
+{
+ GetEngineControl()->fTransport.SetCommand(TransportCommandStart);
+}
+
+// Must be RT safe: directly write in the transport shared mem
+void JackClient::TransportStop()
+{
+ GetEngineControl()->fTransport.SetCommand(TransportCommandStop);
+}
+
+// Never called concurently with the server
+// TODO check concurency with SetSyncCallback
+
+void JackClient::CallSyncCallback()
+{
+ JackTransportEngine& transport = GetEngineControl()->fTransport;
+ jack_position_t* cur_pos = transport.ReadCurrentState();
+ jack_transport_state_t transport_state = transport.GetState();
+
+ switch (transport_state) {
+
+ case JackTransportStarting: // Starting...
+ if (fSync == NULL) {
+ GetClientControl()->fTransportState = JackTransportRolling;
+ } else if (GetClientControl()->fTransportState == JackTransportStarting) {
+ if (fSync(transport_state, cur_pos, fSyncArg))
+ GetClientControl()->fTransportState = JackTransportRolling;
+ }
+ break;
+
+ case JackTransportRolling:
+ if (fSync != NULL && GetClientControl()->fTransportState == JackTransportStarting) { // Client still not ready
+ if (fSync(transport_state, cur_pos, fSyncArg))
+ GetClientControl()->fTransportState = JackTransportRolling;
+ }
+ break;
+
+ case JackTransportSynching:
+ // New pos when transport engine is stopped...
+ if (fSync != NULL) {
+ fSync(JackTransportStopped, cur_pos, fSyncArg);
+ GetClientControl()->fTransportState = JackTransportStopped;
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+void JackClient::CallTimebaseCallback()
+{
+ JackTransportEngine& transport = GetEngineControl()->fTransport;
+
+ if (fTimebase != NULL && fTimebaseArg != NULL && GetClientControl()->fRefNum == transport.GetTimebaseMaster()) {
+
+ jack_transport_state_t transport_state = transport.GetState();
+ jack_position_t* cur_pos = transport.WriteNextStateStart(1);
+
+ switch (transport_state) {
+
+ case JackTransportRolling:
+ fTimebase(transport_state, GetEngineControl()->fBufferSize, cur_pos, false, fTimebaseArg);
+ break;
+
+ case JackTransportSynching:
+ fTimebase(JackTransportStopped, GetEngineControl()->fBufferSize, cur_pos, true, fTimebaseArg);
+ break;
+
+ default:
+ break;
+ }
+
+ transport.WriteNextStateStop(1);
+ }
+}
+
+//---------------------
+// Callback management
+//---------------------
+
+void JackClient::OnShutdown(JackShutdownCallback callback, void *arg)
+{
+ if (IsActive()) {
+ jack_error("You cannot set callbacks on an active client");
+ } else {
+ fShutdownArg = arg;
+ fShutdown = callback;
+ }
+}
+
+int JackClient::SetProcessCallback(JackProcessCallback callback, void *arg)
+{
+ if (IsActive()) {
+ jack_error("You cannot set callbacks on an active client");
+ return -1;
+ } else {
+ fProcessArg = arg;
+ fProcess = callback;
+ return 0;
+ }
+}
+
+int JackClient::SetXRunCallback(JackXRunCallback callback, void *arg)
+{
+ if (IsActive()) {
+ jack_error("You cannot set callbacks on an active client");
+ return -1;
+ } else {
+ fXrunArg = arg;
+ fXrun = callback;
+ return 0;
+ }
+}
+
+int JackClient::SetInitCallback(JackThreadInitCallback callback, void *arg)
+{
+ if (IsActive()) {
+ jack_error("You cannot set callbacks on an active client");
+ return -1;
+ } else {
+ fInitArg = arg;
+ fInit = callback;
+ return 0;
+ }
+}
+
+int JackClient::SetGraphOrderCallback(JackGraphOrderCallback callback, void *arg)
+{
+ JackLog("SetGraphOrderCallback \n");
+
+ if (IsActive()) {
+ jack_error("You cannot set callbacks on an active client");
+ return -1;
+ } else {
+ fGraphOrder = callback;
+ fGraphOrderArg = arg;
+ return 0;
+ }
+}
+
+int JackClient::SetBufferSizeCallback(JackBufferSizeCallback callback, void *arg)
+{
+ if (IsActive()) {
+ jack_error("You cannot set callbacks on an active client");
+ return -1;
+ } else {
+ fBufferSizeArg = arg;
+ fBufferSize = callback;
+ return 0;
+ }
+}
+
+int JackClient::SetFreewheelCallback(JackFreewheelCallback callback, void *arg)
+{
+ if (IsActive()) {
+ jack_error("You cannot set callbacks on an active client");
+ return -1;
+ } else {
+ fFreewheelArg = arg;
+ fFreewheel = callback;
+ return 0;
+ }
+}
+
+int JackClient::SetPortRegistrationCallback(JackPortRegistrationCallback callback, void *arg)
+{
+ if (IsActive()) {
+ jack_error("You cannot set callbacks on an active client");
+ return -1;
+ } else {
+ fPortRegistrationArg = arg;
+ fPortRegistration = callback;
+ return 0;
+ }
+}
+
+} // end of namespace
+