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:
Scarriffle
2026-08-13 18:17:40 +02:00
parent 9df2ddb80b
commit 7a3d2c1c39
6 changed files with 385 additions and 2 deletions

View File

@@ -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)))
}
}