import Accelerate import Foundation import SpeechCore /// 128-bin log-mel extractor matching HF WhisperFeatureExtractor /// (n_fft=400, hop=160, periodic hann, reflect padding, slaney mel, /// log10 -> clamp to max-8 -> (x+4)/4), then zero-padded to 3000 frames. package final class MelExtractor { package static let nFFT = 400 package static let hop = 160 package static let nMels = 128 package static let nFreq = 201 // nFFT/2 + 1 package static let framesPadded = 3000 package static let maxSamples = 480_000 /// mel-frame buckets matching the multifunction tower (5s / 10s / 30s) package static let buckets = [500, 1000, 3000] package static func bucket(forFrames frames: Int) -> Int { for b in buckets where frames <= b { return b } return buckets.last! } private let window: [Float] // 400 private let melFilters: [Float] // 201*128 row-major [freq][mel] private let dftCos: [Float] // 400*201 [n][f] private let dftSin: [Float] // 400*201 package init(hannWindow: [Float], melFilters: [Float]) { precondition(hannWindow.count == Self.nFFT) precondition(melFilters.count == Self.nFreq * Self.nMels) self.window = hannWindow self.melFilters = melFilters var cosM = [Float](repeating: 0, count: Self.nFFT * Self.nFreq) var sinM = [Float](repeating: 0, count: Self.nFFT * Self.nFreq) for n in 0.. (mel: [Float], encLen: Int, bucketFrames: Int) { var x = samples if x.count > Self.maxSamples { x = Array(x[0.. (T,128) var mel = [Float](repeating: 0, count: tFrames * Self.nMels) cblas_sgemm(CblasRowMajor, CblasNoTrans, CblasNoTrans, Int32(tFrames), Int32(Self.nMels), Int32(Self.nFreq), 1, re, Int32(Self.nFreq), melFilters, Int32(Self.nMels), 0, &mel, Int32(Self.nMels)) // log10(max(x,1e-10)); clamp to globalMax-8; (x+4)/4 var clamped = vDSP.clip(mel, to: 1e-10...Float.greatestFiniteMagnitude) var logMel = [Float](repeating: 0, count: clamped.count) var count = Int32(clamped.count) vvlog10f(&logMel, &clamped, &count) let maxVal = vDSP.maximum(logMel) logMel = vDSP.clip(logMel, to: (maxVal - 8.0)...Float.greatestFiniteMagnitude) logMel = vDSP.add(4.0, logMel) logMel = vDSP.divide(logMel, 4.0) // transpose (T,128) -> (128, bucket) zero-padded let bucketFrames = Self.bucket(forFrames: tFrames) var out = [Float](repeating: 0, count: Self.nMels * bucketFrames) let tUse = min(tFrames, bucketFrames) for t in 0..