shithub: audio-stretch

Download patch

ref: b56508b18124746746909f74dfad5b626580f55f
parent: 068d11f041e60f9dc4b13fe72c114fb5a5f407b5
author: David Bryant <david@wavpack.com>
date: Wed Oct 12 12:37:34 EDT 2022

issue #5: experimental version incorporating cascaded instances for extended stretch range

--- a/main.c
+++ b/main.c
@@ -121,8 +121,8 @@
                     case 'R': case 'r':
                         ratio = strtod (++*argv, argv);
 
-                        if (ratio < 0.5 || ratio > 2.0) {
-                            fprintf (stderr, "\nratio must be from 0.5 to 2.0!\n");
+                        if (ratio < 0.25 || ratio > 4.0) {
+                            fprintf (stderr, "\nratio must be from 0.25 to 4.0!\n");
                             return -1;
                         }
 
@@ -308,11 +308,17 @@
 
     min_period = WaveHeader.SampleRate / upper_frequency;
     max_period = WaveHeader.SampleRate / lower_frequency;
-    int fast_mode = (force_fast || WaveHeader.SampleRate >= 32000) && !force_normal;
+    int flags = 0;
 
+    if (ratio < 0.5 || ratio > 2.0)
+        flags |= STRETCH_DUAL_FLAG;
+
+    if ((force_fast || WaveHeader.SampleRate >= 32000) && !force_normal)
+        flags |= STRETCH_FAST_FLAG;
+
     if (verbose_mode)
         fprintf (stderr, "initializing stretch library with period range = %d to %d, %d channels, %s\n",
-            min_period, max_period, WaveHeader.NumChannels, fast_mode ? "fast mode" : "normal mode");
+            min_period, max_period, WaveHeader.NumChannels, (flags & STRETCH_FAST_FLAG) ? "fast mode" : "normal mode");
 
     if (!quiet_mode && ratio == 1.0 && !cycle_ratio)
         fprintf (stderr, "warning: a ratio of 1.0 will do nothing but copy the WAV file!\n");
@@ -320,7 +326,7 @@
     if (!quiet_mode && ratio != 1.0 && cycle_ratio && !scale_rate)
         fprintf (stderr, "warning: specifying ratio with cycling doesn't do anything (unless scaling rate)\n");
 
