From 957cf404715db6795d562379d60bce3f3f1475d8 Mon Sep 17 00:00:00 2001 From: dagargo Date: Thu, 4 Dec 2025 18:32:05 +0100 Subject: [PATCH 1/7] Change verbosity levels to let level 2 print the status As printing too much might lead to xruns, ratios can be printed on level 2 more safely. --- src/dll.c | 22 +++++++++++----------- src/engine.c | 8 ++++---- src/resampler.c | 10 +++++----- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/dll.c b/src/dll.c index fd5aa1f..c5c4aa5 100644 --- a/src/dll.c +++ b/src/dll.c @@ -50,7 +50,7 @@ ow_dll_overbridge_init (void *data, double samplerate, uint32_t frames) struct ow_dll *dll = data; struct ow_dll_overbridge *dll_ob = &dll->dll_overbridge; - debug_print (2, + debug_print (3, "Initializing Overbridge side of DLL (%.1f Hz, %d frames)...", samplerate, frames); @@ -68,13 +68,13 @@ ow_dll_overbridge_update (void *data, uint32_t frames, uint64_t t) struct ow_dll *dll = data; struct ow_dll_overbridge *dll_ob = &dll->dll_overbridge; - debug_print (4, "Updating Overbridge side of DLL..."); + debug_print (5, "Updating Overbridge side of DLL..."); time = UINT64_USEC_TO_DOUBLE_SEC (t); if (dll_ob->boot) { - debug_print (3, "Booting Overbridge side of DLL..."); + debug_print (4, "Booting Overbridge side of DLL..."); dll_ob->i0.time = time; dll_ob->i1.time = dll_ob->i0.time + dll_ob->dt; @@ -97,7 +97,7 @@ ow_dll_overbridge_update (void *data, uint32_t frames, uint64_t t) dll_ob->i0.frames = dll_ob->i1.frames; dll_ob->i1.frames += frames; - debug_print (4, "time: %3.6f; t0: %3.6f: t1: %3.6f; f0: % 8d; f1: % 8d", + debug_print (5, "time: %3.6f; t0: %3.6f: t1: %3.6f; f0: % 8d; f1: % 8d", time, dll_ob->i0.time, dll_ob->i1.time, dll_ob->i0.frames, dll_ob->i1.frames); } @@ -110,7 +110,7 @@ ow_dll_host_update_error (struct ow_dll *dll, uint64_t t) int32_t delta_frames_exp, delta_frames_act; double time = UINT64_USEC_TO_DOUBLE_SEC (t); - debug_print (4, "Updating error in host side of DLL (time %lu)...", t); + debug_print (5, "Updating error in host side of DLL (time %lu)...", t); delta_frames_exp = dll->i1.frames - dll->i0.frames; dn = wrap_time (time - dll->i0.time, dll->t_quantum); @@ -121,14 +121,14 @@ ow_dll_host_update_error (struct ow_dll *dll, uint64_t t) if (dll->boot) { - debug_print (3, "Booting host side of DLL..."); + debug_print (4, "Booting host side of DLL..."); int n = (int) (floor (dll->err + 0.5)); dll->frames += n; dll->err -= n; dll->boot = 0; } - debug_print (4, + debug_print (5, "delta_frames_exp: %d; delta_frames_act: %d; delta_overbridge: %f; DLL target delay: %d; DLL error: %f", delta_frames_exp, delta_frames_act, delta_overbridge, dll->target_delay, dll->err); @@ -137,20 +137,20 @@ ow_dll_host_update_error (struct ow_dll *dll, uint64_t t) inline void ow_dll_host_update (struct ow_dll *dll) { - debug_print (4, "Updating host side of DLL..."); + debug_print (5, "Updating host side of DLL..."); dll->z1 += dll->w0 * (dll->w1 * dll->err - dll->z1); dll->z2 += dll->w0 * (dll->z1 - dll->z2); dll->z3 += dll->w2 * dll->z2; dll->ratio = 1.0 - dll->z2 - dll->z3; - debug_print (4, "DLL ratio: %f", dll->ratio); + debug_print (5, "DLL ratio: %f", dll->ratio); } inline void ow_dll_host_init (struct ow_dll *dll) { - debug_print (2, "Initializing host side of DLL..."); + debug_print (3, "Initializing host side of DLL..."); dll->boot = 1; dll->t_quantum = ldexp (1e-6, 28); //28 bits as used in UINT64_USEC_TO_DOUBLE_SEC dll->dll_overbridge.boot = 1; @@ -161,7 +161,7 @@ ow_dll_host_reset (struct ow_dll *dll, double output_samplerate, double input_samplerate, uint32_t output_frames, uint32_t input_frames) { - debug_print (2, "Resetting the DLL..."); + debug_print (3, "Resetting the DLL..."); dll->z1 = 0.0; dll->z2 = 0.0; diff --git a/src/engine.c b/src/engine.c index 69b2f34..7dd96dc 100644 --- a/src/engine.c +++ b/src/engine.c @@ -220,7 +220,7 @@ set_usb_output_data_blks (struct ow_engine *engine) ow_engine_get_status (engine) == OW_ENGINE_STATUS_RUN) { bytes = ow_bytes_to_frame_bytes (rsh2o, engine->h2o_frame_size); - debug_print (2, "h2o: Emptying buffer (%zu B) and running...", + debug_print (3, "h2o: Emptying buffer (%zu B) and running...", bytes); engine->context->read (engine->context->h2o_audio, NULL, bytes); engine->reading_at_h2o_end = 1; @@ -232,7 +232,7 @@ set_usb_output_data_blks (struct ow_engine *engine) { if (engine->reading_at_h2o_end) { - debug_print (2, "h2o: Clearing buffer and stopping reading..."); + debug_print (3, "h2o: Clearing buffer and stopping reading..."); memset (engine->h2o_transfer_buf, 0, engine->h2o_transfer_size); engine->reading_at_h2o_end = 0; engine->latency_h2o_max = engine->latency_h2o_min; @@ -257,7 +257,7 @@ set_usb_output_data_blks (struct ow_engine *engine) } else if (rsh2o > engine->h2o_frame_size) //At least 2 frames to apply resampling to { - debug_print (2, + debug_print (3, "h2o: Audio ring buffer underflow (%zu B < %zu B). Fixed by resampling.", rsh2o, engine->h2o_transfer_size); frames = rsh2o / engine->h2o_frame_size; @@ -292,7 +292,7 @@ set_usb_output_data_blks (struct ow_engine *engine) } else { - debug_print (2, "h2o: Not enough data (%zu B). Waiting...", rsh2o); + debug_print (3, "h2o: Not enough data (%zu B). Waiting...", rsh2o); memset (engine->h2o_transfer_buf, 0, engine->h2o_transfer_size); } diff --git a/src/resampler.c b/src/resampler.c index 3845a56..38346da 100644 --- a/src/resampler.c +++ b/src/resampler.c @@ -154,7 +154,7 @@ ow_resampler_clear_buffers (struct ow_resampler *resampler) size_t rso2h, bytes; struct ow_context *context = resampler->engine->context; - debug_print (2, "Clearing buffers..."); + debug_print (3, "Clearing buffers..."); resampler->h2o_queue_len = 0; resampler->reading_at_o2h_end = 0; @@ -172,7 +172,7 @@ ow_resampler_clear_buffers (struct ow_resampler *resampler) static void ow_resampler_reset_buffers (struct ow_resampler *resampler) { - debug_print (2, "Resetting buffers..."); + debug_print (3, "Resetting buffers..."); resampler->o2h_bufsize = resampler->bufsize * resampler->o2h_frame_size; resampler->h2o_bufsize = resampler->bufsize * resampler->h2o_frame_size; @@ -257,7 +257,7 @@ resampler_h2o_reader (void *cb_data, float **data) if (resampler->h2o_queue_len == 0) { - debug_print (2, "h2o: Can not read data from queue"); + debug_print (3, "h2o: Can not read data from queue"); return resampler->bufsize; } @@ -293,7 +293,7 @@ resampler_o2h_reader (void *cb_data, float **data) } else { - debug_print (2, + debug_print (3, "o2h: Audio ring buffer underflow (%zu B < %zu B). No fix possible.", rso2h, resampler->engine->o2h_transfer_size); @@ -311,7 +311,7 @@ resampler_o2h_reader (void *cb_data, float **data) if (rso2h >= resampler->o2h_bufsize) { bytes = ow_bytes_to_frame_bytes (rso2h, resampler->o2h_bufsize); - debug_print (2, "o2h: Emptying buffer (%zu B) and running...", + debug_print (3, "o2h: Emptying buffer (%zu B) and running...", bytes); context->read (context->o2h_audio, NULL, bytes); resampler->reading_at_o2h_end = 1; From 4a33ff430ad6d4ab7f784ee425c74bc7ac6c16af Mon Sep 17 00:00:00 2001 From: dagargo Date: Thu, 4 Dec 2025 18:35:51 +0100 Subject: [PATCH 2/7] Set resampler status to tune on xrun The idea is to increase the loop filter bandwidth to adapt faster to possible changes in the ratio. No reset of the resampler is needed with this. --- src/jclient.c | 9 +++++++-- src/jclient.h | 1 + src/overwitch.h | 5 +++-- src/resampler.c | 14 +++++++++++++- 4 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/jclient.c b/src/jclient.c index 835af65..679668c 100644 --- a/src/jclient.c +++ b/src/jclient.c @@ -57,7 +57,7 @@ jclient_thread_xrun_cb (void *cb_data) { struct jclient *jclient = cb_data; error_print ("JACK xrun"); - ow_resampler_reset (jclient->resampler); + jclient->xrun = 1; return 0; } @@ -197,6 +197,7 @@ static inline int jclient_process_cb (jack_nframes_t nframes, void *arg) { float *f; + int xrun; jack_default_audio_sample_t *buffer[OB_MAX_TRACKS]; struct jclient *jclient = arg; jack_nframes_t current_frames; @@ -206,6 +207,9 @@ jclient_process_cb (jack_nframes_t nframes, void *arg) struct ow_engine *engine = ow_resampler_get_engine (jclient->resampler); const struct ow_device_desc *desc = &ow_engine_get_device (engine)->desc; + xrun = jclient->xrun; + jclient->xrun = 0; + if (jack_get_cycle_times (jclient->client, ¤t_frames, ¤t_usecs, &next_usecs, &period_usecs)) { @@ -213,7 +217,7 @@ jclient_process_cb (jack_nframes_t nframes, void *arg) goto err; } - if (ow_resampler_compute_ratios (jclient->resampler, current_usecs, + if (ow_resampler_compute_ratios (jclient->resampler, current_usecs, xrun, jclient_audio_running, jclient->client)) { goto err; @@ -318,6 +322,7 @@ jclient_run (struct jclient *jclient) struct ow_engine *engine; const struct ow_device_desc *desc; + jclient->xrun = 0; jclient->output_ports = NULL; jclient->input_ports = NULL; jclient->context.h2o_audio = NULL; diff --git a/src/jclient.h b/src/jclient.h index d86fda1..8285911 100644 --- a/src/jclient.h +++ b/src/jclient.h @@ -45,6 +45,7 @@ struct jclient pthread_spinlock_t lock; int running; pthread_t thread; + int xrun; }; void jclient_check_jack_server (jclient_notify_status_t); diff --git a/src/overwitch.h b/src/overwitch.h index 2d9c6a0..ddf7113 100644 --- a/src/overwitch.h +++ b/src/overwitch.h @@ -272,8 +272,9 @@ int ow_resampler_read_audio (struct ow_resampler *resampler); int ow_resampler_write_audio (struct ow_resampler *resampler); -int ow_resampler_compute_ratios (struct ow_resampler *resampler, uint64_t, - void (*)(void *), void *); +int ow_resampler_compute_ratios (struct ow_resampler *resampler, + uint64_t time, int xrun, void (*)(void *), + void *); void ow_resampler_reset_latencies (struct ow_resampler *resampler); diff --git a/src/resampler.c b/src/resampler.c index 38346da..ad033c8 100644 --- a/src/resampler.c +++ b/src/resampler.c @@ -399,7 +399,7 @@ ow_resampler_write_audio (struct ow_resampler *resampler) int ow_resampler_compute_ratios (struct ow_resampler *resampler, - uint64_t current_usecs, + uint64_t current_usecs, int xrun, void (*audio_running_cb) (void *), void *cb_data) { ow_engine_status_t engine_status; @@ -503,6 +503,18 @@ ow_resampler_compute_ratios (struct ow_resampler *resampler, audio_running_cb (cb_data); } + if (status == OW_RESAMPLER_STATUS_RUN && xrun) + { + debug_print (1, "%s (%s): Tuning resampler...", resampler->engine->name, + resampler->engine->overbridge_name); + + ow_dll_host_set_loop_filter (dll, 0.5, resampler->bufsize, + resampler->samplerate); + + ow_resampler_set_status (resampler, OW_RESAMPLER_STATUS_TUNE); + + resampler->phase_start_usecs = current_usecs; + } resampler->log_cycles++; if (resampler->log_cycles == resampler->log_control_cycles) From 28e5b5941d944940205b2dbf9bcbf30b69757fbc Mon Sep 17 00:00:00 2001 From: dagargo Date: Thu, 4 Dec 2025 18:45:17 +0100 Subject: [PATCH 3/7] Clear resampler buffers on xrun --- src/resampler.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/resampler.c b/src/resampler.c index ad033c8..4a8a470 100644 --- a/src/resampler.c +++ b/src/resampler.c @@ -508,6 +508,8 @@ ow_resampler_compute_ratios (struct ow_resampler *resampler, debug_print (1, "%s (%s): Tuning resampler...", resampler->engine->name, resampler->engine->overbridge_name); + ow_resampler_clear_buffers (resampler); + ow_dll_host_set_loop_filter (dll, 0.5, resampler->bufsize, resampler->samplerate); From 1e7acbf19d3252c69f9676c877b1b55e8808a8e1 Mon Sep 17 00:00:00 2001 From: dagargo Date: Fri, 5 Dec 2025 16:16:09 +0100 Subject: [PATCH 4/7] Add retune state to resampler The idea is having a state to do exactly the same as with the tune state with the addition of audio processing. This might create audio artifacts, specially when the xrun comes with big fluctuations in time measurements, but avoids a complete resetting of the resampler, which skips the audio processing for as long as 7 s. --- po/ca.po | 16 ++++++++++------ po/en.po | 16 ++++++++++------ po/es.po | 16 ++++++++++------ po/overwitch.pot | 18 +++++++++++------- src/jclient.c | 1 - src/message.c | 2 ++ src/overwitch.h | 3 ++- src/resampler.c | 39 ++++++++++++++++++++++++--------------- 8 files changed, 69 insertions(+), 42 deletions(-) diff --git a/po/ca.po b/po/ca.po index 58cb309..0ba8ca5 100644 --- a/po/ca.po +++ b/po/ca.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: overwitch 1.1\n" "Report-Msgid-Bugs-To: dagargo@gmail.com\n" -"POT-Creation-Date: 2025-08-19 12:36+0200\n" +"POT-Creation-Date: 2025-12-06 10:11+0100\n" "PO-Revision-Date: 2022-09-21 19:08+0200\n" "Last-Translator: David García Goñi \n" "Language-Team: Catalan\n" @@ -17,25 +17,25 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: src/main.c:153 +#: src/main.c:168 msgid "Stop All Devices" msgstr "Para tots el dispositius" -#: src/main.c:154 +#: src/main.c:169 msgid "Start All Devices" msgstr "Arranca tots el dispositius" -#: src/main.c:165 +#: src/main.c:180 #, c-format msgid "JACK at %.5g kHz, %u period" msgstr "JACK a %.5g kHz, període de %u" -#: src/main.c:169 +#: src/main.c:184 #, c-format msgid "Target latency: %.1f ms" msgstr "Latència objectiu: %.1f ms" -#: src/main.c:439 +#: src/main.c:454 msgid "Acknowledgements" msgstr "Agraïments" @@ -63,6 +63,10 @@ msgstr "Ajustant" msgid "Running" msgstr "Executant" +#: src/message.c:146 +msgid "Retuning" +msgstr "Reajustant" + #: res/overwitch.ui:11 msgid "Show All Columns" msgstr "Mostra totes les columnes" diff --git a/po/en.po b/po/en.po index 45efc3c..ed70aca 100644 --- a/po/en.po +++ b/po/en.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: overwitch 1.1\n" "Report-Msgid-Bugs-To: dagargo@gmail.com\n" -"POT-Creation-Date: 2025-08-19 12:36+0200\n" +"POT-Creation-Date: 2025-12-06 10:11+0100\n" "PO-Revision-Date: 2022-09-21 19:03+0200\n" "Last-Translator: David García Goñi \n" "Language-Team: English\n" @@ -17,25 +17,25 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: src/main.c:153 +#: src/main.c:168 msgid "Stop All Devices" msgstr "" -#: src/main.c:154 +#: src/main.c:169 msgid "Start All Devices" msgstr "" -#: src/main.c:165 +#: src/main.c:180 #, c-format msgid "JACK at %.5g kHz, %u period" msgstr "" -#: src/main.c:169 +#: src/main.c:184 #, c-format msgid "Target latency: %.1f ms" msgstr "" -#: src/main.c:439 +#: src/main.c:454 msgid "Acknowledgements" msgstr "" @@ -63,6 +63,10 @@ msgstr "" msgid "Running" msgstr "" +#: src/message.c:146 +msgid "Retuning" +msgstr "" + #: res/overwitch.ui:11 msgid "Show All Columns" msgstr "" diff --git a/po/es.po b/po/es.po index 63d13e2..db2001b 100644 --- a/po/es.po +++ b/po/es.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: overwitch 1.1\n" "Report-Msgid-Bugs-To: dagargo@gmail.com\n" -"POT-Creation-Date: 2025-08-19 12:36+0200\n" +"POT-Creation-Date: 2025-12-06 10:11+0100\n" "PO-Revision-Date: 2022-09-21 19:06+0200\n" "Last-Translator: David García Goñi \n" "Language-Team: Spanish\n" @@ -17,25 +17,25 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: src/main.c:153 +#: src/main.c:168 msgid "Stop All Devices" msgstr "Parar todos los dispositivos" -#: src/main.c:154 +#: src/main.c:169 msgid "Start All Devices" msgstr "Arrancar todos los dispositivos" -#: src/main.c:165 +#: src/main.c:180 #, c-format msgid "JACK at %.5g kHz, %u period" msgstr "JACK a %.5g kHz, periodo de %u" -#: src/main.c:169 +#: src/main.c:184 #, c-format msgid "Target latency: %.1f ms" msgstr "Latencia objetivo: %.1f ms" -#: src/main.c:439 +#: src/main.c:454 msgid "Acknowledgements" msgstr "Agradecimientos" @@ -63,6 +63,10 @@ msgstr "Ajustando" msgid "Running" msgstr "Ejecutando" +#: src/message.c:146 +msgid "Retuning" +msgstr "Reajustando" + #: res/overwitch.ui:11 msgid "Show All Columns" msgstr "Mostrar todas las columnas" diff --git a/po/overwitch.pot b/po/overwitch.pot index 3291198..7f4c6ef 100644 --- a/po/overwitch.pot +++ b/po/overwitch.pot @@ -6,9 +6,9 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: overwitch 2.1\n" +"Project-Id-Version: overwitch 2.1.1\n" "Report-Msgid-Bugs-To: dagargo@gmail.com\n" -"POT-Creation-Date: 2025-08-19 12:36+0200\n" +"POT-Creation-Date: 2025-12-06 10:11+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,25 +17,25 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: src/main.c:153 +#: src/main.c:168 msgid "Stop All Devices" msgstr "" -#: src/main.c:154 +#: src/main.c:169 msgid "Start All Devices" msgstr "" -#: src/main.c:165 +#: src/main.c:180 #, c-format msgid "JACK at %.5g kHz, %u period" msgstr "" -#: src/main.c:169 +#: src/main.c:184 #, c-format msgid "Target latency: %.1f ms" msgstr "" -#: src/main.c:439 +#: src/main.c:454 msgid "Acknowledgements" msgstr "" @@ -63,6 +63,10 @@ msgstr "" msgid "Running" msgstr "" +#: src/message.c:146 +msgid "Retuning" +msgstr "" + #: res/overwitch.ui:11 msgid "Show All Columns" msgstr "" diff --git a/src/jclient.c b/src/jclient.c index 679668c..4d35a17 100644 --- a/src/jclient.c +++ b/src/jclient.c @@ -251,7 +251,6 @@ jclient_process_cb (jack_nframes_t nframes, void *arg) goto err; } - return 0; err: diff --git a/src/message.c b/src/message.c index e83d476..2f8b0cb 100644 --- a/src/message.c +++ b/src/message.c @@ -142,6 +142,8 @@ get_status_string (ow_resampler_status_t status) return _("Tuning"); case OW_RESAMPLER_STATUS_RUN: return _("Running"); + case OW_RESAMPLER_STATUS_RETUNE: + return _("Retuning"); } return NULL; } diff --git a/src/overwitch.h b/src/overwitch.h index ddf7113..a53522b 100644 --- a/src/overwitch.h +++ b/src/overwitch.h @@ -102,7 +102,8 @@ typedef enum OW_RESAMPLER_STATUS_READY, OW_RESAMPLER_STATUS_BOOT, OW_RESAMPLER_STATUS_TUNE, - OW_RESAMPLER_STATUS_RUN + OW_RESAMPLER_STATUS_RUN, + OW_RESAMPLER_STATUS_RETUNE } ow_resampler_status_t; typedef enum diff --git a/src/resampler.c b/src/resampler.c index 4a8a470..292dac3 100644 --- a/src/resampler.c +++ b/src/resampler.c @@ -336,6 +336,7 @@ ow_resampler_read_audio (struct ow_resampler *resampler) error_print ("o2h: Unexpected frames with ratio %f (output %ld, expected %d)", resampler->o2h_ratio, gen_frames, resampler->bufsize); + return -1; } @@ -485,36 +486,44 @@ ow_resampler_compute_ratios (struct ow_resampler *resampler, resampler->phase_start_usecs = current_usecs; } - if (status == OW_RESAMPLER_STATUS_TUNE && - current_usecs - resampler->phase_start_usecs > TUNING_PERIOD_US && - ow_dll_tuned (dll, TUNING_ERROR)) + if (status == OW_RESAMPLER_STATUS_TUNE || + status == OW_RESAMPLER_STATUS_RETUNE) { - debug_print (1, "%s (%s): Running resampler...", - resampler->engine->name, - resampler->engine->overbridge_name); + if (xrun) + { + ow_resampler_clear_buffers (resampler); + resampler->phase_start_usecs = current_usecs; + } + else if (current_usecs - resampler->phase_start_usecs > TUNING_PERIOD_US + && ow_dll_tuned (dll, TUNING_ERROR)) + { + debug_print (1, "%s (%s): Running resampler...", + resampler->engine->name, + resampler->engine->overbridge_name); - ow_dll_host_set_loop_filter (dll, 0.05, resampler->bufsize, - resampler->samplerate); + ow_dll_host_set_loop_filter (dll, 0.05, resampler->bufsize, + resampler->samplerate); - ow_engine_set_status (resampler->engine, OW_ENGINE_STATUS_RUN); + ow_engine_set_status (resampler->engine, OW_ENGINE_STATUS_RUN); - ow_resampler_set_status (resampler, OW_RESAMPLER_STATUS_RUN); + ow_resampler_set_status (resampler, OW_RESAMPLER_STATUS_RUN); - audio_running_cb (cb_data); + audio_running_cb (cb_data); + } } if (status == OW_RESAMPLER_STATUS_RUN && xrun) { - debug_print (1, "%s (%s): Tuning resampler...", resampler->engine->name, + debug_print (1, "%s (%s): Retuning resampler...", + resampler->engine->name, resampler->engine->overbridge_name); - ow_resampler_clear_buffers (resampler); - ow_dll_host_set_loop_filter (dll, 0.5, resampler->bufsize, resampler->samplerate); - ow_resampler_set_status (resampler, OW_RESAMPLER_STATUS_TUNE); + ow_resampler_set_status (resampler, OW_RESAMPLER_STATUS_RETUNE); + ow_resampler_clear_buffers (resampler); resampler->phase_start_usecs = current_usecs; } From d4790fb11df317196ad4f8b80c8582ecc2e96be5 Mon Sep 17 00:00:00 2001 From: dagargo Date: Sat, 6 Dec 2025 08:39:45 +0100 Subject: [PATCH 5/7] Allow lower values for blocks It is possible to use 6 blocks by adding a delay after the control transfer. Without this, devices crash occasionally. --- res/overwitch.ui | 2 +- src/common.c | 2 +- src/engine.c | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/res/overwitch.ui b/res/overwitch.ui index 7141f39..7825572 100644 --- a/res/overwitch.ui +++ b/res/overwitch.ui @@ -31,7 +31,7 @@ - 8.0 + 6.0 4 1 24 diff --git a/src/common.c b/src/common.c index 5ce92cb..4b2971f 100644 --- a/src/common.c +++ b/src/common.c @@ -25,7 +25,7 @@ #define OW_XFR_TIMEOUT_MIN 0 #define OW_XFR_TIMEOUT_MAX 25 -#define OW_BLOCKS_MIN 8 +#define OW_BLOCKS_MIN 6 #define OW_BLOCKS_MAX 32 void diff --git a/src/engine.c b/src/engine.c index 7dd96dc..c9344c5 100644 --- a/src/engine.c +++ b/src/engine.c @@ -1108,6 +1108,8 @@ ow_engine_load_overbridge_name (struct ow_engine *engine) error_print ("Error on USB control in transfer: %s", libusb_strerror (res)); } + + usleep (100000); // This is required to not send the next packet immediately, which can make devices crash. } static void LIBUSB_CALL From ea90ec18df38fd935cbc69609298e2783f2bd2fd Mon Sep 17 00:00:00 2001 From: dagargo Date: Fri, 5 Dec 2025 16:15:06 +0100 Subject: [PATCH 6/7] Add DLL ratio limits --- src/dll.c | 14 ++++++++++++++ src/dll.h | 2 ++ 2 files changed, 16 insertions(+) diff --git a/src/dll.c b/src/dll.c index c5c4aa5..1a5ad90 100644 --- a/src/dll.c +++ b/src/dll.c @@ -28,6 +28,8 @@ #define UINT64_USEC_TO_DOUBLE_SEC(t) (SEC_PER_USEC * (int)(t & 0x0FFFFFFF)) #define MODTIME_THRESHOLD 200 //A value smaller than the maximum returned by UINT64_USEC_TO_DOUBLE_SEC +#define MAX_RATIO_ERROR 2 + double wrap_time (double d, double q) { @@ -143,6 +145,16 @@ ow_dll_host_update (struct ow_dll *dll) dll->z2 += dll->w0 * (dll->z1 - dll->z2); dll->z3 += dll->w2 * dll->z2; dll->ratio = 1.0 - dll->z2 - dll->z3; + if (dll->ratio > dll->max_ratio) + { + error_print ("Using DLL max ratio instead of %f", dll->ratio); + dll->ratio = dll->max_ratio; + } + else if (dll->ratio < dll->min_ratio) + { + error_print ("Using DLL min ratio instead of %f", dll->ratio); + dll->ratio = dll->min_ratio; + } debug_print (5, "DLL ratio: %f", dll->ratio); } @@ -168,6 +180,8 @@ ow_dll_host_reset (struct ow_dll *dll, double output_samplerate, dll->z3 = 0.0; dll->ratio = output_samplerate / input_samplerate; + dll->max_ratio = dll->ratio * MAX_RATIO_ERROR; + dll->min_ratio = dll->ratio / MAX_RATIO_ERROR; dll->frames = -input_frames / dll->ratio; diff --git a/src/dll.h b/src/dll.h index 91502dc..d462631 100644 --- a/src/dll.h +++ b/src/dll.h @@ -41,6 +41,8 @@ struct ow_dll_overbridge struct ow_dll { double ratio; + double max_ratio; + double min_ratio; uint32_t frames; double w0; double w1; From 62893f3e2bdf599fe8b69b0dc4206cb21d9902f4 Mon Sep 17 00:00:00 2001 From: dagargo Date: Sat, 6 Dec 2025 12:20:53 +0100 Subject: [PATCH 7/7] Recover from out-of-range ratio values There is a scenario in which the ratios are outside reasonable values and maximum and minimum values are used. In this cases, retuning the resampler will ensure the lowest latencies. --- src/dll.c | 8 +++++++- src/dll.h | 2 +- src/resampler.c | 13 +++++++++---- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/dll.c b/src/dll.c index 1a5ad90..79520b9 100644 --- a/src/dll.c +++ b/src/dll.c @@ -136,9 +136,11 @@ ow_dll_host_update_error (struct ow_dll *dll, uint64_t t) dll->target_delay, dll->err); } -inline void +inline int ow_dll_host_update (struct ow_dll *dll) { + int err = 0; + debug_print (5, "Updating host side of DLL..."); dll->z1 += dll->w0 * (dll->w1 * dll->err - dll->z1); @@ -148,15 +150,19 @@ ow_dll_host_update (struct ow_dll *dll) if (dll->ratio > dll->max_ratio) { error_print ("Using DLL max ratio instead of %f", dll->ratio); + err = 1; dll->ratio = dll->max_ratio; } else if (dll->ratio < dll->min_ratio) { error_print ("Using DLL min ratio instead of %f", dll->ratio); dll->ratio = dll->min_ratio; + err = 1; } debug_print (5, "DLL ratio: %f", dll->ratio); + + return err; } inline void diff --git a/src/dll.h b/src/dll.h index d462631..c1b334b 100644 --- a/src/dll.h +++ b/src/dll.h @@ -71,7 +71,7 @@ void ow_dll_host_set_loop_filter (struct ow_dll *, double, uint32_t, double); void ow_dll_host_update_error (struct ow_dll *dll, uint64_t time); -void ow_dll_host_update (struct ow_dll *dll); +int ow_dll_host_update (struct ow_dll *dll); void ow_dll_host_load_dll_overbridge (struct ow_dll *dll); diff --git a/src/resampler.c b/src/resampler.c index 292dac3..033c2f5 100644 --- a/src/resampler.c +++ b/src/resampler.c @@ -403,6 +403,7 @@ ow_resampler_compute_ratios (struct ow_resampler *resampler, uint64_t current_usecs, int xrun, void (*audio_running_cb) (void *), void *cb_data) { + int retune_required = xrun; ow_engine_status_t engine_status; struct ow_dll *dll = &resampler->dll; ow_resampler_status_t status; @@ -463,7 +464,10 @@ ow_resampler_compute_ratios (struct ow_resampler *resampler, return 0; } - ow_dll_host_update (dll); + if (ow_dll_host_update (dll)) + { + retune_required = 1; // Something serious happened to the ratio. + } ow_resampler_set_ratios_from_dll (resampler); @@ -489,9 +493,8 @@ ow_resampler_compute_ratios (struct ow_resampler *resampler, if (status == OW_RESAMPLER_STATUS_TUNE || status == OW_RESAMPLER_STATUS_RETUNE) { - if (xrun) + if (retune_required) { - ow_resampler_clear_buffers (resampler); resampler->phase_start_usecs = current_usecs; } else if (current_usecs - resampler->phase_start_usecs > TUNING_PERIOD_US @@ -509,10 +512,12 @@ ow_resampler_compute_ratios (struct ow_resampler *resampler, ow_resampler_set_status (resampler, OW_RESAMPLER_STATUS_RUN); audio_running_cb (cb_data); + + ow_resampler_clear_buffers (resampler); } } - if (status == OW_RESAMPLER_STATUS_RUN && xrun) + if (status == OW_RESAMPLER_STATUS_RUN && retune_required) { debug_print (1, "%s (%s): Retuning resampler...", resampler->engine->name,