forked from BasedHardware/omi
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathAudioLevelWaveformView.swift
More file actions
133 lines (114 loc) · 3.87 KB
/
Copy pathAudioLevelWaveformView.swift
File metadata and controls
133 lines (114 loc) · 3.87 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
import OmiTheme
import SwiftUI
/// Animated audio level waveform visualization
/// Shows 12 vertical bars that scale with audio level
struct AudioLevelWaveformView: View {
let level: Float
let barCount: Int
let isActive: Bool
init(level: Float, barCount: Int = 12, isActive: Bool = true) {
self.level = level
self.barCount = barCount
self.isActive = isActive
}
/// Fixed width computed from bar count and spacing so sizeThatFits() can
/// short-circuit without traversing child bars.
private var fixedWidth: CGFloat {
let barWidth: CGFloat = 3
let spacing: CGFloat = 3
return CGFloat(barCount) * barWidth + CGFloat(barCount - 1) * spacing
}
var body: some View {
HStack(spacing: OmiSpacing.hairline) {
ForEach(0..<barCount, id: \.self) { index in
BarView(
level: level,
index: index,
totalBars: barCount,
isActive: isActive
)
}
}
.frame(width: fixedWidth, height: 32) // Fixed size — prevents sizeThatFits() tree traversal
}
}
private struct BarView: View {
let level: Float
let index: Int
let totalBars: Int
let isActive: Bool
private let minHeight: CGFloat = 6
private let maxHeight: CGFloat = 32
private let barWidth: CGFloat = 3
/// Calculate height based on audio level with some variation per bar
private var barHeight: CGFloat {
guard isActive else { return minHeight }
// Apply sensitivity boost - amplify low levels more
let boostedLevel = pow(CGFloat(level), 0.5) * 2.5 // Square root makes low levels more visible
let clampedLevel = min(1.0, boostedLevel)
// Create variation across bars (center bars taller)
let centerOffset = abs(CGFloat(index) - CGFloat(totalBars - 1) / 2.0) / (CGFloat(totalBars) / 2.0)
let variation = 1.0 - (centerOffset * 0.4) // Center bars up to 40% taller
// Scale with audio level
let scaledLevel = clampedLevel * variation
// Deterministic per-bar variation for organic feel (avoid CGFloat.random which
// produces different values on every body evaluation, causing unnecessary layout)
let hash = sin(CGFloat(index) * 1.618 + 0.5)
let deterministicVariation = 0.85 + 0.3 * (hash * 0.5 + 0.5)
let height = minHeight + (maxHeight - minHeight) * scaledLevel * deterministicVariation
return max(minHeight, min(maxHeight, height))
}
private var barColor: Color {
guard isActive else { return Ink.secondary.opacity(0.5) }
// Color intensity based on level
let boostedLevel = min(1.0, pow(CGFloat(level), 0.5) * 2.5)
if boostedLevel > 0.6 {
return Ink.accent
} else if boostedLevel > 0.2 {
return Ink.primary
} else if boostedLevel > 0.02 {
return Ink.secondary
}
return Ink.secondary.opacity(0.5)
}
var body: some View {
RoundedRectangle(cornerRadius: 1.5)
.fill(barColor)
.frame(width: barWidth, height: barHeight)
// No .omiAnimation() — each animation generates ~5 intermediate layout frames at 60fps,
// and every frame triggers a full view-tree sizeThatFits() traversal.
// At 5 Hz update rate, the visual steps are small enough to look smooth.
}
}
#if canImport(PreviewsMacros)
#Preview {
VStack(spacing: OmiSpacing.xl) {
// Idle state
HStack {
Text("Idle:")
.foregroundColor(Ink.primary)
AudioLevelWaveformView(level: 0.0, isActive: false)
}
// Low level
HStack {
Text("Low:")
.foregroundColor(Ink.primary)
AudioLevelWaveformView(level: 0.1)
}
// Medium level
HStack {
Text("Medium:")
.foregroundColor(Ink.primary)
AudioLevelWaveformView(level: 0.4)
}
// High level
HStack {
Text("High:")
.foregroundColor(Ink.primary)
AudioLevelWaveformView(level: 0.8)
}
}
.padding()
.background(Ink.surface)
}
#endif