vOOice/VoiceInk/Views/Recorder/AudioVisualizerView.swift
2025-08-08 20:44:20 +05:45

114 lines
3.8 KiB
Swift

import SwiftUI
struct AudioVisualizer: View {
let audioMeter: AudioMeter
let color: Color
let isActive: Bool
private let barCount = 12
private let minHeight: CGFloat = 5
private let maxHeight: CGFloat = 32
private let barWidth: CGFloat = 3.5
private let barSpacing: CGFloat = 2.3
private let hardThreshold: Double = 0.3
private let sensitivityMultipliers: [Double]
@State private var barHeights: [CGFloat]
@State private var targetHeights: [CGFloat]
init(audioMeter: AudioMeter, color: Color, isActive: Bool) {
self.audioMeter = audioMeter
self.color = color
self.isActive = isActive
self.sensitivityMultipliers = (0..<barCount).map { _ in
Double.random(in: 0.2...1.9)
}
_barHeights = State(initialValue: Array(repeating: minHeight, count: barCount))
_targetHeights = State(initialValue: Array(repeating: minHeight, count: barCount))
}
var body: some View {
HStack(spacing: barSpacing) {
ForEach(0..<barCount, id: \.self) { index in
RoundedRectangle(cornerRadius: 1.7)
.fill(color)
.frame(width: barWidth, height: barHeights[index])
}
}
.onChange(of: audioMeter) { _, newValue in
if isActive {
updateBars(with: Float(newValue.averagePower))
} else {
resetBars()
}
}
.onChange(of: isActive) { _, newValue in
if !newValue {
resetBars()
}
}
}
private func updateBars(with audioLevel: Float) {
let rawLevel = max(0, min(1, Double(audioLevel)))
let adjustedLevel = rawLevel < hardThreshold ? 0 : (rawLevel - hardThreshold) / (1.0 - hardThreshold)
let range = maxHeight - minHeight
let center = barCount / 2
for i in 0..<barCount {
let distanceFromCenter = abs(i - center)
let positionMultiplier = 1.0 - (Double(distanceFromCenter) / Double(center)) * 0.4
// Use randomized sensitivity
let sensitivityAdjustedLevel = adjustedLevel * positionMultiplier * sensitivityMultipliers[i]
let targetHeight = minHeight + CGFloat(sensitivityAdjustedLevel) * range
let isDecaying = targetHeight < targetHeights[i]
let smoothingFactor: CGFloat = isDecaying ? 0.6 : 0.3 // Adjusted smoothing
targetHeights[i] = targetHeights[i] * (1 - smoothingFactor) + targetHeight * smoothingFactor
// Only update if change is significant enough to matter visually
if abs(barHeights[i] - targetHeights[i]) > 0.5 {
withAnimation(
isDecaying
? .spring(response: 0.4, dampingFraction: 0.8)
: .spring(response: 0.3, dampingFraction: 0.7)
) {
barHeights[i] = targetHeights[i]
}
}
}
}
private func resetBars() {
withAnimation(.easeOut(duration: 0.15)) {
barHeights = Array(repeating: minHeight, count: barCount)
targetHeights = Array(repeating: minHeight, count: barCount)
}
}
}
struct StaticVisualizer: View {
private let barCount = 12
private let barWidth: CGFloat = 3.5
private let staticHeight: CGFloat = 5.0
private let barSpacing: CGFloat = 2.3
let color: Color
var body: some View {
HStack(spacing: barSpacing) {
ForEach(0..<barCount, id: \.self) { index in
RoundedRectangle(cornerRadius: 1.7)
.fill(color)
.frame(width: barWidth, height: staticHeight)
}
}
}
}