@@ -67,8 +67,8 @@ namespace Jack | |||||
fprintf(file, "set xlabel \"audio cycles\"\n"); | fprintf(file, "set xlabel \"audio cycles\"\n"); | ||||
fprintf(file, "set ylabel \"frames\"\n"); | fprintf(file, "set ylabel \"frames\"\n"); | ||||
fprintf(file, "plot "); | fprintf(file, "plot "); | ||||
fprintf(file, "\"JackAudioAdapter.log\" using 2 title \"Ringbuffer error\" with lines,"); | |||||
fprintf(file, "\"JackAudioAdapter.log\" using 3 title \"Ringbuffer error with timing correction\" with lines"); | |||||
fprintf(file, "\"JackAudioAdapter.log\" using 2 title \"Ringbuffer error\" with dots,"); | |||||
fprintf(file, "\"JackAudioAdapter.log\" using 3 title \"Ringbuffer error with timing correction\" with dots"); | |||||
fprintf(file, "\n unset multiplot\n"); | fprintf(file, "\n unset multiplot\n"); | ||||
fprintf(file, "set output 'AdapterTiming1.svg\n"); | fprintf(file, "set output 'AdapterTiming1.svg\n"); | ||||
@@ -81,8 +81,8 @@ namespace Jack | |||||
fprintf(file, "set xlabel \"audio cycles\"\n"); | fprintf(file, "set xlabel \"audio cycles\"\n"); | ||||
fprintf(file, "set ylabel \"frames\"\n"); | fprintf(file, "set ylabel \"frames\"\n"); | ||||
fprintf(file, "plot "); | fprintf(file, "plot "); | ||||
fprintf(file, "\"JackAudioAdapter.log\" using 2 title \"Consumer interrupt period\" with lines,"); | |||||
fprintf(file, "\"JackAudioAdapter.log\" using 3 title \"Producer interrupt period\" with lines\n"); | |||||
fprintf(file, "\"JackAudioAdapter.log\" using 2 title \"Consumer interrupt period\" with dots,"); | |||||
fprintf(file, "\"JackAudioAdapter.log\" using 3 title \"Producer interrupt period\" with dots\n"); | |||||
fprintf(file, "unset multiplot\n"); | fprintf(file, "unset multiplot\n"); | ||||
fprintf(file, "unset output\n"); | fprintf(file, "unset output\n"); | ||||
@@ -97,8 +97,8 @@ namespace Jack | |||||
fprintf(file, "set xlabel \"audio cycles\"\n"); | fprintf(file, "set xlabel \"audio cycles\"\n"); | ||||
fprintf(file, "set ylabel \"resampling ratio\"\n"); | fprintf(file, "set ylabel \"resampling ratio\"\n"); | ||||
fprintf(file, "plot "); | fprintf(file, "plot "); | ||||
fprintf(file, "\"JackAudioAdapter.log\" using 4 title \"Ratio 1\" with lines,"); | |||||
fprintf(file, "\"JackAudioAdapter.log\" using 5 title \"Ratio 2\" with lines"); | |||||
fprintf(file, "\"JackAudioAdapter.log\" using 4 title \"Ratio 1\" with dots,"); | |||||
fprintf(file, "\"JackAudioAdapter.log\" using 5 title \"Ratio 2\" with dots"); | |||||
fprintf(file, "\n unset multiplot\n"); | fprintf(file, "\n unset multiplot\n"); | ||||
fprintf(file, "set output 'AdapterTiming2.svg\n"); | fprintf(file, "set output 'AdapterTiming2.svg\n"); | ||||
@@ -111,8 +111,8 @@ namespace Jack | |||||
fprintf(file, "set xlabel \"audio cycles\"\n"); | fprintf(file, "set xlabel \"audio cycles\"\n"); | ||||
fprintf(file, "set ylabel \"resampling ratio\"\n"); | fprintf(file, "set ylabel \"resampling ratio\"\n"); | ||||
fprintf(file, "plot "); | fprintf(file, "plot "); | ||||
fprintf(file, "\"JackAudioAdapter.log\" using 4 title \"Ratio 1\" with lines,"); | |||||
fprintf(file, "\"JackAudioAdapter.log\" using 5 title \"Ratio 2\" with lines\n"); | |||||
fprintf(file, "\"JackAudioAdapter.log\" using 4 title \"Ratio 1\" with dots,"); | |||||
fprintf(file, "\"JackAudioAdapter.log\" using 5 title \"Ratio 2\" with dots\n"); | |||||
fprintf(file, "unset multiplot\n"); | fprintf(file, "unset multiplot\n"); | ||||
fprintf(file, "unset output\n"); | fprintf(file, "unset output\n"); | ||||
@@ -127,8 +127,8 @@ namespace Jack | |||||
fprintf(file, "set xlabel \"audio cycles\"\n"); | fprintf(file, "set xlabel \"audio cycles\"\n"); | ||||
fprintf(file, "set ylabel \"frames\"\n"); | fprintf(file, "set ylabel \"frames\"\n"); | ||||
fprintf(file, "plot "); | fprintf(file, "plot "); | ||||
fprintf(file, "\"JackAudioAdapter.log\" using 6 title \"Frames position in consumer ringbuffer\" with lines,"); | |||||
fprintf(file, "\"JackAudioAdapter.log\" using 7 title \"Frames position in producer ringbuffer\" with lines"); | |||||
fprintf(file, "\"JackAudioAdapter.log\" using 6 title \"Frames position in consumer ringbuffer\" with dots,"); | |||||
fprintf(file, "\"JackAudioAdapter.log\" using 7 title \"Frames position in producer ringbuffer\" with dots"); | |||||
fprintf(file, "\n unset multiplot\n"); | fprintf(file, "\n unset multiplot\n"); | ||||
fprintf(file, "set output 'AdapterTiming3.svg\n"); | fprintf(file, "set output 'AdapterTiming3.svg\n"); | ||||
@@ -141,8 +141,8 @@ namespace Jack | |||||
fprintf(file, "set xlabel \"audio cycles\"\n"); | fprintf(file, "set xlabel \"audio cycles\"\n"); | ||||
fprintf(file, "set ylabel \"frames\"\n"); | fprintf(file, "set ylabel \"frames\"\n"); | ||||
fprintf(file, "plot "); | fprintf(file, "plot "); | ||||
fprintf(file, "\"JackAudioAdapter.log\" using 6 title \"Frames position in consumer ringbuffer\" with lines,"); | |||||
fprintf(file, "\"JackAudioAdapter.log\" using 7 title \"Frames position in producer ringbuffer\" with lines\n"); | |||||
fprintf(file, "\"JackAudioAdapter.log\" using 6 title \"Frames position in consumer ringbuffer\" with dots,"); | |||||
fprintf(file, "\"JackAudioAdapter.log\" using 7 title \"Frames position in producer ringbuffer\" with dots\n"); | |||||
fprintf(file, "unset multiplot\n"); | fprintf(file, "unset multiplot\n"); | ||||
fprintf(file, "unset output\n"); | fprintf(file, "unset output\n"); | ||||
@@ -173,9 +173,11 @@ namespace Jack | |||||
for (int i = 0; i < fCaptureChannels; i++) { | for (int i = 0; i < fCaptureChannels; i++) { | ||||
fCaptureRingBuffer[i]->Reset(fRingbufferCurSize); | fCaptureRingBuffer[i]->Reset(fRingbufferCurSize); | ||||
fPIControllerCapture[i]->Reset(); | |||||
} | } | ||||
for (int i = 0; i < fPlaybackChannels; i++) { | for (int i = 0; i < fPlaybackChannels; i++) { | ||||
fPlaybackRingBuffer[i]->Reset(fRingbufferCurSize); | fPlaybackRingBuffer[i]->Reset(fRingbufferCurSize); | ||||
fPIControllerPlayback[i]->Reset(); | |||||
} | } | ||||
} | } | ||||
@@ -191,9 +193,11 @@ namespace Jack | |||||
#else | #else | ||||
void JackAudioAdapterInterface::Create() | void JackAudioAdapterInterface::Create() | ||||
{ | { | ||||
//ringbuffers | |||||
fCaptureRingBuffer = new JackResampler*[fCaptureChannels]; | fCaptureRingBuffer = new JackResampler*[fCaptureChannels]; | ||||
fPIControllerCapture = new JackPIController*[fCaptureChannels]; | |||||
fPlaybackRingBuffer = new JackResampler*[fPlaybackChannels]; | fPlaybackRingBuffer = new JackResampler*[fPlaybackChannels]; | ||||
fPIControllerPlayback = new JackPIController*[fPlaybackChannels]; | |||||
if (fAdaptative) { | if (fAdaptative) { | ||||
AdaptRingBufferSize(); | AdaptRingBufferSize(); | ||||
@@ -205,13 +209,22 @@ namespace Jack | |||||
jack_info("Fixed ringbuffer size = %d frames", fRingbufferCurSize); | jack_info("Fixed ringbuffer size = %d frames", fRingbufferCurSize); | ||||
} | } | ||||
if (fResampleRatioPerChannel){ | |||||
jack_info("Resample ratio tracked independently for each capture and playback channel"); | |||||
} else { | |||||
jack_info("Shared resample ratio tracking for all capture and playback channels"); | |||||
} | |||||
for (int i = 0; i < fCaptureChannels; i++ ) { | for (int i = 0; i < fCaptureChannels; i++ ) { | ||||
fCaptureRingBuffer[i] = new JackLibSampleRateResampler(fQuality); | fCaptureRingBuffer[i] = new JackLibSampleRateResampler(fQuality); | ||||
fCaptureRingBuffer[i]->Reset(fRingbufferCurSize); | fCaptureRingBuffer[i]->Reset(fRingbufferCurSize); | ||||
fPIControllerCapture[i] = new JackPIController(double(fHostSampleRate) / double(fAdaptedSampleRate)); | |||||
} | } | ||||
for (int i = 0; i < fPlaybackChannels; i++ ) { | for (int i = 0; i < fPlaybackChannels; i++ ) { | ||||
fPlaybackRingBuffer[i] = new JackLibSampleRateResampler(fQuality); | fPlaybackRingBuffer[i] = new JackLibSampleRateResampler(fQuality); | ||||
fPlaybackRingBuffer[i]->Reset(fRingbufferCurSize); | fPlaybackRingBuffer[i]->Reset(fRingbufferCurSize); | ||||
fPIControllerPlayback[i] = new JackPIController(double(fHostSampleRate) / double(fAdaptedSampleRate)); | |||||
} | } | ||||
if (fCaptureChannels > 0) { | if (fCaptureChannels > 0) { | ||||
@@ -227,40 +240,79 @@ namespace Jack | |||||
{ | { | ||||
for (int i = 0; i < fCaptureChannels; i++) { | for (int i = 0; i < fCaptureChannels; i++) { | ||||
delete(fCaptureRingBuffer[i]); | delete(fCaptureRingBuffer[i]); | ||||
delete(fPIControllerCapture[i]); | |||||
} | } | ||||
for (int i = 0; i < fPlaybackChannels; i++) { | for (int i = 0; i < fPlaybackChannels; i++) { | ||||
delete (fPlaybackRingBuffer[i]); | delete (fPlaybackRingBuffer[i]); | ||||
delete(fPIControllerPlayback[i]); | |||||
} | } | ||||
delete[] fCaptureRingBuffer; | delete[] fCaptureRingBuffer; | ||||
delete [] fPIControllerCapture; | |||||
delete[] fPlaybackRingBuffer; | delete[] fPlaybackRingBuffer; | ||||
delete [] fPIControllerPlayback; | |||||
} | |||||
double JackAudioAdapterInterface::GetSharedRatio(int delta_frames) | |||||
{ | |||||
if (0 < fCaptureChannels) { | |||||
return fPIControllerCapture[0]->GetRatio(fCaptureRingBuffer[0]->GetReadBalance() - delta_frames); | |||||
} | |||||
else if (0 < fPlaybackChannels) { | |||||
return fPIControllerPlayback[0]->GetRatio(fPlaybackRingBuffer[0]->GetReadBalance() - delta_frames); | |||||
} | |||||
return 1; | |||||
} | } | ||||
int JackAudioAdapterInterface::PushAndPull(float** inputBuffer, float** outputBuffer, unsigned int frames) | int JackAudioAdapterInterface::PushAndPull(float** inputBuffer, float** outputBuffer, unsigned int frames) | ||||
{ | { | ||||
// Most of the time we can use a shared ratio calculated on a single input or output channel ringbuffer and apply this ratio also to all other channels. | |||||
// In some situations we see that there is a drift between the channels coming from or going to the same device. | |||||
// On those devices we need to activate the fResampleRatioPerChannel option so that every channel will calculate and keep track of its own resample ratio | |||||
// in order to keep his ringbuffer in a good balance which is half used for reading and half used for writing. | |||||
// Not using this option when it is needed will result in buffer errors followed by resets for all the buffers. | |||||
// cfr. https://github.com/jackaudio/jack2/issues/534 | |||||
bool failure = false; | bool failure = false; | ||||
fRunning = true; | fRunning = true; | ||||
// Finer estimation of the position in the ringbuffer | // Finer estimation of the position in the ringbuffer | ||||
int delta_frames = (fPullAndPushTime > 0) ? (int)((float(long(GetMicroSeconds() - fPullAndPushTime)) * float(fAdaptedSampleRate)) / 1000000.f) : 0; | int delta_frames = (fPullAndPushTime > 0) ? (int)((float(long(GetMicroSeconds() - fPullAndPushTime)) * float(fAdaptedSampleRate)) / 1000000.f) : 0; | ||||
double ratio = 1; | |||||
// TODO : done like this just to avoid crash when input only or output only... | |||||
if (fCaptureChannels > 0) { | |||||
ratio = fPIControler.GetRatio(fCaptureRingBuffer[0]->GetError() - delta_frames); | |||||
} else if (fPlaybackChannels > 0) { | |||||
ratio = fPIControler.GetRatio(fPlaybackRingBuffer[0]->GetError() - delta_frames); | |||||
double ratioC[fCaptureChannels]; | |||||
double ratioP[fPlaybackChannels]; | |||||
if(!fResampleRatioPerChannel) { | |||||
double shared_ratio = GetSharedRatio(delta_frames); | |||||
for (int i = 0; i < fCaptureChannels; i++) { | |||||
ratioC[i] = shared_ratio; | |||||
} | |||||
for (int i = 0; i < fPlaybackChannels; i++) { | |||||
ratioP[i] = shared_ratio; | |||||
} | |||||
#ifdef JACK_MONITOR | |||||
if (fCaptureRingBuffer && fCaptureRingBuffer[0] != NULL) { | |||||
fTable.Write(fCaptureRingBuffer[0]->GetReadBalance(), fCaptureRingBuffer[0]->GetReadBalance() - delta_frames, shared_ratio, 1/shared_ratio, fCaptureRingBuffer[0]->ReadSpace(), fCaptureRingBuffer[0]->ReadSpace()); | |||||
} | |||||
#endif // JACK_MONITOR | |||||
} | |||||
else { | |||||
for (int i = 0; i < fCaptureChannels; i++) { | |||||
ratioC[i] = fPIControllerCapture[i]->GetRatio(fCaptureRingBuffer[i]->GetReadBalance() - delta_frames); | |||||
} | |||||
for (int i = 0; i < fPlaybackChannels; i++) { | |||||
ratioP[i] = fPIControllerPlayback[i]->GetRatio(fPlaybackRingBuffer[i]->GetWriteBalance() + delta_frames); | |||||
} | |||||
#ifdef JACK_MONITOR | |||||
if (fCaptureRingBuffer && fCaptureRingBuffer[0] != NULL) { | |||||
fTable.Write(fCaptureRingBuffer[0]->GetReadBalance(), fCaptureRingBuffer[0]->GetReadBalance() - delta_frames, ratioC[0], 1/ratioC[0], fCaptureRingBuffer[0]->ReadSpace(), fCaptureRingBuffer[0]->ReadSpace()); | |||||
} | |||||
#endif // JACK_MONITOR | |||||
} | } | ||||
#ifdef JACK_MONITOR | |||||
if (fCaptureRingBuffer && fCaptureRingBuffer[0] != NULL) | |||||
fTable.Write(fCaptureRingBuffer[0]->GetError(), fCaptureRingBuffer[0]->GetError() - delta_frames, ratio, 1/ratio, fCaptureRingBuffer[0]->ReadSpace(), fCaptureRingBuffer[0]->ReadSpace()); | |||||
#endif | |||||
// Push/pull from ringbuffer | // Push/pull from ringbuffer | ||||
for (int i = 0; i < fCaptureChannels; i++) { | for (int i = 0; i < fCaptureChannels; i++) { | ||||
fCaptureRingBuffer[i]->SetRatio(ratio); | |||||
fCaptureRingBuffer[i]->SetRatio(ratioC[i]); | |||||
if (inputBuffer[i]) { | if (inputBuffer[i]) { | ||||
if (fCaptureRingBuffer[i]->WriteResample(inputBuffer[i], frames) < frames) { | if (fCaptureRingBuffer[i]->WriteResample(inputBuffer[i], frames) < frames) { | ||||
failure = true; | failure = true; | ||||
@@ -269,7 +321,7 @@ namespace Jack | |||||
} | } | ||||
for (int i = 0; i < fPlaybackChannels; i++) { | for (int i = 0; i < fPlaybackChannels; i++) { | ||||
fPlaybackRingBuffer[i]->SetRatio(1/ratio); | |||||
fPlaybackRingBuffer[i]->SetRatio(1 / ratioP[i]); | |||||
if (outputBuffer[i]) { | if (outputBuffer[i]) { | ||||
if (fPlaybackRingBuffer[i]->ReadResample(outputBuffer[i], frames) < frames) { | if (fPlaybackRingBuffer[i]->ReadResample(outputBuffer[i], frames) < frames) { | ||||
failure = true; | failure = true; | ||||
@@ -347,14 +399,32 @@ namespace Jack | |||||
int JackAudioAdapterInterface::SetHostSampleRate(jack_nframes_t sample_rate) | int JackAudioAdapterInterface::SetHostSampleRate(jack_nframes_t sample_rate) | ||||
{ | { | ||||
fHostSampleRate = sample_rate; | fHostSampleRate = sample_rate; | ||||
fPIControler.Init(double(fHostSampleRate) / double(fAdaptedSampleRate)); | |||||
if (NULL != fPIControllerCapture) { | |||||
for (int i = 0; i < fCaptureChannels; i++) { | |||||
fPIControllerCapture[i]->Init(double(fHostSampleRate) / double(fAdaptedSampleRate)); | |||||
} | |||||
} | |||||
if (NULL != fPIControllerPlayback) { | |||||
for (int i = 0; i < fPlaybackChannels; i++) { | |||||
fPIControllerPlayback[i]->Init(double(fHostSampleRate) / double(fAdaptedSampleRate)); | |||||
} | |||||
} | |||||
return 0; | return 0; | ||||
} | } | ||||
int JackAudioAdapterInterface::SetAdaptedSampleRate(jack_nframes_t sample_rate) | int JackAudioAdapterInterface::SetAdaptedSampleRate(jack_nframes_t sample_rate) | ||||
{ | { | ||||
fAdaptedSampleRate = sample_rate; | fAdaptedSampleRate = sample_rate; | ||||
fPIControler.Init(double(fHostSampleRate) / double(fAdaptedSampleRate)); | |||||
if (NULL != fPIControllerCapture) { | |||||
for (int i = 0; i < fCaptureChannels; i++) { | |||||
fPIControllerCapture[i]->Init(double(fHostSampleRate) / double(fAdaptedSampleRate)); | |||||
} | |||||
} | |||||
if (NULL != fPIControllerPlayback) { | |||||
for (int i = 0; i < fPlaybackChannels; i++ ) { | |||||
fPIControllerPlayback[i]->Init(double(fHostSampleRate) / double(fAdaptedSampleRate)); | |||||
} | |||||
} | |||||
return 0; | return 0; | ||||
} | } | ||||
@@ -82,9 +82,9 @@ namespace Jack | |||||
jack_nframes_t fAdaptedBufferSize; | jack_nframes_t fAdaptedBufferSize; | ||||
jack_nframes_t fAdaptedSampleRate; | jack_nframes_t fAdaptedSampleRate; | ||||
//PI controller | |||||
JackPIControler fPIControler; | |||||
//PI controler | |||||
JackPIController** fPIControllerCapture; | |||||
JackPIController** fPIControllerPlayback; | |||||
JackResampler** fCaptureRingBuffer; | JackResampler** fCaptureRingBuffer; | ||||
JackResampler** fPlaybackRingBuffer; | JackResampler** fPlaybackRingBuffer; | ||||
@@ -94,6 +94,7 @@ namespace Jack | |||||
bool fRunning; | bool fRunning; | ||||
bool fAdaptative; | bool fAdaptative; | ||||
bool fResampleRatioPerChannel; | |||||
void ResetRingBuffers(); | void ResetRingBuffers(); | ||||
void AdaptRingBufferSize(); | void AdaptRingBufferSize(); | ||||
@@ -108,13 +109,14 @@ namespace Jack | |||||
fHostSampleRate(sample_rate), | fHostSampleRate(sample_rate), | ||||
fAdaptedBufferSize(buffer_size), | fAdaptedBufferSize(buffer_size), | ||||
fAdaptedSampleRate(sample_rate), | fAdaptedSampleRate(sample_rate), | ||||
fPIControler(sample_rate / sample_rate, 256), | |||||
fPIControllerCapture(NULL), fPIControllerPlayback(NULL), | |||||
fCaptureRingBuffer(NULL), fPlaybackRingBuffer(NULL), | fCaptureRingBuffer(NULL), fPlaybackRingBuffer(NULL), | ||||
fQuality(0), | fQuality(0), | ||||
fRingbufferCurSize(ring_buffer_size), | fRingbufferCurSize(ring_buffer_size), | ||||
fPullAndPushTime(0), | fPullAndPushTime(0), | ||||
fRunning(false), | fRunning(false), | ||||
fAdaptative(true) | |||||
fAdaptative(true), | |||||
fResampleRatioPerChannel(false) | |||||
{} | {} | ||||
JackAudioAdapterInterface(jack_nframes_t host_buffer_size, | JackAudioAdapterInterface(jack_nframes_t host_buffer_size, | ||||
@@ -128,12 +130,14 @@ namespace Jack | |||||
fHostSampleRate(host_sample_rate), | fHostSampleRate(host_sample_rate), | ||||
fAdaptedBufferSize(adapted_buffer_size), | fAdaptedBufferSize(adapted_buffer_size), | ||||
fAdaptedSampleRate(adapted_sample_rate), | fAdaptedSampleRate(adapted_sample_rate), | ||||
fPIControler(host_sample_rate / host_sample_rate, 256), | |||||
fPIControllerCapture(NULL), fPIControllerPlayback(NULL), | |||||
fCaptureRingBuffer(NULL), fPlaybackRingBuffer(NULL), | |||||
fQuality(0), | fQuality(0), | ||||
fRingbufferCurSize(ring_buffer_size), | fRingbufferCurSize(ring_buffer_size), | ||||
fPullAndPushTime(0), | fPullAndPushTime(0), | ||||
fRunning(false), | fRunning(false), | ||||
fAdaptative(true) | |||||
fAdaptative(true), | |||||
fResampleRatioPerChannel(false) | |||||
{} | {} | ||||
virtual ~JackAudioAdapterInterface() | virtual ~JackAudioAdapterInterface() | ||||
@@ -168,6 +172,7 @@ namespace Jack | |||||
virtual int GetInputLatency(int port_index) { return 0; } | virtual int GetInputLatency(int port_index) { return 0; } | ||||
virtual int GetOutputLatency(int port_index) { return 0; } | virtual int GetOutputLatency(int port_index) { return 0; } | ||||
double GetSharedRatio(int delta_frames); | |||||
int PushAndPull(jack_default_audio_sample_t** inputBuffer, jack_default_audio_sample_t** outputBuffer, unsigned int frames); | int PushAndPull(jack_default_audio_sample_t** inputBuffer, jack_default_audio_sample_t** outputBuffer, unsigned int frames); | ||||
int PullAndPush(jack_default_audio_sample_t** inputBuffer, jack_default_audio_sample_t** outputBuffer, unsigned int frames); | int PullAndPush(jack_default_audio_sample_t** inputBuffer, jack_default_audio_sample_t** outputBuffer, unsigned int frames); | ||||
@@ -220,102 +220,44 @@ namespace Jack | |||||
#endif | #endif | ||||
/* | /* | ||||
Torben Hohn PI controller from JACK1 | |||||
Torben Hohn PI controler from JACK1 | |||||
*/ | */ | ||||
struct JackPIControler { | |||||
struct JackPIController { | |||||
double resample_mean; | |||||
double static_resample_factor; | double static_resample_factor; | ||||
double* offset_array; | |||||
double* window_array; | |||||
int offset_differential_index; | |||||
double offset_integral; | double offset_integral; | ||||
double catch_factor; | double catch_factor; | ||||
double catch_factor2; | double catch_factor2; | ||||
double pclamp; | |||||
double controlquant; | |||||
int smooth_size; | |||||
double hann(double x) | double hann(double x) | ||||
{ | { | ||||
return 0.5 * (1.0 - cos(2 * M_PI * x)); | return 0.5 * (1.0 - cos(2 * M_PI * x)); | ||||
} | } | ||||
JackPIControler(double resample_factor, int fir_size) | |||||
JackPIController(double resample_factor) | |||||
{ | { | ||||
resample_mean = resample_factor; | |||||
static_resample_factor = resample_factor; | static_resample_factor = resample_factor; | ||||
offset_array = new double[fir_size]; | |||||
window_array = new double[fir_size]; | |||||
offset_differential_index = 0; | |||||
offset_integral = 0.0; | offset_integral = 0.0; | ||||
smooth_size = fir_size; | |||||
for (int i = 0; i < fir_size; i++) { | |||||
offset_array[i] = 0.0; | |||||
window_array[i] = hann(double(i) / (double(fir_size) - 1.0)); | |||||
} | |||||
// These values could be configurable | // These values could be configurable | ||||
catch_factor = 100000; | catch_factor = 100000; | ||||
catch_factor2 = 10000; | catch_factor2 = 10000; | ||||
pclamp = 15.0; | |||||
controlquant = 10000.0; | |||||
} | } | ||||
~JackPIControler() | |||||
~JackPIController() | |||||
{ | { | ||||
delete[] offset_array; | |||||
delete[] window_array; | |||||
} | } | ||||
void Init(double resample_factor) | void Init(double resample_factor) | ||||
{ | { | ||||
resample_mean = resample_factor; | |||||
static_resample_factor = resample_factor; | static_resample_factor = resample_factor; | ||||
} | } | ||||
/* | |||||
double GetRatio(int fill_level) | |||||
void Reset() | |||||
{ | { | ||||
double offset = fill_level; | |||||
// Save offset. | |||||
offset_array[(offset_differential_index++) % smooth_size] = offset; | |||||
// Build the mean of the windowed offset array basically fir lowpassing. | |||||
double smooth_offset = 0.0; | |||||
for (int i = 0; i < smooth_size; i++) { | |||||
smooth_offset += offset_array[(i + offset_differential_index - 1) % smooth_size] * window_array[i]; | |||||
} | |||||
smooth_offset /= double(smooth_size); | |||||
// This is the integral of the smoothed_offset | |||||
offset_integral += smooth_offset; | |||||
// Clamp offset : the smooth offset still contains unwanted noise which would go straight onto the resample coeff. | |||||
// It only used in the P component and the I component is used for the fine tuning anyways. | |||||
if (fabs(smooth_offset) < pclamp) | |||||
smooth_offset = 0.0; | |||||
// Ok, now this is the PI controller. | |||||
// u(t) = K * (e(t) + 1/T \int e(t') dt') | |||||
// Kp = 1/catch_factor and T = catch_factor2 Ki = Kp/T | |||||
double current_resample_factor | |||||
= static_resample_factor - smooth_offset / catch_factor - offset_integral / catch_factor / catch_factor2; | |||||
// Now quantize this value around resample_mean, so that the noise which is in the integral component doesn't hurt. | |||||
current_resample_factor = floor((current_resample_factor - resample_mean) * controlquant + 0.5) / controlquant + resample_mean; | |||||
// Calculate resample_mean so we can init ourselves to saner values. | |||||
resample_mean = 0.9999 * resample_mean + 0.0001 * current_resample_factor; | |||||
return current_resample_factor; | |||||
offset_integral = 0.0; | |||||
} | } | ||||
*/ | |||||
double GetRatio(int error) | double GetRatio(int error) | ||||
{ | { | ||||
@@ -329,20 +271,6 @@ namespace Jack | |||||
// Kp = 1/catch_factor and T = catch_factor2 Ki = Kp/T | // Kp = 1/catch_factor and T = catch_factor2 Ki = Kp/T | ||||
return static_resample_factor - smooth_offset/catch_factor - offset_integral/catch_factor/catch_factor2; | return static_resample_factor - smooth_offset/catch_factor - offset_integral/catch_factor/catch_factor2; | ||||
} | } | ||||
void OurOfBounds() | |||||
{ | |||||
int i; | |||||
// Set the resample_rate... we need to adjust the offset integral, to do this. | |||||
// first look at the PI controller, this code is just a special case, which should never execute once | |||||
// everything is swung in. | |||||
offset_integral = - (resample_mean - static_resample_factor) * catch_factor * catch_factor2; | |||||
// Also clear the array. we are beginning a new control cycle. | |||||
for (i = 0; i < smooth_size; i++) { | |||||
offset_array[i] = 0.0; | |||||
} | |||||
} | |||||
}; | }; | ||||
} | } | ||||
@@ -995,9 +995,11 @@ struct JackNetAdapter : public JackAudioAdapterInterface { | |||||
if (fCaptureChannels > 0) { | if (fCaptureChannels > 0) { | ||||
fCaptureRingBuffer = new JackResampler*[fCaptureChannels]; | fCaptureRingBuffer = new JackResampler*[fCaptureChannels]; | ||||
fPIControllerCapture = new JackPIController*[fCaptureChannels]; | |||||
} | } | ||||
if (fPlaybackChannels > 0) { | if (fPlaybackChannels > 0) { | ||||
fPlaybackRingBuffer = new JackResampler*[fPlaybackChannels]; | fPlaybackRingBuffer = new JackResampler*[fPlaybackChannels]; | ||||
fPIControllerPlayback = new JackPIController*[fPlaybackChannels]; | |||||
} | } | ||||
if (fAdaptative) { | if (fAdaptative) { | ||||
@@ -1013,10 +1015,12 @@ struct JackNetAdapter : public JackAudioAdapterInterface { | |||||
for (int i = 0; i < fCaptureChannels; i++ ) { | for (int i = 0; i < fCaptureChannels; i++ ) { | ||||
fCaptureRingBuffer[i] = new JackResampler(); | fCaptureRingBuffer[i] = new JackResampler(); | ||||
fCaptureRingBuffer[i]->Reset(fRingbufferCurSize); | fCaptureRingBuffer[i]->Reset(fRingbufferCurSize); | ||||
fPIControllerCapture[i] = new JackPIController(double(fHostSampleRate) / double(fAdaptedSampleRate)); | |||||
} | } | ||||
for (int i = 0; i < fPlaybackChannels; i++ ) { | for (int i = 0; i < fPlaybackChannels; i++ ) { | ||||
fPlaybackRingBuffer[i] = new JackResampler(); | fPlaybackRingBuffer[i] = new JackResampler(); | ||||
fPlaybackRingBuffer[i]->Reset(fRingbufferCurSize); | fPlaybackRingBuffer[i]->Reset(fRingbufferCurSize); | ||||
fPIControllerPlayback[i] = new JackPIController(double(fHostSampleRate) / double(fAdaptedSampleRate)); | |||||
} | } | ||||
if (fCaptureChannels > 0) { | if (fCaptureChannels > 0) { | ||||
@@ -1036,9 +1040,11 @@ struct JackNetAdapter : public JackAudioAdapterInterface { | |||||
{ | { | ||||
for (int i = 0; i < fCaptureChannels; i++ ) { | for (int i = 0; i < fCaptureChannels; i++ ) { | ||||
fCaptureRingBuffer[i]->Reset(fRingbufferCurSize); | fCaptureRingBuffer[i]->Reset(fRingbufferCurSize); | ||||
fPIControllerCapture[i]->Reset(); | |||||
} | } | ||||
for (int i = 0; i < fPlaybackChannels; i++ ) { | for (int i = 0; i < fPlaybackChannels; i++ ) { | ||||
fPlaybackRingBuffer[i]->Reset(fRingbufferCurSize); | fPlaybackRingBuffer[i]->Reset(fRingbufferCurSize); | ||||
fPIControllerPlayback[i]->Reset(); | |||||
} | } | ||||
} | } | ||||
@@ -124,6 +124,9 @@ namespace Jack | |||||
fRingbufferCurSize = param->value.ui; | fRingbufferCurSize = param->value.ui; | ||||
fAdaptative = false; | fAdaptative = false; | ||||
break; | break; | ||||
case 'R': | |||||
fResampleRatioPerChannel = true; | |||||
break; | |||||
} | } | ||||
} | } | ||||
@@ -462,6 +465,10 @@ extern "C" | |||||
value.i = false; | value.i = false; | ||||
jack_driver_descriptor_add_parameter(desc, &filler, "auto-connect", 'c', JackDriverParamBool, &value, NULL, "Auto connect netadapter to system ports", NULL); | jack_driver_descriptor_add_parameter(desc, &filler, "auto-connect", 'c', JackDriverParamBool, &value, NULL, "Auto connect netadapter to system ports", NULL); | ||||
value.i = false; | |||||
jack_driver_descriptor_add_parameter(desc, &filler, "multi-ratios", 'R', JackDriverParamBool, &value, NULL, "Calculate resample ratio per channel" | |||||
, "Calculate resample ratio per channel in case of repeated ringbuffer errors because of clock drift between the channels from the same device."); | |||||
return desc; | return desc; | ||||
} | } | ||||
@@ -65,10 +65,14 @@ class JackRingBuffer | |||||
virtual unsigned int ReadSpace(); | virtual unsigned int ReadSpace(); | ||||
virtual unsigned int WriteSpace(); | virtual unsigned int WriteSpace(); | ||||
unsigned int GetError() | |||||
unsigned int GetReadBalance() | |||||
{ | { | ||||
return (jack_ringbuffer_read_space(fRingBuffer) / sizeof(float)) - (fRingBufferSize / 2); | return (jack_ringbuffer_read_space(fRingBuffer) / sizeof(float)) - (fRingBufferSize / 2); | ||||
} | } | ||||
unsigned int GetWriteBalance() | |||||
{ | |||||
return (jack_ringbuffer_write_space(fRingBuffer) / sizeof(float)) - (fRingBufferSize / 2); | |||||
} | |||||
}; | }; | ||||
@@ -86,6 +86,9 @@ namespace Jack | |||||
fRingbufferCurSize = param->value.ui; | fRingbufferCurSize = param->value.ui; | ||||
fAdaptative = false; | fAdaptative = false; | ||||
break; | break; | ||||
case 'R': | |||||
fResampleRatioPerChannel = true; | |||||
break; | |||||
} | } | ||||
} | } | ||||
@@ -228,6 +231,9 @@ extern "C" | |||||
value.ui = 32768; | value.ui = 32768; | ||||
jack_driver_descriptor_add_parameter(desc, &filler, "ring-buffer", 'g', JackDriverParamUInt, &value, NULL, "Fixed ringbuffer size", "Fixed ringbuffer size (if not set => automatic adaptative)"); | jack_driver_descriptor_add_parameter(desc, &filler, "ring-buffer", 'g', JackDriverParamUInt, &value, NULL, "Fixed ringbuffer size", "Fixed ringbuffer size (if not set => automatic adaptative)"); | ||||
value.i = false; | |||||
jack_driver_descriptor_add_parameter(desc, &filler, "multi-ratios", 'R', JackDriverParamBool, &value, NULL, "Calculate resample ratio per channel" | |||||
, "Calculate resample ratio per channel in case of repeated ringbuffer errors because of clock drift between the channels from the same device."); | |||||
return desc; | return desc; | ||||
} | } | ||||
@@ -409,6 +409,10 @@ JackCoreAudioAdapter::JackCoreAudioAdapter(jack_nframes_t buffer_size, jack_nfra | |||||
case 's': | case 's': | ||||
fClockDriftCompensate = true; | fClockDriftCompensate = true; | ||||
break; | break; | ||||
case 'R': | |||||
fResampleRatioPerChannel = true; | |||||
break; | |||||
} | } | ||||
} | } | ||||
@@ -1745,6 +1749,10 @@ extern "C" | |||||
value.i = false; | value.i = false; | ||||
jack_driver_descriptor_add_parameter(desc, &filler, "auto-connect", 'c', JackDriverParamBool, &value, NULL, "Auto connect audioadapter to system ports", NULL); | jack_driver_descriptor_add_parameter(desc, &filler, "auto-connect", 'c', JackDriverParamBool, &value, NULL, "Auto connect audioadapter to system ports", NULL); | ||||
value.i = false; | |||||
jack_driver_descriptor_add_parameter(desc, &filler, "multi-ratios", 'R', JackDriverParamBool, &value, NULL, "Calculate resample ratio per channel" | |||||
, "Calculate resample ratio per channel in case of repeated ringbuffer errors because of clock drift between the channels from the same device."); | |||||
return desc; | return desc; | ||||
} | } | ||||
@@ -187,6 +187,10 @@ JackOSSAdapter::JackOSSAdapter(jack_nframes_t buffer_size, jack_nframes_t sample | |||||
fAdaptative = false; | fAdaptative = false; | ||||
break; | break; | ||||
case 'R': | |||||
fResampleRatioPerChannel = true; | |||||
break; | |||||
} | } | ||||
} | } | ||||
@@ -674,6 +678,10 @@ extern "C" | |||||
value.i = 32768; | value.i = 32768; | ||||
jack_driver_descriptor_add_parameter(desc, &filler, "ring-buffer", 'g', JackDriverParamInt, &value, NULL, "Fixed ringbuffer size", "Fixed ringbuffer size (if not set => automatic adaptative)"); | jack_driver_descriptor_add_parameter(desc, &filler, "ring-buffer", 'g', JackDriverParamInt, &value, NULL, "Fixed ringbuffer size", "Fixed ringbuffer size (if not set => automatic adaptative)"); | ||||
value.i = false; | |||||
jack_driver_descriptor_add_parameter(desc, &filler, "multi-ratios", 'R', JackDriverParamBool, &value, NULL, "Calculate resample ratio per channel" | |||||
, "Calculate resample ratio per channel in case of repeated ringbuffer errors because of clock drift between the channels from the same device."); | |||||
return desc; | return desc; | ||||
} | } | ||||
@@ -92,6 +92,9 @@ namespace Jack | |||||
fRingbufferCurSize = param->value.ui; | fRingbufferCurSize = param->value.ui; | ||||
fAdaptative = false; | fAdaptative = false; | ||||
break; | break; | ||||
case 'R': | |||||
fResampleRatioPerChannel = true; | |||||
break; | |||||
} | } | ||||
} | } | ||||
@@ -235,6 +238,10 @@ extern "C" | |||||
value.ui = 32768; | value.ui = 32768; | ||||
jack_driver_descriptor_add_parameter(desc, &filler, "ring-buffer", 'g', JackDriverParamUInt, &value, NULL, "Fixed ringbuffer size", "Fixed ringbuffer size (if not set => automatic adaptative)"); | jack_driver_descriptor_add_parameter(desc, &filler, "ring-buffer", 'g', JackDriverParamUInt, &value, NULL, "Fixed ringbuffer size", "Fixed ringbuffer size (if not set => automatic adaptative)"); | ||||
value.i = false; | |||||
jack_driver_descriptor_add_parameter(desc, &filler, "multi-ratios", 'R', JackDriverParamBool, &value, NULL, "Calculate resample ratio per channel" | |||||
, "Calculate resample ratio per channel in case of repeated ringbuffer errors because of clock drift between the channels from the same device."); | |||||
return desc; | return desc; | ||||
} | } | ||||