secs_types/ipc.rs
1//! IPC between the screen and the service.
2//!
3//! The screen sends a [`Request`] as `{"method": "...", "params": {...}}` and
4//! always receives an envelope: `{"ok": true, "data": Response}` or
5//! `{"ok": false, "error": IpcError}`. The service never throws across this
6//! seam. Long jobs report progress through the event sink supplied with the call.
7//!
8//! Errors carry a code and parameters, not prose: the screen turns them into
9//! a sentence in the current language and keeps `detail` (the raw text) for
10//! the details view and the log.
11
12use std::collections::BTreeMap;
13
14use serde::{Deserialize, Serialize};
15use ts_rs::TS;
16
17use crate::calibration::{CalibrationSettings, Mode};
18use crate::compute::{ComputePreference, ComputeReport, ComputeStatus};
19pub use crate::settings_edit::CalibrationSettingsEditParams;
20
21/// A call from the screen.
22///
23/// The method names come from the variant names in one place: serde and
24/// strum both rename them to snake_case, and `method_names_match_the_wire`
25/// checks that the two agree.
26#[derive(
27 Debug, Clone, PartialEq, Serialize, Deserialize, TS, strum::VariantNames, strum::IntoStaticStr,
28)]
29#[serde(tag = "method", content = "params", rename_all = "snake_case")]
30#[strum(serialize_all = "snake_case")]
31#[ts(export)]
32pub enum Request {
33 /// Version, the upstream release this build is ported from, and the
34 /// platform. The first call the screen makes.
35 Bootstrap,
36 /// The `ProjectNew` Request variant.
37 ProjectNew(ProjectNewParams),
38 /// The `ProjectOpen` Request variant.
39 ProjectOpen(ProjectPathParams),
40 /// The `ProjectSave` Request variant.
41 ProjectSave(ProjectSaveParams),
42 /// The `ProjectInfo` Request variant.
43 ProjectInfo,
44 /// The `SampleLoad` Request variant.
45 SampleLoad(SampleLoadParams),
46 /// The `Calibrate` Request variant.
47 Calibrate(CalibrateParams),
48 /// The `RecommendXo` Request variant.
49 RecommendXo,
50 /// The `Mimo` Request variant.
51 Mimo(MimoParams),
52 /// The `Results` Request variant.
53 Results,
54 /// The `Export` Request variant.
55 Export(ExportParams),
56 /// The `JobCancel` Request variant.
57 JobCancel(JobCancelParams),
58 /// The `Jobs` Request variant.
59 Jobs,
60 /// The `MeasureMethods` Request variant.
61 MeasureMethods,
62 /// The `MeasureConfigure` Request variant.
63 MeasureConfigure(MeasureConfigureParams),
64 /// Returns the measurement settings stored in the open project.
65 MeasureConfigGet,
66 /// Returns one recording-status entry per arranged position.
67 MeasurementRecords,
68 /// Returns the project's microphone calibration and attachment counts.
69 MicCalibrationGet,
70 /// Reads, validates and stores a microphone calibration file in the project.
71 MicCalibrationSet(MicCalibrationSetParams),
72 /// Removes the project's selected microphone calibration file.
73 MicCalibrationClear,
74 /// Attaches the selected file to sweep measurements that have no mic curve.
75 MicCalibrationAttach,
76 /// Returns stored per-role level and SPL values.
77 LevelsGet,
78 /// Fills only missing per-role level values with upstream defaults.
79 LevelsFillDefaults,
80 /// Stops the current 2.5-second level burst without stopping its monitor.
81 LevelStop,
82 /// The `MeasurePosition` Request variant.
83 MeasurePosition(MeasurePositionParams),
84 /// The `PositionClear` Request variant.
85 PositionClear(PositionClearParams),
86 /// The `Undo` Request variant.
87 Undo(UndoParams),
88 /// The `RewLaunch` Request variant.
89 RewLaunch,
90 /// The `MdatScan` Request variant.
91 MdatScan(MdatScanParams),
92 /// The `MdatImport` Request variant.
93 MdatImport(MdatImportParams),
94 /// Returns the editable speaker configuration.
95 ConfigGet,
96 /// Replaces the speaker configuration with a built-in preset.
97 ConfigApplyPreset(ConfigPresetParams),
98 /// Changes the number of configured subwoofers.
99 ConfigSetSubCount(ConfigSubCountParams),
100 /// Changes one speaker or one of its bands.
101 ConfigSetSpeaker(ConfigSetSpeakerParams),
102 /// Adds the first unused upstream speaker role.
103 ConfigAddSpeaker,
104 /// Removes one configured speaker.
105 ConfigRemoveSpeaker(ConfigIndexParams),
106 /// Renumbers outputs in configuration order.
107 ConfigRenumber,
108 /// Returns structured configuration validation notices.
109 ConfigValidate,
110 /// Writes the upstream patch-sheet CSV.
111 PatchSheetCsv(PathParams),
112 /// Returns the editable DSP profile.
113 DspGet,
114 /// Applies a DSP preset or manual fields.
115 DspSet(DspSetParams),
116 /// Returns measurement arrangement and timing options.
117 LayoutGet,
118 /// Changes measurement arrangement or timing options.
119 LayoutSet(LayoutSetParams),
120 /// Returns the current per-position and total estimate.
121 PositionEstimate,
122 /// Returns the target curve and presets.
123 TargetGet,
124 /// Replaces target control points.
125 TargetSetPoints(TargetPointsParams),
126 /// Applies a built-in target preset.
127 TargetApplyPreset(TargetPresetParams),
128 /// Saves the target curve as upstream JSON.
129 TargetSave(PathParams),
130 /// Loads the target curve from upstream JSON.
131 TargetLoad(PathParams),
132 /// Returns calibration settings stored in the project.
133 CalibrationSettingsGet,
134 /// Stores calibration settings in the project.
135 CalibrationSettingsSet(CalibrationSettingsParams),
136 /// Applies one upstream calibration-screen edit and stores the settings.
137 CalibrationSettingsEdit(CalibrationSettingsEditParams),
138 /// Filter warnings from current settings (gain warnings require a run).
139 SubWarnings,
140 /// Optional REW facts read through the injected transport.
141 RewInfo,
142 /// Changes REW's audio driver and returns refreshed REW facts.
143 RewDriverSet(RewDriverSetParams),
144 /// Returns numeric capabilities for one calibration mode.
145 ModeSpec(ModeSpecParams),
146 /// Returns application-local settings.
147 AppSettingsGet,
148 /// Stores application-local settings.
149 AppSettingsSet(AppSettingsParams),
150 /// Reads CamillaDSP and Equalizer APO application state.
151 ApplyStatus,
152 /// Exports and applies the current design to CamillaDSP.
153 ApplyCamilla(ApplyCamillaParams),
154 /// Exports and applies the current design to Equalizer APO.
155 ApplyEqapo,
156 /// Removes SECS 2R's managed Equalizer APO include.
157 RevertEqapo,
158 /// Validates and stores application settings for both apply targets.
159 ApplySettingsSet(ApplySettings),
160 /// Installs the Windows managed host service with an elevation prompt.
161 HostInstall,
162 /// Stops and removes the Windows managed host service with an elevation prompt.
163 HostUninstall,
164 /// Clears every target persisted by the managed host service.
165 HostStopManaging,
166 /// Reports the application update state and the one-time "updated" notice.
167 UpdateStatus,
168 /// Checks for a newer release now, whatever the stored policy.
169 UpdateCheck,
170 /// Downloads the newer release found by the last check.
171 UpdateDownload,
172 /// Installs the downloaded release and starts the new version.
173 UpdateRestart,
174 /// Marks the one-time "updated" notice as shown.
175 UpdateNoticeDismiss,
176 /// Opens one release's page in the system browser.
177 UpdateOpenRelease(UpdateOpenReleaseParams),
178 /// Reports the opt-in local API server and token.
179 LocalApiStatus,
180 /// Replaces the local API bearer token and restarts its server when needed.
181 LocalApiTokenRegenerate,
182 /// Available compute devices and the effective application preference.
183 ComputeStatus,
184 /// Opens the official GPU runtime installation guide for this platform.
185 OpenGpuHelp,
186 /// Smoothed, normalized measurement curves and their shared frequency grid.
187 MeasurementCurves(MeasurementCurvesParams),
188 /// Ready-to-draw calibration curves and axis limits for one result tab.
189 ResultCurves(ResultCurvesParams),
190 /// Numeric metrics, filter counts, BA rows and coded result notices.
191 ResultLines,
192 /// Copies the last BA output alignment into the manual sub settings.
193 SubTakeValues,
194 /// Opens a project-file picker through the application host.
195 DialogOpenProject,
196 /// Opens a project save-file picker through the application host.
197 DialogSaveProject(DialogSuggestedParams),
198 /// Opens an mdat-file picker through the application host.
199 DialogPickMdat,
200 /// Opens a folder picker through the application host.
201 DialogPickFolder(DialogPickFolderParams),
202 /// Opens a typed save-file picker through the application host.
203 DialogSaveFile(DialogSaveFileParams),
204 /// Opens a typed file picker through the application host.
205 DialogOpenFile(DialogOpenFileParams),
206 /// Opens a folder in the operating-system file manager.
207 OpenFolder(OpenFolderParams),
208 /// Starts the live input level monitor.
209 LevelMonitorStart(LevelMonitorStartParams),
210 /// Stops the live input level monitor.
211 LevelMonitorStop,
212 /// Plays a role's level-check signal for 2.5 seconds.
213 LevelPlay(LevelPlayParams),
214 /// Starts watching measurement-method status.
215 StatusWatchStart,
216 /// Stops watching measurement-method status.
217 StatusWatchStop,
218 /// Returns the stored or derived export base folder.
219 ExportFolderGet,
220 /// Stores the export base folder in the open project.
221 ExportFolderSet(ExportFolderSetParams),
222}
223
224impl Request {
225 /// Every method name, as it appears on the wire, derived from the
226 /// variants. `secs-policy` checks that each one is registered in
227 /// `features.toml` as `ipc.<method>`.
228 pub const METHODS: &'static [&'static str] = <Self as strum::VariantNames>::VARIANTS;
229
230 /// The wire name of this request's method.
231 pub fn method(&self) -> &'static str {
232 self.into()
233 }
234}
235
236/// The successful result of a [`Request`], tagged with the same method name.
237#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
238#[serde(tag = "method", content = "data", rename_all = "snake_case")]
239#[ts(export)]
240pub enum Response {
241 /// The `Bootstrap` Response variant.
242 Bootstrap(BootstrapInfo),
243 /// The `ProjectNew` Response variant.
244 ProjectNew(ProjectInfo),
245 /// The `ProjectOpen` Response variant.
246 ProjectOpen(ProjectInfo),
247 /// The `ProjectSave` Response variant.
248 ProjectSave(ProjectInfo),
249 /// The `ProjectInfo` Response variant.
250 ProjectInfo(ProjectInfo),
251 /// The `SampleLoad` Response variant.
252 SampleLoad(JobStarted),
253 /// The `Calibrate` Response variant.
254 Calibrate(JobStarted),
255 /// The `RecommendXo` Response variant.
256 RecommendXo(JobStarted),
257 /// The `Mimo` Response variant.
258 Mimo(JobStarted),
259 /// The `Results` Response variant.
260 Results(ResultsSummary),
261 /// The `Export` Response variant.
262 Export(JobStarted),
263 /// The `JobCancel` Response variant.
264 JobCancel(JobSnapshot),
265 /// The `Jobs` Response variant.
266 Jobs(Vec<JobSnapshot>),
267 /// The `MeasureMethods` Response variant.
268 MeasureMethods(MeasureMethodsResult),
269 /// The `MeasureConfigure` Response variant.
270 MeasureConfigure(MeasureConfigResult),
271 /// Measurement settings stored in the open project.
272 MeasureConfigGet(MeasureConfigResult),
273 /// Recording status for every arranged position.
274 MeasurementRecords(MeasurementRecordsResult),
275 /// Selected microphone calibration and attachment counts.
276 MicCalibrationGet(MicCalibrationStatus),
277 /// Selected microphone calibration after storing a file.
278 MicCalibrationSet(MicCalibrationStatus),
279 /// Empty microphone calibration status after clearing the selection.
280 MicCalibrationClear(MicCalibrationStatus),
281 /// Microphone calibration status after attaching it to eligible sweeps.
282 MicCalibrationAttach(MicCalibrationStatus),
283 /// Stored per-role level and SPL values.
284 LevelsGet(LevelsResult),
285 /// Stored values after filling missing level defaults.
286 LevelsFillDefaults(LevelsResult),
287 /// Confirms that the current level burst was stopped.
288 LevelStop(()),
289 /// The `MeasurePosition` Response variant.
290 MeasurePosition(JobStarted),
291 /// The `PositionClear` Response variant.
292 PositionClear(UndoResult),
293 /// The `Undo` Response variant.
294 Undo(ProjectInfo),
295 /// The `RewLaunch` Response variant.
296 RewLaunch(RewStatusResult),
297 /// The `MdatScan` Response variant.
298 MdatScan(MdatScanResult),
299 /// The `MdatImport` Response variant.
300 MdatImport(JobStarted),
301 /// The `ConfigGet` Response variant.
302 ConfigGet(ConfigResult),
303 /// The `ConfigApplyPreset` Response variant.
304 ConfigApplyPreset(ConfigMutationResult),
305 /// The `ConfigSetSubCount` Response variant.
306 ConfigSetSubCount(ConfigMutationResult),
307 /// The `ConfigSetSpeaker` Response variant.
308 ConfigSetSpeaker(ConfigResult),
309 /// The `ConfigAddSpeaker` Response variant.
310 ConfigAddSpeaker(ConfigResult),
311 /// The `ConfigRemoveSpeaker` Response variant.
312 ConfigRemoveSpeaker(ConfigResult),
313 /// The `ConfigRenumber` Response variant.
314 ConfigRenumber(ConfigResult),
315 /// The `ConfigValidate` Response variant.
316 ConfigValidate(Vec<Notice>),
317 /// The `PatchSheetCsv` Response variant.
318 PatchSheetCsv(PathResult),
319 /// The `DspGet` Response variant.
320 DspGet(DspResult),
321 /// The `DspSet` Response variant.
322 DspSet(DspResult),
323 /// The `LayoutGet` Response variant.
324 LayoutGet(LayoutResult),
325 /// The `LayoutSet` Response variant.
326 LayoutSet(LayoutMutationResult),
327 /// The `PositionEstimate` Response variant.
328 PositionEstimate(PositionEstimateResult),
329 /// The `TargetGet` Response variant.
330 TargetGet(TargetResult),
331 /// The `TargetSetPoints` Response variant.
332 TargetSetPoints(TargetResult),
333 /// The `TargetApplyPreset` Response variant.
334 TargetApplyPreset(TargetResult),
335 /// The `TargetSave` Response variant.
336 TargetSave(PathResult),
337 /// The `TargetLoad` Response variant.
338 TargetLoad(TargetResult),
339 /// The `CalibrationSettingsGet` Response variant.
340 CalibrationSettingsGet(CalibrationSettings),
341 /// The `CalibrationSettingsSet` Response variant.
342 CalibrationSettingsSet(CalibrationSettings),
343 /// Settings after one edit.
344 CalibrationSettingsEdit(CalibrationSettings),
345 /// Current settings-only filter warnings.
346 SubWarnings(Vec<Notice>),
347 /// Current REW facts; unavailable fields are null.
348 RewInfo(RewInfoResult),
349 /// Current REW facts after changing its driver.
350 RewDriverSet(RewInfoResult),
351 /// The `ModeSpec` Response variant.
352 ModeSpec(ModeSpecResult),
353 /// The `AppSettingsGet` Response variant.
354 AppSettingsGet(AppSettings),
355 /// The `AppSettingsSet` Response variant.
356 AppSettingsSet(AppSettings),
357 /// Current state of both application targets.
358 ApplyStatus(ApplyStatus),
359 /// The CamillaDSP application job.
360 ApplyCamilla(JobStarted),
361 /// The Equalizer APO application job.
362 ApplyEqapo(JobStarted),
363 /// Confirms removal of the managed Equalizer APO include.
364 RevertEqapo(()),
365 /// Stored application settings for both targets.
366 ApplySettingsSet(ApplySettings),
367 /// Confirms that the Windows managed host service installer was started.
368 HostInstall(()),
369 /// Confirms that the Windows managed host service uninstaller was started.
370 HostUninstall(()),
371 /// Confirms that every managed target was cleared.
372 HostStopManaging(()),
373 /// Application update state.
374 UpdateStatus(UpdateStatus),
375 /// Update state after the check finished.
376 UpdateCheck(UpdateStatus),
377 /// Update state once the download started.
378 UpdateDownload(UpdateStatus),
379 /// Confirms that installing and restarting started.
380 UpdateRestart(()),
381 /// Update state without the notice.
382 UpdateNoticeDismiss(UpdateStatus),
383 /// Confirms that the release page was handed to the system browser.
384 UpdateOpenRelease(()),
385 /// Current local API server and token state.
386 LocalApiStatus(LocalApiStatus),
387 /// Local API state after replacing its bearer token.
388 LocalApiTokenRegenerate(LocalApiStatus),
389 /// Device capability and automatic-selection result.
390 ComputeStatus(ComputeStatus),
391 /// GPU installation documentation opened by the application host.
392 OpenGpuHelp(()),
393 /// Measurement curves on the common display grid.
394 MeasurementCurves(MeasurementCurvesResult),
395 /// Calibration tab curves, ranges and staleness.
396 ResultCurves(ResultCurvesResult),
397 /// Numeric summary lines and BA table.
398 ResultLines(ResultLinesResult),
399 /// Stored settings after taking the last sub alignment.
400 SubTakeValues(CalibrationSettings),
401 /// The chosen project path, or null when cancelled.
402 DialogOpenProject(Option<String>),
403 /// The chosen project destination, or null when cancelled.
404 DialogSaveProject(Option<String>),
405 /// The chosen mdat path, or null when cancelled.
406 DialogPickMdat(Option<String>),
407 /// The chosen folder, or null when cancelled.
408 DialogPickFolder(Option<String>),
409 /// The chosen save destination, or null when cancelled.
410 DialogSaveFile(Option<String>),
411 /// The chosen source path, or null when cancelled.
412 DialogOpenFile(Option<String>),
413 /// Confirms that the folder-open request reached the host.
414 OpenFolder(()),
415 /// The live level-monitor job.
416 LevelMonitorStart(JobStarted),
417 /// The stopped live level-monitor job, when one was running.
418 LevelMonitorStop(Option<JobSnapshot>),
419 /// Confirms that role playback was accepted.
420 LevelPlay(()),
421 /// The measurement-status watch job.
422 StatusWatchStart(JobStarted),
423 /// The stopped status-watch job, when one was running.
424 StatusWatchStop(Option<JobSnapshot>),
425 /// The current export base folder.
426 ExportFolderGet(ExportFolderResult),
427 /// The stored export base folder.
428 ExportFolderSet(ExportFolderResult),
429}
430
431/// Optional measurement selection; an absent selection includes every recorded pair.
432#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS, Default)]
433#[ts(export)]
434pub struct MeasurementCurvesParams {
435 /// Position numbers to include.
436 #[ts(type = "number[] | null")]
437 pub positions: Option<Vec<i64>>,
438 /// Speaker roles to include.
439 pub roles: Option<Vec<String>>,
440}
441/// A smoothed measurement curve shifted by the result's shared reference.
442#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
443#[ts(export)]
444pub struct MeasurementCurve {
445 /// Speaker role.
446 pub role: String,
447 /// Measurement position.
448 #[ts(type = "number")]
449 pub position: i64,
450 /// Configuration kind: main, sub or top.
451 pub kind: String,
452 /// Levels in dB on the shared grid.
453 pub values: Vec<f64>,
454}
455/// Display limits; both endpoints are data values, not logarithms.
456#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
457#[ts(export)]
458pub struct CurveAxes {
459 /// Frequency bounds in Hz.
460 pub frequency_hz: [f64; 2],
461 /// Level or delay bounds.
462 pub value: [f64; 2],
463 /// Vertical unit: db or ms.
464 pub unit: String,
465}
466/// Frequency band used to set zero dB on the measurement graph.
467#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)]
468#[serde(rename_all = "snake_case")]
469#[ts(export)]
470pub enum CurveReferenceBand {
471 /// 200–2000 Hz mean of a main speaker.
472 Mid,
473 /// 40–120 Hz mean of a subwoofer.
474 Sub,
475}
476/// The stored measurement that sets zero dB for every displayed curve.
477#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
478#[ts(export)]
479pub struct CurveReference {
480 /// Configured speaker role.
481 pub role: String,
482 /// Measurement position.
483 #[ts(type = "number")]
484 pub position: i64,
485 /// Frequency band used for the mean.
486 pub band: CurveReferenceBand,
487}
488/// Measurement graph data.
489#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
490#[ts(export)]
491pub struct MeasurementCurvesResult {
492 /// Shared 280-point logarithmic frequency grid in Hz.
493 pub grid: Vec<f64>,
494 /// One curve per recorded role and position.
495 pub curves: Vec<MeasurementCurve>,
496 /// Stored curve and band that set zero dB, or absent when nothing is measured.
497 pub reference: Option<CurveReference>,
498 /// Upstream default axis bounds.
499 pub axes: CurveAxes,
500}
501/// The calibration graph to display.
502#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)]
503#[serde(rename_all = "snake_case")]
504#[ts(export)]
505pub enum ResultTab {
506 /// Sound pressure level relative to the target reference.
507 Spl,
508 /// Group delay in milliseconds.
509 Gd,
510 /// Crossover preview.
511 Xo,
512 /// Bass alignment.
513 Ba,
514}
515/// Selection for one ready-to-draw result graph.
516#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
517#[ts(export)]
518pub struct ResultCurvesParams {
519 /// Graph tab.
520 pub tab: ResultTab,
521 /// Selected main role; absent selects the first available channel.
522 pub channel: Option<String>,
523 /// Crossover group; absent selects the selected channel's group.
524 pub group: Option<String>,
525 /// Dragged crossover in Hz; absent uses the configured value.
526 pub xo_hz: Option<f64>,
527 /// Apply input correction to the XO preview; defaults to true.
528 pub corrected: Option<bool>,
529}
530/// One named series on a result graph.
531#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
532#[ts(export)]
533pub struct ResultCurve {
534 /// Series identity: before, after, target, mimo, main, sub, sum_before or sum_after.
535 pub kind: String,
536 /// Speaker role for per-sub curves, otherwise absent.
537 pub role: Option<String>,
538 /// Values on the response's shared grid.
539 pub values: Vec<f64>,
540}
541/// A result graph whose calculations are entirely owned by the service.
542#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
543#[ts(export)]
544pub struct ResultCurvesResult {
545 /// Requested tab.
546 pub tab: ResultTab,
547 /// Shared frequency grid in Hz.
548 pub grid: Vec<f64>,
549 /// Ordered drawable series.
550 pub curves: Vec<ResultCurve>,
551 /// Upstream default axis bounds.
552 pub axes: CurveAxes,
553 /// Whether the retained result no longer matches current inputs.
554 pub stale: bool,
555 /// Whether the graph comes from a dedicated subs-only result.
556 pub subs_only: bool,
557 /// RC lower and upper bounds, with an unknown auto upper bound represented as null.
558 pub rc_band_hz: Option<(f64, Option<f64>)>,
559 /// Extra correction bands (ADR 0024), `(lo, hi)` in Hz, sorted; the
560 /// graph shades outside `rc_band_hz` and these together. Empty when
561 /// there are none or the tab shows no RC band.
562 #[serde(default)]
563 pub rc_bands_hz: Vec<(f64, f64)>,
564 /// Selected crossover frequency for the draggable line.
565 pub xo_hz: Option<f64>,
566}
567/// Values behind one channel's metrics and filters lines.
568#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
569#[ts(export)]
570pub struct ChannelResultLine {
571 /// Main role.
572 pub role: String,
573 /// Before and after percentage within one dB, 20–200 Hz.
574 pub within_1db_pct: [f64; 2],
575 /// Before and after group-delay spread, 20–100 Hz, in ms.
576 pub gd_spread_ms: [f64; 2],
577 /// Number of input PEQs.
578 pub peq_count: usize,
579 /// FIR taps, absent when no FIR is used.
580 pub fir_taps: Option<usize>,
581 /// Crossover frequency, absent without bass management.
582 pub xo_hz: Option<f64>,
583 /// Fresh, enabled MIMO support path count, absent when not shown.
584 pub mimo_support_count: Option<usize>,
585}
586/// One sub's output alignment and fixed filters.
587#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
588#[ts(export)]
589pub struct SubResultRow {
590 /// Sub role.
591 pub role: String,
592 /// Alignment mode code.
593 pub mode: String,
594 /// Output gain in dB.
595 pub gain_db: f64,
596 /// Output delay in ms.
597 pub delay_ms: f64,
598 /// Output polarity, plus or minus one.
599 pub polarity: i8,
600 /// Ordered all-pass frequency and Q pairs.
601 pub allpass: Vec<(f64, f64)>,
602 /// High-pass frequency and filter type.
603 pub high_pass: (f64, String),
604 /// Low-pass frequency and filter type.
605 pub low_pass: (f64, String),
606 /// Fixed and generated peaking filters as frequency, Q and gain.
607 pub peq: Vec<(f64, f64, f64)>,
608}
609/// A typed BA layout summary.
610#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
611#[ts(export)]
612pub struct SubLayoutLine {
613 /// Bundle name.
614 pub bundle: String,
615 /// Layout code, dba or multi.
616 pub kind: String,
617 /// All members.
618 pub members: Vec<String>,
619 /// Front row roles.
620 pub front: Vec<String>,
621 /// Rear row roles.
622 pub rear: Vec<String>,
623 /// Rear delay in ms.
624 pub rear_ms: f64,
625 /// Automatic rear delay in ms, when available.
626 pub auto_ms: Option<f64>,
627 /// Manual rear trim in ms.
628 pub trim_ms: f64,
629 /// Shared peaking filters.
630 pub peq: Vec<(f64, f64, f64)>,
631}
632/// BA metrics and table, independent of the selected main channel.
633#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
634#[ts(export)]
635pub struct SubResultLines {
636 /// Whether this BA result is stale.
637 pub stale: bool,
638 /// Whether a dedicated subs-only run supplies these values.
639 pub subs_only: bool,
640 /// Metric band in Hz.
641 pub band_hz: [f64; 2],
642 /// Before and after flatness in dB.
643 pub flatness_db: [f64; 2],
644 /// Before and after across-seat spread, absent for a single seat.
645 pub seat_spread_db: Option<[f64; 2]>,
646 /// Number of complete sub positions.
647 pub positions: usize,
648 /// Output rows in configuration order.
649 pub rows: Vec<SubResultRow>,
650 /// Bundle layouts.
651 pub layouts: Vec<SubLayoutLine>,
652 /// Filter and gain warning codes and their arguments.
653 pub warnings: Vec<Notice>,
654}
655/// Numeric calibration summary lines; no localized sentences cross IPC.
656#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
657#[ts(export)]
658pub struct ResultLinesResult {
659 /// Whether the full calibration result is stale.
660 pub stale: bool,
661 /// Per-main metrics and filter counts.
662 pub channels: Vec<ChannelResultLine>,
663 /// Total delay in milliseconds, absent before calibration.
664 pub total_delay_ms: Option<f64>,
665 /// Engine notes as codes and arguments.
666 pub notes: Vec<Notice>,
667 /// RC ceiling provenance and room-volume notices.
668 pub rc: Vec<Notice>,
669 /// BA metrics, table, layouts and warnings when at least two subs are available.
670 pub ba: Option<SubResultLines>,
671}
672
673/// What the screen needs before it draws anything.
674#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
675#[ts(export)]
676pub struct BootstrapInfo {
677 /// Product name shown in the title bar and the about view.
678 pub app_name: String,
679 /// The SECS 2R version (the workspace version).
680 pub version: String,
681 /// True while the major version is 0: every 0.x build is a prerelease.
682 pub prerelease: bool,
683 /// The SECS 2 release this build follows.
684 pub upstream: UpstreamBase,
685 /// The operating-system platform running this build.
686 pub platform: Platform,
687 /// App log directory, when the caller supplied application data storage.
688 #[serde(default, skip_serializing_if = "Option::is_none")]
689 #[ts(optional)]
690 pub log_dir: Option<String>,
691}
692/// The upstream SECS 2 release a build is ported from (`UPSTREAM.toml`).
693#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
694#[ts(export)]
695pub struct UpstreamBase {
696 /// Upstream product name from `UPSTREAM.toml`.
697 pub name: String,
698 /// Upstream repository URL from `UPSTREAM.toml`.
699 pub repository: String,
700 /// Upstream release version from `UPSTREAM.toml`.
701 pub version: String,
702 /// Forty-character upstream Git commit from `UPSTREAM.toml`.
703 pub commit: String,
704 /// Upstream snapshot date from `UPSTREAM.toml`.
705 pub date: String,
706}
707/// The operating-system platform running the service.
708#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
709#[ts(export)]
710pub struct Platform {
711 /// `windows`, `macos` or `linux` (`std::env::consts::OS`).
712 pub os: String,
713 /// `x86_64` or `aarch64` (`std::env::consts::ARCH`).
714 pub arch: String,
715}
716
717/// Inputs for creating a project.
718#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
719#[ts(export)]
720pub struct ProjectNewParams {
721 /// Destination `.secs` file path.
722 pub path: String,
723 /// Speaker-layout preset name.
724 pub preset: String,
725 /// Number of subwoofers.
726 pub sub_count: u8,
727 /// Whether to create the fixed 7.1.4 sample-room project.
728 pub sample: bool,
729 /// Whether this is a tutorial session using the sample room. The session
730 /// cannot be saved or exported; its measurements and results live in a
731 /// temporary folder that is removed when the session ends, and `path` is
732 /// only reported back.
733 #[serde(default)]
734 pub tutorial: bool,
735}
736/// Inputs for loading embedded or synthetic sample measurements.
737#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS, Default)]
738#[ts(export)]
739pub struct SampleLoadParams {
740 /// Synthetic position count, 2 to 17 (the largest layout); absent loads
741 /// the one-position embedded room. Other counts are refused with
742 /// `measurement_input` (`field = positions`).
743 pub positions: Option<u32>,
744}
745/// A project file path.
746#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
747#[ts(export)]
748pub struct ProjectPathParams {
749 /// Source `.secs` file path.
750 pub path: String,
751}
752/// Inputs for saving the open project.
753#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS, Default)]
754#[ts(export)]
755pub struct ProjectSaveParams {
756 /// Optional save-as `.secs` path; absent saves in place.
757 pub path: Option<String>,
758}
759/// Screen-facing information about the open project.
760#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
761#[ts(export)]
762pub struct ProjectInfo {
763 /// Open `.secs` file path.
764 pub path: String,
765 /// Project display name.
766 pub name: String,
767 /// Speaker-layout preset name.
768 pub preset: String,
769 /// Number of configured subwoofers.
770 pub sub_count: usize,
771 /// Measured position numbers.
772 pub positions: Vec<i64>,
773 /// Whether the in-memory project has unsaved changes.
774 pub dirty: bool,
775 /// Whether this open project is a non-writable tutorial session.
776 pub tutorial: bool,
777 /// Warnings produced while loading legacy project entries.
778 pub migration_warnings: Vec<MigrationWarning>,
779}
780/// A project migration warning code and affected entry.
781#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
782#[ts(export)]
783pub struct MigrationWarning {
784 /// Stable warning code translated by the screen.
785 pub code: String,
786 /// Project entry affected by the warning.
787 pub entry: String,
788}
789
790/// Optional settings for a calibration run.
791#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS, Default)]
792#[ts(export)]
793pub struct CalibrateParams {
794 /// Settings override; absent uses the open project.
795 pub settings: Option<CalibrationSettings>,
796 /// Designs the subwoofers only (upstream [서브만 계산]); the result is
797 /// kept apart and shown by the BA tab until the next full run.
798 #[serde(default)]
799 pub subs_only: bool,
800 /// Computes MIMO after the correction in the same job. Absent on the
801 /// wire means false, so the TypeScript field is optional.
802 #[serde(default)]
803 #[ts(as = "Option<bool>", optional)]
804 pub mimo: bool,
805 /// Uses reduced CPU resources for an automatic refresh.
806 #[serde(default)]
807 #[ts(as = "Option<bool>", optional)]
808 pub background: bool,
809}
810/// Optional settings for an XO recommendation run. The raw request may omit
811/// `params`, which is equivalent to this default value.
812#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS, Default)]
813#[ts(export)]
814pub struct RecommendXoParams {
815 /// Uses reduced CPU resources for an automatic refresh.
816 #[serde(default)]
817 #[ts(as = "Option<bool>", optional)]
818 pub background: bool,
819}
820/// Optional settings for a MIMO run.
821#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS, Default)]
822#[ts(export)]
823pub struct MimoParams {
824 /// Settings override; absent uses the open project.
825 pub settings: Option<CalibrationSettings>,
826}
827
828/// A service-assigned long-running job identifier.
829#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, TS)]
830#[serde(transparent)]
831#[ts(export)]
832pub struct JobId(#[ts(type = "number")] pub u64);
833/// The operation performed by a long-running job.
834#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, TS)]
835#[serde(rename_all = "snake_case")]
836#[ts(export)]
837pub enum JobKind {
838 /// Records all configured roles at one measurement position.
839 Measure,
840 /// Loads embedded or deterministic synthetic sample measurements.
841 SampleLoad,
842 /// Imports assigned measurements from a REW mdat file.
843 MdatImport,
844 /// Computes a calibration design from project measurements.
845 Calibrate,
846 /// Scores crossover-frequency candidates.
847 RecommendXo,
848 /// Computes MIMO support paths.
849 Mimo,
850 /// Writes one selected export bundle.
851 Export,
852 /// Exports and applies the current design to a running target.
853 Apply,
854 /// Streams microphone levels and controls level-check playback.
855 LevelMonitor,
856 /// Watches measurement-method status changes.
857 StatusWatch,
858}
859/// The current lifecycle state of a job.
860#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)]
861#[serde(rename_all = "snake_case")]
862#[ts(export)]
863pub enum JobStatus {
864 /// The worker is still running and may accept cancellation.
865 Running,
866 /// Cancellation was requested and the worker has not stopped yet.
867 CancelRequested,
868 /// The worker completed and produced a result.
869 Succeeded,
870 /// The worker stopped with an error.
871 Failed,
872 /// The caller cancelled the operation.
873 Cancelled,
874}
875impl JobStatus {
876 /// Whether the job can no longer change state.
877 pub fn is_terminal(self) -> bool {
878 matches!(self, Self::Succeeded | Self::Failed | Self::Cancelled)
879 }
880}
881/// The immediate response after a job starts.
882#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
883#[ts(export)]
884pub struct JobStarted {
885 /// Identifier assigned to the job.
886 pub job: JobId,
887 /// Operation the job performs.
888 pub kind: JobKind,
889}
890/// The latest observable state of a job.
891#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
892#[ts(export)]
893pub struct JobSnapshot {
894 /// Identifier assigned to the job.
895 pub job: JobId,
896 /// Operation the job performs.
897 pub kind: JobKind,
898 /// Current lifecycle state.
899 pub status: JobStatus,
900 /// Whether a cancellation request can still prevent commit.
901 pub cancellable: bool,
902 /// Most recently reported stage progress.
903 pub progress: Option<JobProgress>,
904 /// Failure once finished unsuccessfully.
905 pub error: Option<IpcError>,
906}
907/// Progress within one named job stage.
908#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
909#[ts(export)]
910pub struct JobProgress {
911 /// Stable stage code translated by the screen.
912 pub stage: String,
913 #[ts(type = "number")]
914 /// Completed work units in this stage.
915 pub done: u64,
916 #[ts(type = "number")]
917 /// Total work units in this stage.
918 pub total: u64,
919}
920/// A pushed lifecycle event for a long-running job.
921#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
922#[serde(tag = "event", rename_all = "snake_case")]
923#[ts(export)]
924pub enum JobEvent {
925 /// Announces a newly accepted job and its operation.
926 Started { job: JobId, kind: JobKind },
927 /// Reports completed and total work units for a stable stage code.
928 Progress {
929 job: JobId,
930 stage: String,
931 #[ts(type = "number")]
932 done: u64,
933 #[ts(type = "number")]
934 total: u64,
935 },
936 /// Reports a presentation-neutral log code and template parameters.
937 Log {
938 job: JobId,
939 code: String,
940 params: BTreeMap<String, String>,
941 },
942 /// Reports a terminal status and an ephemeral operation result or error.
943 /// Calibration and MIMO carry only a marker; read `results` for their summary.
944 Finished {
945 job: JobId,
946 status: JobStatus,
947 result: Option<Box<JobResult>>,
948 error: Option<IpcError>,
949 },
950 /// Reports a live input level at the monitor cadence.
951 Meter {
952 job: JobId,
953 rms_dbfs: f64,
954 peak_dbfs: f64,
955 spl_db: Option<f64>,
956 clipping: bool,
957 },
958 /// Reports a changed measurement-method status snapshot.
959 MeasureStatus {
960 job: JobId,
961 status: MeasureMethodsResult,
962 },
963}
964/// The successful result produced by a job.
965#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
966#[serde(tag = "kind", content = "data", rename_all = "snake_case")]
967#[ts(export)]
968pub enum JobResult {
969 /// Calibration committed; fetch the current screen summary with `results`.
970 Calibration,
971 /// Crossover recommendation computed for the current project.
972 Xo(XoRecommendation),
973 /// MIMO committed; fetch the current screen summary with `results`.
974 Mimo,
975 /// Paths written by an exporter.
976 Export(ExportResult),
977 /// A design applied to CamillaDSP or Equalizer APO.
978 Apply(ApplyResult),
979 /// Project state after a completed measurement.
980 Measurement(ProjectInfo),
981 /// Project state after an mdat import.
982 MdatImport(ProjectInfo),
983 /// A long-lived monitor or watcher stopped without a payload.
984 Stopped,
985}
986/// The job to cancel.
987#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
988#[ts(export)]
989pub struct JobCancelParams {
990 /// Identifier of the job to cancel.
991 pub job: JobId,
992}
993
994/// The latest calibration and MIMO results.
995#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
996#[ts(export)]
997pub struct ResultsSummary {
998 /// Latest calibration result, including stale state.
999 pub calibration: Option<CalibrationSummary>,
1000 /// Latest MIMO result, including stale state.
1001 pub mimo: Option<MimoSummary>,
1002 /// Whether the MIMO result goes into an export, and if not, why.
1003 pub mimo_export: MimoExportDecision,
1004 /// Whether a stored XO recommendation no longer matches its inputs.
1005 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
1006 #[ts(as = "Option<bool>", optional)]
1007 pub xo_rec_stale: bool,
1008}
1009/// Whether the MIMO result goes into an export, and if not, why.
1010#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)]
1011#[serde(rename_all = "snake_case")]
1012#[ts(export)]
1013pub enum MimoExportDecision {
1014 /// The current MIMO support paths go into the export.
1015 Included,
1016 /// MIMO is switched off in the current settings.
1017 Off,
1018 /// No MIMO result has been computed or restored.
1019 None,
1020 /// The MIMO result does not match the current inputs or compute environment.
1021 Stale,
1022 /// MIMO needs at least three measured positions.
1023 TooFewPositions,
1024 /// The current MIMO result contains no support paths.
1025 NoSupports,
1026}
1027/// A presentation-neutral note or warning code with template parameters.
1028#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1029#[ts(export)]
1030pub struct Notice {
1031 /// Stable snake_case code derived from the engine note variant.
1032 pub code: String,
1033 /// String values for the screen message template.
1034 pub params: BTreeMap<String, String>,
1035}
1036
1037/// The screen-facing summary of one calibration design.
1038#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
1039#[ts(export)]
1040pub struct CalibrationSummary {
1041 /// Whether this result was restored from the project cache instead of computed in this
1042 /// session; staged and before/after mdat need a live design.
1043 #[serde(default)]
1044 pub cached: bool,
1045 /// Whether current project inputs differ from this result.
1046 pub stale: bool,
1047 /// Which input first differs: measurements, settings or target.
1048 pub stale_reason: Option<String>,
1049 /// Calibration mode used to compute this result.
1050 pub mode: Mode,
1051 /// Ultra-mode latency limit used to compute this result, in milliseconds.
1052 pub ultra_ms: f64,
1053 /// Values for the upstream verdict card.
1054 pub verdict: VerdictSummary,
1055 /// Per-channel before-and-after metrics.
1056 pub scorecard: Vec<ChannelScore>,
1057 /// Late 20–150 Hz energy before and after correction at every measured position.
1058 #[serde(default)]
1059 pub late_energy: Vec<LateEnergy>,
1060 /// Total filter latency in milliseconds.
1061 pub latency_ms: f64,
1062 /// Informational codes and values translated by the screen.
1063 pub notes: Vec<Notice>,
1064 /// Main-channel roles in design order; filter coefficients stay in Rust.
1065 pub main_roles: Vec<String>,
1066 /// Subwoofer roles in design order; filter coefficients stay in Rust.
1067 pub sub_roles: Vec<String>,
1068}
1069/// Values shown by the upstream `step_calibration.py` verdict card.
1070#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
1071#[ts(export)]
1072pub struct VerdictSummary {
1073 /// Share within ±1 dB before correction, in percent.
1074 pub within_1db_before_pct: f64,
1075 /// Share within ±1 dB after correction, in percent.
1076 pub within_1db_after_pct: f64,
1077 /// Low-band group-delay spread before correction, in milliseconds.
1078 pub low_band_time_spread_before_ms: f64,
1079 /// Low-band group-delay spread after correction, in milliseconds.
1080 pub low_band_time_spread_after_ms: f64,
1081 /// Delay added by correction, in milliseconds.
1082 pub added_delay_ms: f64,
1083 /// Across-position level spread after correction, in dB.
1084 pub position_spread_db: f64,
1085}
1086/// Before-and-after scorecard metrics for one speaker role.
1087#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
1088#[ts(export)]
1089pub struct ChannelScore {
1090 /// Internal speaker role.
1091 pub role: String,
1092 /// Metrics before correction.
1093 pub before: MetricsSummary,
1094 /// Metrics after correction.
1095 pub after: MetricsSummary,
1096}
1097/// Late 20–150 Hz energy for one main role at one measured position.
1098#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
1099#[ts(export)]
1100pub struct LateEnergy {
1101 /// Internal speaker role.
1102 pub role: String,
1103 /// Measured position number.
1104 pub position: u32,
1105 /// Late-to-early energy before correction, in dB.
1106 pub before_db: f64,
1107 /// Late-to-early energy after correction, in dB.
1108 pub after_db: f64,
1109}
1110/// The upstream `Metrics` fields for one response.
1111#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, TS)]
1112#[ts(export)]
1113pub struct MetricsSummary {
1114 /// Reference level from upstream `Metrics.level`, in dB.
1115 pub level: f64,
1116 /// Share within ±1 dB from 20 Hz, in percent.
1117 pub in1_20: f64,
1118 /// Share within ±2 dB from 20 Hz, in percent.
1119 pub in2_20: f64,
1120 /// Share within ±1 dB from 200 Hz, in percent.
1121 pub in1_200: f64,
1122 /// Share within ±2 dB from 200 Hz, in percent.
1123 pub in2_200: f64,
1124 /// Share within ±1 dB from 2,000 Hz, in percent.
1125 pub in1_2000: f64,
1126 /// Share within ±2 dB from 2,000 Hz, in percent.
1127 pub in2_2000: f64,
1128 /// Phase rotation from upstream `Metrics.turns`, in turns.
1129 pub turns: f64,
1130 /// Group-delay spread near 20 Hz, in milliseconds.
1131 pub gd_20: f64,
1132 /// Group-delay spread near 100 Hz, in milliseconds.
1133 pub gd_100: f64,
1134 /// Group-delay spread near 500 Hz, in milliseconds.
1135 pub gd_500: f64,
1136 /// Largest absolute 1/24-octave group-delay peak, in milliseconds.
1137 pub gd24_abs: f64,
1138 /// Frequency of the largest absolute group-delay peak, in Hz.
1139 pub gd24_abs_hz: f64,
1140 /// Largest signed 1/24-octave group-delay peak, in milliseconds.
1141 pub gd24_max: f64,
1142 /// Span of 1/24-octave group delay, in milliseconds.
1143 pub gd24_span: f64,
1144 /// Group-delay spread near 30 Hz, in milliseconds.
1145 pub gd_30: f64,
1146 /// Pre-ringing level from upstream `Metrics.pre_db`, in dB.
1147 pub pre_db: f64,
1148}
1149/// The screen-facing summary of one MIMO run.
1150#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
1151#[ts(export)]
1152pub struct MimoSummary {
1153 /// Actual computation backend, retained with the result.
1154 #[serde(default)]
1155 pub compute: ComputeReport,
1156 /// Whether current calibration inputs differ from this MIMO result.
1157 pub stale: bool,
1158 /// Realization kind, `iir` or `fir`.
1159 pub kind: String,
1160 /// Upper support frequency in Hz.
1161 pub fsiso: f64,
1162 /// Number of generated support paths.
1163 pub support_count: usize,
1164 /// Total FIR-tap budget across all generated paths.
1165 pub fir_taps_total: usize,
1166 /// Informational codes and values translated by the screen.
1167 pub notes: Vec<Notice>,
1168 /// Number of distinct input roles with at least one generated support path.
1169 pub input_count: usize,
1170 /// Per-channel metrics after MIMO at the main position.
1171 #[serde(default)]
1172 pub scorecard: Vec<MimoChannelScore>,
1173 /// Late 20–150 Hz energy after MIMO at every measured position.
1174 #[serde(default)]
1175 pub late_energy: Vec<MimoLateEnergy>,
1176}
1177/// Scorecard metrics after MIMO for one main role.
1178#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
1179#[ts(export)]
1180pub struct MimoChannelScore {
1181 /// Internal speaker role.
1182 pub role: String,
1183 /// Metrics after the MIMO support contributions are added.
1184 pub after_mimo: MetricsSummary,
1185}
1186/// Late 20–150 Hz energy after MIMO for one main role and measured position.
1187#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
1188#[ts(export)]
1189pub struct MimoLateEnergy {
1190 /// Internal speaker role.
1191 pub role: String,
1192 /// Measured position number.
1193 pub position: u32,
1194 /// Late-to-early energy after MIMO, in dB.
1195 pub after_mimo_db: f64,
1196}
1197/// Crossover candidates and the best value for each group.
1198#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
1199#[ts(export)]
1200pub struct XoRecommendation {
1201 /// Best crossover frequency in Hz for each speaker group.
1202 pub best: Vec<(String, f64)>,
1203 /// Evaluated crossover frequencies in Hz.
1204 pub candidates: Vec<f64>,
1205}
1206
1207/// The supported export bundle.
1208#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)]
1209#[serde(rename_all = "snake_case")]
1210#[ts(export)]
1211pub enum ExportKind {
1212 /// CamillaDSP configuration and FIR coefficient WAV files.
1213 CamillaDsp,
1214 /// REW mdat containing the upstream staged responses.
1215 StagedMdat,
1216 /// REW mdat containing the original measurements, the correction results
1217 /// or both ([`ExportParams::mdat_scope`]; upstream's before-and-after mdat).
1218 BeforeAfterMdat,
1219 /// Per-channel filter values as text plus FIR WAV files (upstream
1220 /// `calibration.export_filter_text`). Results with RC only.
1221 FilterText,
1222 /// A complete Equalizer APO configuration: `config.txt`, the filter file
1223 /// and FIR WAV files (upstream `calibration.export_eqapo`). RC, BA and
1224 /// MIMO support paths, which upstream refuses (docs/DEVIATIONS.md F12).
1225 EqualizerApo,
1226 /// One complete 48 kHz impulse response and response CSV per reachable
1227 /// input-to-output path, including RC, BA, LFE and MIMO operations.
1228 Convolution,
1229}
1230
1231/// Why a filter export cannot carry the current result (upstream
1232/// `calibration.filter_export_block`).
1233#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)]
1234#[serde(rename_all = "snake_case")]
1235#[ts(export)]
1236pub enum ExportBlock {
1237 /// The result has subs (BA); filter text carries RC only.
1238 Subs,
1239 /// The result includes MIMO; filter text does not carry it.
1240 Mimo,
1241 /// The result was restored from the cache and is stale; mdat needs the measurements' spectra,
1242 /// so it must be recalibrated first.
1243 StaleCache,
1244}
1245
1246impl ExportBlock {
1247 /// The `params.reason` value of an [`ErrorCode::ExportScope`] error.
1248 pub fn name(self) -> &'static str {
1249 match self {
1250 Self::Subs => "subs",
1251 Self::Mimo => "mimo",
1252 Self::StaleCache => "stale_cache",
1253 }
1254 }
1255}
1256
1257/// Upstream `filter_export_block`: whether `kind` can carry a result with
1258/// subs (`has_subs`) and MIMO support paths (`has_mimo`). CamillaDSP, the
1259/// mdat exports, Equalizer APO and convolution carry everything; filter text
1260/// carries RC only (subs are checked first, as upstream). Upstream stops MIMO at
1261/// Equalizer APO as well; its writer already routes the support paths, and
1262/// `secs-filters/tests/eqapo_playback.rs` plays them through the engine
1263/// (DEVIATIONS F12). The screen mirrors this rule (`screens/export/types.ts`).
1264pub fn filter_export_block(
1265 kind: ExportKind,
1266 has_subs: bool,
1267 has_mimo: bool,
1268) -> Option<ExportBlock> {
1269 match kind {
1270 ExportKind::FilterText if has_subs => Some(ExportBlock::Subs),
1271 ExportKind::FilterText if has_mimo => Some(ExportBlock::Mimo),
1272 _ => None,
1273 }
1274}
1275
1276/// Whether an export format can carry the current result. Stale cached
1277/// calibration blocks mdat; filter formats retain [`filter_export_block`].
1278pub fn export_block(
1279 kind: ExportKind,
1280 has_subs: bool,
1281 has_mimo: bool,
1282 stale_cache: bool,
1283) -> Option<ExportBlock> {
1284 match kind {
1285 ExportKind::StagedMdat | ExportKind::BeforeAfterMdat if stale_cache => {
1286 Some(ExportBlock::StaleCache)
1287 }
1288 _ => filter_export_block(kind, has_subs, has_mimo),
1289 }
1290}
1291/// What the REW mdat export ([`ExportKind::BeforeAfterMdat`]) carries
1292/// (docs/DEVIATIONS.md F19). The file name says which: `원 측정` for the
1293/// measurements, `보정 결과` for the computed responses.
1294#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, TS)]
1295#[serde(rename_all = "snake_case")]
1296#[ts(export)]
1297pub enum MdatScope {
1298 /// The main position's before-and-after responses (upstream's file) and
1299 /// the original measurements of the other positions.
1300 #[default]
1301 All,
1302 /// The original measurements of every position, as stored; needs no
1303 /// correction result.
1304 Measurements,
1305 /// The computed responses at the main position only.
1306 Results,
1307}
1308
1309/// Sample formats accepted by CamillaDSP's Wasapi backend.
1310#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)]
1311#[ts(export)]
1312pub enum CamillaWasapiFormat {
1313 /// Signed 32-bit integer.
1314 #[serde(rename = "S32")]
1315 S32,
1316 /// Signed 24-bit integer.
1317 #[serde(rename = "S24")]
1318 S24,
1319 /// Signed 16-bit integer.
1320 #[serde(rename = "S16")]
1321 S16,
1322 /// 32-bit floating point.
1323 #[serde(rename = "F32")]
1324 F32,
1325}
1326
1327impl CamillaWasapiFormat {
1328 /// CamillaDSP configuration spelling.
1329 pub fn name(self) -> &'static str {
1330 match self {
1331 Self::S32 => "S32",
1332 Self::S24 => "S24",
1333 Self::S16 => "S16",
1334 Self::F32 => "F32",
1335 }
1336 }
1337}
1338
1339/// CamillaDSP capture backend. Channel counts come from the export plan.
1340#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS, Default)]
1341#[serde(tag = "type", rename_all = "snake_case")]
1342#[ts(export)]
1343pub enum CamillaCaptureDevice {
1344 /// File placeholder compatible with the existing export.
1345 #[default]
1346 File,
1347 /// Windows Audio Session API capture.
1348 Wasapi {
1349 /// Endpoint name; absent selects the system default.
1350 device: Option<String>,
1351 /// Sample representation.
1352 format: CamillaWasapiFormat,
1353 /// Request exclusive access.
1354 exclusive: bool,
1355 /// Capture a playback endpoint in shared mode.
1356 loopback: bool,
1357 },
1358 /// ASIO capture. Requires CamillaDSP's ASIO build (`camilladsp-windows-asio`).
1359 Asio {
1360 /// ASIO driver name.
1361 device: String,
1362 },
1363}
1364
1365/// CamillaDSP playback backend. Channel counts come from the export plan.
1366#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS, Default)]
1367#[serde(tag = "type", rename_all = "snake_case")]
1368#[ts(export)]
1369pub enum CamillaPlaybackDevice {
1370 /// File placeholder compatible with the existing export.
1371 #[default]
1372 File,
1373 /// Windows Audio Session API playback.
1374 Wasapi {
1375 /// Endpoint name; absent selects the system default.
1376 device: Option<String>,
1377 /// Sample representation.
1378 format: CamillaWasapiFormat,
1379 /// Request exclusive access.
1380 exclusive: bool,
1381 },
1382 /// ASIO playback. Requires CamillaDSP's ASIO build (`camilladsp-windows-asio`).
1383 Asio {
1384 /// ASIO driver name.
1385 device: String,
1386 },
1387}
1388
1389/// Capture and playback backends for a CamillaDSP export.
1390#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS, Default)]
1391#[ts(export)]
1392pub struct CamillaDevices {
1393 /// Capture backend.
1394 pub capture: CamillaCaptureDevice,
1395 /// Playback backend.
1396 pub playback: CamillaPlaybackDevice,
1397}
1398
1399/// The export bundle and destination folder.
1400#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1401#[ts(export)]
1402pub struct ExportParams {
1403 /// Export bundle to write.
1404 pub kind: ExportKind,
1405 /// Destination folder path. An empty path uses the project's export folder
1406 /// and applies the upstream timestamped subfolder convention.
1407 pub folder: String,
1408 /// What the REW mdat carries; absent means [`MdatScope::All`]. Only
1409 /// [`ExportKind::BeforeAfterMdat`] takes it; other kinds refuse it.
1410 #[serde(default, skip_serializing_if = "Option::is_none")]
1411 #[ts(optional)]
1412 pub mdat_scope: Option<MdatScope>,
1413 /// Optional stable export name. Named exports replace the matching bundle.
1414 #[serde(default, skip_serializing_if = "Option::is_none")]
1415 #[ts(optional)]
1416 pub design_name: Option<String>,
1417 /// Optional CamillaDSP Windows backends. File placeholders are the default.
1418 #[serde(default, skip_serializing_if = "Option::is_none")]
1419 #[ts(optional)]
1420 pub camilla_devices: Option<CamillaDevices>,
1421 /// Additional convolution output rates. Other export kinds refuse a non-empty list.
1422 #[serde(default)]
1423 #[ts(optional = nullable)]
1424 pub extra_rates: Vec<u32>,
1425}
1426/// Files written by a completed export job.
1427#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1428#[ts(export)]
1429pub struct ExportResult {
1430 /// Paths of all files written by the exporter.
1431 pub files: Vec<String>,
1432 /// Sample rate of the files in Hz. REW mdat always reports 48000.
1433 pub output_rate: u32,
1434 /// Informational notices produced while resolving the output rate.
1435 #[serde(default)]
1436 pub notes: Vec<Notice>,
1437 /// Actual export folder written by the job.
1438 pub folder: String,
1439 /// Whether the exported calibration differed from current project inputs.
1440 pub stale: bool,
1441}
1442
1443// P28 host, live-monitor, watch and export-folder types. Keep this block
1444// contiguous so the parallel M5 IPC additions merge without interleaving.
1445/// Optional suggested name for a save dialog.
1446#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS, Default)]
1447#[ts(export)]
1448pub struct DialogSuggestedParams {
1449 /// Suggested file name.
1450 pub suggested: String,
1451}
1452/// Optional starting folder for a folder picker.
1453#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS, Default)]
1454#[ts(export)]
1455pub struct DialogPickFolderParams {
1456 /// Starting folder, when known.
1457 pub start: Option<String>,
1458}
1459/// The kind of file a host save dialog accepts.
1460#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)]
1461#[serde(rename_all = "snake_case")]
1462#[ts(export)]
1463pub enum SaveFileKind {
1464 /// UTF-8 patch-sheet CSV.
1465 PatchSheet,
1466 /// Target-curve JSON.
1467 Target,
1468}
1469/// Typed save-file dialog parameters.
1470#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1471#[ts(export)]
1472pub struct DialogSaveFileParams {
1473 /// File type and filter.
1474 pub kind: SaveFileKind,
1475 /// Suggested file name.
1476 pub suggested: String,
1477}
1478/// The kind of file a host open dialog accepts.
1479#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)]
1480#[serde(rename_all = "snake_case")]
1481#[ts(export)]
1482pub enum OpenFileKind {
1483 /// Target-curve JSON.
1484 Target,
1485 /// Microphone response calibration text (`.txt`, `.cal` or `.frd`).
1486 MicCalibration,
1487 /// CamillaDSP executable selected for managed launching.
1488 CamillaExe,
1489}
1490/// Typed open-file dialog parameters.
1491#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1492#[ts(export)]
1493pub struct DialogOpenFileParams {
1494 /// File type and filter.
1495 pub kind: OpenFileKind,
1496}
1497/// Folder to reveal in the operating-system file manager.
1498#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1499#[ts(export)]
1500pub struct OpenFolderParams {
1501 /// Folder path.
1502 pub path: String,
1503}
1504/// Optional role to associate with a level-monitor start.
1505#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS, Default)]
1506#[ts(export)]
1507pub struct LevelMonitorStartParams {
1508 /// Speaker role initially selected for playback.
1509 pub role: Option<String>,
1510}
1511/// Role and output level for one 2.5-second level-check burst.
1512#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
1513#[ts(export)]
1514pub struct LevelPlayParams {
1515 /// Internal speaker role.
1516 pub role: String,
1517 /// Master output level in dBFS.
1518 pub master_dbfs: f64,
1519}
1520/// Export base-folder update.
1521#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1522#[ts(export)]
1523pub struct ExportFolderSetParams {
1524 /// Folder path to store in the open project.
1525 pub folder: String,
1526}
1527/// Current export base folder and whether it came from project storage.
1528#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1529#[ts(export)]
1530pub struct ExportFolderResult {
1531 /// Export base folder.
1532 pub folder: String,
1533 /// True when `extra.export.folder` supplied this value.
1534 pub stored: bool,
1535}
1536
1537// Measurement request and response types.
1538/// A validated microphone calibration file selected for the open project.
1539#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
1540#[ts(export)]
1541pub struct MicCalibrationFile {
1542 /// File name as selected by the user.
1543 pub name: String,
1544 /// Full selected path; the project retains the decoded text separately.
1545 pub path: String,
1546 /// miniDSP serial number from `SERNO`, when present.
1547 pub serial: Option<String>,
1548 /// USB microphone sensitivity factor in dB.
1549 pub sens_factor_db: Option<f64>,
1550 /// Analog input gain from `AGain`, in dB.
1551 pub analog_gain_db: Option<f64>,
1552 /// Analog microphone sensitivity at 94 dB SPL, in dBFS.
1553 pub sensitivity_dbfs: Option<f64>,
1554 /// Quoted metadata lines other than the sensitivity header.
1555 pub notes: Vec<String>,
1556 /// Number of frequency-response points.
1557 pub points: u32,
1558 /// Lowest tabulated frequency in Hz.
1559 pub min_hz: f64,
1560 /// Highest tabulated frequency in Hz.
1561 pub max_hz: f64,
1562 /// Whether any response point includes phase.
1563 pub has_phase: bool,
1564}
1565/// Selected microphone calibration and recorded-measurement attachment counts.
1566#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
1567#[ts(export)]
1568pub struct MicCalibrationStatus {
1569 /// The project's selected file, or absent when none is selected.
1570 pub file: Option<MicCalibrationFile>,
1571 /// Recorded position-role pairs whose sidecar attaches a `meterCal` curve.
1572 pub attached: u32,
1573 /// Sweep measurements without a `meterCal` curve that can receive the file.
1574 pub attachable: u32,
1575}
1576/// A microphone calibration file selected by full path.
1577#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1578#[ts(export)]
1579pub struct MicCalibrationSetParams {
1580 /// Full path selected by the user.
1581 pub path: String,
1582}
1583
1584/// Measurement methods available on this host.
1585#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS, Default)]
1586#[ts(export)]
1587pub struct MeasureMethodsResult {
1588 /// Stable identifiers for available measurement methods.
1589 pub methods: Vec<String>,
1590 /// Native endpoints with their channel counts.
1591 pub native_devices: Vec<MeasureDevice>,
1592 /// Native enumeration failure, without hiding the other methods.
1593 pub native_error: Option<IpcError>,
1594 /// REW process, API, and license status.
1595 pub rew: RewStatusResult,
1596 /// Positions present in the unmodified sample room.
1597 pub mock_positions: Vec<i64>,
1598 /// Roles present in the unmodified sample room.
1599 pub mock_roles: Vec<String>,
1600}
1601/// An enumerated native audio endpoint.
1602#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1603#[ts(export)]
1604pub struct MeasureDevice {
1605 /// Backend endpoint identifier.
1606 pub id: String,
1607 /// Backend endpoint display name.
1608 pub name: String,
1609 /// Maximum number of input channels.
1610 pub input_channels: usize,
1611 /// Maximum number of output channels.
1612 pub output_channels: usize,
1613}
1614/// Measurement method and device settings.
1615#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS, Default)]
1616#[ts(export)]
1617pub struct MeasureConfigureParams {
1618 /// Measurement method identifier.
1619 pub method: String,
1620 /// Optional input device identifier.
1621 pub input_device: Option<String>,
1622 /// Optional output device identifier.
1623 pub output_device: Option<String>,
1624 /// Optional one-based input channel number.
1625 pub input_channel: Option<u16>,
1626 /// One-based output channels in measurement-plan role order; empty preserves wiring.
1627 #[serde(default)]
1628 pub output_channels: Vec<u16>,
1629 /// Optional sweep length in samples.
1630 pub sweep_length: Option<usize>,
1631 /// Optional sweep repetition count; allowed values are 1, 2, 4 and 8.
1632 pub repetitions: Option<u8>,
1633 /// Optional sweep level in dBFS.
1634 pub sweep_level_dbfs: Option<f64>,
1635 /// Optional timing-reference mode.
1636 pub timing: Option<String>,
1637 /// Optional one-based loopback input channel.
1638 pub loopback_input_channel: Option<u16>,
1639 /// Optional acoustic timing-reference output channel.
1640 pub timing_ref_output: Option<u16>,
1641 /// Optional share preference: auto, exclusive, or shared.
1642 pub share_mode: Option<String>,
1643}
1644/// The active measurement settings.
1645#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS, Default)]
1646#[ts(export)]
1647pub struct MeasureConfigResult {
1648 /// Active measurement method identifier.
1649 pub method: String,
1650 /// Active sweep level in dBFS.
1651 pub sweep_level_dbfs: Option<f64>,
1652 /// Active input endpoint identifier; empty selects the default.
1653 pub input_device: String,
1654 /// Active output endpoint identifier; empty selects the default.
1655 pub output_device: String,
1656 /// One-based microphone input channel.
1657 pub input_channel: u16,
1658 /// Roles in the same order as output_channels.
1659 pub roles: Vec<String>,
1660 /// One-based output channels in measurement-plan role order.
1661 pub output_channels: Vec<u16>,
1662 /// Sweep length in samples.
1663 pub sweep_length: usize,
1664 /// Active repetition count after timing-reference normalization.
1665 pub repetitions: u8,
1666 /// Sweep sample rate in Hz.
1667 pub sample_rate: u32,
1668 /// Supported sweep lengths in samples.
1669 pub sweep_lengths: Vec<usize>,
1670 /// Timing-reference mode.
1671 pub timing: String,
1672 /// Optional one-based loopback input channel.
1673 pub loopback_input_channel: Option<u16>,
1674 /// Optional acoustic timing-reference output channel.
1675 pub timing_ref_output: Option<u16>,
1676 /// Share preference: auto, exclusive, or shared.
1677 pub share_mode: String,
1678}
1679/// Recording status and upstream source values for one arranged position.
1680#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1681#[ts(export)]
1682pub struct PositionRecord {
1683 /// One-based arranged position number.
1684 #[ts(type = "number")]
1685 pub position: i64,
1686 /// Whether every planned role is recorded at this position.
1687 pub done: bool,
1688 /// Planned roles that have a recording at this position.
1689 pub roles: Vec<String>,
1690 /// Source file/value by recorded role; native and REW sweeps use `measured`.
1691 pub sources: BTreeMap<String, String>,
1692}
1693/// Recording status for the complete measurement arrangement.
1694#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1695#[ts(export)]
1696pub struct MeasurementRecordsResult {
1697 /// One entry per arranged position.
1698 pub records: Vec<PositionRecord>,
1699}
1700/// Stored level-check readings, keyed by measurement role (upstream `extra['levels']`).
1701#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
1702#[ts(export)]
1703pub struct LevelsResult {
1704 /// Measured input level in dBFS by role: the meter reading while that channel played.
1705 pub levels: BTreeMap<String, f64>,
1706 /// Measured sound-pressure level in dB SPL by role.
1707 pub spl: BTreeMap<String, f64>,
1708}
1709/// The measurement position to record.
1710#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1711#[ts(export)]
1712pub struct MeasurePositionParams {
1713 /// One-based measurement position number.
1714 pub position: i64,
1715}
1716/// The measurement position to clear.
1717#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1718#[ts(export)]
1719pub struct PositionClearParams {
1720 /// One-based measurement position number.
1721 pub position: i64,
1722}
1723/// A service-issued undo token.
1724#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1725#[ts(export)]
1726pub struct UndoParams {
1727 /// Opaque service-issued undo token.
1728 pub token: String,
1729}
1730/// An undo token and the project state it restores.
1731#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1732#[ts(export)]
1733pub struct UndoResult {
1734 /// Opaque token for the inverse action.
1735 pub token: String,
1736 /// Project state after the action.
1737 pub project: ProjectInfo,
1738}
1739/// Whether REW is running, reachable, and licensed as Pro.
1740#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS, Default)]
1741#[ts(export)]
1742pub struct RewStatusResult {
1743 /// Whether an REW process is running.
1744 pub running: bool,
1745 /// Whether the REW API responds.
1746 pub reachable: bool,
1747 /// Observed automatic-sweep license state: unknown until success or rejection.
1748 pub pro: Option<bool>,
1749}
1750/// A requested REW audio driver.
1751#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1752#[ts(export)]
1753pub struct RewDriverSetParams {
1754 /// Driver name as REW reports it (`ASIO` or `Java`).
1755 pub driver: String,
1756}
1757/// Facts from REW, distinct from project-stored measurement preferences.
1758#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS, Default)]
1759#[ts(export)]
1760pub struct RewInfoResult {
1761 pub executable_path: Option<String>,
1762 pub running: bool,
1763 pub reachable: bool,
1764 pub version: Option<String>,
1765 /// REW's current audio driver, or null when it could not be read.
1766 pub driver: Option<String>,
1767 /// Observed automatic-sweep license state: unknown until success or rejection.
1768 pub pro: Option<bool>,
1769 /// REW's current input device, read from its own audio preferences.
1770 pub input_device: Option<String>,
1771 /// REW's current output device, read from its own audio preferences.
1772 pub output_device: Option<String>,
1773 /// REW's current input channel name.
1774 pub input: Option<String>,
1775 pub blocking: Option<bool>,
1776 /// Empty means no file loaded; null means unknown or per-input calibration.
1777 pub input_calibration_file: Option<String>,
1778 pub input_devices: Option<Vec<RewDeviceInfo>>,
1779 pub output_devices: Option<Vec<RewDeviceInfo>>,
1780 pub input_channel_names: Option<Vec<String>>,
1781 pub sample_rate: Option<f64>,
1782 pub sweep: Option<RewSweepInfo>,
1783 pub level_dbfs: Option<f64>,
1784}
1785
1786#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1787#[ts(export)]
1788pub struct RewDeviceInfo {
1789 pub id: String,
1790 pub name: String,
1791 pub channels: usize,
1792}
1793
1794#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
1795#[ts(export)]
1796pub struct RewSweepInfo {
1797 pub start_hz: Option<f64>,
1798 pub end_hz: Option<f64>,
1799 pub length: Option<String>,
1800}
1801
1802/// The REW mdat file to scan.
1803#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1804#[ts(export)]
1805pub struct MdatScanParams {
1806 /// Source `.mdat` file path.
1807 pub path: String,
1808}
1809/// Measurements, suggested assignments, and problem codes found in an mdat file.
1810#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS, Default)]
1811#[ts(export)]
1812pub struct MdatScanResult {
1813 /// Measurements in file order, including assignment guesses.
1814 pub measurements: Vec<MdatScanMeasurement>,
1815 /// Suggested measurement-to-role assignments.
1816 pub suggestions: Vec<MdatAssignment>,
1817 /// Stable problem codes and template parameters.
1818 pub problems: Vec<Notice>,
1819}
1820/// One scanned measurement with an unambiguous row identifier.
1821#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1822#[ts(export)]
1823pub struct MdatScanMeasurement {
1824 /// Zero-based row index, also usable when titles repeat.
1825 pub index: usize,
1826 /// Original measurement title.
1827 pub title: String,
1828 /// Suggested internal role, or no assignment.
1829 pub role: Option<String>,
1830 /// Guessed one-based measurement position.
1831 pub position: i64,
1832 /// Whether the title explicitly names a position, including position 1.
1833 pub position_tagged: bool,
1834 /// Low-band classifier result, absent when no IR is available.
1835 pub looks_like_sub: Option<bool>,
1836}
1837/// An mdat measurement assigned to a speaker role.
1838#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1839#[ts(export)]
1840pub struct MdatAssignment {
1841 /// Measurement title from the mdat file.
1842 pub measurement: String,
1843 /// Internal speaker role.
1844 pub role: String,
1845 /// Optional zero-based row index to disambiguate repeated titles.
1846 pub index: Option<usize>,
1847 /// Optional per-row position overriding the import destination.
1848 pub position: Option<i64>,
1849}
1850/// An mdat file, its assignments, and destination position.
1851#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1852#[ts(export)]
1853pub struct MdatImportParams {
1854 /// Source `.mdat` file path.
1855 pub path: String,
1856 /// Accepted measurement-to-role assignments.
1857 pub assignments: Vec<MdatAssignment>,
1858 /// Destination measurement position number.
1859 pub position: i64,
1860}
1861
1862// Project editing request and response types.
1863/// A filesystem path used by a project editor.
1864#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1865#[ts(export)]
1866pub struct PathParams {
1867 /// File system path chosen by the user.
1868 pub path: String,
1869}
1870/// A path written by an editor.
1871#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1872#[ts(export)]
1873pub struct PathResult {
1874 /// File system path that was written.
1875 pub path: String,
1876}
1877/// A preset change which may need measurement-clearing confirmation.
1878#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1879#[ts(export)]
1880pub struct ConfigPresetParams {
1881 /// Preset name (`2.0`, `2.1`, `2.2`, `4.0`, `4.1`, `4.2`, `5.1`, `7.1`, `7.1.4`, `9.1.6`, or `custom` for an empty layout).
1882 pub preset: String,
1883 /// Clears existing measurements; without it a project with measurements answers `confirm_required` (U5).
1884 #[serde(default)]
1885 pub confirm: bool,
1886}
1887/// A sub-count change which may need measurement-clearing confirmation.
1888#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1889#[ts(export)]
1890pub struct ConfigSubCountParams {
1891 /// Number of subwoofers, 0 to 8.
1892 pub count: i64,
1893 /// Clears existing measurements; without it a project with measurements answers `confirm_required` (U5).
1894 #[serde(default)]
1895 pub confirm: bool,
1896}
1897/// One speaker index.
1898#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1899#[ts(export)]
1900pub struct ConfigIndexParams {
1901 /// Speaker index in `ConfigResult.speakers`.
1902 pub index: usize,
1903}
1904/// Optional edits to one speaker or one of its bands.
1905#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1906#[ts(export)]
1907pub struct ConfigSetSpeakerParams {
1908 /// Speaker index in `ConfigResult.speakers`.
1909 pub index: usize,
1910 /// New role code; a sub role forces a single SUB band (`step2_config.py:988-1002`).
1911 pub role: Option<String>,
1912 /// New size: `small` (crossover applied) or `large` (full range).
1913 pub size: Option<String>,
1914 /// Band the output and polarity edits apply to; absent means the first band.
1915 pub band_index: Option<usize>,
1916 /// New measurement output channel (1-based).
1917 pub out: Option<i64>,
1918 /// New polarity: `+` normal or `-` inverted.
1919 pub pol: Option<String>,
1920}
1921/// One configured output band.
1922#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1923#[ts(export)]
1924pub struct ConfigBand {
1925 /// Band name: `FULL`, `LF`, `MF`, `HF` or `SUB`.
1926 pub band: String,
1927 /// Measurement output channel (1-based).
1928 pub out: i64,
1929 /// Polarity: `+` normal or `-` inverted.
1930 pub pol: String,
1931}
1932/// One configured speaker.
1933#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1934#[ts(export)]
1935pub struct ConfigSpeaker {
1936 /// Role code (`L`, `C`, `SW1`, `Tfl`, ...).
1937 pub role: String,
1938 /// `small` (crossover applied) or `large` (full range); subs are `sub`.
1939 pub size: String,
1940 /// Bands in order; one band unless bi-amped.
1941 pub bands: Vec<ConfigBand>,
1942}
1943/// One upstream speaker group in upstream order.
1944#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1945#[ts(export)]
1946pub struct ConfigGroup {
1947 /// Stable presentation group id: front, center, sub, surround, back, wide, top, height or other.
1948 pub id: String,
1949 /// Upstream group name used as the project and crossover-map key.
1950 pub key: String,
1951 /// Indices into `ConfigResult.speakers` that belong to this group.
1952 pub speaker_indices: Vec<usize>,
1953}
1954/// Values derived from the complete speaker configuration.
1955#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1956#[ts(export)]
1957pub struct ConfigSummary {
1958 /// Number of speakers.
1959 pub speaker_count: usize,
1960 /// Number of measurement outputs in use.
1961 pub outputs: Vec<i64>,
1962 /// Highest output channel in use.
1963 pub max_channel: i64,
1964 /// Number of output channels used twice or more.
1965 pub duplicate_outputs: Vec<i64>,
1966 /// Number of subwoofers.
1967 pub sub_count: usize,
1968}
1969/// Screen-facing editable speaker configuration.
1970#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1971#[ts(export)]
1972pub struct ConfigResult {
1973 /// Speakers in table order.
1974 pub speakers: Vec<ConfigSpeaker>,
1975 /// Preset name, or `custom` after any manual edit.
1976 pub preset: String,
1977 /// Measurement output device name (`config.device`).
1978 pub device: String,
1979 /// Table group header rows in upstream order.
1980 pub groups: Vec<ConfigGroup>,
1981 /// Counts the summary line shows.
1982 pub summary: ConfigSummary,
1983}
1984/// Configuration after a destructive edit and its optional inverse token.
1985#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1986#[ts(export)]
1987pub struct ConfigMutationResult {
1988 /// The configuration after the edit.
1989 pub config: ConfigResult,
1990 /// Token for `undo` when the edit cleared measurements (U5).
1991 pub undo_token: Option<String>,
1992}
1993/// Output sample-rate selection stored with a DSP profile.
1994#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)]
1995#[serde(rename_all = "snake_case")]
1996#[ts(export)]
1997pub enum OutputRateChoice {
1998 /// Resolve the rate from the default output endpoint when exporting.
1999 Auto,
2000 /// Always export at this rate in Hz.
2001 Fixed(u32),
2002}
2003
2004/// The manually editable DSP profile fields.
2005#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
2006#[ts(export)]
2007pub struct DspFields {
2008 /// Output sample rate.
2009 pub output_rate: OutputRateChoice,
2010 /// Whether this profile permits changing the output rate.
2011 pub output_rate_editable: bool,
2012 /// FIR taps per channel.
2013 pub fir_taps: i64,
2014 /// Total FIR taps the DSP can run across channels.
2015 pub fir_budget: i64,
2016 /// IIR (PEQ) bands per channel.
2017 pub iir_bands: i64,
2018 /// All-pass bands per channel.
2019 pub allpass_bands: i64,
2020 /// Bass-management path delays the DSP offers.
2021 pub bm_path_delays: i64,
2022}
2023/// One DSP preset in the built-in table.
2024#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
2025#[ts(export)]
2026pub struct DspPreset {
2027 /// Preset key (`camilla`, `tesira`, `qsys`, `minidsp`, `custom`).
2028 pub key: String,
2029 /// Device name shown in the device list.
2030 pub name: String,
2031 /// The preset's filter resources.
2032 pub fields: DspFields,
2033}
2034/// A preset selection or a complete manual edit.
2035#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS, Default)]
2036#[ts(export)]
2037pub struct DspSetParams {
2038 /// Selects a preset; absent with `fields` set means a manual edit.
2039 pub preset: Option<String>,
2040 /// Manual values; any manual edit makes the profile `custom`.
2041 pub fields: Option<DspFields>,
2042}
2043/// Editable DSP state and derived resolution values.
2044#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
2045#[ts(export)]
2046pub struct DspResult {
2047 /// Current profile key.
2048 pub profile: String,
2049 /// Current device name.
2050 pub name: String,
2051 /// Current filter resources.
2052 pub fields: DspFields,
2053 /// Every preset, for the device list.
2054 pub presets: Vec<DspPreset>,
2055 /// FIR window length the taps give, in ms (note line).
2056 pub window_ms: f64,
2057 /// Frequency resolution the taps give, in Hz (note line).
2058 pub resolution_hz: f64,
2059}
2060/// Measurement timing-reference settings stored under `extra.devices`.
2061#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
2062#[ts(export)]
2063pub struct LayoutTiming {
2064 /// Timing reference: `acoustic`, `loopback` or `none`.
2065 pub reference: String,
2066 /// Reference speaker role for the acoustic reference (default `R`).
2067 pub role: String,
2068 /// Reference output channel for the loopback reference (1-based).
2069 pub output: i64,
2070}
2071/// One measurement microphone point in centimetres.
2072#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
2073#[ts(export)]
2074pub struct LayoutPoint {
2075 /// Position number; 1 is the listening position.
2076 pub position: usize,
2077 /// Left-right offset from the centre seat, in cm (`layout_map.py`).
2078 pub x_cm: f64,
2079 /// Front-back offset from the centre seat, in cm (`layout_map.py`).
2080 pub y_cm: f64,
2081 /// Height step: 0 at ear height, +1 or -1 for ±20 cm.
2082 pub height: i8,
2083}
2084/// One supported measurement arrangement.
2085#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
2086#[ts(export)]
2087pub struct LayoutArrangement {
2088 /// Number of positions: 1, 9, 13 or 17.
2089 pub count: usize,
2090 /// Microphone points of this arrangement.
2091 pub points: Vec<LayoutPoint>,
2092}
2093/// Editable arrangement and measurement options.
2094#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
2095#[ts(export)]
2096pub struct LayoutResult {
2097 /// Chosen arrangement (`extra['array']`).
2098 pub count: usize,
2099 /// Start delay before each position as stored (`extra['start_delay']`):
2100 /// one of [`crate::rules::START_DELAYS`], `끄기` or `5 초` to `15 초`.
2101 pub start_delay: String,
2102 /// Timing reference and its reference speaker or output.
2103 pub timing: LayoutTiming,
2104 /// Every arrangement with its geometry, for the cards and maps.
2105 pub arrangements: Vec<LayoutArrangement>,
2106}
2107/// Optional arrangement and timing edits.
2108#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS, Default)]
2109#[ts(export)]
2110pub struct LayoutSetParams {
2111 /// New arrangement; changing it clears measurements (U5).
2112 pub count: Option<usize>,
2113 /// New start delay, one of [`crate::rules::START_DELAYS`]; any other
2114 /// value answers `invalid_request` (field `start_delay`).
2115 pub start_delay: Option<String>,
2116 /// New timing reference.
2117 pub timing: Option<LayoutTiming>,
2118 /// Clears existing measurements; without it a project with measurements answers `confirm_required` (U5).
2119 #[serde(default)]
2120 pub confirm: bool,
2121}
2122/// Arrangement after editing and its optional inverse token.
2123#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
2124#[ts(export)]
2125pub struct LayoutMutationResult {
2126 /// The arrangement and options after the edit.
2127 pub layout: LayoutResult,
2128 /// Token for `undo` when the edit cleared measurements (U5).
2129 pub undo_token: Option<String>,
2130}
2131/// Position-duration estimate from upstream step 4 semantics.
2132#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
2133#[ts(export)]
2134pub struct PositionEstimateResult {
2135 /// Number of positions in the arrangement.
2136 pub positions: usize,
2137 /// Estimated time per position, in seconds (`step4_layout.position_seconds`).
2138 pub per_position_seconds: f64,
2139 /// Estimated time for every position, without moving the microphone, in seconds.
2140 pub total_seconds: f64,
2141}
2142/// Target control points.
2143#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
2144#[ts(export)]
2145pub struct TargetPointsParams {
2146 /// Target points as (Hz, dB); clamped to 10–24000 Hz and ±30 dB, at least two.
2147 pub points: Vec<(f64, f64)>,
2148}
2149/// One built-in target preset selection.
2150#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
2151#[ts(export)]
2152pub struct TargetPresetParams {
2153 /// Target preset: `flat`, `harman` or `cinema`.
2154 pub preset: String,
2155}
2156/// Target curve, selected preset, and built-in preset table.
2157#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
2158#[ts(export)]
2159pub struct TargetResult {
2160 /// Target points as (Hz, dB).
2161 pub points: Vec<(f64, f64)>,
2162 /// Preset name, or absent when the points were edited (custom).
2163 pub preset: Option<String>,
2164 /// Preset names the target menu offers.
2165 pub presets: Vec<(String, Vec<(f64, f64)>)>,
2166}
2167/// Complete calibration settings replacement.
2168#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
2169#[ts(export)]
2170pub struct CalibrationSettingsParams {
2171 /// The complete calibration settings to store.
2172 pub settings: CalibrationSettings,
2173}
2174/// Mode whose numeric specification should be returned.
2175#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
2176#[ts(export)]
2177pub struct ModeSpecParams {
2178 /// Correction mode: `zero`, `mixed`, `linear` or `ultra`.
2179 pub mode: Mode,
2180 /// Latency limit for the ultra mode, in ms.
2181 pub ultra_ms: Option<f64>,
2182 /// Whether MIMO support paths use FIR instead of IIR.
2183 #[serde(default, skip_serializing_if = "Option::is_none")]
2184 #[ts(optional)]
2185 pub mimo_fir: Option<bool>,
2186}
2187/// Presentation-neutral values underlying the upstream mode specification line.
2188#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
2189#[ts(export)]
2190pub struct ModeSpecResult {
2191 /// Room-correction filters: `iir` (IIR only) or `iir_fir` (IIR plus FIR).
2192 pub rc_filter: String,
2193 /// FIR taps the mode uses (0 without FIR).
2194 pub fir_taps: usize,
2195 /// PEQ bands per channel, when the mode uses IIR.
2196 pub peq_bands: Option<usize>,
2197 /// MIMO support filter kind (`fir` when requested, otherwise `iir`).
2198 pub mimo_filter: String,
2199 /// Delay the FIR may add ahead of the main arrival (`lift_ms`), in ms.
2200 pub lift_ms: f64,
2201 /// Octave fraction of the excess-phase smoothing (6, or 24 in linear and ultra modes).
2202 pub phase_frac: f64,
2203 /// Upper latency bound of the mode, in ms.
2204 pub max_latency_ms: f64,
2205}
2206/// Application theme preference.
2207#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS, Default)]
2208#[serde(rename_all = "snake_case")]
2209#[ts(export)]
2210pub enum AppTheme {
2211 /// Follow the operating-system preference.
2212 #[default]
2213 System,
2214 /// Always use the light theme.
2215 Light,
2216 /// Always use the dark theme.
2217 Dark,
2218}
2219/// The skin set the screen draws with (docs/skins, ADR 0010).
2220#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS, Default)]
2221#[serde(rename_all = "snake_case")]
2222#[ts(export)]
2223pub enum AppSkin {
2224 /// The instrument skin: the default tokens.
2225 #[default]
2226 Default,
2227 /// Hardware Rack: a 19-inch rack faceplate.
2228 Rack,
2229 /// Signal Matrix: a routing board of cells on a grid.
2230 Matrix,
2231}
2232/// The logo on the rail and the window (docs/DEVIATIONS.md U12). The
2233/// installed program's own icon is always `Expert`.
2234#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS, Default)]
2235#[serde(rename_all = "snake_case")]
2236#[ts(export)]
2237pub enum AppIcon {
2238 /// "전문가 모드": upstream's confirmed logo (`logo_b8.svg`).
2239 #[default]
2240 Expert,
2241 /// "킹반인 모드": SECS 2R's own logo (`apps/secs2r/icons/logo/civilian.svg`),
2242 /// a measured response and its correction on the target.
2243 Civilian,
2244}
2245/// Opt-in loopback API settings.
2246#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)]
2247#[ts(export)]
2248pub struct LocalApiSettings {
2249 /// Whether the app starts the local API server.
2250 pub enabled: bool,
2251 /// Loopback TCP port; zero asks the operating system for a free port.
2252 pub port: u16,
2253}
2254impl Default for LocalApiSettings {
2255 fn default() -> Self {
2256 Self {
2257 enabled: false,
2258 port: crate::local_api::DEFAULT_PORT,
2259 }
2260 }
2261}
2262
2263/// How far SECS 2R controls one application target.
2264#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, TS)]
2265#[serde(rename_all = "snake_case")]
2266#[ts(export)]
2267pub enum ApplyLevel {
2268 /// Only write ordinary export files.
2269 #[default]
2270 Files,
2271 /// Apply on explicit request while the app is running.
2272 Apply,
2273 /// Keep the applied design active through the managed service.
2274 Manage,
2275}
2276
2277/// CamillaDSP application settings.
2278#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
2279#[ts(export)]
2280pub struct CamillaApply {
2281 /// Selected control level.
2282 pub level: ApplyLevel,
2283 /// Plain websocket address in `host:port` form.
2284 pub address: String,
2285 /// CamillaDSP executable selected by the user for managed operation.
2286 #[serde(default, skip_serializing_if = "Option::is_none")]
2287 #[ts(optional)]
2288 pub exe_path: Option<String>,
2289}
2290impl Default for CamillaApply {
2291 fn default() -> Self {
2292 Self {
2293 level: ApplyLevel::Files,
2294 address: "127.0.0.1:1234".into(),
2295 exe_path: None,
2296 }
2297 }
2298}
2299
2300/// Equalizer APO application settings.
2301#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, TS)]
2302#[ts(export)]
2303pub struct EqApoApply {
2304 /// Selected control level.
2305 pub level: ApplyLevel,
2306}
2307
2308/// Settings for applying designs to external processors.
2309#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, TS)]
2310#[ts(export)]
2311pub struct ApplySettings {
2312 /// CamillaDSP settings.
2313 pub camilla: CamillaApply,
2314 /// Equalizer APO settings.
2315 pub eqapo: EqApoApply,
2316}
2317
2318/// Optional device choices for a CamillaDSP application export.
2319#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, TS)]
2320#[ts(export)]
2321pub struct ApplyCamillaParams {
2322 /// Capture and playback choices; absent keeps file placeholders.
2323 #[serde(default, skip_serializing_if = "Option::is_none")]
2324 #[ts(optional)]
2325 pub camilla_devices: Option<CamillaDevices>,
2326}
2327
2328/// Equalizer APO edition detected from its installed path.
2329#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)]
2330#[serde(rename_all = "snake_case")]
2331#[ts(export)]
2332pub enum EqApoVariant {
2333 /// The SourceForge Equalizer APO release.
2334 Official,
2335 /// EqualizerAPO-XT.
2336 Xt,
2337 /// An installation whose path does not identify either edition.
2338 Unknown,
2339}
2340
2341/// Equalizer APO installation values supplied by the operating-system adapter.
2342#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
2343pub struct EqApoInstall {
2344 /// Equalizer APO configuration directory.
2345 pub config_path: String,
2346 /// Equalizer APO installation directory.
2347 pub install_path: String,
2348 /// Detected edition.
2349 pub variant: EqApoVariant,
2350}
2351
2352/// State of SECS 2R's managed lines in Equalizer APO's `config.txt`.
2353#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)]
2354#[serde(rename_all = "snake_case")]
2355#[ts(export)]
2356pub enum IncludeState {
2357 /// No managed marker exists.
2358 Absent,
2359 /// The marker and include line form one intact pair.
2360 Present,
2361 /// A marker exists without exactly one intact include pair.
2362 Damaged,
2363}
2364
2365/// One CamillaDSP process kept running by the host service.
2366#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
2367#[ts(export)]
2368pub struct ManagedCamilla {
2369 pub exe_path: String,
2370 pub config_path: String,
2371 pub port: u16,
2372 pub design_id: String,
2373}
2374
2375/// One Equalizer APO include kept intact by the host service.
2376#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
2377#[ts(export)]
2378pub struct ManagedEqApo {
2379 pub config_path: String,
2380 pub design_id: String,
2381}
2382
2383/// Targets persisted by the host service.
2384#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, TS)]
2385#[ts(export)]
2386pub struct HostManaged {
2387 #[serde(default, skip_serializing_if = "Option::is_none")]
2388 #[ts(optional)]
2389 pub camilla: Option<ManagedCamilla>,
2390 #[serde(default, skip_serializing_if = "Option::is_none")]
2391 #[ts(optional)]
2392 pub eqapo: Option<ManagedEqApo>,
2393 /// Interactive session that selected these targets.
2394 #[serde(default, skip_serializing_if = "Option::is_none")]
2395 #[ts(skip)]
2396 pub owner_session_id: Option<u32>,
2397 /// Stable SID bytes of that session's user token.
2398 #[serde(default, skip_serializing_if = "Option::is_none")]
2399 #[ts(skip)]
2400 pub owner_user_sid: Option<String>,
2401}
2402
2403/// A request sent over the local managed-host pipe.
2404#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
2405#[serde(tag = "method", content = "params", rename_all = "snake_case")]
2406#[ts(export)]
2407pub enum HostRequest {
2408 Status,
2409 SetManaged(HostManaged),
2410 StopManaging,
2411 Log {
2412 lines: u32,
2413 },
2414 /// Replaces the service with the signed `secs2r-host.exe` next to
2415 /// `secs2r-host.json` in `from_dir` (ADR 0020).
2416 UpdateSelf {
2417 from_dir: String,
2418 },
2419}
2420
2421/// One JSON Lines entry from the managed host log.
2422#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
2423#[ts(export)]
2424pub struct HostLogLine {
2425 /// UTC timestamp in RFC 3339 form.
2426 pub timestamp: String,
2427 /// Stable machine-readable event name.
2428 pub event: String,
2429 /// Event-specific string values such as a PID, path, or reason.
2430 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
2431 pub fields: BTreeMap<String, String>,
2432}
2433
2434/// Why a managed CamillaDSP process is not simply running.
2435#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
2436#[serde(tag = "kind", rename_all = "snake_case")]
2437#[ts(export)]
2438pub enum ManagedProcessState {
2439 Running,
2440 BackingOff { until: String },
2441 GivenUp { reason: String },
2442 Stopped,
2443 NoSession,
2444 OtherUser,
2445}
2446
2447/// Current CamillaDSP supervision state.
2448#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
2449#[ts(export)]
2450pub struct ManagedProcess {
2451 pub state: ManagedProcessState,
2452 #[ts(type = "number | null")]
2453 pub pid: Option<u32>,
2454 pub restarts_in_window: u32,
2455}
2456
2457impl Default for ManagedProcess {
2458 fn default() -> Self {
2459 Self {
2460 state: ManagedProcessState::Stopped,
2461 pid: None,
2462 restarts_in_window: 0,
2463 }
2464 }
2465}
2466
2467/// Status returned by the managed host service.
2468#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
2469#[ts(export)]
2470pub struct HostStatus {
2471 pub version: String,
2472 pub managed: HostManaged,
2473 pub camilla: ManagedProcess,
2474 pub eqapo_include: IncludeState,
2475 pub last_repair: Option<String>,
2476}
2477
2478/// One response returned over the local managed-host pipe.
2479#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
2480#[serde(tag = "kind", content = "data", rename_all = "snake_case")]
2481#[ts(export)]
2482pub enum HostResponse {
2483 Status(Box<HostStatus>),
2484 Ok,
2485 Log(Vec<HostLogLine>),
2486 Error(IpcError),
2487}
2488
2489/// Windows service state visible to the app before a pipe call.
2490#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, TS)]
2491#[serde(rename_all = "snake_case")]
2492#[ts(export)]
2493pub enum ServiceState {
2494 #[default]
2495 NotInstalled,
2496 Stopped,
2497 Running,
2498 Unreachable,
2499}
2500
2501/// How the running CamillaDSP configuration compares with the current result.
2502#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
2503#[serde(tag = "kind", rename_all = "snake_case")]
2504#[ts(export)]
2505pub enum DesignMatch {
2506 /// The title and embedded identity match the current design.
2507 This { design_id: String },
2508 /// CamillaDSP runs another titled configuration.
2509 Other { title: String },
2510 /// There is not enough information to identify the running design.
2511 Unknown,
2512}
2513
2514/// CamillaDSP status mirrored from the platform-neutral application client.
2515#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
2516#[ts(export)]
2517pub struct CamillaStatus {
2518 pub version: String,
2519 pub state: String,
2520 pub stop_reason: Option<String>,
2521 pub title: Option<String>,
2522 pub description: Option<String>,
2523 pub config_path: Option<String>,
2524 pub capture_rate: Option<u32>,
2525 #[ts(type = "number | null")]
2526 pub clipped_samples: Option<u64>,
2527 pub processing_load_pct: Option<f64>,
2528 pub playback_peak_db: Option<Vec<f64>>,
2529}
2530
2531/// Reachability and running design reported by CamillaDSP.
2532#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
2533#[ts(export)]
2534pub struct CamillaApplyStatus {
2535 pub reachable: bool,
2536 pub error: Option<IpcError>,
2537 pub status: Option<CamillaStatus>,
2538 pub design: DesignMatch,
2539}
2540
2541/// Installed and applied state of Equalizer APO.
2542#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
2543#[ts(export)]
2544pub struct EqApoApplyStatus {
2545 pub installed: bool,
2546 pub variant: EqApoVariant,
2547 pub config_path: Option<String>,
2548 pub include: IncludeState,
2549 pub applied_design_id: Option<String>,
2550}
2551
2552/// Current state of both application targets.
2553#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
2554#[ts(export)]
2555pub struct ApplyStatus {
2556 pub camilla: CamillaApplyStatus,
2557 pub eqapo: EqApoApplyStatus,
2558 /// Windows managed-host service state.
2559 #[serde(default)]
2560 pub host: ServiceState,
2561 /// Detailed host state when the service is running and reachable.
2562 #[serde(default, skip_serializing_if = "Option::is_none")]
2563 #[ts(optional)]
2564 pub host_status: Option<Box<HostStatus>>,
2565 /// Design ID of the current result, absent without one; the screen
2566 /// compares Equalizer APO's `applied_design_id` with it.
2567 #[serde(default)]
2568 pub current_design_id: Option<String>,
2569}
2570
2571/// Result of applying an exported design.
2572#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
2573#[serde(tag = "kind", rename_all = "snake_case")]
2574#[ts(export)]
2575pub enum ApplyOutcome {
2576 /// The target accepted the design.
2577 Applied,
2578 /// No CamillaDSP was running; the managed host will start it with the design.
2579 HandedOver,
2580 /// CamillaDSP rejected reload and the previous path was restored.
2581 RolledBack { error: IpcError },
2582}
2583
2584/// Completed application job result.
2585#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
2586#[ts(export)]
2587pub struct ApplyResult {
2588 pub outcome: ApplyOutcome,
2589 pub design_id: String,
2590 pub folder: String,
2591 pub stale: bool,
2592}
2593
2594/// Application-local settings, independent of an open project.
2595#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
2596#[ts(export)]
2597pub struct AppSettings {
2598 /// Application-local compute preference; never stored in an upstream project.
2599 #[serde(default)]
2600 pub compute: ComputePreference,
2601 /// Screen language: `ko` or `en`.
2602 pub language: String,
2603 /// Application theme preference.
2604 #[serde(default)]
2605 pub theme: AppTheme,
2606 /// Logo on the rail and the window.
2607 #[serde(default)]
2608 pub icon: AppIcon,
2609 /// Skin set the screen draws with.
2610 #[serde(default)]
2611 pub skin: AppSkin,
2612 /// Opt-in loopback API server.
2613 #[serde(default)]
2614 pub local_api: LocalApiSettings,
2615 /// Applying designs to CamillaDSP and Equalizer APO.
2616 #[serde(default)]
2617 pub apply: ApplySettings,
2618 /// When to look for and install new releases.
2619 #[serde(default)]
2620 pub updates: UpdatePolicy,
2621 /// Recompute stale recommendations and calibration after edits (ADR 0029).
2622 #[serde(default)]
2623 pub auto_refresh: bool,
2624 /// Recently opened project paths, newest first (at most 10).
2625 pub recent_projects: Vec<String>,
2626 /// Last window size in logical pixels.
2627 pub last_window_size: Option<(u32, u32)>,
2628}
2629impl Default for AppSettings {
2630 fn default() -> Self {
2631 Self {
2632 language: "ko".into(),
2633 compute: ComputePreference::Auto,
2634 theme: AppTheme::System,
2635 icon: AppIcon::Expert,
2636 skin: AppSkin::Default,
2637 local_api: LocalApiSettings::default(),
2638 apply: ApplySettings::default(),
2639 updates: UpdatePolicy::Auto,
2640 auto_refresh: false,
2641 recent_projects: Vec::new(),
2642 last_window_size: None,
2643 }
2644 }
2645}
2646/// Complete application-local settings replacement.
2647#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
2648#[ts(export)]
2649pub struct AppSettingsParams {
2650 /// The complete app settings to store.
2651 pub settings: AppSettings,
2652}
2653
2654/// When SECS 2R looks for and installs new releases (ADR 0020).
2655#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, TS)]
2656#[serde(rename_all = "snake_case")]
2657#[ts(export)]
2658pub enum UpdatePolicy {
2659 /// Check, download in the background and install when the app closes.
2660 #[default]
2661 Auto,
2662 /// Check and report a newer release; download only on request.
2663 Notify,
2664 /// Never contact the release server.
2665 Off,
2666}
2667
2668/// When a downloaded release is installed.
2669#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)]
2670#[serde(rename_all = "snake_case")]
2671#[ts(export)]
2672pub enum UpdateInstall {
2673 /// When the app closes, or at once with a restart.
2674 OnExit,
2675 /// Only when the user asks (a package that needs an administrator password).
2676 OnRequest,
2677}
2678
2679/// Where the application update stands.
2680#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
2681#[serde(tag = "kind", rename_all = "snake_case")]
2682#[ts(export)]
2683pub enum UpdatePhase {
2684 /// The policy is `off`, or this build has no updater.
2685 Off,
2686 /// No check has finished since the app started.
2687 Idle,
2688 /// A check is running.
2689 Checking,
2690 /// The last check found no newer release.
2691 UpToDate,
2692 /// A newer release waits for a download request (policy `notify`).
2693 Available { version: String },
2694 /// A newer release is being downloaded.
2695 Downloading {
2696 version: String,
2697 #[ts(type = "number")]
2698 received: u64,
2699 #[ts(type = "number | null")]
2700 total: Option<u64>,
2701 },
2702 /// A newer release is downloaded and verified.
2703 Ready {
2704 version: String,
2705 install: UpdateInstall,
2706 },
2707 /// The installer is starting; the app closes next.
2708 Installing { version: String },
2709 /// A newer release exists, but this portable copy is replaced by hand.
2710 Portable { version: String, url: String },
2711 /// The last check, download or install failed.
2712 Failed { reason: String },
2713}
2714
2715/// Shown once when the app starts a newer version than it ran last time.
2716#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
2717#[ts(export)]
2718pub struct UpdateNotice {
2719 /// Version that downloaded the update.
2720 pub from: String,
2721 /// Version now running.
2722 pub to: String,
2723 /// The release's CHANGELOG section (Markdown).
2724 pub notes: String,
2725 /// Releases between `from` and `to` were skipped; their notes are only on
2726 /// the release page.
2727 pub skipped: bool,
2728 /// The release page of `to`.
2729 pub release_url: String,
2730}
2731
2732/// The release whose page [`Request::UpdateOpenRelease`] opens.
2733#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
2734#[ts(export)]
2735pub struct UpdateOpenReleaseParams {
2736 /// A release version such as `0.36.0`, without the `v` of its tag.
2737 pub version: String,
2738}
2739
2740/// Application update state for the screen.
2741#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
2742#[ts(export)]
2743pub struct UpdateStatus {
2744 /// Version now running.
2745 pub current: String,
2746 /// Stored policy.
2747 pub policy: UpdatePolicy,
2748 pub phase: UpdatePhase,
2749 /// When the last check finished, RFC 3339 UTC.
2750 pub checked_at: Option<String>,
2751 /// The one-time "updated" notice, until dismissed.
2752 pub notice: Option<UpdateNotice>,
2753}
2754
2755/// A downloaded release waiting for its first start: `update.json` in the
2756/// app's config folder, written by the app or the host shell and read by the
2757/// app on its next start. Not sent over IPC.
2758#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
2759pub struct PendingUpdate {
2760 /// Version that downloaded the update.
2761 pub from: String,
2762 /// Version that will run after the install.
2763 pub to: String,
2764 /// The release's CHANGELOG section (Markdown).
2765 pub notes: String,
2766 /// The release before `to`, from the update manifest's `previous` field.
2767 #[serde(default, skip_serializing_if = "Option::is_none")]
2768 pub previous: Option<String>,
2769}
2770
2771/// App-facing state of the opt-in local API server.
2772#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
2773#[ts(export)]
2774pub struct LocalApiStatus {
2775 /// Configured enabled state.
2776 pub enabled: bool,
2777 /// Configured port, or the selected free port while running with port zero.
2778 pub port: u16,
2779 /// Whether a listener is currently running.
2780 pub running: bool,
2781 /// Most recent startup failure, when present.
2782 pub error: Option<String>,
2783 /// Loopback base URL.
2784 pub url: String,
2785 /// Current bearer token.
2786 pub token: String,
2787 /// Full token-file path.
2788 pub token_path: String,
2789}
2790
2791/// Why a call failed. The screen maps each code to a written message with the
2792/// cause and the next action.
2793#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)]
2794#[serde(rename_all = "snake_case")]
2795#[ts(export)]
2796pub enum ErrorCode {
2797 /// The request did not parse or named an unknown method.
2798 InvalidRequest,
2799 /// A bug: the service failed in a way it does not expect.
2800 Internal,
2801 /// The method is registered but not connected yet.
2802 NotImplemented,
2803 /// The method opens a window or program and is unavailable to local API clients.
2804 UiOnly,
2805 /// The selected operation or control level is unavailable on this host.
2806 Unsupported,
2807 /// A target file could not be changed because access was denied.
2808 PermissionDenied,
2809 /// CamillaDSP or Equalizer APO refused or failed an application request.
2810 ApplyFailed,
2811 /// The call requires an open project.
2812 ProjectNotOpen,
2813 /// Opening, reading, or writing project data failed.
2814 ProjectIo,
2815 /// Another job in the same group is running.
2816 JobBusy,
2817 /// The named job does not exist.
2818 JobNotFound,
2819 /// The caller cancelled the operation.
2820 Cancelled,
2821 /// Project measurements or settings are not valid engine inputs.
2822 EngineInput,
2823 /// Explicit GPU execution was requested but no usable GPU backend is available.
2824 ComputeUnavailable,
2825 /// The requested exporter failed.
2826 ExportFailed,
2827 /// The chosen export cannot carry the result (upstream
2828 /// `filter_export_block`); `params.reason` is an [`ExportBlock`] name.
2829 ExportScope,
2830 /// Cached results no longer match project inputs.
2831 StaleInputs,
2832 /// The audio backend or selected endpoint is unavailable.
2833 DeviceUnavailable,
2834 /// Recording completed but was discarded because it had a gap or corrupt samples.
2835 RecordingFailed,
2836 /// The backend refused a pinned audio share mode.
2837 ShareModeRefused,
2838 /// The REW API cannot be reached.
2839 RewUnreachable,
2840 /// REW requires a Pro license for this operation.
2841 RewProRequired,
2842 /// REW returned an HTTP or schema failure.
2843 RewFailed,
2844 /// A measurement setting or plan field is invalid.
2845 MeasurementInput,
2846 /// Tutorial sessions cannot save or export project data.
2847 TutorialBlocked,
2848 /// A destructive edit needs explicit confirmation while measurements exist.
2849 ConfirmRequired,
2850}
2851/// A coded IPC failure with template parameters and diagnostic detail.
2852#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
2853#[ts(export)]
2854pub struct IpcError {
2855 /// Stable error code translated by the screen.
2856 pub code: ErrorCode,
2857 /// Values for the message template of `code` (file names, counts, ...).
2858 pub params: BTreeMap<String, String>,
2859 /// Whether trying the same call again can succeed.
2860 pub retryable: bool,
2861 /// The raw error text, for the details view and the log only.
2862 pub detail: String,
2863}
2864impl IpcError {
2865 /// Creates a non-retryable error without template parameters.
2866 pub fn new(code: ErrorCode, detail: impl Into<String>) -> Self {
2867 Self {
2868 code,
2869 params: BTreeMap::new(),
2870 retryable: false,
2871 detail: detail.into(),
2872 }
2873 }
2874 /// Adds one screen message-template parameter.
2875 pub fn with_param(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
2876 self.params.insert(key.into(), value.into());
2877 self
2878 }
2879}
2880
2881/// The reply to every call: `{"ok": true, "data": ...}` or
2882/// `{"ok": false, "error": ...}`.
2883///
2884/// The TypeScript side declares the same union in `src/ipc/client.ts`
2885/// (`Envelope`); `envelope_wire_shape` below pins the JSON both sides rely on.
2886#[derive(Debug, Clone, PartialEq)]
2887pub enum Envelope {
2888 /// The `Ok` Envelope variant.
2889 Ok(Box<Response>),
2890 /// The `Err` Envelope variant.
2891 Err(IpcError),
2892}
2893impl Serialize for Envelope {
2894 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2895 use serde::ser::SerializeStruct;
2896 let mut state = serializer.serialize_struct("Envelope", 2)?;
2897 match self {
2898 Self::Ok(data) => {
2899 state.serialize_field("ok", &true)?;
2900 state.serialize_field("data", data)?;
2901 }
2902 Self::Err(error) => {
2903 state.serialize_field("ok", &false)?;
2904 state.serialize_field("error", error)?;
2905 }
2906 }
2907 state.end()
2908 }
2909}
2910
2911#[cfg(test)]
2912mod tests {
2913 use super::*;
2914
2915 /// One value of every variant. A new variant fails to compile here until
2916 /// it gets an example, which `method_names_match_the_wire` then checks.
2917 fn examples() -> Vec<Request> {
2918 vec![
2919 Request::Bootstrap,
2920 Request::ProjectNew(ProjectNewParams {
2921 path: "x.secs".into(),
2922 preset: "2.1".into(),
2923 sub_count: 1,
2924 sample: false,
2925 tutorial: false,
2926 }),
2927 Request::ProjectOpen(ProjectPathParams {
2928 path: "x.secs".into(),
2929 }),
2930 Request::ProjectSave(ProjectSaveParams::default()),
2931 Request::ProjectInfo,
2932 Request::SampleLoad(SampleLoadParams::default()),
2933 Request::Calibrate(CalibrateParams::default()),
2934 Request::RecommendXo,
2935 Request::Mimo(MimoParams::default()),
2936 Request::Results,
2937 Request::Export(ExportParams {
2938 kind: ExportKind::CamillaDsp,
2939 folder: ".".into(),
2940 mdat_scope: None,
2941 design_name: None,
2942 camilla_devices: None,
2943 extra_rates: Vec::new(),
2944 }),
2945 Request::JobCancel(JobCancelParams { job: JobId(1) }),
2946 Request::Jobs,
2947 Request::MeasureMethods,
2948 Request::MeasureConfigure(MeasureConfigureParams::default()),
2949 Request::MeasureConfigGet,
2950 Request::MeasurementRecords,
2951 Request::MicCalibrationGet,
2952 Request::MicCalibrationSet(MicCalibrationSetParams {
2953 path: "mic.txt".into(),
2954 }),
2955 Request::MicCalibrationClear,
2956 Request::MicCalibrationAttach,
2957 Request::LevelsGet,
2958 Request::LevelsFillDefaults,
2959 Request::LevelStop,
2960 Request::MeasurePosition(MeasurePositionParams { position: 1 }),
2961 Request::PositionClear(PositionClearParams { position: 1 }),
2962 Request::Undo(UndoParams { token: "x".into() }),
2963 Request::RewLaunch,
2964 Request::MdatScan(MdatScanParams {
2965 path: "x.mdat".into(),
2966 }),
2967 Request::MdatImport(MdatImportParams {
2968 path: "x.mdat".into(),
2969 assignments: vec![],
2970 position: 1,
2971 }),
2972 Request::ConfigGet,
2973 Request::ConfigApplyPreset(ConfigPresetParams {
2974 preset: "2.1".into(),
2975 confirm: false,
2976 }),
2977 Request::ConfigSetSubCount(ConfigSubCountParams {
2978 count: 2,
2979 confirm: false,
2980 }),
2981 Request::ConfigSetSpeaker(ConfigSetSpeakerParams {
2982 index: 0,
2983 role: Some("L".into()),
2984 size: None,
2985 band_index: Some(0),
2986 out: Some(1),
2987 pol: Some("+".into()),
2988 }),
2989 Request::ConfigAddSpeaker,
2990 Request::ConfigRemoveSpeaker(ConfigIndexParams { index: 0 }),
2991 Request::ConfigRenumber,
2992 Request::ConfigValidate,
2993 Request::PatchSheetCsv(PathParams {
2994 path: "patch.csv".into(),
2995 }),
2996 Request::DspGet,
2997 Request::DspSet(DspSetParams {
2998 preset: Some("tesira".into()),
2999 fields: None,
3000 }),
3001 Request::LayoutGet,
3002 Request::LayoutSet(LayoutSetParams {
3003 count: Some(9),
3004 ..Default::default()
3005 }),
3006 Request::PositionEstimate,
3007 Request::TargetGet,
3008 Request::TargetSetPoints(TargetPointsParams {
3009 points: vec![(20.0, 0.0), (20_000.0, 0.0)],
3010 }),
3011 Request::TargetApplyPreset(TargetPresetParams {
3012 preset: "flat".into(),
3013 }),
3014 Request::TargetSave(PathParams {
3015 path: "target.json".into(),
3016 }),
3017 Request::TargetLoad(PathParams {
3018 path: "target.json".into(),
3019 }),
3020 Request::CalibrationSettingsGet,
3021 Request::CalibrationSettingsEdit(CalibrationSettingsEditParams {
3022 edit: crate::settings_edit::SettingEdit::MimoOn { on: true },
3023 }),
3024 Request::SubWarnings,
3025 Request::RewInfo,
3026 Request::RewDriverSet(RewDriverSetParams {
3027 driver: "ASIO".into(),
3028 }),
3029 Request::CalibrationSettingsSet(CalibrationSettingsParams {
3030 settings: CalibrationSettings::default(),
3031 }),
3032 Request::ModeSpec(ModeSpecParams {
3033 mode: Mode::Mixed,
3034 ultra_ms: None,
3035 mimo_fir: None,
3036 }),
3037 Request::AppSettingsGet,
3038 Request::AppSettingsSet(AppSettingsParams {
3039 settings: AppSettings::default(),
3040 }),
3041 Request::ApplyStatus,
3042 Request::ApplyCamilla(ApplyCamillaParams::default()),
3043 Request::ApplyEqapo,
3044 Request::RevertEqapo,
3045 Request::ApplySettingsSet(ApplySettings::default()),
3046 Request::HostInstall,
3047 Request::HostUninstall,
3048 Request::HostStopManaging,
3049 Request::UpdateStatus,
3050 Request::UpdateCheck,
3051 Request::UpdateDownload,
3052 Request::UpdateRestart,
3053 Request::UpdateNoticeDismiss,
3054 Request::UpdateOpenRelease(UpdateOpenReleaseParams {
3055 version: "0.36.0".into(),
3056 }),
3057 Request::LocalApiStatus,
3058 Request::LocalApiTokenRegenerate,
3059 Request::ComputeStatus,
3060 Request::OpenGpuHelp,
3061 Request::MeasurementCurves(MeasurementCurvesParams::default()),
3062 Request::ResultCurves(ResultCurvesParams {
3063 tab: ResultTab::Spl,
3064 channel: None,
3065 group: None,
3066 xo_hz: None,
3067 corrected: None,
3068 }),
3069 Request::ResultLines,
3070 Request::SubTakeValues,
3071 Request::DialogOpenProject,
3072 Request::DialogSaveProject(DialogSuggestedParams {
3073 suggested: "x.secs".into(),
3074 }),
3075 Request::DialogPickMdat,
3076 Request::DialogPickFolder(DialogPickFolderParams { start: None }),
3077 Request::DialogSaveFile(DialogSaveFileParams {
3078 kind: SaveFileKind::PatchSheet,
3079 suggested: "x.csv".into(),
3080 }),
3081 Request::DialogOpenFile(DialogOpenFileParams {
3082 kind: OpenFileKind::Target,
3083 }),
3084 Request::OpenFolder(OpenFolderParams { path: ".".into() }),
3085 Request::LevelMonitorStart(LevelMonitorStartParams::default()),
3086 Request::LevelMonitorStop,
3087 Request::LevelPlay(LevelPlayParams {
3088 role: "L".into(),
3089 master_dbfs: -20.0,
3090 }),
3091 Request::StatusWatchStart,
3092 Request::StatusWatchStop,
3093 Request::ExportFolderGet,
3094 Request::ExportFolderSet(ExportFolderSetParams { folder: ".".into() }),
3095 ]
3096 }
3097 #[test]
3098 fn method_names_match_the_wire() {
3099 let examples = examples();
3100 assert_eq!(examples.len(), Request::METHODS.len());
3101 for request in examples {
3102 let wire = serde_json::to_value(&request).unwrap();
3103 assert_eq!(wire["method"], request.method());
3104 }
3105 }
3106 #[test]
3107 fn every_method_name_is_listed_and_parses() {
3108 for request in examples() {
3109 let wire = serde_json::to_value(&request).unwrap();
3110 let parsed: Request = serde_json::from_value(wire).unwrap();
3111 assert_eq!(parsed.method(), request.method());
3112 }
3113 }
3114 #[test]
3115 fn old_app_settings_without_local_api_use_the_disabled_default() {
3116 let settings: AppSettings = serde_json::from_str(
3117 r#"{"language":"ko","recent_projects":[],"last_window_size":null}"#,
3118 )
3119 .unwrap();
3120 assert_eq!(settings.local_api, LocalApiSettings::default());
3121 assert!(!settings.local_api.enabled);
3122 assert_eq!(settings.local_api.port, crate::local_api::DEFAULT_PORT);
3123 }
3124
3125 #[test]
3126 fn calibrate_mimo_defaults_false_on_the_wire() {
3127 let old: CalibrateParams =
3128 serde_json::from_str(r#"{"settings":null,"subs_only":false}"#).unwrap();
3129 assert!(!old.mimo);
3130 assert!(!old.background);
3131 assert_eq!(CalibrateParams::default(), old);
3132 let xo: RecommendXoParams = serde_json::from_str("{}").unwrap();
3133 assert!(!xo.background);
3134 assert!(
3135 serde_json::from_str::<RecommendXoParams>(r#"{"background":true}"#)
3136 .unwrap()
3137 .background
3138 );
3139 let wire = serde_json::to_value(CalibrateParams {
3140 mimo: true,
3141 ..Default::default()
3142 })
3143 .unwrap();
3144 assert_eq!(wire["mimo"], true);
3145 }
3146
3147 #[test]
3148 fn mdat_scope_is_optional_on_the_wire() {
3149 let old: ExportParams =
3150 serde_json::from_str(r#"{"kind":"before_after_mdat","folder":""}"#).unwrap();
3151 assert_eq!(old.mdat_scope, None);
3152 assert!(old.extra_rates.is_empty());
3153 assert_eq!(old.mdat_scope.unwrap_or_default(), MdatScope::All);
3154 let wire = serde_json::to_value(&old).unwrap();
3155 assert!(wire.get("mdat_scope").is_none());
3156 let scoped = ExportParams {
3157 mdat_scope: Some(MdatScope::Measurements),
3158 design_name: None,
3159 camilla_devices: None,
3160 ..old
3161 };
3162 let wire = serde_json::to_value(&scoped).unwrap();
3163 assert_eq!(wire["mdat_scope"], "measurements");
3164 assert_eq!(
3165 serde_json::from_value::<ExportParams>(wire).unwrap(),
3166 scoped
3167 );
3168 }
3169 #[test]
3170 fn envelope_wire_shape() {
3171 let ok = Envelope::Ok(Box::new(Response::Bootstrap(BootstrapInfo {
3172 app_name: "SECS 2R".into(),
3173 version: "0.0.1".into(),
3174 prerelease: true,
3175 upstream: UpstreamBase {
3176 name: "SECS".into(),
3177 repository: "https://example.invalid/secs".into(),
3178 version: "Beta".into(),
3179 commit: "0".repeat(40),
3180 date: "2026-10-12".into(),
3181 },
3182 platform: Platform {
3183 os: "linux".into(),
3184 arch: "x86_64".into(),
3185 },
3186 log_dir: None,
3187 })));
3188 let value = serde_json::to_value(ok).unwrap();
3189 assert_eq!(value["ok"], true);
3190 assert_eq!(value["data"]["method"], "bootstrap");
3191 let value = serde_json::to_value(Envelope::Err(IpcError::new(
3192 ErrorCode::InvalidRequest,
3193 "bad",
3194 )))
3195 .unwrap();
3196 assert_eq!(value["ok"], false);
3197 assert_eq!(value["error"]["code"], "invalid_request");
3198 }
3199
3200 #[test]
3201 fn compute_completion_events_are_payload_free_markers() {
3202 for (result, kind) in [
3203 (JobResult::Calibration, "calibration"),
3204 (JobResult::Mimo, "mimo"),
3205 ] {
3206 let event = JobEvent::Finished {
3207 job: JobId(1),
3208 status: JobStatus::Succeeded,
3209 result: Some(Box::new(result)),
3210 error: None,
3211 };
3212 let wire = serde_json::to_vec(&event).unwrap();
3213 let value: serde_json::Value = serde_json::from_slice(&wire).unwrap();
3214 assert_eq!(value["result"], serde_json::json!({ "kind": kind }));
3215 assert!(wire.len() < 128, "completion must not carry a design");
3216 assert_eq!(serde_json::from_slice::<JobEvent>(&wire).unwrap(), event);
3217 }
3218 }
3219
3220 #[test]
3221 fn job_snapshot_is_metadata_without_a_result_payload() {
3222 let snapshot = JobSnapshot {
3223 job: JobId(42),
3224 kind: JobKind::Mimo,
3225 status: JobStatus::Succeeded,
3226 cancellable: false,
3227 progress: None,
3228 error: None,
3229 };
3230 let wire = serde_json::to_value(&snapshot).unwrap();
3231 assert!(wire.get("result").is_none());
3232 assert_eq!(
3233 serde_json::from_value::<JobSnapshot>(wire).unwrap(),
3234 snapshot
3235 );
3236 }
3237
3238 #[test]
3239 fn filter_export_scope_follows_upstream_but_eqapo_carries_mimo_f12() {
3240 use ExportKind::*;
3241 for kind in [
3242 CamillaDsp,
3243 StagedMdat,
3244 BeforeAfterMdat,
3245 EqualizerApo,
3246 Convolution,
3247 ] {
3248 for (subs, mimo) in [(false, false), (true, false), (false, true), (true, true)] {
3249 assert_eq!(filter_export_block(kind, subs, mimo), None, "{kind:?}");
3250 }
3251 }
3252 assert_eq!(filter_export_block(FilterText, false, false), None);
3253 assert_eq!(
3254 export_block(StagedMdat, false, false, true),
3255 Some(ExportBlock::StaleCache)
3256 );
3257 assert_eq!(
3258 export_block(BeforeAfterMdat, true, true, true),
3259 Some(ExportBlock::StaleCache)
3260 );
3261 assert_eq!(export_block(CamillaDsp, true, true, true), None);
3262 assert_eq!(
3263 filter_export_block(FilterText, true, false),
3264 Some(ExportBlock::Subs)
3265 );
3266 assert_eq!(
3267 filter_export_block(FilterText, true, true),
3268 Some(ExportBlock::Subs)
3269 );
3270 assert_eq!(
3271 filter_export_block(FilterText, false, true),
3272 Some(ExportBlock::Mimo)
3273 );
3274 assert_eq!(serde_json::to_value(FilterText).unwrap(), "filter_text");
3275 assert_eq!(serde_json::to_value(EqualizerApo).unwrap(), "equalizer_apo");
3276 assert_eq!(serde_json::to_value(Convolution).unwrap(), "convolution");
3277 assert_eq!(
3278 serde_json::to_value(ExportBlock::Subs).unwrap(),
3279 ExportBlock::Subs.name()
3280 );
3281 assert_eq!(
3282 serde_json::to_value(ExportBlock::Mimo).unwrap(),
3283 ExportBlock::Mimo.name()
3284 );
3285 assert_eq!(
3286 serde_json::to_value(ErrorCode::ExportScope).unwrap(),
3287 "export_scope"
3288 );
3289 }
3290}