bbc858ae047e — Chris Cannam 3 months ago
Add SpectralPeakPick to do conversions between frequency and bin
1 files changed, 66 insertions(+), 0 deletions(-)

M peakpick.sml
M peakpick.sml +66 -0
@@ 484,3 484,69 @@ structure SubBandPeakPick = struct
         end
 
 end
+
+(** A specialisation of SubBandPeakPick for use when the vector
+    represents bins from a STFT output, in which the bin index is
+    proportional to frequency.
+ *)
+structure SpectralPeakPick = struct
+
+    (** A sub-band, specified by start and end frequencies. If end is
+        NONE, the band continues to the end of the origin vector. The
+        p-parameter is used for peak picking within the band.
+     *)
+    type band = { f0 : real, f1 : real option, p : int }
+
+    (** A list of sub-bands. The bands do not have to be contiguous
+        and may even overlap, though that is probably undesirable as
+        it may mean returning duplicate peaks.
+     *)
+    type bands = band list
+
+    (** Convert a band to make it usable by SubBandPeakPick directly
+     *)
+    fun mapBand { rate, n } ({ p, f0, f1 } : band)
+        : SubBandPeakPick.band =
+        let val binFor = Pitch.binForFrequency { rate = rate, n = n }
+            val start = floor (binFor f0)
+        in
+            { p = p,
+              start = start,
+              count = Option.map
+                          (fn f1' => (ceil (binFor f1' + 1.0e~8) - start))
+                          f1
+            }
+        end
+                      
+    (** Return a list of indices of local peaks within the vector,
+        dividing it up into sub-bands following the given band
+        list. The parameter n is the number of bins, used for
+        conversion between frequency and bin number; the supplied
+        vector does not need to have all n bins but it must start at
+        nominal bin zero (DC).
+     *)
+    fun findPeaks { bands : bands,
+                    rate : SignalTypes.rate,
+                    n : int
+                  } (v : RealVector.vector)
+        : int list =
+        SubBandPeakPick.findPeaks {
+            bands = map (mapBand { rate = rate, n = n }) bands
+        } v
+            
+    (** Return a list of indices of local peaks within the vector,
+        dividing it up into sub-bands following the given band list
+        with boundaries snapped to their nearest (simple) trough bin.
+     *)
+    fun findPeaksSnapped { bands : bands,
+                           rate : SignalTypes.rate,
+                           n : int,
+                           resolveDownwards : bool
+                         } (v : RealVector.vector)
+        : int list =
+        SubBandPeakPick.findPeaksSnapped {
+            bands = map (mapBand { rate = rate, n = n }) bands,
+            resolveDownwards = resolveDownwards
+        } v
+
+end