Browse Source

Merge with 'newer-midi' and resolve conflict in JackCoreMidiDriver.cpp. Hope this doesn't break anything, as I can't test it.

tags/1.9.8
Devin Anderson 15 years ago
parent
commit
17cb2dd586
21 changed files with 394 additions and 156 deletions
  1. +9
    -0
      ChangeLog
  2. +43
    -15
      common/JackAudioDriver.cpp
  3. +6
    -2
      common/JackAudioDriver.h
  4. +44
    -3
      common/JackDriver.cpp
  5. +18
    -3
      common/JackDriver.h
  6. +6
    -6
      common/JackEngine.cpp
  7. +60
    -14
      common/JackFreewheelDriver.cpp
  8. +10
    -0
      common/JackFreewheelDriver.h
  9. +8
    -8
      common/JackInternalClient.cpp
  10. +48
    -7
      common/JackLoopbackDriver.cpp
  11. +10
    -1
      common/JackLoopbackDriver.h
  12. +39
    -47
      common/JackMidiDriver.cpp
  13. +9
    -3
      common/JackMidiDriver.h
  14. +17
    -2
      common/JackThreadedDriver.cpp
  15. +8
    -1
      common/JackThreadedDriver.h
  16. +6
    -6
      common/varargs.h
  17. +8
    -0
      linux/alsa/JackAlsaDriver.cpp
  18. +4
    -0
      linux/alsa/alsa_driver.c
  19. +1
    -0
      linux/alsa/alsa_driver.h
  20. +33
    -34
      macosx/coreaudio/JackCoreAudioAdapter.cpp
  21. +7
    -4
      macosx/coreaudio/JackCoreAudioDriver.cpp

+ 9
- 0
ChangeLog View File

@@ -34,6 +34,15 @@ Valerio Pilo
Jackdmp changes log
---------------------------

2011-03-24 Stephane Letz <letz@grame.fr>

* Implement renaming in JackDriver::Open to avoid name collision (thanks Devin Anderson).
* Correct alsa_driver_restart (thanks Devin Anderson).

2011-03-23 Stephane Letz <letz@grame.fr>

* Devin Anderson server-ctl-proposal branch merged on trunk: improved control API, slave backend reworked.

2011-03-14 Stephane Letz <letz@grame.fr>

* Correct JackEngine::NotifyGraphReorder, update JackDebugClient with latest API.


+ 43
- 15
common/JackAudioDriver.cpp View File

@@ -193,9 +193,9 @@ int JackAudioDriver::ProcessNull()
JackDriver::CycleTakeBeginTime();

if (fEngineControl->fSyncMode) {
ProcessGraphSync();
ProcessGraphSyncMaster();
} else {
ProcessGraphAsync();
ProcessGraphAsyncMaster();
}

// Keep end cycle time
@@ -230,9 +230,9 @@ int JackAudioDriver::ProcessAsync()

// Process graph
if (fIsMaster) {
ProcessGraphAsync();
ProcessGraphAsyncMaster();
} else {
fGraphManager->ResumeRefNum(&fClientControl, fSynchroTable);
ProcessGraphAsyncSlave();
}

// Keep end cycle time
@@ -255,12 +255,12 @@ int JackAudioDriver::ProcessSync()

