shithub: audio-stretch

Download patch

ref: 92cb3bdbce88273529348958e92649c94782fc2e
parent: c0576b103d2a13975e7e2644fa9b0a76020603ec
author: David Bryant <david@wavpack.com>
date: Tue Oct 18 15:26:44 EDT 2022

issue #5: fix a few edge cases and reduce latency of cascaded instances, add/correct comments

--- a/stretch.c
+++ b/stretch.c
@@ -180,6 +180,8 @@
     int16_t *outbuf = output;
     float next_ratio;
 
+    /* if there's a cascaded instance after this one, try to do as much of the ratio here and the rest in "next" */
+
     if (cnxt->next) {
         outbuf = cnxt->intermediate;
 
@@ -197,30 +199,31 @@
 
     num_samples *= cnxt->num_chans;
 
+    /* this really should not happen, but a good idea to clamp in case */
+
     if (ratio < 0.5)
         ratio = 0.5;
     else if (ratio > 2.0)
         ratio = 2.0;
 
-    /* while we have samples to do... */
+    /* while we have pending samples to read into our buffer */
 
-    do {
-        /* if there are more samples and room for them, copy in */
+    while (num_samples) {
 
-        if (num_samples && cnxt->head < cnxt->inbuff_samples) {
-            int samples_to_copy = num_samples;
+        /* copy in as many samples as we have room for */
 
-            if (samples_to_copy > cnxt->inbuff_samples - cnxt->head)
-                samples_to_copy = cnxt->inbuff_samples - cnxt->head; 
+        int samples_to_copy = num_samples;
 
-            memcpy (cnxt->inbuff + cnxt->head, samples, samples_to_copy * sizeof (cnxt->inbuff [0]));
-            num_samples -= samples_to_copy;
-            samples += samples_to_copy;
-            cnxt->head += samples_to_copy;
-        }
+        if (samples_to_copy > cnxt->inbuff_samples - cnxt->head)
+            samples_to_copy = cnxt->inbuff_samples - cnxt->head;
 
-        /* while there are enough samples to process, do so */
+        memcpy (cnxt->inbuff + cnxt->head, samples, samples_to_copy * sizeof (cnxt->inbuff [0]));
+        num_samples -= samples_to_copy;
+        samples += samples_to_copy;
+        cnxt->head += samples_to_copy;
 
+        /* while there are enough samples to process (3 or 4 times the longest period), do so */
+
         while (cnxt->tail >= cnxt->longest && cnxt->head - cnxt->tail >= cnxt->longest * (cnxt->fast_mode ? 3 : 2)) {
             float process_ratio;
             int period;
@@ -296,15 +299,15 @@
             else
                 fprintf (stderr, "stretch_samples: fatal programming error: process_ratio == %g\n", process_ratio);
 
+            /* if there's another cascaded instance after this, pass the just stretched samples into that */
+
             if (cnxt->next) {
                 next_samples += stretch_samples (cnxt->next, outbuf, out_samples / cnxt->num_chans, output + next_samples * cnxt->num_chans, next_ratio);
                 out_samples = 0;
             }
-        }
 
-        /* if we're almost done with buffer, copy the rest back to beginning */
+            /* finally, left-justify the samples in the buffer leaving one longest period of history */
 
-        if (cnxt->head == cnxt->inbuff_samples) {
             int samples_to_move = cnxt->inbuff_samples - cnxt->tail + cnxt->longest;
 
             memmove (cnxt->inbuff, cnxt->inbuff + cnxt->tail - cnxt->longest,
@@ -313,9 +316,30 @@
             cnxt->head -= cnxt->tail - cnxt->longest;
             cnxt->tail = cnxt->longest;
         }
+    }
 
-    } while (num_samples);
+    /*
+     * This code is not strictly required, but will reduce latency, especially in the dual-instance case, by
+     * always flushing all pending samples if no actual stretching is desired (i.e., ratio is 1.0 and there's
+     * no error to compensate for). This case is more common now than previously because of the gap detection
+     * and cascaded instances.
+     */
 
+    if (ratio == 1.0 && !cnxt->outsamples_error && cnxt->head != cnxt->tail) {
+        int samples_leftover = cnxt->head - cnxt->tail;
+
+        if (cnxt->next)
+            next_samples += stretch_samples (cnxt->next, cnxt->inbuff + cnxt->tail, samples_leftover / cnxt->num_chans,
+                output + next_samples * cnxt->num_chans, next_ratio);
+        else {
+            memcpy (outbuf + out_samples, cnxt->inbuff + cnxt->tail, samples_leftover * sizeof (*output));
+            out_samples += samples_leftover;
+        }
+
+        memmove (cnxt->inbuff, cnxt->inbuff + cnxt->head - cnxt->longest, cnxt->longest * sizeof (cnxt->inbuff [0]));
+        cnxt->head = cnxt->tail = cnxt->longest;
+    }
+
     return cnxt->next ? next_samples : out_samples / cnxt->num_chans;
 }  
 
@@ -327,16 +351,19 @@
 int stretch_flush (StretchHandle handle, int16_t *output)
 {
     struct stretch_cnxt *cnxt = (struct stretch_cnxt *) handle;
-    int samples_leftover = (cnxt->head - cnxt->tail) / cnxt->num_chans;
-    int samples_flushed;
+    int samples_leftover = cnxt->head - cnxt->tail;
+    int samples_flushed = 0;
 
-    if (cnxt->next && samples_leftover)
-        samples_flushed = stretch_samples (cnxt->next, cnxt->inbuff + cnxt->tail, samples_leftover, output, 1.0);
-    else if (cnxt->next)
-        samples_flushed = stretch_flush (cnxt->next, output);
+    if (cnxt->next) {
+        if (samples_leftover)
+            samples_flushed = stretch_samples (cnxt->next, cnxt->inbuff + cnxt->tail, samples_leftover / cnxt->num_chans, output, 1.0);
+
+        if (!samples_flushed)
+            samples_flushed = stretch_flush (cnxt->next, output);
+    }
     else {
-        memcpy (output, cnxt->inbuff + cnxt->tail, samples_leftover * cnxt->num_chans * sizeof (*output));
-        samples_flushed = samples_leftover;
+        memcpy (output, cnxt->inbuff + cnxt->tail, samples_leftover * sizeof (*output));
+        samples_flushed = samples_leftover / cnxt->num_chans;
     }
 
     cnxt->tail = cnxt->head;