class RendererConfig(DumpableAttrs, always_dump="*", exclude="viewport_width viewport_height"): width: int height: int line_width: float = with_units("px", default=1.5) grid_line_width: float = with_units("px", default=1.0) @property def divided_width(self): return round(self.width / self.res_divisor) @property def divided_height(self): return round(self.height / self.res_divisor) bg_color: str = "#000000" init_line_color: str = default_color() grid_color: Optional[str] = None stereo_grid_opacity: float = 0.25 midline_color: str = "#404040" v_midline: bool = False h_midline: bool = False # Label settings label_font: Font = attr.ib(factory=Font) label_position: LabelPosition = LabelPosition.LeftTop # The text will be located (label_padding_ratio * label_font.size) from the corner. label_padding_ratio: float = with_units("px/pt", default=0.5) label_color_override: Optional[str] = None @property def get_label_color(self): return coalesce(self.label_color_override, self.init_line_color) antialiasing: bool = True # Performance (skipped when recording to video) res_divisor: float = 1.0 # Debugging only viewport_width: float = 1 viewport_height: float = 1 def __attrs_post_init__(self) -> None: # round(np.int32 / float) == np.float32, but we want int. assert isinstance(self.width, (int, float)) assert isinstance(self.height, (int, float)) # Both before_* functions should be idempotent, AKA calling twice does no harm. def before_preview(self) -> None: """ Called *once* before preview. Does nothing. """ pass def before_record(self) -> None: """ Called *once* before recording video. Eliminates res_divisor. """ self.res_divisor = 1
class CorrelationTriggerConfig( MainTriggerConfig, always_dump=""" pitch_tracking slope_strength slope_width """ # deprecated " buffer_falloff ", ): # get_trigger() # Edge/area finding sign_strength: float = 0 edge_strength: float # Slope detection slope_strength: float = 0 slope_width: float = with_units("period", default=0.07) # Correlation detection (meow~ =^_^=) buffer_strength: float = 1 # Both data and buffer uses Gaussian windows. std = wave_period * falloff. # get_trigger() data_falloff: float = 1.5 # _update_buffer() buffer_falloff: float = 0.5 # Maximum distance to move trigger_diameter: Optional[float] = 0.5 recalc_semitones: float = 1.0 lag_prevention: LagPrevention = attr.ib(factory=LagPrevention) # _update_buffer responsiveness: float # Period/frequency estimation (not in GUI) max_freq: float = with_units("Hz", default=4000) # Pitch tracking = compute spectrum. pitch_tracking: Optional["SpectrumConfig"] = None # region Legacy Aliases trigger_strength = Alias("edge_strength") falloff_width = Alias("buffer_falloff") # endregion def __attrs_post_init__(self) -> None: MainTriggerConfig.__attrs_post_init__(self) validate_param(self, "slope_width", 0, 0.5) validate_param(self, "responsiveness", 0, 1) # TODO trigger_falloff >= 0 validate_param(self, "buffer_falloff", 0, np.inf)
class Config( KeywordAttrs, always_dump=""" begin_time end_time render_subfps trigger_subsampling render_subsampling trigger_stereo render_stereo show_internals """, ): """ Default values indicate optional attributes. """ master_audio: Optional[str] begin_time: float = with_units("s", default=0) end_time: Optional[float] = None fps: int trigger_ms: int = with_units("ms") render_ms: int = with_units("ms") # Performance trigger_subsampling: int = 1 render_subsampling: int = 1 # Performance (skipped when recording to video) render_subfps: int = 1 render_fps = property(lambda self: Fraction(self.fps, self.render_subfps)) # FFmpeg accepts FPS as a fraction. (decimals may work, but are inaccurate.) def before_preview(self) -> None: """ Called *once* before preview. Decreases render fps/etc. """ self.render.before_preview() def before_record(self) -> None: """ Called *once* before recording video. Force high-quality rendering. """ self.render_subfps = 1 self.render.before_record() # End Performance amplification: float # Stereo config trigger_stereo: Flatten = Flatten.SumAvg render_stereo: Flatten = Flatten.SumAvg trigger: ITriggerConfig # Can be overriden per Wave # Multiplies by trigger_width, render_width. Can override trigger. channels: List[ChannelConfig] layout: LayoutConfig render: RendererConfig show_internals: List[str] = attr.Factory(list) benchmark_mode: BenchmarkMode = attr.ib( BenchmarkMode.NONE, converter=BenchmarkMode.by_name )
class RendererConfig(DumpableAttrs, always_dump="*"): width: int height: int line_width: float = with_units("px", default=1.5) bg_color: str = "#000000" init_line_color: str = default_color() grid_color: Optional[str] = None stereo_grid_opacity: float = 0.5 midline_color: Optional[str] = None v_midline: bool = False h_midline: bool = False antialiasing: bool = True # Performance (skipped when recording to video) res_divisor: float = 1.0 def __attrs_post_init__(self) -> None: # round(np.int32 / float) == np.float32, but we want int. assert isinstance(self.width, (int, float)) assert isinstance(self.height, (int, float)) def before_preview(self) -> None: """ Called *once* before preview. Decreases render resolution/etc. """ self.width = round(self.width / self.res_divisor) self.height = round(self.height / self.res_divisor) self.line_width /= self.res_divisor def before_record(self) -> None: """ Called *once* before recording video. Does nothing yet. """ pass
class MainTriggerConfig(_TriggerConfig, KeywordAttrs, always_dump="edge_direction post_trigger post_radius"): if TYPE_CHECKING: def __call__(self, wave: "Wave", *args, **kwargs) -> "MainTrigger": return self.cls(wave, self, *args, **kwargs) # Must be 1 or -1. # MainTrigger.__init__() multiplies `wave.amplification *= edge_direction`. # get_trigger() should ignore `edge_direction` and look for rising edges. edge_direction: int = 1 # Optional trigger for postprocessing post_trigger: Optional["PostTriggerConfig"] = None post_radius: Optional[int] = with_units("smp", default=3) def __attrs_post_init__(self): if self.edge_direction not in [-1, 1]: raise CorrError( f"{obj_name(self)}.edge_direction must be {{-1, 1}}") if self.post_trigger: self.post_trigger.parent = self if self.post_radius is None: name = obj_name(self) raise CorrError( f"Cannot supply {name}.post_trigger without supplying {name}.post_radius" )
class Font(DumpableAttrs, always_dump="*"): # Font file selection family: Optional[str] = None bold: bool = False italic: bool = False # Font size size: float = with_units("pt", default=20) # QFont implementation details toString: str = None
class Config( KeywordAttrs, always_dump=""" begin_time end_time render_subfps trigger_subsampling render_subsampling trigger_stereo render_stereo """, ): """Default values indicate optional attributes.""" master_audio: Optional[str] begin_time: float = with_units("s", default=0) end_time: Optional[float] = None fps: int trigger_ms: int = with_units("ms") render_ms: int = with_units("ms") # Performance trigger_subsampling: int = 1 render_subsampling: int = 1 # Performance (skipped when recording to video) render_subfps: int = 2 render_fps = property(lambda self: Fraction(self.fps, self.render_subfps)) # FFmpeg accepts FPS as a fraction. (decimals may work, but are inaccurate.) # Both before_* functions should be idempotent, AKA calling twice does no harm. def before_preview(self) -> None: """Called *once* before preview. Does nothing.""" self.render.before_preview() def before_record(self) -> None: """Called *once* before recording video. Force high-quality rendering.""" self.render_subfps = 1 self.trigger_subsampling = 1 self.render_subsampling = 1 self.render.before_record() # End Performance amplification: float # Stereo config trigger_stereo: FlattenOrStr = Flatten.SumAvg render_stereo: FlattenOrStr = Flatten.SumAvg trigger: CorrelationTriggerConfig # Can be overriden per Wave # Multiplies by trigger_width, render_width. Can override trigger. channels: List[ChannelConfig] default_label: DefaultLabel = DefaultLabel.NoLabel layout: LayoutConfig render: RendererConfig ffmpeg_cli: FFmpegOutputConfig = attr.ib(factory=lambda: FFmpegOutputConfig(None)) def get_ffmpeg_cfg(self, video_path: str) -> FFmpegOutputConfig: return attr.evolve(self.ffmpeg_cli, path=os.path.abspath(video_path)) benchmark_mode: BenchmarkMode = attr.ib( BenchmarkMode.NONE, converter=BenchmarkMode.by_name )
class Foo(DumpableAttrs): xs: int = with_units("xs") ys: int = with_units("ys", default=2) no_unit: int = 3
class CorrelationTriggerConfig( MainTriggerConfig, always_dump=""" mean_responsiveness pitch_tracking slope_width """ # deprecated " buffer_falloff ", ): # get_trigger() # Edge/area finding sign_strength: float = 0 mean_responsiveness: float = 1.0 edge_strength: float # Slope detection slope_width: float = with_units("period", default=0.25) # Correlation detection buffer_strength: float = 1 # Below a specific correlation quality, discard the buffer entirely. reset_below: float = 0 # _update_buffer() (not in GUI) buffer_falloff: float = 0.5 # Maximum distance to move (in terms of trigger_ms/trigger_samp) (not in GUI) trigger_diameter: float = 0.5 # Maximum distance to move (in terms of estimated wave period) (not in GUI) trigger_radius_periods: Optional[float] = 1.5 # (not in GUI) recalc_semitones: float = 1.0 # _update_buffer responsiveness: float # Period/frequency estimation (not in GUI) max_freq: float = with_units("Hz", default=4000) # Pitch tracking = compute spectrum. (GUI only has a checkbox) pitch_tracking: Optional[SpectrumConfig] = None # region Legacy Aliases trigger_strength = Alias("edge_strength") falloff_width = Alias("buffer_falloff") data_falloff = Alias("trigger_radius_periods") # endregion def __attrs_post_init__(self) -> None: MainTriggerConfig.__attrs_post_init__(self) # Don't validate slope_width. validate_param(self, "responsiveness", 0, 1) # TODO trigger_falloff >= 0 validate_param(self, "buffer_falloff", 0, np.inf)