// // SceneSignal.swift // Focale // // Author: Simon-Pierre Boucher // Contact: contact@spboucher.ai // // Cheap physical signals read off the capture device at shutter time. // No inference — these come straight from the sensor. // import Foundation struct SceneSignal: Codable, Sendable { /// Estimated scene brightness in EV100 (log2 scale). Lower = darker. var luminanceEV: Double? /// Rough device/subject motion classification. var motion: MotionLevel /// Subject distance in meters when LiDAR/depth is available. var subjectDistance: Double? /// Convenience flag used by recipe scene triggers. var isLowLight: Bool static let unknown = SceneSignal( luminanceEV: nil, motion: .unknown, subjectDistance: nil, isLowLight: false ) } enum MotionLevel: String, Codable, Sendable { case still case moving case unknown } extension SceneSignal { /// EV100 from the current exposure triplet: EV = log2(1 / (t * S/100)). /// Good enough to distinguish "sunlit" from "dim room" for scene triggers. static func estimateEV(iso: Float, shutterSeconds: Double) -> Double? { guard iso > 0, shutterSeconds > 0 else { return nil } return log2(1.0 / (shutterSeconds * Double(iso) / 100.0)) } }