d6d2c82e2f3e — Chris Cannam 2 years ago
Introduce PullResampler to make the callback-driven resample logic available to code other than direct users of ResamplingStatefulStreamFn
1 files changed, 119 insertions(+), 15 deletions(-)

M resampling-fn.sml
M resampling-fn.sml +119 -15
@@ 1,3 1,110 @@ 
+
+(** Structure to manage state within a resampling stream, pulling its
+    source samples from a provided callback function. This handles the
+    fact that BqResample has no internal reservoir and so does not
+    always return precisely the expected number of samples for a given
+    input count. With a PullResampler we always get the requested
+    number of samples, with a variable number pulled from the source
+    callback instead.
+*)
+structure PullResampler : sig
+
+              type state
+              type source = int -> RealMatrix.matrix option
+
+              val new : BqResample.parameters -> real -> state
+              val reset : state -> unit
+              val pull : source -> state * int -> RealMatrix.matrix option
+              
+end = struct
+
+    structure M = RealMatrix
+
+    type state = {
+        ratio : real,
+        resampler : BqResample.t,
+        spare : M.matrix ref
+    }
+
+    type source = int -> M.matrix option
+
+    fun emptySpare channels =
+        M.fromRows
+            (List.tabulate (channels, fn _ => RealVector.fromList []))
+                                  
+    fun new (parameters : BqResample.parameters)
+            (ratio : real)
+        : state =
+        { ratio = ratio,
+          resampler = BqResample.new parameters,
+          spare = ref (emptySpare (#channels parameters))
+        }
+
+    fun reset (state as { ratio, resampler, spare })
+        : unit =
+        (BqResample.reset resampler;
+         spare := emptySpare (BqResample.channels resampler))
+            
+    fun processWithoutSpare (source : source)
+                            (state as { ratio, resampler, spare } : state,
+                             n : int)
+        : M.matrix option =
+        let val () = if M.columns (! spare) > 0
+                     then raise Fail "Internal error: processWithoutSpare called when we do have spare"
+                     else ()
+            val incount = Real.ceil (Real.fromInt n / ratio)
+            val inmaybe = source incount
+            fun process' (insamples, final) =
+                let val resampled = BqResample.resample
+                                        (resampler, M.toRowsV insamples,
+                                         ratio, final)
+                    val m = M.fromRowsV resampled
+                    val have = M.columns m
+                in
+                    if have = n
+                    then SOME m
+                    else if have > n
+                    then (spare := M.columnSlice (m, n, SOME (have - n));
+                          SOME (M.columnSlice (m, 0, SOME n)))
+                    else if final
+                    then SOME m
+                    else case processWithoutSpare source (state, n - have) of
+                             NONE => SOME m
+                           | SOME m' => SOME (M.concatHorizontal [m, m'])
+                end
+        in
+            case inmaybe of
+                SOME insamples => process' (insamples, false)
+              | NONE =>
+                case process' (M.const (M.ROW_MAJOR,
+                                        { rows = BqResample.channels resampler,
+                                          columns = 0 },
+                                        0.0),
+                               true) of
+                    SOME outsamples => if M.columns outsamples > 0
+                                       then SOME outsamples
+                                       else NONE
+                  | NONE => NONE
+        end
+
+    fun pull (source : source)
+             (state as { ratio, resampler, spare } : state, n : int)
+        : M.matrix option =
+        let val spmat = !spare
+            val have = M.columns spmat
+        in
+            if have = 0
+            then processWithoutSpare source (state, n)
+            else if have >= n
+            then (spare := M.columnSlice (spmat, n, SOME (have - n));
+                  SOME (M.columnSlice (spmat, 0, SOME n)))
+            else (spare := emptySpare (BqResample.channels resampler);
+                  case pull source (state, n - have) of
+                      NONE => SOME spmat
+                    | SOME m => SOME (M.concatHorizontal [spmat, m]))
+        end
+          
+end
 
 functor ResamplingStatefulStreamFn (S: REAL_STATEFUL_SAMPLE_STREAM) :
         sig

          
@@ 14,8 121,7 @@ functor ResamplingStatefulStreamFn (S: R
         rate : SignalTypes.rate,
         ratio : real,
         upstream : S.stream,
-        resampler : BqResample.t option,
-        spare : M.matrix ref,
+        resampler : PullResampler.state option,
         quality : BqResample.quality,
         upstreamStartTime : RealTime.t ref,
         lastSeekTime : RealTime.t ref,

          
@@ 60,7 166,7 @@ functor ResamplingStatefulStreamFn (S: R
             NOT_SIMPLE => (SampleStreamLog.debug (fn () => ["ResamplingStatefulStreamFn: not seekable with non-simple ratio of %1", SampleStreamLog.R ratio]);
                            SampleStreamTypes.NON_SEEKABLE)
           | _ => S.seekable upstream
-
+(*!!!
     fun readWithoutUsingSpare
 	    (s as { ratio, upstream, resampler, spare, ... } : stream, n) :
         M.matrix option =

          
@@ 119,12 225,14 @@ functor ResamplingStatefulStreamFn (S: R
                       NONE => SOME spmat
                     | SOME m => SOME (M.concatHorizontal [spmat, m]))
         end
-
+*)
     fun read (s as { upstream, resampler, frameCounter, ... } : stream, n) :
         M.matrix option =
         let val mopt = case resampler of
                            NONE => S.read (upstream, n)
-                         | SOME r => readResampling (s, n)
+                         | SOME r => PullResampler.pull
+                                         (fn count => S.read (upstream, count))
+                                         (r, n)
         in
             case mopt of
                 NONE => NONE

          
@@ 132,13 240,11 @@ functor ResamplingStatefulStreamFn (S: R
                            mopt)
         end
 
-    fun reset (s as { resampler, upstream, spare,
+    fun reset (s as { resampler, upstream,
                       lastSeekTime, frameCounter, ... } : stream) =
-        (case resampler of NONE => ()
-                         | SOME r => BqResample.reset r;
-         spare := M.fromRows (List.tabulate
-				  (S.channels upstream,
-				   fn _ => RealVector.fromList []));
+        (case resampler of
+             NONE => ()
+           | SOME r => PullResampler.reset r;
          lastSeekTime := S.time upstream;
          frameCounter := 0
         )

          
@@ 208,12 314,13 @@ functor ResamplingStatefulStreamFn (S: R
             val resampler =
                 if Real.== (ratio, 1.0)
                 then NONE
-                else SOME (BqResample.new
+                else SOME (PullResampler.new 
                                { channels = S.channels s,
                                  quality = quality,
                                  dynamism = BqResample.RATIO_MOSTLY_FIXED,
                                  ratio_change = BqResample.SUDDEN_RATIO_CHANGE
                                }
+                               ratio
                           )
 	in
 	    {

          
@@ 221,9 328,6 @@ functor ResamplingStatefulStreamFn (S: R
               ratio = ratio,
               upstream = s,
               resampler = resampler,
-              spare = ref (M.fromRows (List.tabulate
-					   (S.channels s,
-					    fn _ => RealVector.fromList []))),
               quality = quality,
               upstreamStartTime = ref (S.time s),
               lastSeekTime = ref (S.time s),