Pegel auch für Ausgabe und Mikrofon
Die Programmzeilen hatten ihren mitlaufenden Ausschlag, die beiden Systemregler nicht. Jetzt beide, in derselben Form: der Pegel läuft im Regler mit, nicht daneben. Der Ausgangspegel kommt über einen globalen Tap — denselben Weg wie der Mixer, nur ohne Stummschalten und ohne Ausgabe. Ausgenommen werden die Programme, die der Mixer bereits abgreift: ein Tap sieht das Signal vor dem Stummschalten, sie zählten sonst doppelt — einmal als Original, das niemand hört, und einmal als das, was Onyx daraus macht. Ändert sich diese Liste, wird der Tap neu aufgebaut. Der Mikrofonpegel geht nur mit echter Aufnahme. Es gibt keine Eigenschaft am Gerät, die den Ausschlag verrät — einen Pegel bekommt nur, wer das Signal liest, und genau das meldet macOS mit dem orangen Punkt. Das ist richtig so, also läuft die Messung nur, solange die Ansicht offen ist, und hört beim Schließen sofort auf. Ein Zähler statt eines Schalters, weil Panel und Popover gleichzeitig offen sein können. Geräte, die keine Fließkommawerte liefern, bleiben ohne Pegel: als Fließkomma gelesene Ganzzahlen ergeben keinen falschen Ausschlag, sondern Unsinn. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -86,6 +86,11 @@ public struct MixerView: View {
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.animation(Onyx.Motion.value, value: mixer.groups.count)
|
||||
// Die Pegel laufen nur, solange die Ansicht sichtbar ist. Der
|
||||
// Mikrofonpegel schaltet dabei den orangen Punkt an — ein Messgerät,
|
||||
// das im Hintergrund weiterläuft, wäre hier das Falsche.
|
||||
.onAppear { system.levels.addConsumer() }
|
||||
.onDisappear { system.levels.removeConsumer() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -463,6 +468,7 @@ private struct SystemSection: View {
|
||||
devices: system.outputDevices,
|
||||
selected: system.outputDeviceID,
|
||||
volume: system.outputVolume,
|
||||
level: system.levels.output,
|
||||
muted: system.outputMuted,
|
||||
adjustable: system.outputIsAdjustable,
|
||||
compact: compact,
|
||||
@@ -478,6 +484,7 @@ private struct SystemSection: View {
|
||||
devices: system.inputDevices,
|
||||
selected: system.inputDeviceID,
|
||||
volume: system.inputVolume,
|
||||
level: system.levels.input,
|
||||
muted: system.inputMuted,
|
||||
adjustable: system.inputIsAdjustable,
|
||||
compact: compact,
|
||||
@@ -510,6 +517,7 @@ private struct SystemRow: View {
|
||||
let devices: [AudioDevice]
|
||||
let selected: AudioObjectID
|
||||
let volume: Double
|
||||
let level: Double
|
||||
let muted: Bool
|
||||
let adjustable: Bool
|
||||
let compact: Bool
|
||||
@@ -558,7 +566,8 @@ private struct SystemRow: View {
|
||||
.foregroundStyle(muted ? Onyx.Color.critical : Onyx.Color.textSecondary)
|
||||
|
||||
if adjustable {
|
||||
SystemVolumeBar(volume: volume, isActive: !muted, onChange: onVolume)
|
||||
SystemVolumeBar(volume: volume, level: level,
|
||||
isActive: !muted, onChange: onVolume)
|
||||
Text("\(Int(volume * 100)) %")
|
||||
.font(Onyx.Font.metricSmall).monospacedDigit()
|
||||
.foregroundStyle(Onyx.Color.textTertiary)
|
||||
@@ -583,6 +592,7 @@ private struct SystemRow: View {
|
||||
/// Systemlautstärke geht von null bis hundert, mehr gibt Core Audio nicht her.
|
||||
private struct SystemVolumeBar: View {
|
||||
let volume: Double
|
||||
let level: Double
|
||||
let isActive: Bool
|
||||
let onChange: (Double) -> Void
|
||||
|
||||
@@ -596,10 +606,24 @@ private struct SystemVolumeBar: View {
|
||||
|
||||
ZStack(alignment: .leading) {
|
||||
Capsule().fill(Onyx.Color.surface).frame(height: 6)
|
||||
let fillWidth = travel * fraction + knob / 2
|
||||
|
||||
Capsule()
|
||||
.fill(Onyx.Color.accent)
|
||||
.frame(width: travel * fraction + knob / 2, height: 6)
|
||||
.frame(width: fillWidth, height: 6)
|
||||
.opacity(isActive ? 1 : 0.3)
|
||||
|
||||
// Derselbe mitlaufende Pegel wie in den Programmzeilen. Er
|
||||
// sitzt **im** Regler, weil eine zweite Leiste daneben doppelt
|
||||
// so viel Grafik für dieselbe Auskunft wäre.
|
||||
if isActive, level > 0.001 {
|
||||
Capsule()
|
||||
.fill(.white.opacity(0.4))
|
||||
.frame(width: fillWidth * meterFraction, height: 6)
|
||||
.blendMode(.plusLighter)
|
||||
.allowsHitTesting(false)
|
||||
}
|
||||
|
||||
Circle()
|
||||
.fill(.white)
|
||||
.frame(width: knob, height: knob)
|
||||
@@ -620,4 +644,11 @@ private struct SystemVolumeBar: View {
|
||||
.frame(height: 16)
|
||||
.animation(Onyx.Motion.value, value: isDragging)
|
||||
}
|
||||
|
||||
/// Ausschläge sind linear in der Amplitude und damit optisch nutzlos:
|
||||
/// halb so laut sieht aus wie fast nichts. Die Wurzel spreizt den unteren
|
||||
/// Bereich so weit, dass man überhaupt etwas sieht.
|
||||
private var meterFraction: Double {
|
||||
min(1, sqrt(min(max(level, 0), 1)))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user