import AVFoundation import SwiftUI actor MicLevelMonitor { private let engine = AVAudioEngine() private var update: (@Sendable (Double) -> Void)? private var running = false private var smoothedLevel: Double = 0 func start(onLevel: @Sendable @escaping (Double) -> Void) async throws { self.update = onLevel if self.running { return } let input = self.engine.inputNode let format = input.outputFormat(forBus: 0) input.removeTap(onBus: 0) input.installTap(onBus: 0, bufferSize: 512, format: format) { [weak self] buffer, _ in guard let self else { return } let level = Self.normalizedLevel(from: buffer) Task { await self.push(level: level) } } self.engine.prepare() try self.engine.start() self.running = true } func stop() { guard self.running else { return } self.engine.inputNode.removeTap(onBus: 0) self.engine.stop() self.running = false } private func push(level: Double) { self.smoothedLevel = (self.smoothedLevel * 0.45) + (level * 0.55) guard let update else { return } let value = self.smoothedLevel Task { @MainActor in update(value) } } private static func normalizedLevel(from buffer: AVAudioPCMBuffer) -> Double { guard let channel = buffer.floatChannelData?[0] else { return 0 } let frameCount = Int(buffer.frameLength) guard frameCount > 0 else { return 0 } var sum: Float = 0 for i in 0.. Double(idx) RoundedRectangle(cornerRadius: 2) .fill(fill ? self.segmentColor(for: idx) : Color.gray.opacity(0.35)) .frame(width: 14, height: 10) } } .padding(4) .background( RoundedRectangle(cornerRadius: 6) .stroke(Color.gray.opacity(0.25), lineWidth: 1)) } private func segmentColor(for idx: Int) -> Color { let fraction = Double(idx + 1) / Double(self.segments) if fraction < 0.65 { return .green } if fraction < 0.85 { return .yellow } return .red } }