-    stretcher = stretch_init (min_period, max_period, WaveHeader.NumChannels, fast_mode);
+    stretcher = stretch_init (min_period, max_period, WaveHeader.NumChannels, flags);
 
     if (!stretcher) {
         fprintf (stderr, "can't initialize stretcher\n");
@@ -338,7 +344,7 @@
     write_pcm_wav_header (outfile, 0, WaveHeader.NumChannels, 2, scaled_rate);
 
     int16_t *inbuffer = malloc (BUFFER_SAMPLES * WaveHeader.BlockAlign);
-    int16_t *outbuffer = malloc ((BUFFER_SAMPLES * 2 + max_period * 4) * WaveHeader.BlockAlign);
+    int16_t *outbuffer = malloc ((BUFFER_SAMPLES * 4 + max_period * 8) * WaveHeader.BlockAlign);
 
     if (!inbuffer || !outbuffer) {
         fprintf (stderr, "can't allocate required memory!\n");
--- a/stretch.c
+++ b/stretch.c
@@ -49,6 +49,9 @@
     int16_t *inbuff, *calcbuff;
     float outsamples_error;
     uint32_t *results;
+
+    struct stretch_cnxt *next;
+    int16_t *intermediate;
 };
 
 static void merge_blocks (int16_t *output, int16_t *input1, int16_t *input2, int samples);
@@ -63,11 +66,11 @@
  * worst-case performance will suffer if too short a period is selected.
  */
 
-StretchHandle stretch_init (int shortest_period, int longest_period, int num_channels, int fast_mode)
+StretchHandle stretch_init (int shortest_period, int longest_period, int num_channels, int flags)
 {
     struct stretch_cnxt *cnxt;
 
-    if (fast_mode) {
+    if (flags & STRETCH_FAST_FLAG) {
         longest_period = (longest_period + 1) & ~1;
         shortest_period &= ~1;
     }
@@ -83,23 +86,28 @@
         cnxt->inbuff_samples = longest_period * num_channels * 6;
         cnxt->inbuff = calloc (cnxt->inbuff_samples, sizeof (*cnxt->inbuff));
 
-        if (num_channels == 2 || fast_mode)
+        if (num_channels == 2 || (flags & STRETCH_FAST_FLAG))
             cnxt->calcbuff = calloc (longest_period * num_channels, sizeof (*cnxt->calcbuff));
 
-        if (fast_mode)
+        if ((flags & STRETCH_FAST_FLAG))
             cnxt->results = calloc (longest_period, sizeof (*cnxt->results));
     }
 
-    if (!cnxt || !cnxt->inbuff || (num_channels == 2 && fast_mode && !cnxt->calcbuff) || (fast_mode && !cnxt->results)) {
+    if (!cnxt || !cnxt->inbuff || (num_channels == 2 && (flags & STRETCH_FAST_FLAG) && !cnxt->calcbuff) || ((flags & STRETCH_FAST_FLAG) && !cnxt->results)) {
         fprintf (stderr, "stretch_init(): out of memory!\n");
         return NULL;
     }
 
     cnxt->head = cnxt->tail = cnxt->longest = longest_period * num_channels;
+    cnxt->fast_mode = (flags & STRETCH_FAST_FLAG) ? 1 : 0;
     cnxt->shortest = shortest_period * num_channels;
     cnxt->num_chans = num_channels;
-    cnxt->fast_mode = fast_mode;
 
+    if (flags & STRETCH_DUAL_FLAG) {
+        cnxt->next = stretch_init (shortest_period, longest_period, num_channels, flags & ~STRETCH_DUAL_FLAG);
+        cnxt->intermediate = calloc (longest_period * num_channels * 4, sizeof (*cnxt->intermediate));
+    }
+
     return (StretchHandle) cnxt;
 }
 
@@ -110,8 +118,11 @@
 
 void stretch_reset (StretchHandle handle)
 {
-  struct stretch_cnxt *cnxt = (struct stretch_cnxt *) handle;
-  cnxt->head = cnxt->tail = cnxt->longest;
+    struct stretch_cnxt *cnxt = (struct stretch_cnxt *) handle;
+    cnxt->head = cnxt->tail = cnxt->longest;
+
+    if (cnxt->next)
+        cnxt->next->head = cnxt->next->tail = cnxt->next->longest;
 }
 
 
@@ -127,8 +138,14 @@
 int stretch_samples (StretchHandle handle, const int16_t *samples, int num_samples, int16_t *output, float ratio)
 {
     struct stretch_cnxt *cnxt = (struct stretch_cnxt *) handle;
-    int out_samples = 0;
+    int out_samples = 0, next_samples = 0;
+    int16_t *outbuf = output;
 
+    if (cnxt->next) {
+        outbuf = cnxt->intermediate;
+        ratio = sqrt (ratio);
+    }
+
     num_samples *= cnxt->num_chans;
 
     if (ratio < 0.5)
@@ -177,7 +194,7 @@
                 process_ratio = ceil (ratio * 2.0) / 2.0;
 
             if (process_ratio == 0.5) {
-                merge_blocks (output + out_samples, cnxt->inbuff + cnxt->tail,
+                merge_blocks (outbuf + out_samples, cnxt->inbuff + cnxt->tail,
                     cnxt->inbuff + cnxt->tail + period, period);
                 cnxt->outsamples_error += period - (period * 2.0 * ratio);
                 out_samples += period;
@@ -184,22 +201,22 @@
                 cnxt->tail += period * 2;
             }
             else if (process_ratio == 1.0) {
-                memcpy (output + out_samples, cnxt->inbuff + cnxt->tail, period * 2 * sizeof (cnxt->inbuff [0]));
+                memcpy (outbuf + out_samples, cnxt->inbuff + cnxt->tail, period * 2 * sizeof (cnxt->inbuff [0]));
                 cnxt->outsamples_error += (period * 2.0) - (period * 2.0 * ratio);
                 out_samples += period * 2;
                 cnxt->tail += period * 2;
             }
             else if (process_ratio == 1.5) {
-                memcpy (output + out_samples, cnxt->inbuff + cnxt->tail, period * sizeof (cnxt->inbuff [0]));
-                merge_blocks (output + out_samples + period, cnxt->inbuff + cnxt->tail + period,
+                memcpy (outbuf + out_samples, cnxt->inbuff + cnxt->tail, period * sizeof (cnxt->inbuff [0]));
+                merge_blocks (outbuf + out_samples + period, cnxt->inbuff + cnxt->tail + period,
                     cnxt->inbuff + cnxt->tail, period);
-                memcpy (output + out_samples + period * 2, cnxt->inbuff + cnxt->tail + period, period * sizeof (cnxt->inbuff [0]));
+                memcpy (outbuf + out_samples + period * 2, cnxt->inbuff + cnxt->tail + period, period * sizeof (cnxt->inbuff [0]));
                 cnxt->outsamples_error += (period * 3.0) - (period * 2.0 * ratio);
                 out_samples += period * 3;
                 cnxt->tail += period * 2;
             }
             else if (process_ratio == 2.0) {
-                merge_blocks (output + out_samples, cnxt->inbuff + cnxt->tail,
+                merge_blocks (outbuf + out_samples, cnxt->inbuff + cnxt->tail,
                     cnxt->inbuff + cnxt->tail - period, period * 2);
 
                 cnxt->outsamples_error += (period * 2.0) - (period * ratio);
@@ -207,7 +224,7 @@
                 cnxt->tail += period;
 
                 if (cnxt->fast_mode) {
-                    merge_blocks (output + out_samples, cnxt->inbuff + cnxt->tail,
+                    merge_blocks (outbuf + out_samples, cnxt->inbuff + cnxt->tail,
                         cnxt->inbuff + cnxt->tail - period, period * 2);
 
                     cnxt->outsamples_error += (period * 2.0) - (period * ratio);
@@ -217,6 +234,11 @@
             }
             else
                 fprintf (stderr, "stretch_samples: fatal programming error: process_ratio == %g\n", process_ratio);
+
+            if (cnxt->next) {
+                next_samples += stretch_samples (cnxt->next, outbuf, out_samples / cnxt->num_chans, output + next_samples * cnxt->num_chans, ratio);
+                out_samples = 0;
+            }
         }
 
         /* if we're almost done with buffer, copy the rest back to beginning */
@@ -233,7 +255,7 @@
 
     } while (num_samples);
 
-    return out_samples / cnxt->num_chans;
+    return cnxt->next ? next_samples : out_samples / cnxt->num_chans;
 }  
 
 /* flush any leftover samples out at normal speed */
@@ -258,6 +280,10 @@
     free (cnxt->calcbuff);
     free (cnxt->results);
     free (cnxt->inbuff);
+
+    if (cnxt->next)
+        stretch_deinit (cnxt->next);
+
     free (cnxt);
 }
 
--- a/stretch.h
+++ b/stretch.h
@@ -27,6 +27,9 @@
 
 #include <stdint.h>
 
+#define STRETCH_FAST_FLAG    0x1
+#define STRETCH_DUAL_FLAG    0x2
+
 #ifdef __cplusplus
 extern "C" {
 #endif
@@ -33,7 +36,7 @@
 
 typedef void *StretchHandle;
 
-StretchHandle stretch_init (int shortest_period, int longest_period, int num_chans, int fast_mode);
+StretchHandle stretch_init (int shortest_period, int longest_period, int num_chans, int flags);
 int stretch_samples (StretchHandle handle, const int16_t *samples, int num_samples, int16_t *output, float ratio);
 int stretch_flush (StretchHandle handle, int16_t *output);
 void stretch_reset (StretchHandle handle);