import AVFoundation import Metal public final class AudioFeatureExtractor { public let sampleRate: Int public let nMels: Int public let nFft: Int public let hopLength: Int public let fMin: Float public let fMax: Float public init( sampleRate: Int = 16000, nMels: Int = 128, nFft: Int = 400, hopLength: Int = 160, fMin: Float = 0, fMax: Float = 8000 ) { self.sampleRate = sampleRate self.nMels = nMels self.nFft = nFft self.hopLength = hopLength self.fMin = fMin self.fMax = fMax } public func extractMelSpectrogram(from audioData: [Float]) -> [[Float]] { let numFrames = (audioData.count - nFft) / hopLength + 1 var melSpec = [[Float]](repeating: [Float](repeating: 0, count: nMels), count: numFrames) for frameIdx in 0.. [Float] { let n = frame.count return frame.enumerated().map { i, val in val * 0.5 * (1.0 - cos(2.0 * Float.pi * Float(i) / Float(n - 1))) } } private func computeSpectrum(_ frame: [Float]) -> [Float] { let n = frame.count var spectrum = [Float](repeating: 0, count: n / 2 + 1) for k in 0.. [Float] { var melEnergies = [Float](repeating: 0, count: nMels) let melPoints = createMelFilterbank() for melIdx in 0.. [[Float]] { var filterbank = [[Float]](repeating: [Float](repeating: 0, count: nFft / 2 + 1), count: nMels) let melMin = hzToMel(fMin) let melMax = hzToMel(fMax) let melPoints = (0.. Float { 2595.0 * log10(1.0 + hz / 700.0) } private func melToHz(_ mel: Float) -> Float { 700.0 * (pow(10.0, mel / 2595.0) - 1.0) } // ── GPU-accelerated mel spectrogram ── public func extractMelSpectrogramGPU( engine: MarkBaseEngine, audioData: [Float] ) throws -> [[Float]] { let device = engine.device let spectrumSize = nFft / 2 + 1 let numFrames = (audioData.count - nFft) / hopLength + 1 let melBufferSize = numFrames * nMels let filterbank2D = createMelFilterbank() var flatFilterbank = [Float](repeating: 0, count: nMels * spectrumSize) for m in 0.. [Float] { let asset = AVURLAsset(url: url) let reader = try AVAssetReader(asset: asset) let output = AVAssetReaderAudioMixOutput(audioTracks: asset.tracks, audioSettings: nil) reader.add(output) reader.startReading() var samples: [Float] = [] while reader.status == .reading { let buffer = output.copyNextSampleBuffer() if let buffer = buffer { let blockBuffer = CMSampleBufferGetDataBuffer(buffer) if let blockBuffer = blockBuffer { let length = CMBlockBufferGetDataLength(blockBuffer) var data = [Float](repeating: 0, count: length / MemoryLayout.stride) CMBlockBufferCopyDataBytes(blockBuffer, atOffset: 0, dataLength: length, destination: &data) samples.append(contentsOf: data) } } } return samples } }