import Testing import Foundation @testable import AudioProvider // Hier wird in den Ton eingegriffen, der aus den Lautsprechern kommt. Ein // Rechenfehler klingt nicht falsch — er knackt, verzerrt oder ist schmerzhaft // laut. Deshalb liegt die Rechnung als reine Funktion vor und wird vollständig // durchgeprüft, bevor sie an einen Audiopuffer kommt. @Suite("Verstärkung") struct GainTests { @Test("Volle Lautstärke lässt das Signal unverändert") func unityIsTransparent() { #expect(Gain.factor(forVolume: 1.0) == 1.0) } @Test("Stumm ist wirklich still") func zeroIsSilent() { #expect(Gain.factor(forVolume: 0) == 0) } @Test("Die Skala ist wahrnehmungsgerecht, nicht linear") func scaleIsPerceptual() { // Halber Reglerweg soll halb so laut klingen, nicht halbe Amplitude // haben — das sind rund −10 dB, also etwa Faktor 0,3. Linear wäre der // Regler in der unteren Hälfte praktisch wirkungslos. let half = Gain.factor(forVolume: 0.5) #expect(half > 0.2 && half < 0.4) } @Test("Die Skala steigt durchgehend") func scaleIsMonotonic() { var previous = -1.0 for step in 0...100 { let value = Gain.factor(forVolume: Double(step) / 100) #expect(value >= previous) previous = value } } @Test("Über 100 Prozent wird tatsächlich verstärkt") func boostAmplifies() { #expect(Gain.factor(forVolume: 2.0) > 1.0) } @Test("Der Regelbereich ist begrenzt — auch bei absurder Eingabe") func rangeIsClamped() { #expect(Gain.factor(forVolume: -5) == 0) // Die Kappung sitzt beim höchsten überhaupt erreichbaren Wert. Ob ein // Programm so weit aufgedreht werden **darf**, entscheidet der Overdrive // im VolumeStore, nicht die Umrechnung hier. #expect(Gain.factor(forVolume: 99) == Gain.factor(forVolume: Gain.overdriveMaximum)) } } @Suite("Begrenzer") struct LimiterTests { @Test("Leise Signale gehen unverändert durch") func quietPassesThrough() { #expect(abs(SoftClip.apply(0.3) - 0.3) < 0.001) #expect(abs(SoftClip.apply(-0.3) + 0.3) < 0.001) } @Test("Nichts verlässt je den gültigen Bereich") func neverExceedsFullScale() { // Der eigentliche Zweck: bei 200 Prozent entstehen Werte weit über 1,0. // Ungebremst werden die von der Hardware hart abgeschnitten, und das // knackt hörbar. for value in stride(from: -8.0, through: 8.0, by: 0.05) { let result = SoftClip.apply(Float(value)) #expect(result <= 1.0 && result >= -1.0) } } @Test("Die Kennlinie ist punktsymmetrisch") func curveIsSymmetric() { for value in stride(from: 0.0, through: 4.0, by: 0.1) { let positive = SoftClip.apply(Float(value)) let negative = SoftClip.apply(Float(-value)) #expect(abs(positive + negative) < 0.0001) } } @Test("Die Kennlinie steigt durchgehend — sonst klingt sie verzerrt") func curveIsMonotonic() { var previous = Float(-2) for value in stride(from: -6.0, through: 6.0, by: 0.01) { let result = SoftClip.apply(Float(value)) #expect(result >= previous - 0.0001) previous = result } } @Test("Null bleibt null") func zeroStaysZero() { #expect(SoftClip.apply(0) == 0) } } @Suite("Lautstärke-Gedächtnis") struct VolumeStoreTests { @Test("Ein gemerkter Wert kommt zurück") func roundTrip() { var store = VolumeStore() store.set(0.4, for: "com.spotify.client") #expect(store.volume(for: "com.spotify.client") == 0.4) } @Test("Ein unbekanntes Programm startet auf voller Lautstärke") func unknownDefaultsToFull() { // Nicht auf null: ein Programm, das nach dem Start stumm ist, wirkt // kaputt, und niemand sucht die Ursache im Mixer. #expect(VolumeStore().volume(for: "com.example.neu") == 1.0) } @Test("Stummschaltung ist getrennt von der Lautstärke") func muteIsSeparate() { // Sonst verliert man beim Stummschalten den eingestellten Wert und // muss ihn danach neu suchen. var store = VolumeStore() store.set(0.6, for: "app") store.setMuted(true, for: "app") #expect(store.isMuted(for: "app")) #expect(store.volume(for: "app") == 0.6) store.setMuted(false, for: "app") #expect(store.volume(for: "app") == 0.6) } @Test("Die wirksame Verstärkung ist null, solange stumm geschaltet ist") func mutedMeansSilent() { var store = VolumeStore() store.set(0.8, for: "app") store.setMuted(true, for: "app") #expect(store.effectiveGain(for: "app") == 0) } @Test("Werte außerhalb des Bereichs werden eingefangen") func valuesAreClamped() { var store = VolumeStore() store.set(-1, for: "app") #expect(store.volume(for: "app") == 0) store.set(999, for: "app") #expect(store.volume(for: "app") == Gain.maximumVolume) } }