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