// Process graph
if (fIsMaster) {
if (ProcessGraphSync() < 0) {
if (ProcessGraphSyncMaster() < 0) {
jack_error("JackAudioDriver::ProcessSync: process error, skip cycle...");
goto end;
}
} else {
if (fGraphManager->ResumeRefNum(&fClientControl, fSynchroTable) < 0) {
if (ProcessGraphSyncSlave() < 0) {
jack_error("JackAudioDriver::ProcessSync: process error, skip cycle...");
goto end;
}
@@ -279,27 +279,50 @@ end:
return 0;
}

void JackAudioDriver::ProcessGraphAsync()
void JackAudioDriver::ProcessGraphAsyncMaster()
{
// fBeginDateUst is set in the "low level" layer, fEndDateUst is from previous cycle
if (!fEngine->Process(fBeginDateUst, fEndDateUst))
jack_error("JackAudioDriver::ProcessGraphAsync: Process error");
fGraphManager->ResumeRefNum(&fClientControl, fSynchroTable);
if (ProcessSlaves() < 0)
jack_error("JackAudioDriver::ProcessGraphAsync: ProcessSlaves error");
jack_error("JackAudioDriver::ProcessGraphAsyncMaster: Process error");

if (fGraphManager->ResumeRefNum(&fClientControl, fSynchroTable) < 0)
jack_error("JackAudioDriver::ProcessGraphAsyncMaster: ResumeRefNum error");

if (ProcessReadSlaves() < 0)
jack_error("JackAudioDriver::ProcessGraphAsyncMaster: ProcessReadSlaves error");

if (ProcessWriteSlaves() < 0)
jack_error("JackAudioDriver::ProcessGraphAsyncMaster: ProcessWriteSlaves error");
}

void JackAudioDriver::ProcessGraphAsyncSlave()
{
if (fGraphManager->ResumeRefNum(&fClientControl, fSynchroTable) < 0)
jack_error("JackAudioDriver::ProcessGraphAsyncSlave: ResumeRefNum error");
}

int JackAudioDriver::ProcessGraphSync()
int JackAudioDriver::ProcessGraphSyncMaster()
{
int res = 0;

// fBeginDateUst is set in the "low level" layer, fEndDateUst is from previous cycle
if (fEngine->Process(fBeginDateUst, fEndDateUst)) {
fGraphManager->ResumeRefNum(&fClientControl, fSynchroTable);
if (ProcessSlaves() < 0) {
jack_error("JackAudioDriver::ProcessGraphSync: ProcessSlaves error, engine may now behave abnormally!!");
if (fGraphManager->ResumeRefNum(&fClientControl, fSynchroTable) < 0) {
jack_error("JackAudioDriver::ProcessGraphSyncMaster: ResumeRefNum error");
res = -1;
}

if (ProcessReadSlaves() < 0) {
jack_error("JackAudioDriver::ProcessGraphSync: ProcessReadSlaves error, engine may now behave abnormally!!");
res = -1;
}

if (ProcessWriteSlaves() < 0) {
jack_error("JackAudioDriver::ProcessGraphSync: ProcessWriteSlaves error, engine may now behave abnormally!!");
res = -1;
}

if (fGraphManager->SuspendRefNum(&fClientControl, fSynchroTable, DRIVER_TIMEOUT_FACTOR * fEngineControl->fTimeOutUsecs) < 0) {
jack_error("JackAudioDriver::ProcessGraphSync: SuspendRefNum error, engine may now behave abnormally!!");
res = -1;
@@ -312,6 +335,11 @@ int JackAudioDriver::ProcessGraphSync()
return res;
}

int JackAudioDriver::ProcessGraphSyncSlave()
{
return fGraphManager->ResumeRefNum(&fClientControl, fSynchroTable);
}

int JackAudioDriver::Start()
{
int res = JackDriver::Start();


+ 6
- 2
common/JackAudioDriver.h View File

@@ -35,8 +35,12 @@ class SERVER_EXPORT JackAudioDriver : public JackDriver

protected:

void ProcessGraphAsync();
int ProcessGraphSync();
void ProcessGraphAsyncMaster();
void ProcessGraphAsyncSlave();

int ProcessGraphSyncMaster();
int ProcessGraphSyncSlave();

void WaitUntilNextCycle();

virtual int ProcessAsync();


+ 44
- 3
common/JackDriver.cpp View File

@@ -82,7 +82,7 @@ int JackDriver::Open()
return 0;
}

int JackDriver::Open (bool capturing,
int JackDriver::Open(bool capturing,
bool playing,
int inchannels,
int outchannels,
@@ -95,6 +95,15 @@ int JackDriver::Open (bool capturing,
jack_log("JackDriver::Open capture_driver_name = %s", capture_driver_name);
jack_log("JackDriver::Open playback_driver_name = %s", playback_driver_name);
int refnum = -1;
char name_res[JACK_CLIENT_NAME_SIZE + 1];
int status;

// Check name and possibly rename
if (fEngine->ClientCheck(fClientControl.fName, -1, name_res, JACK_PROTOCOL_VERSION, (int)JackNullOption, (int*)&status) < 0) {
jack_error("Client name = %s conflits with another running client", fClientControl.fName);
return -1;
}
strcpy(fClientControl.fName, name_res);

if (fEngine->ClientInternalOpen(fClientControl.fName, &refnum, &fEngineControl, &fGraphManager, this, false) != 0) {
jack_error("Cannot allocate internal client for driver");
@@ -137,6 +146,15 @@ int JackDriver::Open(jack_nframes_t buffer_size,
jack_log("JackDriver::Open capture_driver_name = %s", capture_driver_name);
jack_log("JackDriver::Open playback_driver_name = %s", playback_driver_name);
int refnum = -1;
char name_res[JACK_CLIENT_NAME_SIZE + 1];
int status;

// Check name and possibly rename
if (fEngine->ClientCheck(fClientControl.fName, -1, name_res, JACK_PROTOCOL_VERSION, (int)JackNullOption, (int*)&status) < 0) {
jack_error("Client name = %s conflits with another running client", fClientControl.fName);
return -1;
}
strcpy(fClientControl.fName, name_res);

if (fEngine->ClientInternalOpen(fClientControl.fName, &refnum, &fEngineControl, &fGraphManager, this, false) != 0) {
jack_error("Cannot allocate internal client for driver");
@@ -282,19 +300,42 @@ void JackDriver::RemoveSlave(JackDriverInterface* slave)
fSlaveList.remove(slave);
}

int JackDriver::ProcessSlaves()
int JackDriver::ProcessReadSlaves()
{
int res = 0;
list<JackDriverInterface*>::const_iterator it;
for (it = fSlaveList.begin(); it != fSlaveList.end(); it++) {
JackDriverInterface* slave = *it;
if (slave->Process() < 0)
if (slave->ProcessRead() < 0)
res = -1;

}
return res;
}

int JackDriver::ProcessWriteSlaves()
{
int res = 0;
list<JackDriverInterface*>::const_iterator it;
for (it = fSlaveList.begin(); it != fSlaveList.end(); it++) {
JackDriverInterface* slave = *it;
if (slave->ProcessWrite() < 0)
res = -1;

}
return res;
}

int JackDriver::ProcessRead()
{
return 0;
}

int JackDriver::ProcessWrite()
{
return 0;
}

int JackDriver::Process()
{
return 0;


+ 18
- 3
common/JackDriver.h View File

@@ -34,6 +34,7 @@ namespace Jack
class JackLockedEngine;
class JackGraphManager;
struct JackEngineControl;
class JackSlaveDriverInterface;

/*!
\brief The base interface for drivers.
@@ -91,10 +92,17 @@ class SERVER_EXPORT JackDriverInterface

virtual void SetMaster(bool onoff) = 0;
virtual bool GetMaster() = 0;

virtual void AddSlave(JackDriverInterface* slave) = 0;
virtual void RemoveSlave(JackDriverInterface* slave) = 0;

virtual std::list<JackDriverInterface*> GetSlaves() = 0;
virtual int ProcessSlaves() = 0;

virtual int ProcessReadSlaves() = 0;
virtual int ProcessWriteSlaves() = 0;

virtual int ProcessRead() = 0;
virtual int ProcessWrite() = 0;

virtual bool IsRealTime() const = 0;
virtual bool IsRunning() const = 0;
@@ -159,11 +167,11 @@ class SERVER_EXPORT JackDriver : public JackDriverClientInterface

void AddSlave(JackDriverInterface* slave);
void RemoveSlave(JackDriverInterface* slave);

std::list<JackDriverInterface*> GetSlaves()
{
return fSlaveList;
}
int ProcessSlaves();

virtual int Open();

@@ -200,10 +208,17 @@ class SERVER_EXPORT JackDriver : public JackDriverClientInterface
virtual int Write();

virtual int Start();
virtual int StartSlaves();
virtual int Stop();

virtual int StartSlaves();
virtual int StopSlaves();

int ProcessReadSlaves();
int ProcessWriteSlaves();

int ProcessRead();
int ProcessWrite();

virtual bool IsFixedBufferSize();
virtual int SetBufferSize(jack_nframes_t buffer_size);
virtual int SetSampleRate(jack_nframes_t sample_rate);


+ 6
- 6
common/JackEngine.cpp View File

@@ -519,7 +519,7 @@ void JackEngine::EnsureUUID(int uuid)

for (int i = 0; i < CLIENT_NUM; i++) {
JackClientInterface* client = fClientTable[i];
if (client && (client->GetClientControl()->fSessionID==uuid)) {
if (client && (client->GetClientControl()->fSessionID == uuid)) {
client->GetClientControl()->fSessionID = GetNewUUID();
}
}
@@ -550,13 +550,13 @@ int JackEngine::GetClientRefNum(const char* name)
// Used for external clients
int JackEngine::ClientExternalOpen(const char* name, int pid, int uuid, int* ref, int* shared_engine, int* shared_client, int* shared_graph_manager)
{
char real_name[JACK_CLIENT_NAME_SIZE+1];
char real_name[JACK_CLIENT_NAME_SIZE + 1];

if (uuid < 0) {
uuid = GetNewUUID();
strncpy(real_name, name, JACK_CLIENT_NAME_SIZE);
} else {
std::map<int,std::string>::iterator res = fReservationMap.find(uuid);
std::map<int, std::string>::iterator res = fReservationMap.find(uuid);
if (res != fReservationMap.end()) {
strncpy(real_name, res->second.c_str(), JACK_CLIENT_NAME_SIZE);
fReservationMap.erase(uuid);
@@ -567,7 +567,7 @@ int JackEngine::ClientExternalOpen(const char* name, int pid, int uuid, int* ref
EnsureUUID(uuid);
}

jack_log("JackEngine::ClientExternalOpen: uuid=%d, name = %s ", uuid, real_name);
jack_log("JackEngine::ClientExternalOpen: uuid = %d, name = %s ", uuid, real_name);

int refnum = AllocateRefnum();
if (refnum < 0) {
@@ -958,7 +958,7 @@ void JackEngine::SessionNotify(int refnum, const char *target, jack_session_even
if (client && client->GetClientControl()->fCallback[kSessionCallback]) {

// check if this is a notification to a specific client.
if (target!=NULL && strlen(target)!=0) {
if (target != NULL && strlen(target) != 0) {
if (strcmp(target, client->GetClientControl()->fName)) {
continue;
}
@@ -1018,7 +1018,7 @@ void JackEngine::GetUUIDForClientName(const char *client_name, char *uuid_res, i
for (int i = 0; i < CLIENT_NUM; i++) {
JackClientInterface* client = fClientTable[i];

if (client && (strcmp(client_name, client->GetClientControl()->fName)==0)) {
if (client && (strcmp(client_name, client->GetClientControl()->fName) == 0)) {
snprintf(uuid_res, JACK_UUID_SIZE, "%d", client->GetClientControl()->fSessionID);
*result = 0;
return;


+ 60
- 14
common/JackFreewheelDriver.cpp View File

@@ -28,26 +28,72 @@ namespace Jack

int JackFreewheelDriver::Process()
{
if (fIsMaster) {
jack_log("JackFreewheelDriver::Process master %lld", fEngineControl->fTimeOutUsecs);
JackDriver::CycleTakeBeginTime();
fEngine->Process(fBeginDateUst, fEndDateUst);
fGraphManager->ResumeRefNum(&fClientControl, fSynchroTable); // Signal all clients
int res = 0;

jack_log("JackFreewheelDriver::Process master %lld", fEngineControl->fTimeOutUsecs);
JackDriver::CycleTakeBeginTime();

if (fEngine->Process(fBeginDateUst, fEndDateUst)) {

if (fGraphManager->ResumeRefNum(&fClientControl, fSynchroTable)) { // Signal all clients
jack_error("JackFreewheelDriver::Process: ResumeRefNum error");
res = -1;
}

if (fGraphManager->SuspendRefNum(&fClientControl, fSynchroTable, FREEWHEEL_DRIVER_TIMEOUT * 1000000) < 0) { // Wait for all clients to finish for 10 sec
jack_error("JackFreewheelDriver::ProcessSync SuspendRefNum error");
jack_error("JackFreewheelDriver::ProcessSync: SuspendRefNum error");
/* We have a client time-out error, but still continue to process, until a better recovery strategy is chosen */
return 0;
}
} else {
fGraphManager->ResumeRefNum(&fClientControl, fSynchroTable); // Signal all clients
if (fEngineControl->fSyncMode) {
if (fGraphManager->SuspendRefNum(&fClientControl, fSynchroTable, DRIVER_TIMEOUT_FACTOR * fEngineControl->fTimeOutUsecs) < 0) {
jack_error("JackFreewheelDriver::ProcessSync SuspendRefNum error");
return -1;
}
}

} else { // Graph not finished: do not activate it
jack_error("JackFreewheelDriver::Process: Process error");
res = -1;
}

return res;
}

int JackFreewheelDriver::ProcessRead()
{
return (fEngineControl->fSyncMode) ? ProcessReadSync() : ProcessReadAsync();
}

int JackFreewheelDriver::ProcessWrite()
{
return (fEngineControl->fSyncMode) ? ProcessWriteSync() : ProcessWriteAsync();
}

int JackFreewheelDriver::ProcessReadSync()
{
if (fGraphManager->ResumeRefNum(&fClientControl, fSynchroTable) < 0) { // Signal all clients
jack_error("JackFreewheelDriver::ProcessReadSync: ResumeRefNum error");
return -1;
}
return 0;
}

int JackFreewheelDriver::ProcessWriteSync()
{
if (fGraphManager->SuspendRefNum(&fClientControl, fSynchroTable, DRIVER_TIMEOUT_FACTOR * fEngineControl->fTimeOutUsecs) < 0) {
jack_error("JackFreewheelDriver::ProcessSync SuspendRefNum error");
return -1;
}
return 0;
}

int JackFreewheelDriver::ProcessReadAsync()
{
if (fGraphManager->ResumeRefNum(&fClientControl, fSynchroTable) < 0) { // Signal all clients
jack_error("JackFreewheelDriver::ProcessReadAsync: ResumeRefNum error");
return -1;
}
return 0;
}

int JackFreewheelDriver::ProcessWriteAsync()
{
return 0;
}

} // end of namespace

+ 10
- 0
common/JackFreewheelDriver.h View File

@@ -46,6 +46,16 @@ class JackFreewheelDriver : public JackDriver
}

int Process();

int ProcessRead();
int ProcessWrite();

int ProcessReadSync();
int ProcessWriteSync();

int ProcessReadAsync();
int ProcessWriteAsync();

};

} // end of namespace


+ 8
- 8
common/JackInternalClient.cpp View File

@@ -123,7 +123,7 @@ int JackLoadableInternalClient::Init(const char* so_name)
{
char path_to_so[JACK_PATH_MAX + 1];
BuildClientPath(path_to_so, sizeof(path_to_so), so_name);
fHandle = LoadJackModule(path_to_so);
jack_log("JackLoadableInternalClient::JackLoadableInternalClient path_to_so = %s", path_to_so);

@@ -151,7 +151,7 @@ int JackLoadableInternalClient1::Init(const char* so_name)
if (JackLoadableInternalClient::Init(so_name) < 0) {
return -1;
}
fInitialize = (InitializeCallback)GetJackProc(fHandle, "jack_initialize");
if (fInitialize == NULL) {
UnloadJackModule(fHandle);
@@ -167,7 +167,7 @@ int JackLoadableInternalClient2::Init(const char* so_name)
if (JackLoadableInternalClient::Init(so_name) < 0) {
return -1;
}
fInitialize = (InternalInitializeCallback)GetJackProc(fHandle, "jack_internal_initialize");
if (fInitialize == NULL) {
UnloadJackModule(fHandle);
@@ -181,7 +181,7 @@ int JackLoadableInternalClient2::Init(const char* so_name)
JackLoadableInternalClient1::JackLoadableInternalClient1(JackServer* server, JackSynchro* table, const char* object_data)
: JackLoadableInternalClient(server, table)
{
strncpy(fObjectData, object_data, JACK_LOAD_INIT_LIMIT);
strncpy(fObjectData, object_data, JACK_LOAD_INIT_LIMIT);
}

JackLoadableInternalClient2::JackLoadableInternalClient2(JackServer* server, JackSynchro* table, const JSList* parameters)
@@ -201,7 +201,7 @@ JackLoadableInternalClient::~JackLoadableInternalClient()
int JackLoadableInternalClient1::Open(const char* server_name, const char* name, int uuid, jack_options_t options, jack_status_t* status)
{
int res = -1;
if (JackInternalClient::Open(server_name, name, uuid, options, status) == 0) {
if (fInitialize((jack_client_t*)this, fObjectData) == 0) {
res = 0;
@@ -210,14 +210,14 @@ int JackLoadableInternalClient1::Open(const char* server_name, const char* name,
fFinish = NULL;
}
}
return res;
}

int JackLoadableInternalClient2::Open(const char* server_name, const char* name, int uuid, jack_options_t options, jack_status_t* status)
{
int res = -1;
if (JackInternalClient::Open(server_name, name, uuid, options, status) == 0) {
if (fInitialize((jack_client_t*)this, fParameters) == 0) {
res = 0;
@@ -226,7 +226,7 @@ int JackLoadableInternalClient2::Open(const char* server_name, const char* name,
fFinish = NULL;
}
}
return res;
}



+ 48
- 7
common/JackLoopbackDriver.cpp View File

@@ -30,20 +30,61 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
namespace Jack
{

int JackLoopbackDriver::Process()
int JackLoopbackDriver::ProcessRead()
{
return (fEngineControl->fSyncMode) ? ProcessReadSync() : ProcessReadAsync();
}

int JackLoopbackDriver::ProcessWrite()
{
return (fEngineControl->fSyncMode) ? ProcessWriteSync() : ProcessWriteAsync();
}

int JackLoopbackDriver::ProcessReadSync()
{
int res = 0;

// Loopback copy
for (int i = 0; i < fCaptureChannels; i++) {
memcpy(GetInputBuffer(i), GetOutputBuffer(i), sizeof(jack_default_audio_sample_t) * fEngineControl->fBufferSize);
}

fGraphManager->ResumeRefNum(&fClientControl, fSynchroTable); // Signal all clients
if (fEngineControl->fSyncMode) {
if (fGraphManager->SuspendRefNum(&fClientControl, fSynchroTable, DRIVER_TIMEOUT_FACTOR * fEngineControl->fTimeOutUsecs) < 0) {
jack_error("JackLoopbackDriver::ProcessSync SuspendRefNum error");
return -1;
}
if (fGraphManager->ResumeRefNum(&fClientControl, fSynchroTable) < 0) {
jack_error("JackLoopbackDriver::ProcessReadSync - ResumeRefNum error");
res = -1;
}

return res;
}

int JackLoopbackDriver::ProcessWriteSync()
{
if (fGraphManager->SuspendRefNum(&fClientControl, fSynchroTable, DRIVER_TIMEOUT_FACTOR * fEngineControl->fTimeOutUsecs) < 0) {
jack_error("JackLoopbackDriver::ProcessWriteSync SuspendRefNum error");
return -1;
}
return 0;
}

int JackLoopbackDriver::ProcessReadAsync()
{
int res = 0;

// Loopback copy
for (int i = 0; i < fCaptureChannels; i++) {
memcpy(GetInputBuffer(i), GetOutputBuffer(i), sizeof(jack_default_audio_sample_t) * fEngineControl->fBufferSize);
}

if (fGraphManager->ResumeRefNum(&fClientControl, fSynchroTable) < 0) {
jack_error("JackLoopbackDriver::ProcessReadAsync - ResumeRefNum error");
res = -1;
}

return res;
}

int JackLoopbackDriver::ProcessWriteAsync()
{
return 0;
}



+ 10
- 1
common/JackLoopbackDriver.h View File

@@ -33,6 +33,14 @@ namespace Jack
class JackLoopbackDriver : public JackAudioDriver
{

private:

virtual int ProcessReadSync();
virtual int ProcessWriteSync();

virtual int ProcessReadAsync();
virtual int ProcessWriteAsync();

public:

JackLoopbackDriver(JackLockedEngine* engine, JackSynchro* table)
@@ -41,7 +49,8 @@ class JackLoopbackDriver : public JackAudioDriver
virtual ~JackLoopbackDriver()
{}

int Process();
virtual int ProcessRead();
virtual int ProcessWrite();
};

} // end of namespace


+ 39
- 47
common/JackMidiDriver.cpp View File

@@ -74,9 +74,12 @@ int JackMidiDriver::Attach()
jack_port_id_t port_index;
char name[JACK_CLIENT_NAME_SIZE + JACK_PORT_NAME_SIZE];
char alias[JACK_CLIENT_NAME_SIZE + JACK_PORT_NAME_SIZE];
jack_latency_range_t latency_range;
jack_nframes_t latency = fEngineControl->fBufferSize;
int i;

jack_log("JackMidiDriver::Attach fBufferSize = %ld fSampleRate = %ld", fEngineControl->fBufferSize, fEngineControl->fSampleRate);
latency_range.max = latency_range.min = latency;

for (i = 0; i < fCaptureChannels; i++) {
snprintf(alias, sizeof(alias) - 1, "%s:%s:out%d", fAliasName, fCaptureDriverName, i + 1);
@@ -87,10 +90,16 @@ int JackMidiDriver::Attach()
}
port = fGraphManager->GetPort(port_index);
port->SetAlias(alias);
port->SetLatencyRange(JackCaptureLatency, &latency_range);
fCapturePortList[i] = port_index;
jack_log("JackMidiDriver::Attach fCapturePortList[i] port_index = %ld", port_index);
}

if (!fEngineControl->fSyncMode) {
latency += fEngineControl->fBufferSize;;
latency_range.max = latency_range.min = latency;
}

for (i = 0; i < fPlaybackChannels; i++) {
snprintf(alias, sizeof(alias) - 1, "%s:%s:in%d", fAliasName, fPlaybackDriverName, i + 1);
snprintf(name, sizeof(name) - 1, "%s:playback_%d", fClientControl.fName, i + 1);
@@ -100,6 +109,7 @@ int JackMidiDriver::Attach()
}
port = fGraphManager->GetPort(port_index);
port->SetAlias(alias);
port->SetLatencyRange(JackPlaybackLatency, &latency_range);
fPlaybackPortList[i] = port_index;
jack_log("JackMidiDriver::Attach fPlaybackPortList[i] port_index = %ld", port_index);
}
@@ -138,101 +148,83 @@ int JackMidiDriver::ProcessNull()
return 0;
}

/*
int JackMidiDriver::Process()
int JackMidiDriver::ProcessRead()
{
if (Read() < 0) {
jack_error("JackMidiDriver::Process: read error, skip cycle");
return 0; // Skip cycle, but continue processing...
}

if (fEngineControl->fSyncMode) {
int res = 0;
if (fGraphManager->ResumeRefNum(&fClientControl, fSynchroTable) < 0) {
jack_error("JackMidiDriver::Process - ResumeRefNum error");
res = -1;
}
if (fGraphManager->SuspendRefNum(&fClientControl, fSynchroTable,
DRIVER_TIMEOUT_FACTOR *
fEngineControl->fTimeOutUsecs) < 0) {
jack_error("JackMidiDriver::Process - SuspendRefNum error");
res = -1;
}
if (Write() < 0) {
jack_error("JackMidiDriver::Process - Write error");
}
return res;
}

// Not in sync mode

if (Write() < 0) {
jack_error("JackMidiDriver::Process - Write error");
} else {
fGraphManager->ResumeRefNum(&fClientControl, fSynchroTable);
}
return 0;
return (fEngineControl->fSyncMode) ? ProcessReadSync() : ProcessReadAsync();
}
*/

int JackMidiDriver::Process()
int JackMidiDriver::ProcessWrite()
{
return (fEngineControl->fSyncMode) ? ProcessSync() : ProcessAsync();
return (fEngineControl->fSyncMode) ? ProcessWriteSync() : ProcessWriteAsync();
}

int JackMidiDriver::ProcessSync()
int JackMidiDriver::ProcessReadSync()
{
int res = 0;

// Read input buffers for the current cycle
if (Read() < 0) {
jack_error("JackMidiDriver::ProcessSync: read error, skip cycle");
return 0; // Skip cycle, but continue processing...
jack_error("JackMidiDriver::ProcessReadSync: read error, skip cycle");
res = -1;
}

if (fGraphManager->ResumeRefNum(&fClientControl, fSynchroTable) < 0) {
jack_error("JackMidiDriver::ProcessSync - ResumeRefNum error");
jack_error("JackMidiDriver::ProcessReadSync - ResumeRefNum error");
res = -1;
}

return res;
}

int JackMidiDriver::ProcessWriteSync()
{
int res = 0;

if (fGraphManager->SuspendRefNum(&fClientControl, fSynchroTable,
DRIVER_TIMEOUT_FACTOR *
fEngineControl->fTimeOutUsecs) < 0) {
jack_error("JackMidiDriver::ProcessSync - SuspendRefNum error");
jack_error("JackMidiDriver::ProcessWriteSync - SuspendRefNum error");
res = -1;
}

// Write output buffers from the current cycle
if (Write() < 0) {
jack_error("JackMidiDriver::ProcessSync - Write error");
jack_error("JackMidiDriver::ProcessWriteSync - Write error");
res = -1;
}

return res;
}

int JackMidiDriver::ProcessAsync()
int JackMidiDriver::ProcessReadAsync()
{
int res = 0;

// Read input buffers for the current cycle
if (Read() < 0) {
jack_error("JackMidiDriver::ProcessAsync: read error, skip cycle");
return 0; // Skip cycle, but continue processing...
jack_error("JackMidiDriver::ProcessReadAsync: read error, skip cycle");
res = -1;
}

// Write output buffers from the previous cycle
if (Write() < 0) {
jack_error("JackMidiDriver::ProcessAsync - Write error");
jack_error("JackMidiDriver::ProcessReadAsync - Write error");
res = -1;
}

if (fGraphManager->ResumeRefNum(&fClientControl, fSynchroTable) < 0) {
jack_error("JackMidiDriver::ProcessAsync - ResumeRefNum error");
jack_error("JackMidiDriver::ProcessReadAsync - ResumeRefNum error");
res = -1;
}

return res;
}

int JackMidiDriver::ProcessWriteAsync()
{
return 0;
}

JackMidiBuffer* JackMidiDriver::GetInputBuffer(int port_index)
{
assert(fCapturePortList[port_index]);


+ 9
- 3
common/JackMidiDriver.h View File

@@ -48,6 +48,12 @@ class SERVER_EXPORT JackMidiDriver : public JackDriver
JackMidiBuffer* GetInputBuffer(int port_index);
JackMidiBuffer* GetOutputBuffer(int port_index);

virtual int ProcessReadSync();
virtual int ProcessWriteSync();

virtual int ProcessReadAsync();
virtual int ProcessWriteAsync();

public:

JackMidiDriver(const char* name, const char* alias, JackLockedEngine* engine, JackSynchro* table);
@@ -63,9 +69,9 @@ class SERVER_EXPORT JackMidiDriver : public JackDriver
jack_nframes_t capture_latency,
jack_nframes_t playback_latency);

virtual int Process();
virtual int ProcessSync();
virtual int ProcessAsync();
virtual int ProcessRead();
virtual int ProcessWrite();
virtual int ProcessNull();

virtual int Attach();


+ 17
- 2
common/JackThreadedDriver.cpp View File

@@ -127,9 +127,24 @@ void JackThreadedDriver::RemoveSlave(JackDriverInterface* slave)
fDriver->RemoveSlave(slave);
}

int JackThreadedDriver::ProcessSlaves()
int JackThreadedDriver::ProcessReadSlaves()
{
return fDriver->ProcessSlaves();
return fDriver->ProcessReadSlaves();
}

int JackThreadedDriver::ProcessWriteSlaves()
{
return fDriver->ProcessWriteSlaves();
}

int JackThreadedDriver::ProcessRead()
{
return fDriver->ProcessRead();
}

int JackThreadedDriver::ProcessWrite()
{
return fDriver->ProcessWrite();
}

std::list<JackDriverInterface*> JackThreadedDriver::GetSlaves()


+ 8
- 1
common/JackThreadedDriver.h View File

@@ -89,10 +89,17 @@ class SERVER_EXPORT JackThreadedDriver : public JackDriverClientInterface, publi

virtual void SetMaster(bool onoff);
virtual bool GetMaster();

virtual void AddSlave(JackDriverInterface* slave);
virtual void RemoveSlave(JackDriverInterface* slave);

virtual std::list<JackDriverInterface*> GetSlaves();
virtual int ProcessSlaves();

virtual int ProcessReadSlaves();
virtual int ProcessWriteSlaves();

virtual int ProcessRead();
virtual int ProcessWrite();

virtual int ClientNotify(int refnum, const char* name, int notify, int sync, const char* message, int value1, int value2);
virtual JackClientControl* GetClientControl() const;


+ 6
- 6
common/varargs.h View File

@@ -39,23 +39,23 @@ extern "C"
}
jack_varargs_t;

static const char* jack_default_server_name (void)
{
static const char* jack_default_server_name (void)
{
const char *server_name;
if ((server_name = getenv("JACK_DEFAULT_SERVER")) == NULL)
server_name = "default";
return server_name;
}

static inline void jack_varargs_init (jack_varargs_t *va)
{
static inline void jack_varargs_init (jack_varargs_t *va)
{
memset (va, 0, sizeof(jack_varargs_t));
va->server_name = (char*)jack_default_server_name();
va->session_id = -1;
}

static inline void jack_varargs_parse (jack_options_t options, va_list ap, jack_varargs_t *va)
{
static inline void jack_varargs_parse (jack_options_t options, va_list ap, jack_varargs_t *va)
{
// initialize default settings
jack_varargs_init (va);



+ 8
- 0
linux/alsa/JackAlsaDriver.cpp View File

@@ -1048,6 +1048,14 @@ void SetTime(jack_time_t time)
g_alsa_driver->SetTimetAux(time);
}

int Restart()
{
int res;
if ((res = g_alsa_driver->Stop()) == 0)
res = g_alsa_driver->Start();
return res;
}

#ifdef __cplusplus
}
#endif


+ 4
- 0
linux/alsa/alsa_driver.c View File

@@ -1159,8 +1159,12 @@ alsa_driver_restart (alsa_driver_t *driver)
int res;

driver->xrun_recovery = 1;
// JACK2
/*
if ((res = driver->nt_stop((struct _jack_driver_nt *) driver))==0)
res = driver->nt_start((struct _jack_driver_nt *) driver);
*/
res = Restart();
driver->xrun_recovery = 0;

if (res && driver->midi)


+ 1
- 0
linux/alsa/alsa_driver.h View File

@@ -278,6 +278,7 @@ void MonitorInput();
void ClearOutput();
void WriteOutput(jack_nframes_t orig_nframes, snd_pcm_sframes_t contiguous, snd_pcm_sframes_t nwritten);
void SetTime(jack_time_t time);
int Restart();

#ifdef __cplusplus
}


+ 33
- 34
macosx/coreaudio/JackCoreAudioAdapter.cpp View File

@@ -168,7 +168,7 @@ OSStatus JackCoreAudioAdapter::SRNotificationCallback(AudioDeviceID inDevice,
switch (inPropertyID) {

case kAudioDevicePropertyNominalSampleRate: {
jack_log("JackCoreAudioDriver::SRNotificationCallback kAudioDevicePropertyNominalSampleRate");
jack_log("JackCoreAudioAdapter::SRNotificationCallback kAudioDevicePropertyNominalSampleRate");
driver->fState = true;
break;
}
@@ -430,12 +430,15 @@ OSStatus JackCoreAudioAdapter::GetDefaultDevice(AudioDeviceID* id)
jack_log("GetDefaultDevice: input = %ld output = %ld", inDefault, outDefault);

// Get the device only if default input and output are the same
if (inDefault == outDefault) {
*id = inDefault;
return noErr;
} else {
if (inDefault != outDefault) {
jack_error("Default input and output devices are not the same !!");
return kAudioHardwareBadDeviceError;
} else if (inDefault == 0) {
jack_error("Default input and output devices are null !!");
return kAudioHardwareBadDeviceError;
} else {
*id = inDefault;
return noErr;
}
}

@@ -444,20 +447,16 @@ OSStatus JackCoreAudioAdapter::GetTotalChannels(AudioDeviceID device, int& chann
OSStatus err = noErr;
UInt32 outSize;
Boolean outWritable;
AudioBufferList* bufferList = 0;

channelCount = 0;
err = AudioDeviceGetPropertyInfo(device, 0, isInput, kAudioDevicePropertyStreamConfiguration, &outSize, &outWritable);
if (err == noErr) {
bufferList = (AudioBufferList*)malloc(outSize);
AudioBufferList bufferList[outSize];
err = AudioDeviceGetProperty(device, 0, isInput, kAudioDevicePropertyStreamConfiguration, &outSize, bufferList);
if (err == noErr) {
for (unsigned int i = 0; i < bufferList->mNumberBuffers; i++)
channelCount += bufferList->mBuffers[i].mNumberChannels;
}

if (bufferList)
free(bufferList);
}

return err;
@@ -604,7 +603,7 @@ int JackCoreAudioAdapter::SetupDevices(const char* capture_driver_uid,

// Use default driver in duplex mode
} else {
jack_log("JackCoreAudioDriver::Open default driver");
jack_log("JackCoreAudioAdapter::Open default driver");
if (GetDefaultDevice(&fDeviceID) != noErr) {
jack_error("Cannot open default device in duplex mode, so aggregate default input and default output");

@@ -1030,14 +1029,14 @@ OSStatus JackCoreAudioAdapter::DestroyAggregateDevice()

osErr = AudioObjectGetPropertyDataSize(fPluginID, &pluginAOPA, 0, NULL, &outDataSize);
if (osErr != noErr) {
jack_error("JackCoreAudioDriver::DestroyAggregateDevice : AudioObjectGetPropertyDataSize error");
jack_error("JackCoreAudioAdapter::DestroyAggregateDevice : AudioObjectGetPropertyDataSize error");
printError(osErr);
return osErr;
}

osErr = AudioObjectGetPropertyData(fPluginID, &pluginAOPA, 0, NULL, &outDataSize, &fDeviceID);
if (osErr != noErr) {
jack_error("JackCoreAudioDriver::DestroyAggregateDevice : AudioObjectGetPropertyData error");
jack_error("JackCoreAudioAdapter::DestroyAggregateDevice : AudioObjectGetPropertyData error");
printError(osErr);
return osErr;
}
@@ -1115,18 +1114,18 @@ OSStatus JackCoreAudioAdapter::CreateAggregateDeviceAux(vector<AudioDeviceID> ca

for (UInt32 i = 0; i < captureDeviceID.size(); i++) {
if (SetupSampleRateAux(captureDeviceID[i], samplerate) < 0) {
jack_error("JackCoreAudioDriver::CreateAggregateDevice : cannot set SR of input device");
jack_error("JackCoreAudioAdapter::CreateAggregateDevice : cannot set SR of input device");
} else {
// Check clock domain
osErr = AudioDeviceGetProperty(captureDeviceID[i], 0, kAudioDeviceSectionGlobal, kAudioDevicePropertyClockDomain, &outSize, &clockdomain);
if (osErr != 0) {
jack_error("JackCoreAudioDriver::CreateAggregateDevice : kAudioDevicePropertyClockDomain error");
jack_error("JackCoreAudioAdapter::CreateAggregateDevice : kAudioDevicePropertyClockDomain error");
printError(osErr);
} else {
keptclockdomain = (keptclockdomain == 0) ? clockdomain : keptclockdomain;
jack_log("JackCoreAudioDriver::CreateAggregateDevice : input clockdomain = %d", clockdomain);
jack_log("JackCoreAudioAdapter::CreateAggregateDevice : input clockdomain = %d", clockdomain);
if (clockdomain != 0 && clockdomain != keptclockdomain) {
jack_error("JackCoreAudioDriver::CreateAggregateDevice : devices do not share the same clock!! clock drift compensation would be needed...");
jack_error("JackCoreAudioAdapter::CreateAggregateDevice : devices do not share the same clock!! clock drift compensation would be needed...");
need_clock_drift_compensation = true;
}
}
@@ -1135,18 +1134,18 @@ OSStatus JackCoreAudioAdapter::CreateAggregateDeviceAux(vector<AudioDeviceID> ca

for (UInt32 i = 0; i < playbackDeviceID.size(); i++) {
if (SetupSampleRateAux(playbackDeviceID[i], samplerate) < 0) {
jack_error("JackCoreAudioDriver::CreateAggregateDevice : cannot set SR of output device");
jack_error("JackCoreAudioAdapter::CreateAggregateDevice : cannot set SR of output device");
} else {
// Check clock domain
osErr = AudioDeviceGetProperty(playbackDeviceID[i], 0, kAudioDeviceSectionGlobal, kAudioDevicePropertyClockDomain, &outSize, &clockdomain);
if (osErr != 0) {
jack_error("JackCoreAudioDriver::CreateAggregateDevice : kAudioDevicePropertyClockDomain error");
jack_error("JackCoreAudioAdapter::CreateAggregateDevice : kAudioDevicePropertyClockDomain error");
printError(osErr);
} else {
keptclockdomain = (keptclockdomain == 0) ? clockdomain : keptclockdomain;
jack_log("JackCoreAudioDriver::CreateAggregateDevice : output clockdomain = %d", clockdomain);
jack_log("JackCoreAudioAdapter::CreateAggregateDevice : output clockdomain = %d", clockdomain);
if (clockdomain != 0 && clockdomain != keptclockdomain) {
jack_error("JackCoreAudioDriver::CreateAggregateDevice : devices do not share the same clock!! clock drift compensation would be needed...");
jack_error("JackCoreAudioAdapter::CreateAggregateDevice : devices do not share the same clock!! clock drift compensation would be needed...");
need_clock_drift_compensation = true;
}
}
@@ -1175,7 +1174,7 @@ OSStatus JackCoreAudioAdapter::CreateAggregateDeviceAux(vector<AudioDeviceID> ca

osErr = AudioHardwareGetPropertyInfo(kAudioHardwarePropertyPlugInForBundleID, &outSize, &outWritable);
if (osErr != noErr) {
jack_error("JackCoreAudioDriver::CreateAggregateDevice : AudioHardwareGetPropertyInfo kAudioHardwarePropertyPlugInForBundleID error");
jack_error("JackCoreAudioAdapter::CreateAggregateDevice : AudioHardwareGetPropertyInfo kAudioHardwarePropertyPlugInForBundleID error");
printError(osErr);
return osErr;
}
@@ -1191,7 +1190,7 @@ OSStatus JackCoreAudioAdapter::CreateAggregateDeviceAux(vector<AudioDeviceID> ca

osErr = AudioHardwareGetProperty(kAudioHardwarePropertyPlugInForBundleID, &outSize, &pluginAVT);
if (osErr != noErr) {
jack_error("JackCoreAudioDriver::CreateAggregateDevice : AudioHardwareGetProperty kAudioHardwarePropertyPlugInForBundleID error");
jack_error("JackCoreAudioAdapter::CreateAggregateDevice : AudioHardwareGetProperty kAudioHardwarePropertyPlugInForBundleID error");
printError(osErr);
return osErr;
}
@@ -1218,13 +1217,13 @@ OSStatus JackCoreAudioAdapter::CreateAggregateDeviceAux(vector<AudioDeviceID> ca
SInt32 system;
Gestalt(gestaltSystemVersion, &system);

jack_log("JackCoreAudioDriver::CreateAggregateDevice : system version = %x limit = %x", system, 0x00001054);
jack_log("JackCoreAudioAdapter::CreateAggregateDevice : system version = %x limit = %x", system, 0x00001054);

// Starting with 10.5.4 systems, the AD can be internal... (better)
if (system < 0x00001054) {
jack_log("JackCoreAudioDriver::CreateAggregateDevice : public aggregate device....");
jack_log("JackCoreAudioAdapter::CreateAggregateDevice : public aggregate device....");
} else {
jack_log("JackCoreAudioDriver::CreateAggregateDevice : private aggregate device....");
jack_log("JackCoreAudioAdapter::CreateAggregateDevice : private aggregate device....");
CFDictionaryAddValue(aggDeviceDict, CFSTR(kAudioAggregateDeviceIsPrivateKey), AggregateDeviceNumberRef);
}

@@ -1306,14 +1305,14 @@ OSStatus JackCoreAudioAdapter::CreateAggregateDeviceAux(vector<AudioDeviceID> ca

osErr = AudioObjectGetPropertyDataSize(fPluginID, &pluginAOPA, 0, NULL, &outDataSize);
if (osErr != noErr) {
jack_error("JackCoreAudioDriver::CreateAggregateDevice : AudioObjectGetPropertyDataSize error");
jack_error("JackCoreAudioAdapter::CreateAggregateDevice : AudioObjectGetPropertyDataSize error");
printError(osErr);
goto error;
}

osErr = AudioObjectGetPropertyData(fPluginID, &pluginAOPA, sizeof(aggDeviceDict), &aggDeviceDict, &outDataSize, outAggregateDevice);
if (osErr != noErr) {
jack_error("JackCoreAudioDriver::CreateAggregateDevice : AudioObjectGetPropertyData error");
jack_error("JackCoreAudioAdapter::CreateAggregateDevice : AudioObjectGetPropertyData error");
printError(osErr);
goto error;
}
@@ -1332,7 +1331,7 @@ OSStatus JackCoreAudioAdapter::CreateAggregateDeviceAux(vector<AudioDeviceID> ca
outDataSize = sizeof(CFMutableArrayRef);
osErr = AudioObjectSetPropertyData(*outAggregateDevice, &pluginAOPA, 0, NULL, outDataSize, &subDevicesArray);
if (osErr != noErr) {
jack_error("JackCoreAudioDriver::CreateAggregateDevice : AudioObjectSetPropertyData for sub-device list error");
jack_error("JackCoreAudioAdapter::CreateAggregateDevice : AudioObjectSetPropertyData for sub-device list error");
printError(osErr);
goto error;
}
@@ -1352,7 +1351,7 @@ OSStatus JackCoreAudioAdapter::CreateAggregateDeviceAux(vector<AudioDeviceID> ca
outDataSize = sizeof(CFStringRef);
osErr = AudioObjectSetPropertyData(*outAggregateDevice, &pluginAOPA, 0, NULL, outDataSize, &captureDeviceUID[0]); // First apture is master...
if (osErr != noErr) {
jack_error("JackCoreAudioDriver::CreateAggregateDevice : AudioObjectSetPropertyData for master device error");
jack_error("JackCoreAudioAdapter::CreateAggregateDevice : AudioObjectSetPropertyData for master device error");
printError(osErr);
goto error;
}
@@ -1370,19 +1369,19 @@ OSStatus JackCoreAudioAdapter::CreateAggregateDeviceAux(vector<AudioDeviceID> ca
// Get the property data size
osErr = AudioObjectGetPropertyDataSize(*outAggregateDevice, &theAddressOwned, theQualifierDataSize, theQualifierData, &outSize);
if (osErr != noErr) {
jack_error("JackCoreAudioDriver::CreateAggregateDevice kAudioObjectPropertyOwnedObjects error");
jack_error("JackCoreAudioAdapter::CreateAggregateDevice kAudioObjectPropertyOwnedObjects error");
printError(osErr);
}

// Calculate the number of object IDs
subDevicesNum = outSize / sizeof(AudioObjectID);
jack_info("JackCoreAudioDriver::CreateAggregateDevice clock drift compensation, number of sub-devices = %d", subDevicesNum);
jack_info("JackCoreAudioAdapter::CreateAggregateDevice clock drift compensation, number of sub-devices = %d", subDevicesNum);
AudioObjectID subDevices[subDevicesNum];
outSize = sizeof(subDevices);

osErr = AudioObjectGetPropertyData(*outAggregateDevice, &theAddressOwned, theQualifierDataSize, theQualifierData, &outSize, subDevices);
if (osErr != noErr) {
jack_error("JackCoreAudioDriver::CreateAggregateDevice kAudioObjectPropertyOwnedObjects error");
jack_error("JackCoreAudioAdapter::CreateAggregateDevice kAudioObjectPropertyOwnedObjects error");
printError(osErr);
}

@@ -1391,7 +1390,7 @@ OSStatus JackCoreAudioAdapter::CreateAggregateDeviceAux(vector<AudioDeviceID> ca
UInt32 theDriftCompensationValue = 1;
osErr = AudioObjectSetPropertyData(subDevices[index], &theAddressDrift, 0, NULL, sizeof(UInt32), &theDriftCompensationValue);
if (osErr != noErr) {
jack_error("JackCoreAudioDriver::CreateAggregateDevice kAudioSubDevicePropertyDriftCompensation error");
jack_error("JackCoreAudioAdapter::CreateAggregateDevice kAudioSubDevicePropertyDriftCompensation error");
printError(osErr);
}
}


+ 7
- 4
macosx/coreaudio/JackCoreAudioDriver.cpp View File

@@ -386,12 +386,15 @@ OSStatus JackCoreAudioDriver::GetDefaultDevice(AudioDeviceID* id)
jack_log("GetDefaultDevice: input = %ld output = %ld", inDefault, outDefault);

// Get the device only if default input and output are the same
if (inDefault == outDefault) {
*id = inDefault;
return noErr;
} else {
if (inDefault != outDefault) {
jack_error("Default input and output devices are not the same !!");
return kAudioHardwareBadDeviceError;
} else if (inDefault == 0) {
jack_error("Default input and output devices are null !!");
return kAudioHardwareBadDeviceError;
} else {
*id = inDefault;
return noErr;
}
}



Loading…
Cancel
Save