| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273 |
- import Accelerate
- import AVFoundation
- import Foundation
- /// Generates waveform data from audio files for visualization.
- struct WaveformGenerator {
- struct WaveformSample: Codable {
- let min: Float
- let max: Float
- }
- /// Generate downsampled waveform from an audio file.
- static func generateWaveform(for track: Track, targetSampleCount: Int = 300) async throws -> [WaveformSample] {
- let url = track.fileURL
- let file = try AVAudioFile(forReading: url)
- guard let buffer = AVAudioPCMBuffer(pcmFormat: file.processingFormat, frameCapacity: AVAudioFrameCount(file.length)) else {
- return []
- }
- try file.read(into: buffer)
- guard let floatData = buffer.floatChannelData else { return [] }
- let frameCount = Int(buffer.frameLength)
- let channelCount = Int(buffer.format.channelCount)
- // Mix to mono
- var monoSamples = [Float](repeating: 0, count: frameCount)
- for ch in 0..<channelCount {
- let channelPtr = floatData[ch]
- for i in 0..<frameCount {
- monoSamples[i] += channelPtr[i]
- }
- }
- if channelCount > 1 {
- var divisor = Float(channelCount)
- vDSP_vsdiv(monoSamples, 1, &divisor, &monoSamples, 1, vDSP_Length(frameCount))
- }
- // Downsample to target count
- let samplesPerBucket = max(1, frameCount / targetSampleCount)
- var waveform: [WaveformSample] = []
- waveform.reserveCapacity(targetSampleCount)
- for i in 0..<targetSampleCount {
- let start = i * samplesPerBucket
- let end = min(start + samplesPerBucket, frameCount)
- let count = vDSP_Length(end - start)
- guard count > 0 else { continue }
- var minVal: Float = 0
- var maxVal: Float = 0
- monoSamples.withUnsafeBufferPointer { ptr in
- let base = ptr.baseAddress! + start
- vDSP_minv(base, 1, &minVal, count)
- vDSP_maxv(base, 1, &maxVal, count)
- }
- waveform.append(WaveformSample(min: minVal, max: maxVal))
- }
- // Cache on the track
- if let encoded = try? JSONEncoder().encode(waveform) {
- track.waveformData = encoded
- }
- return waveform
- }
- /// Decode cached waveform data.
- static func decodeCachedWaveform(from data: Data) -> [WaveformSample]? {
- try? JSONDecoder().decode([WaveformSample].self, from: data)
- }
- }
|