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/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/dll.c b/src/dll.c index fd5aa1f..79520b9 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) { @@ -50,7 +52,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 +70,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 +99,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 +112,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,36 +123,52 @@ 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); } -inline void +inline int ow_dll_host_update (struct ow_dll *dll) { - debug_print (4, "Updating host side of DLL..."); + int err = 0; + + 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; + 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); - debug_print (4, "DLL ratio: %f", dll->ratio); + return err; } 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,13 +179,15 @@ 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; 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..c1b334b 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; @@ -69,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/engine.c b/src/engine.c index 69b2f34..c9344c5 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); } @@ -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 diff --git a/src/jclient.c b/src/jclient.c index 835af65..4d35a17 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; @@ -247,7 +251,6 @@ jclient_process_cb (jack_nframes_t nframes, void *arg) goto err; } - return 0; err: @@ -318,6 +321,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/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 2d9c6a0..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 @@ -272,8 +273,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 3845a56..033c2f5 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; @@ -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; } @@ -399,9 +400,10 @@ 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) { + int retune_required = xrun; ow_engine_status_t engine_status; struct ow_dll *dll = &resampler->dll; ow_resampler_status_t status; @@ -462,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); @@ -485,25 +490,48 @@ 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...", + if (retune_required) + { + 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_engine_set_status (resampler->engine, OW_ENGINE_STATUS_RUN); + + 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 && retune_required) + { + debug_print (1, "%s (%s): Retuning resampler...", resampler->engine->name, resampler->engine->overbridge_name); - ow_dll_host_set_loop_filter (dll, 0.05, resampler->bufsize, + ow_dll_host_set_loop_filter (dll, 0.5, resampler->bufsize, resampler->samplerate); - 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_RETUNE); - audio_running_cb (cb_data); + ow_resampler_clear_buffers (resampler); + resampler->phase_start_usecs = current_usecs; } - resampler->log_cycles++; if (resampler->log_cycles == resampler->log_control_cycles) {