114 lines
3.8 KiB
Swift
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)
|
|
}
|
|
}
|
|
}
|
|
}
|