WaveformGenerator.swift 2.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273
  1. import Accelerate
  2. import AVFoundation
  3. import Foundation
  4. /// Generates waveform data from audio files for visualization.
  5. struct WaveformGenerator {
  6. struct WaveformSample: Codable {
  7. let min: Float
  8. let max: Float
  9. }
  10. /// Generate downsampled waveform from an audio file.
  11. static func generateWaveform(for track: Track, targetSampleCount: Int = 300) async throws -> [WaveformSample] {
  12. let url = track.fileURL
  13. let file = try AVAudioFile(forReading: url)
  14. guard let buffer = AVAudioPCMBuffer(pcmFormat: file.processingFormat, frameCapacity: AVAudioFrameCount(file.length)) else {
  15. return []
  16. }
  17. try file.read(into: buffer)
  18. guard let floatData = buffer.floatChannelData else { return [] }
  19. let frameCount = Int(buffer.frameLength)
  20. let channelCount = Int(buffer.format.channelCount)
  21. // Mix to mono
  22. var monoSamples = [Float](repeating: 0, count: frameCount)
  23. for ch in 0..<channelCount {
  24. let channelPtr = floatData[ch]
  25. for i in 0..<frameCount {
  26. monoSamples[i] += channelPtr[i]
  27. }
  28. }
  29. if channelCount > 1 {
  30. var divisor = Float(channelCount)
  31. vDSP_vsdiv(monoSamples, 1, &divisor, &monoSamples, 1, vDSP_Length(frameCount))
  32. }
  33. // Downsample to target count
  34. let samplesPerBucket = max(1, frameCount / targetSampleCount)
  35. var waveform: [WaveformSample] = []
  36. waveform.reserveCapacity(targetSampleCount)
  37. for i in 0..<targetSampleCount {
  38. let start = i * samplesPerBucket
  39. let end = min(start + samplesPerBucket, frameCount)
  40. let count = vDSP_Length(end - start)
  41. guard count > 0 else { continue }
  42. var minVal: Float = 0
  43. var maxVal: Float = 0
  44. monoSamples.withUnsafeBufferPointer { ptr in
  45. let base = ptr.baseAddress! + start
  46. vDSP_minv(base, 1, &minVal, count)
  47. vDSP_maxv(base, 1, &maxVal, count)
  48. }
  49. waveform.append(WaveformSample(min: minVal, max: maxVal))
  50. }
  51. // Cache on the track
  52. if let encoded = try? JSONEncoder().encode(waveform) {
  53. track.waveformData = encoded
  54. }
  55. return waveform
  56. }
  57. /// Decode cached waveform data.
  58. static func decodeCachedWaveform(from data: Data) -> [WaveformSample]? {
  59. try? JSONDecoder().decode([WaveformSample].self, from: data)
  60. }
  61. }