132 lines
4.3 KiB
Swift
132 lines
4.3 KiB
Swift
import CoreGraphics
|
|
import Testing
|
|
@testable import SstUI
|
|
|
|
struct VUScaleTests {
|
|
@Test func zeroVUIsMinusTwentyDbfs() {
|
|
#expect(VUScale.vu(fromDbfs: -20) == 0)
|
|
#expect(VUScale.vu(fromDbfs: -40) == -20)
|
|
}
|
|
|
|
@Test func fractionSpansTheScale() {
|
|
#expect(VUScale.fraction(forVU: -20) == 0)
|
|
#expect(abs(VUScale.fraction(forVU: 3) - 1) < 1e-9)
|
|
#expect(abs(VUScale.fraction(forVU: 0) - 20.0 / 23.0) < 1e-9)
|
|
}
|
|
|
|
@Test func fractionIsClampedAtBothEnds() {
|
|
#expect(VUScale.fraction(forVU: -60) == 0)
|
|
#expect(VUScale.fraction(forVU: 30) == 1.03)
|
|
#expect(VUScale.fraction(forDbfs: -120) == 0)
|
|
}
|
|
|
|
@Test func angleIsSymmetricAroundVertical() {
|
|
#expect(abs(VUScale.angle(forFraction: 0) + VUScale.angle(forFraction: 1)) < 1e-9)
|
|
#expect(abs(VUScale.angle(forFraction: 0.5)) < 1e-9)
|
|
#expect(abs(VUScale.angle(forFraction: 1) - 45 * .pi / 180) < 1e-9)
|
|
}
|
|
}
|
|
|
|
struct NeedleSpringTests {
|
|
func simulate(_ spring: NeedleSpring, seconds: Double, target: Double = 1) -> (final: Double, peak: Double) {
|
|
var spring = spring
|
|
var peak = 0.0
|
|
let dt = 1.0 / 240
|
|
for _ in 0..<Int(seconds / dt) { peak = max(peak, spring.step(target: target, dt: dt)) }
|
|
return (spring.position, peak)
|
|
}
|
|
|
|
@Test func reachesTheTargetWithinTheResponseTime() {
|
|
let result = simulate(NeedleSpring(), seconds: Motion.needleResponse)
|
|
#expect(abs(result.final - 1) < 0.02)
|
|
}
|
|
|
|
@Test func overshootsSlightlyWithTheTokenDamping() {
|
|
let result = simulate(NeedleSpring(), seconds: 1)
|
|
#expect(result.peak > 1.003)
|
|
#expect(result.peak < 1.03)
|
|
#expect(abs(result.final - 1) < 0.001)
|
|
}
|
|
|
|
@Test func criticalDampingNeverOvershoots() {
|
|
let result = simulate(NeedleSpring(damping: 1), seconds: 1)
|
|
#expect(result.peak <= 1.000001)
|
|
}
|
|
|
|
@Test func largeFrameStepsStayStable() {
|
|
var spring = NeedleSpring()
|
|
for _ in 0..<40 { spring.step(target: 1, dt: 0.05) }
|
|
#expect(abs(spring.position - 1) < 0.001)
|
|
}
|
|
|
|
@Test func restsAtTheStartWithoutATarget() {
|
|
var spring = NeedleSpring()
|
|
for _ in 0..<60 { spring.step(target: 0, dt: 1.0 / 60) }
|
|
#expect(spring.position == 0)
|
|
}
|
|
}
|
|
|
|
struct SegmentGlyphTests {
|
|
@Test func everyDigitHasASegmentPattern() {
|
|
for digit in "0123456789" { #expect(!SegmentGlyph.lit(for: digit).isEmpty) }
|
|
#expect(SegmentGlyph.lit(for: "8").count == 7)
|
|
#expect(SegmentGlyph.lit(for: "1") == [.b, .c])
|
|
#expect(SegmentGlyph.lit(for: ":").isEmpty)
|
|
}
|
|
|
|
@Test func polygonsStayInsideTheDigitBoxAndDiffer() {
|
|
var seen: [[CGPoint]] = []
|
|
for id in SegmentID.allCases {
|
|
let polygon = SegmentGlyph.polygon(id)
|
|
#expect(polygon.count == 6)
|
|
for point in polygon {
|
|
#expect(point.x >= 0 && point.x <= Segment.digitWidth)
|
|
#expect(point.y >= 0 && point.y <= Segment.digitHeight)
|
|
}
|
|
#expect(!seen.contains(polygon))
|
|
seen.append(polygon)
|
|
}
|
|
#expect(seen.count == 7)
|
|
}
|
|
}
|
|
|
|
struct LEDBarTests {
|
|
@Test func tonesFollowTheEightThreeOneSplit() {
|
|
let tones = (0..<12).map { LEDBar.tone(forSegment: $0) }
|
|
#expect(tones.prefix(8).allSatisfy { $0 == .green })
|
|
#expect(tones[8...10].allSatisfy { $0 == .amber })
|
|
#expect(tones[11] == .red)
|
|
}
|
|
|
|
@Test func litCountScalesAndClamps() {
|
|
#expect(LEDBar.litCount(forFraction: 0) == 0)
|
|
#expect(LEDBar.litCount(forFraction: 0.5) == 6)
|
|
#expect(LEDBar.litCount(forFraction: 1) == 12)
|
|
#expect(LEDBar.litCount(forFraction: 1.03) == 12)
|
|
#expect(LEDBar.litCount(forFraction: -1) == 0)
|
|
}
|
|
}
|
|
|
|
struct SpokenLevelTests {
|
|
@Test func nonFiniteInputReturnsStil() {
|
|
#expect(VUScale.spokenLevel(fromDbfs: -.infinity) == "stil")
|
|
#expect(VUScale.spokenLevel(fromDbfs: .nan) == "stil")
|
|
}
|
|
|
|
@Test func referenceLevel() {
|
|
#expect(VUScale.spokenLevel(fromDbfs: -20) == "0 VU")
|
|
}
|
|
|
|
@Test func negativeLevel() {
|
|
#expect(VUScale.spokenLevel(fromDbfs: -30) == "-9 VU")
|
|
}
|
|
|
|
@Test func clampedToMinimum() {
|
|
#expect(VUScale.spokenLevel(fromDbfs: -120) == "-20 VU")
|
|
}
|
|
|
|
@Test func clampedToMaximum() {
|
|
#expect(VUScale.spokenLevel(fromDbfs: 10) == "3 VU")
|
|
}
|
|
}
|