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 /// Reports the opt-in local API server and token.
151 LocalApiStatus,
152 /// Replaces the local API bearer token and restarts its server when needed.
153 LocalApiTokenRegenerate,
154 /// Available compute devices and the effective application preference.
155 ComputeStatus,
156 /// Opens the official GPU runtime installation guide for this platform.
157 OpenGpuHelp,
158 /// Smoothed, normalized measurement curves and their shared frequency grid.
159 MeasurementCurves(MeasurementCurvesParams),
160 /// Ready-to-draw calibration curves and axis limits for one result tab.
161 ResultCurves(ResultCurvesParams),
162 /// Numeric metrics, filter counts, BA rows and coded result notices.
163 ResultLines,
164 /// Copies the last BA output alignment into the manual sub settings.
165 SubTakeValues,
166 /// Opens a project-file picker through the application host.
167 DialogOpenProject,
168 /// Opens a project save-file picker through the application host.
169 DialogSaveProject(DialogSuggestedParams),
170 /// Opens an mdat-file picker through the application host.
171 DialogPickMdat,
172 /// Opens a folder picker through the application host.
173 DialogPickFolder(DialogPickFolderParams),
174 /// Opens a typed save-file picker through the application host.
175 DialogSaveFile(DialogSaveFileParams),
176 /// Opens a typed file picker through the application host.
177 DialogOpenFile(DialogOpenFileParams),
178 /// Opens a folder in the operating-system file manager.
179 OpenFolder(OpenFolderParams),
180 /// Starts the live input level monitor.
181 LevelMonitorStart(LevelMonitorStartParams),
182 /// Stops the live input level monitor.
183 LevelMonitorStop,
184 /// Plays a role's level-check signal for 2.5 seconds.
185 LevelPlay(LevelPlayParams),
186 /// Starts watching measurement-method status.
187 StatusWatchStart,
188 /// Stops watching measurement-method status.
189 StatusWatchStop,
190 /// Returns the stored or derived export base folder.
191 ExportFolderGet,
192 /// Stores the export base folder in the open project.
193 ExportFolderSet(ExportFolderSetParams),
194}
195
196impl Request {
197 /// Every method name, as it appears on the wire, derived from the
198 /// variants. `secs-policy` checks that each one is registered in
199 /// `features.toml` as `ipc.<method>`.
200 pub const METHODS: &'static [&'static str] = <Self as strum::VariantNames>::VARIANTS;
201
202 /// The wire name of this request's method.
203 pub fn method(&self) -> &'static str {
204 self.into()
205 }
206}
207
208/// The successful result of a [`Request`], tagged with the same method name.
209#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
210#[serde(tag = "method", content = "data", rename_all = "snake_case")]
211#[ts(export)]
212pub enum Response {
213 /// The `Bootstrap` Response variant.
214 Bootstrap(BootstrapInfo),
215 /// The `ProjectNew` Response variant.
216 ProjectNew(ProjectInfo),
217 /// The `ProjectOpen` Response variant.
218 ProjectOpen(ProjectInfo),
219 /// The `ProjectSave` Response variant.
220 ProjectSave(ProjectInfo),
221 /// The `ProjectInfo` Response variant.
222 ProjectInfo(ProjectInfo),
223 /// The `SampleLoad` Response variant.
224 SampleLoad(JobStarted),
225 /// The `Calibrate` Response variant.
226 Calibrate(JobStarted),
227 /// The `RecommendXo` Response variant.
228 RecommendXo(JobStarted),
229 /// The `Mimo` Response variant.
230 Mimo(JobStarted),
231 /// The `Results` Response variant.
232 Results(ResultsSummary),
233 /// The `Export` Response variant.
234 Export(JobStarted),
235 /// The `JobCancel` Response variant.
236 JobCancel(JobSnapshot),
237 /// The `Jobs` Response variant.
238 Jobs(Vec<JobSnapshot>),
239 /// The `MeasureMethods` Response variant.
240 MeasureMethods(MeasureMethodsResult),
241 /// The `MeasureConfigure` Response variant.
242 MeasureConfigure(MeasureConfigResult),
243 /// Measurement settings stored in the open project.
244 MeasureConfigGet(MeasureConfigResult),
245 /// Recording status for every arranged position.
246 MeasurementRecords(MeasurementRecordsResult),
247 /// Selected microphone calibration and attachment counts.
248 MicCalibrationGet(MicCalibrationStatus),
249 /// Selected microphone calibration after storing a file.
250 MicCalibrationSet(MicCalibrationStatus),
251 /// Empty microphone calibration status after clearing the selection.
252 MicCalibrationClear(MicCalibrationStatus),
253 /// Microphone calibration status after attaching it to eligible sweeps.
254 MicCalibrationAttach(MicCalibrationStatus),
255 /// Stored per-role level and SPL values.
256 LevelsGet(LevelsResult),
257 /// Stored values after filling missing level defaults.
258 LevelsFillDefaults(LevelsResult),
259 /// Confirms that the current level burst was stopped.
260 LevelStop(()),
261 /// The `MeasurePosition` Response variant.
262 MeasurePosition(JobStarted),
263 /// The `PositionClear` Response variant.
264 PositionClear(UndoResult),
265 /// The `Undo` Response variant.
266 Undo(ProjectInfo),
267 /// The `RewLaunch` Response variant.
268 RewLaunch(RewStatusResult),
269 /// The `MdatScan` Response variant.
270 MdatScan(MdatScanResult),
271 /// The `MdatImport` Response variant.
272 MdatImport(JobStarted),
273 /// The `ConfigGet` Response variant.
274 ConfigGet(ConfigResult),
275 /// The `ConfigApplyPreset` Response variant.
276 ConfigApplyPreset(ConfigMutationResult),
277 /// The `ConfigSetSubCount` Response variant.
278 ConfigSetSubCount(ConfigMutationResult),
279 /// The `ConfigSetSpeaker` Response variant.
280 ConfigSetSpeaker(ConfigResult),
281 /// The `ConfigAddSpeaker` Response variant.
282 ConfigAddSpeaker(ConfigResult),
283 /// The `ConfigRemoveSpeaker` Response variant.
284 ConfigRemoveSpeaker(ConfigResult),
285 /// The `ConfigRenumber` Response variant.
286 ConfigRenumber(ConfigResult),
287 /// The `ConfigValidate` Response variant.
288 ConfigValidate(Vec<Notice>),
289 /// The `PatchSheetCsv` Response variant.
290 PatchSheetCsv(PathResult),
291 /// The `DspGet` Response variant.
292 DspGet(DspResult),
293 /// The `DspSet` Response variant.
294 DspSet(DspResult),
295 /// The `LayoutGet` Response variant.
296 LayoutGet(LayoutResult),
297 /// The `LayoutSet` Response variant.
298 LayoutSet(LayoutMutationResult),
299 /// The `PositionEstimate` Response variant.
300 PositionEstimate(PositionEstimateResult),
301 /// The `TargetGet` Response variant.
302 TargetGet(TargetResult),
303 /// The `TargetSetPoints` Response variant.
304 TargetSetPoints(TargetResult),
305 /// The `TargetApplyPreset` Response variant.
306 TargetApplyPreset(TargetResult),
307 /// The `TargetSave` Response variant.
308 TargetSave(PathResult),
309 /// The `TargetLoad` Response variant.
310 TargetLoad(TargetResult),
311 /// The `CalibrationSettingsGet` Response variant.
312 CalibrationSettingsGet(CalibrationSettings),
313 /// The `CalibrationSettingsSet` Response variant.
314 CalibrationSettingsSet(CalibrationSettings),
315 /// Settings after one edit.
316 CalibrationSettingsEdit(CalibrationSettings),
317 /// Current settings-only filter warnings.
318 SubWarnings(Vec<Notice>),
319 /// Current REW facts; unavailable fields are null.
320 RewInfo(RewInfoResult),
321 /// Current REW facts after changing its driver.
322 RewDriverSet(RewInfoResult),
323 /// The `ModeSpec` Response variant.
324 ModeSpec(ModeSpecResult),
325 /// The `AppSettingsGet` Response variant.
326 AppSettingsGet(AppSettings),
327 /// The `AppSettingsSet` Response variant.
328 AppSettingsSet(AppSettings),
329 /// Current local API server and token state.
330 LocalApiStatus(LocalApiStatus),
331 /// Local API state after replacing its bearer token.
332 LocalApiTokenRegenerate(LocalApiStatus),
333 /// Device capability and automatic-selection result.
334 ComputeStatus(ComputeStatus),
335 /// GPU installation documentation opened by the application host.
336 OpenGpuHelp(()),
337 /// Measurement curves on the common display grid.
338 MeasurementCurves(MeasurementCurvesResult),
339 /// Calibration tab curves, ranges and staleness.
340 ResultCurves(ResultCurvesResult),
341 /// Numeric summary lines and BA table.
342 ResultLines(ResultLinesResult),
343 /// Stored settings after taking the last sub alignment.
344 SubTakeValues(CalibrationSettings),
345 /// The chosen project path, or null when cancelled.
346 DialogOpenProject(Option<String>),
347 /// The chosen project destination, or null when cancelled.
348 DialogSaveProject(Option<String>),
349 /// The chosen mdat path, or null when cancelled.
350 DialogPickMdat(Option<String>),
351 /// The chosen folder, or null when cancelled.
352 DialogPickFolder(Option<String>),
353 /// The chosen save destination, or null when cancelled.
354 DialogSaveFile(Option<String>),
355 /// The chosen source path, or null when cancelled.
356 DialogOpenFile(Option<String>),
357 /// Confirms that the folder-open request reached the host.
358 OpenFolder(()),
359 /// The live level-monitor job.
360 LevelMonitorStart(JobStarted),
361 /// The stopped live level-monitor job, when one was running.
362 LevelMonitorStop(Option<JobSnapshot>),
363 /// Confirms that role playback was accepted.
364 LevelPlay(()),
365 /// The measurement-status watch job.
366 StatusWatchStart(JobStarted),
367 /// The stopped status-watch job, when one was running.
368 StatusWatchStop(Option<JobSnapshot>),
369 /// The current export base folder.
370 ExportFolderGet(ExportFolderResult),
371 /// The stored export base folder.
372 ExportFolderSet(ExportFolderResult),
373}
374
375/// Optional measurement selection; an absent selection includes every recorded pair.
376#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS, Default)]
377#[ts(export)]
378pub struct MeasurementCurvesParams {
379 /// Position numbers to include.
380 #[ts(type = "number[] | null")]
381 pub positions: Option<Vec<i64>>,
382 /// Speaker roles to include.
383 pub roles: Option<Vec<String>>,
384}
385/// A measurement curve, already smoothed and normalized to the mid-band mean.
386#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
387#[ts(export)]
388pub struct MeasurementCurve {
389 /// Speaker role.
390 pub role: String,
391 /// Measurement position.
392 #[ts(type = "number")]
393 pub position: i64,
394 /// Configuration kind: main, sub or top.
395 pub kind: String,
396 /// Levels in dB on the shared grid.
397 pub values: Vec<f64>,
398}
399/// Display limits; both endpoints are data values, not logarithms.
400#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
401#[ts(export)]
402pub struct CurveAxes {
403 /// Frequency bounds in Hz.
404 pub frequency_hz: [f64; 2],
405 /// Level or delay bounds.
406 pub value: [f64; 2],
407 /// Vertical unit: db or ms.
408 pub unit: String,
409}
410/// Measurement graph data.
411#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
412#[ts(export)]
413pub struct MeasurementCurvesResult {
414 /// Shared 280-point logarithmic frequency grid in Hz.
415 pub grid: Vec<f64>,
416 /// One curve per recorded role and position.
417 pub curves: Vec<MeasurementCurve>,
418 /// Upstream default axis bounds.
419 pub axes: CurveAxes,
420}
421/// The calibration graph to display.
422#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)]
423#[serde(rename_all = "snake_case")]
424#[ts(export)]
425pub enum ResultTab {
426 /// Sound pressure level relative to the target reference.
427 Spl,
428 /// Group delay in milliseconds.
429 Gd,
430 /// Crossover preview.
431 Xo,
432 /// Bass alignment.
433 Ba,
434}
435/// Selection for one ready-to-draw result graph.
436#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
437#[ts(export)]
438pub struct ResultCurvesParams {
439 /// Graph tab.
440 pub tab: ResultTab,
441 /// Selected main role; absent selects the first available channel.
442 pub channel: Option<String>,
443 /// Crossover group; absent selects the selected channel's group.
444 pub group: Option<String>,
445 /// Dragged crossover in Hz; absent uses the configured value.
446 pub xo_hz: Option<f64>,
447 /// Apply input correction to the XO preview; defaults to true.
448 pub corrected: Option<bool>,
449}
450/// One named series on a result graph.
451#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
452#[ts(export)]
453pub struct ResultCurve {
454 /// Series identity: before, after, target, mimo, main, sub, sum_before or sum_after.
455 pub kind: String,
456 /// Speaker role for per-sub curves, otherwise absent.
457 pub role: Option<String>,
458 /// Values on the response's shared grid.
459 pub values: Vec<f64>,
460}
461/// A result graph whose calculations are entirely owned by the service.
462#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
463#[ts(export)]
464pub struct ResultCurvesResult {
465 /// Requested tab.
466 pub tab: ResultTab,
467 /// Shared frequency grid in Hz.
468 pub grid: Vec<f64>,
469 /// Ordered drawable series.
470 pub curves: Vec<ResultCurve>,
471 /// Upstream default axis bounds.
472 pub axes: CurveAxes,
473 /// Whether the retained result no longer matches current inputs.
474 pub stale: bool,
475 /// Whether the graph comes from a dedicated subs-only result.
476 pub subs_only: bool,
477 /// RC lower and upper bounds, with an unknown auto upper bound represented as null.
478 pub rc_band_hz: Option<(f64, Option<f64>)>,
479 /// Selected crossover frequency for the draggable line.
480 pub xo_hz: Option<f64>,
481}
482/// Values behind one channel's metrics and filters lines.
483#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
484#[ts(export)]
485pub struct ChannelResultLine {
486 /// Main role.
487 pub role: String,
488 /// Before and after percentage within one dB, 20–200 Hz.
489 pub within_1db_pct: [f64; 2],
490 /// Before and after group-delay spread, 20–100 Hz, in ms.
491 pub gd_spread_ms: [f64; 2],
492 /// Number of input PEQs.
493 pub peq_count: usize,
494 /// FIR taps, absent when no FIR is used.
495 pub fir_taps: Option<usize>,
496 /// Crossover frequency, absent without bass management.
497 pub xo_hz: Option<f64>,
498 /// Fresh, enabled MIMO support path count, absent when not shown.
499 pub mimo_support_count: Option<usize>,
500}
501/// One sub's output alignment and fixed filters.
502#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
503#[ts(export)]
504pub struct SubResultRow {
505 /// Sub role.
506 pub role: String,
507 /// Alignment mode code.
508 pub mode: String,
509 /// Output gain in dB.
510 pub gain_db: f64,
511 /// Output delay in ms.
512 pub delay_ms: f64,
513 /// Output polarity, plus or minus one.
514 pub polarity: i8,
515 /// Ordered all-pass frequency and Q pairs.
516 pub allpass: Vec<(f64, f64)>,
517 /// High-pass frequency and filter type.
518 pub high_pass: (f64, String),
519 /// Low-pass frequency and filter type.
520 pub low_pass: (f64, String),
521 /// Fixed and generated peaking filters as frequency, Q and gain.
522 pub peq: Vec<(f64, f64, f64)>,
523}
524/// A typed BA layout summary.
525#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
526#[ts(export)]
527pub struct SubLayoutLine {
528 /// Bundle name.
529 pub bundle: String,
530 /// Layout code, dba or multi.
531 pub kind: String,
532 /// All members.
533 pub members: Vec<String>,
534 /// Front row roles.
535 pub front: Vec<String>,
536 /// Rear row roles.
537 pub rear: Vec<String>,
538 /// Rear delay in ms.
539 pub rear_ms: f64,
540 /// Automatic rear delay in ms, when available.
541 pub auto_ms: Option<f64>,
542 /// Manual rear trim in ms.
543 pub trim_ms: f64,
544 /// Shared peaking filters.
545 pub peq: Vec<(f64, f64, f64)>,
546}
547/// BA metrics and table, independent of the selected main channel.
548#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
549#[ts(export)]
550pub struct SubResultLines {
551 /// Whether this BA result is stale.
552 pub stale: bool,
553 /// Whether a dedicated subs-only run supplies these values.
554 pub subs_only: bool,
555 /// Metric band in Hz.
556 pub band_hz: [f64; 2],
557 /// Before and after flatness in dB.
558 pub flatness_db: [f64; 2],
559 /// Before and after across-seat spread, absent for a single seat.
560 pub seat_spread_db: Option<[f64; 2]>,
561 /// Number of complete sub positions.
562 pub positions: usize,
563 /// Output rows in configuration order.
564 pub rows: Vec<SubResultRow>,
565 /// Bundle layouts.
566 pub layouts: Vec<SubLayoutLine>,
567 /// Filter and gain warning codes and their arguments.
568 pub warnings: Vec<Notice>,
569}
570/// Numeric calibration summary lines; no localized sentences cross IPC.
571#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
572#[ts(export)]
573pub struct ResultLinesResult {
574 /// Whether the full calibration result is stale.
575 pub stale: bool,
576 /// Per-main metrics and filter counts.
577 pub channels: Vec<ChannelResultLine>,
578 /// Total delay in milliseconds, absent before calibration.
579 pub total_delay_ms: Option<f64>,
580 /// Engine notes as codes and arguments.
581 pub notes: Vec<Notice>,
582 /// RC ceiling provenance and room-volume notices.
583 pub rc: Vec<Notice>,
584 /// BA metrics, table, layouts and warnings when at least two subs are available.
585 pub ba: Option<SubResultLines>,
586}
587
588/// What the screen needs before it draws anything.
589#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
590#[ts(export)]
591pub struct BootstrapInfo {
592 /// Product name shown in the title bar and the about view.
593 pub app_name: String,
594 /// The SECS 2R version (the workspace version).
595 pub version: String,
596 /// True while the major version is 0: every 0.x build is a prerelease.
597 pub prerelease: bool,
598 /// The SECS 2 release this build follows.
599 pub upstream: UpstreamBase,
600 /// The operating-system platform running this build.
601 pub platform: Platform,
602}
603/// The upstream SECS 2 release a build is ported from (`UPSTREAM.toml`).
604#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
605#[ts(export)]
606pub struct UpstreamBase {
607 /// Upstream product name from `UPSTREAM.toml`.
608 pub name: String,
609 /// Upstream repository URL from `UPSTREAM.toml`.
610 pub repository: String,
611 /// Upstream release version from `UPSTREAM.toml`.
612 pub version: String,
613 /// Forty-character upstream Git commit from `UPSTREAM.toml`.
614 pub commit: String,
615 /// Upstream snapshot date from `UPSTREAM.toml`.
616 pub date: String,
617}
618/// The operating-system platform running the service.
619#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
620#[ts(export)]
621pub struct Platform {
622 /// `windows`, `macos` or `linux` (`std::env::consts::OS`).
623 pub os: String,
624 /// `x86_64` or `aarch64` (`std::env::consts::ARCH`).
625 pub arch: String,
626}
627
628/// Inputs for creating a project.
629#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
630#[ts(export)]
631pub struct ProjectNewParams {
632 /// Destination `.secs` file path.
633 pub path: String,
634 /// Speaker-layout preset name.
635 pub preset: String,
636 /// Number of subwoofers.
637 pub sub_count: u8,
638 /// Whether to create the fixed 7.1.4 sample-room project.
639 pub sample: bool,
640 /// Whether this is a tutorial session using the sample room. The session
641 /// cannot be saved or exported; its measurements and results live in a
642 /// temporary folder that is removed when the session ends, and `path` is
643 /// only reported back.
644 #[serde(default)]
645 pub tutorial: bool,
646}
647/// Inputs for loading embedded or synthetic sample measurements.
648#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS, Default)]
649#[ts(export)]
650pub struct SampleLoadParams {
651 /// Synthetic position count, 2 to 17 (the largest layout); absent loads
652 /// the one-position embedded room. Other counts are refused with
653 /// `measurement_input` (`field = positions`).
654 pub positions: Option<u32>,
655}
656/// A project file path.
657#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
658#[ts(export)]
659pub struct ProjectPathParams {
660 /// Source `.secs` file path.
661 pub path: String,
662}
663/// Inputs for saving the open project.
664#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS, Default)]
665#[ts(export)]
666pub struct ProjectSaveParams {
667 /// Optional save-as `.secs` path; absent saves in place.
668 pub path: Option<String>,
669}
670/// Screen-facing information about the open project.
671#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
672#[ts(export)]
673pub struct ProjectInfo {
674 /// Open `.secs` file path.
675 pub path: String,
676 /// Project display name.
677 pub name: String,
678 /// Speaker-layout preset name.
679 pub preset: String,
680 /// Number of configured subwoofers.
681 pub sub_count: usize,
682 /// Measured position numbers.
683 pub positions: Vec<i64>,
684 /// Whether the in-memory project has unsaved changes.
685 pub dirty: bool,
686 /// Whether this open project is a non-writable tutorial session.
687 pub tutorial: bool,
688 /// Warnings produced while loading legacy project entries.
689 pub migration_warnings: Vec<MigrationWarning>,
690}
691/// A project migration warning code and affected entry.
692#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
693#[ts(export)]
694pub struct MigrationWarning {
695 /// Stable warning code translated by the screen.
696 pub code: String,
697 /// Project entry affected by the warning.
698 pub entry: String,
699}
700
701/// Optional settings for a calibration run.
702#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS, Default)]
703#[ts(export)]
704pub struct CalibrateParams {
705 /// Settings override; absent uses the open project.
706 pub settings: Option<CalibrationSettings>,
707 /// Designs the subwoofers only (upstream [서브만 계산]); the result is
708 /// kept apart and shown by the BA tab until the next full run.
709 #[serde(default)]
710 pub subs_only: bool,
711}
712/// Optional settings for a MIMO run.
713#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS, Default)]
714#[ts(export)]
715pub struct MimoParams {
716 /// Settings override; absent uses the open project.
717 pub settings: Option<CalibrationSettings>,
718}
719
720/// A service-assigned long-running job identifier.
721#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, TS)]
722#[serde(transparent)]
723#[ts(export)]
724pub struct JobId(#[ts(type = "number")] pub u64);
725/// The operation performed by a long-running job.
726#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, TS)]
727#[serde(rename_all = "snake_case")]
728#[ts(export)]
729pub enum JobKind {
730 /// Records all configured roles at one measurement position.
731 Measure,
732 /// Loads embedded or deterministic synthetic sample measurements.
733 SampleLoad,
734 /// Imports assigned measurements from a REW mdat file.
735 MdatImport,
736 /// Computes a calibration design from project measurements.
737 Calibrate,
738 /// Scores crossover-frequency candidates.
739 RecommendXo,
740 /// Computes MIMO support paths.
741 Mimo,
742 /// Writes one selected export bundle.
743 Export,
744 /// Streams microphone levels and controls level-check playback.
745 LevelMonitor,
746 /// Watches measurement-method status changes.
747 StatusWatch,
748}
749/// The current lifecycle state of a job.
750#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)]
751#[serde(rename_all = "snake_case")]
752#[ts(export)]
753pub enum JobStatus {
754 /// The worker is still running and may accept cancellation.
755 Running,
756 /// Cancellation was requested and the worker has not stopped yet.
757 CancelRequested,
758 /// The worker completed and produced a result.
759 Succeeded,
760 /// The worker stopped with an error.
761 Failed,
762 /// The caller cancelled the operation.
763 Cancelled,
764}
765impl JobStatus {
766 /// Whether the job can no longer change state.
767 pub fn is_terminal(self) -> bool {
768 matches!(self, Self::Succeeded | Self::Failed | Self::Cancelled)
769 }
770}
771/// The immediate response after a job starts.
772#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
773#[ts(export)]
774pub struct JobStarted {
775 /// Identifier assigned to the job.
776 pub job: JobId,
777 /// Operation the job performs.
778 pub kind: JobKind,
779}
780/// The latest observable state of a job.
781#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
782#[ts(export)]
783pub struct JobSnapshot {
784 /// Identifier assigned to the job.
785 pub job: JobId,
786 /// Operation the job performs.
787 pub kind: JobKind,
788 /// Current lifecycle state.
789 pub status: JobStatus,
790 /// Whether a cancellation request can still prevent commit.
791 pub cancellable: bool,
792 /// Most recently reported stage progress.
793 pub progress: Option<JobProgress>,
794 /// Failure once finished unsuccessfully.
795 pub error: Option<IpcError>,
796}
797/// Progress within one named job stage.
798#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
799#[ts(export)]
800pub struct JobProgress {
801 /// Stable stage code translated by the screen.
802 pub stage: String,
803 #[ts(type = "number")]
804 /// Completed work units in this stage.
805 pub done: u64,
806 #[ts(type = "number")]
807 /// Total work units in this stage.
808 pub total: u64,
809}
810/// A pushed lifecycle event for a long-running job.
811#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
812#[serde(tag = "event", rename_all = "snake_case")]
813#[ts(export)]
814pub enum JobEvent {
815 /// Announces a newly accepted job and its operation.
816 Started { job: JobId, kind: JobKind },
817 /// Reports completed and total work units for a stable stage code.
818 Progress {
819 job: JobId,
820 stage: String,
821 #[ts(type = "number")]
822 done: u64,
823 #[ts(type = "number")]
824 total: u64,
825 },
826 /// Reports a presentation-neutral log code and template parameters.
827 Log {
828 job: JobId,
829 code: String,
830 params: BTreeMap<String, String>,
831 },
832 /// Reports a terminal status and an ephemeral operation result or error.
833 /// Calibration and MIMO carry only a marker; read `results` for their summary.
834 Finished {
835 job: JobId,
836 status: JobStatus,
837 result: Option<Box<JobResult>>,
838 error: Option<IpcError>,
839 },
840 /// Reports a live input level at the monitor cadence.
841 Meter {
842 job: JobId,
843 rms_dbfs: f64,
844 peak_dbfs: f64,
845 spl_db: Option<f64>,
846 clipping: bool,
847 },
848 /// Reports a changed measurement-method status snapshot.
849 MeasureStatus {
850 job: JobId,
851 status: MeasureMethodsResult,
852 },
853}
854/// The successful result produced by a job.
855#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
856#[serde(tag = "kind", content = "data", rename_all = "snake_case")]
857#[ts(export)]
858pub enum JobResult {
859 /// Calibration committed; fetch the current screen summary with `results`.
860 Calibration,
861 /// Crossover recommendation computed for the current project.
862 Xo(XoRecommendation),
863 /// MIMO committed; fetch the current screen summary with `results`.
864 Mimo,
865 /// Paths written by an exporter.
866 Export(ExportResult),
867 /// Project state after a completed measurement.
868 Measurement(ProjectInfo),
869 /// Project state after an mdat import.
870 MdatImport(ProjectInfo),
871 /// A long-lived monitor or watcher stopped without a payload.
872 Stopped,
873}
874/// The job to cancel.
875#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
876#[ts(export)]
877pub struct JobCancelParams {
878 /// Identifier of the job to cancel.
879 pub job: JobId,
880}
881
882/// The latest calibration and MIMO results.
883#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
884#[ts(export)]
885pub struct ResultsSummary {
886 /// Latest calibration result, including stale state.
887 pub calibration: Option<CalibrationSummary>,
888 /// Latest MIMO result, including stale state.
889 pub mimo: Option<MimoSummary>,
890 /// Whether the MIMO result goes into an export, and if not, why.
891 pub mimo_export: MimoExportDecision,
892}
893/// Whether the MIMO result goes into an export, and if not, why.
894#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)]
895#[serde(rename_all = "snake_case")]
896#[ts(export)]
897pub enum MimoExportDecision {
898 /// The current MIMO support paths go into the export.
899 Included,
900 /// MIMO is switched off in the current settings.
901 Off,
902 /// No MIMO result has been computed or restored.
903 None,
904 /// The MIMO result does not match the current inputs or compute environment.
905 Stale,
906 /// MIMO needs at least three measured positions.
907 TooFewPositions,
908 /// The current MIMO result contains no support paths.
909 NoSupports,
910}
911/// A presentation-neutral note or warning code with template parameters.
912#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
913#[ts(export)]
914pub struct Notice {
915 /// Stable snake_case code derived from the engine note variant.
916 pub code: String,
917 /// String values for the screen message template.
918 pub params: BTreeMap<String, String>,
919}
920
921/// The screen-facing summary of one calibration design.
922#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
923#[ts(export)]
924pub struct CalibrationSummary {
925 /// Whether this result was restored from the project cache instead of computed in this
926 /// session; staged and before/after mdat need a live design.
927 #[serde(default)]
928 pub cached: bool,
929 /// Whether current project inputs differ from this result.
930 pub stale: bool,
931 /// Which input first differs: measurements, settings or target.
932 pub stale_reason: Option<String>,
933 /// Calibration mode used to compute this result.
934 pub mode: Mode,
935 /// Ultra-mode latency limit used to compute this result, in milliseconds.
936 pub ultra_ms: f64,
937 /// Values for the upstream verdict card.
938 pub verdict: VerdictSummary,
939 /// Per-channel before-and-after metrics.
940 pub scorecard: Vec<ChannelScore>,
941 /// Late 20–150 Hz energy before and after correction at every measured position.
942 #[serde(default)]
943 pub late_energy: Vec<LateEnergy>,
944 /// Total filter latency in milliseconds.
945 pub latency_ms: f64,
946 /// Informational codes and values translated by the screen.
947 pub notes: Vec<Notice>,
948 /// Main-channel roles in design order; filter coefficients stay in Rust.
949 pub main_roles: Vec<String>,
950 /// Subwoofer roles in design order; filter coefficients stay in Rust.
951 pub sub_roles: Vec<String>,
952}
953/// Values shown by the upstream `step_calibration.py` verdict card.
954#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
955#[ts(export)]
956pub struct VerdictSummary {
957 /// Share within ±1 dB before correction, in percent.
958 pub within_1db_before_pct: f64,
959 /// Share within ±1 dB after correction, in percent.
960 pub within_1db_after_pct: f64,
961 /// Low-band group-delay spread before correction, in milliseconds.
962 pub low_band_time_spread_before_ms: f64,
963 /// Low-band group-delay spread after correction, in milliseconds.
964 pub low_band_time_spread_after_ms: f64,
965 /// Delay added by correction, in milliseconds.
966 pub added_delay_ms: f64,
967 /// Across-position level spread after correction, in dB.
968 pub position_spread_db: f64,
969}
970/// Before-and-after scorecard metrics for one speaker role.
971#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
972#[ts(export)]
973pub struct ChannelScore {
974 /// Internal speaker role.
975 pub role: String,
976 /// Metrics before correction.
977 pub before: MetricsSummary,
978 /// Metrics after correction.
979 pub after: MetricsSummary,
980}
981/// Late 20–150 Hz energy for one main role at one measured position.
982#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
983#[ts(export)]
984pub struct LateEnergy {
985 /// Internal speaker role.
986 pub role: String,
987 /// Measured position number.
988 pub position: u32,
989 /// Late-to-early energy before correction, in dB.
990 pub before_db: f64,
991 /// Late-to-early energy after correction, in dB.
992 pub after_db: f64,
993}
994/// The upstream `Metrics` fields for one response.
995#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, TS)]
996#[ts(export)]
997pub struct MetricsSummary {
998 /// Reference level from upstream `Metrics.level`, in dB.
999 pub level: f64,
1000 /// Share within ±1 dB from 20 Hz, in percent.
1001 pub in1_20: f64,
1002 /// Share within ±2 dB from 20 Hz, in percent.
1003 pub in2_20: f64,
1004 /// Share within ±1 dB from 200 Hz, in percent.
1005 pub in1_200: f64,
1006 /// Share within ±2 dB from 200 Hz, in percent.
1007 pub in2_200: f64,
1008 /// Share within ±1 dB from 2,000 Hz, in percent.
1009 pub in1_2000: f64,
1010 /// Share within ±2 dB from 2,000 Hz, in percent.
1011 pub in2_2000: f64,
1012 /// Phase rotation from upstream `Metrics.turns`, in turns.
1013 pub turns: f64,
1014 /// Group-delay spread near 20 Hz, in milliseconds.
1015 pub gd_20: f64,
1016 /// Group-delay spread near 100 Hz, in milliseconds.
1017 pub gd_100: f64,
1018 /// Group-delay spread near 500 Hz, in milliseconds.
1019 pub gd_500: f64,
1020 /// Largest absolute 1/24-octave group-delay peak, in milliseconds.
1021 pub gd24_abs: f64,
1022 /// Frequency of the largest absolute group-delay peak, in Hz.
1023 pub gd24_abs_hz: f64,
1024 /// Largest signed 1/24-octave group-delay peak, in milliseconds.
1025 pub gd24_max: f64,
1026 /// Span of 1/24-octave group delay, in milliseconds.
1027 pub gd24_span: f64,
1028 /// Group-delay spread near 30 Hz, in milliseconds.
1029 pub gd_30: f64,
1030 /// Pre-ringing level from upstream `Metrics.pre_db`, in dB.
1031 pub pre_db: f64,
1032}
1033/// The screen-facing summary of one MIMO run.
1034#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
1035#[ts(export)]
1036pub struct MimoSummary {
1037 /// Actual computation backend, retained with the result.
1038 #[serde(default)]
1039 pub compute: ComputeReport,
1040 /// Whether current calibration inputs differ from this MIMO result.
1041 pub stale: bool,
1042 /// Realization kind, `iir` or `fir`.
1043 pub kind: String,
1044 /// Upper support frequency in Hz.
1045 pub fsiso: f64,
1046 /// Number of generated support paths.
1047 pub support_count: usize,
1048 /// Total FIR-tap budget across all generated paths.
1049 pub fir_taps_total: usize,
1050 /// Informational codes and values translated by the screen.
1051 pub notes: Vec<Notice>,
1052 /// Number of distinct input roles with at least one generated support path.
1053 pub input_count: usize,
1054 /// Per-channel metrics after MIMO at the main position.
1055 #[serde(default)]
1056 pub scorecard: Vec<MimoChannelScore>,
1057 /// Late 20–150 Hz energy after MIMO at every measured position.
1058 #[serde(default)]
1059 pub late_energy: Vec<MimoLateEnergy>,
1060}
1061/// Scorecard metrics after MIMO for one main role.
1062#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
1063#[ts(export)]
1064pub struct MimoChannelScore {
1065 /// Internal speaker role.
1066 pub role: String,
1067 /// Metrics after the MIMO support contributions are added.
1068 pub after_mimo: MetricsSummary,
1069}
1070/// Late 20–150 Hz energy after MIMO for one main role and measured position.
1071#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
1072#[ts(export)]
1073pub struct MimoLateEnergy {
1074 /// Internal speaker role.
1075 pub role: String,
1076 /// Measured position number.
1077 pub position: u32,
1078 /// Late-to-early energy after MIMO, in dB.
1079 pub after_mimo_db: f64,
1080}
1081/// Crossover candidates and the best value for each group.
1082#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
1083#[ts(export)]
1084pub struct XoRecommendation {
1085 /// Best crossover frequency in Hz for each speaker group.
1086 pub best: Vec<(String, f64)>,
1087 /// Evaluated crossover frequencies in Hz.
1088 pub candidates: Vec<f64>,
1089}
1090
1091/// The supported export bundle.
1092#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)]
1093#[serde(rename_all = "snake_case")]
1094#[ts(export)]
1095pub enum ExportKind {
1096 /// CamillaDSP configuration and FIR coefficient WAV files.
1097 CamillaDsp,
1098 /// REW mdat containing the upstream staged responses.
1099 StagedMdat,
1100 /// REW mdat containing the original measurements, the correction results
1101 /// or both ([`ExportParams::mdat_scope`]; upstream's before-and-after mdat).
1102 BeforeAfterMdat,
1103 /// Per-channel filter values as text plus FIR WAV files (upstream
1104 /// `calibration.export_filter_text`). Results with RC only.
1105 FilterText,
1106 /// A complete Equalizer APO configuration: `config.txt`, the filter file
1107 /// and FIR WAV files (upstream `calibration.export_eqapo`). RC, BA and
1108 /// MIMO support paths, which upstream refuses (docs/DEVIATIONS.md F12).
1109 EqualizerApo,
1110 /// One complete 48 kHz impulse response and response CSV per reachable
1111 /// input-to-output path, including RC, BA, LFE and MIMO operations.
1112 Convolution,
1113}
1114
1115/// Why a filter export cannot carry the current result (upstream
1116/// `calibration.filter_export_block`).
1117#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)]
1118#[serde(rename_all = "snake_case")]
1119#[ts(export)]
1120pub enum ExportBlock {
1121 /// The result has subs (BA); filter text carries RC only.
1122 Subs,
1123 /// The result includes MIMO; filter text does not carry it.
1124 Mimo,
1125 /// The result was restored from the cache and is stale; mdat needs the measurements' spectra,
1126 /// so it must be recalibrated first.
1127 StaleCache,
1128}
1129
1130impl ExportBlock {
1131 /// The `params.reason` value of an [`ErrorCode::ExportScope`] error.
1132 pub fn name(self) -> &'static str {
1133 match self {
1134 Self::Subs => "subs",
1135 Self::Mimo => "mimo",
1136 Self::StaleCache => "stale_cache",
1137 }
1138 }
1139}
1140
1141/// Upstream `filter_export_block`: whether `kind` can carry a result with
1142/// subs (`has_subs`) and MIMO support paths (`has_mimo`). CamillaDSP, the
1143/// mdat exports, Equalizer APO and convolution carry everything; filter text
1144/// carries RC only (subs are checked first, as upstream). Upstream stops MIMO at
1145/// Equalizer APO as well; its writer already routes the support paths, and
1146/// `secs-filters/tests/eqapo_playback.rs` plays them through the engine
1147/// (DEVIATIONS F12). The screen mirrors this rule (`screens/export/types.ts`).
1148pub fn filter_export_block(
1149 kind: ExportKind,
1150 has_subs: bool,
1151 has_mimo: bool,
1152) -> Option<ExportBlock> {
1153 match kind {
1154 ExportKind::FilterText if has_subs => Some(ExportBlock::Subs),
1155 ExportKind::FilterText if has_mimo => Some(ExportBlock::Mimo),
1156 _ => None,
1157 }
1158}
1159
1160/// Whether an export format can carry the current result. Stale cached
1161/// calibration blocks mdat; filter formats retain [`filter_export_block`].
1162pub fn export_block(
1163 kind: ExportKind,
1164 has_subs: bool,
1165 has_mimo: bool,
1166 stale_cache: bool,
1167) -> Option<ExportBlock> {
1168 match kind {
1169 ExportKind::StagedMdat | ExportKind::BeforeAfterMdat if stale_cache => {
1170 Some(ExportBlock::StaleCache)
1171 }
1172 _ => filter_export_block(kind, has_subs, has_mimo),
1173 }
1174}
1175/// What the REW mdat export ([`ExportKind::BeforeAfterMdat`]) carries
1176/// (docs/DEVIATIONS.md F19). The file name says which: `원 측정` for the
1177/// measurements, `보정 결과` for the computed responses.
1178#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, TS)]
1179#[serde(rename_all = "snake_case")]
1180#[ts(export)]
1181pub enum MdatScope {
1182 /// The main position's before-and-after responses (upstream's file) and
1183 /// the original measurements of the other positions.
1184 #[default]
1185 All,
1186 /// The original measurements of every position, as stored; needs no
1187 /// correction result.
1188 Measurements,
1189 /// The computed responses at the main position only.
1190 Results,
1191}
1192
1193/// Sample formats accepted by CamillaDSP's Wasapi backend.
1194#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)]
1195#[ts(export)]
1196pub enum CamillaWasapiFormat {
1197 /// Signed 32-bit integer.
1198 #[serde(rename = "S32")]
1199 S32,
1200 /// Signed 24-bit integer.
1201 #[serde(rename = "S24")]
1202 S24,
1203 /// Signed 16-bit integer.
1204 #[serde(rename = "S16")]
1205 S16,
1206 /// 32-bit floating point.
1207 #[serde(rename = "F32")]
1208 F32,
1209}
1210
1211impl CamillaWasapiFormat {
1212 /// CamillaDSP configuration spelling.
1213 pub fn name(self) -> &'static str {
1214 match self {
1215 Self::S32 => "S32",
1216 Self::S24 => "S24",
1217 Self::S16 => "S16",
1218 Self::F32 => "F32",
1219 }
1220 }
1221}
1222
1223/// CamillaDSP capture backend. Channel counts come from the export plan.
1224#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS, Default)]
1225#[serde(tag = "type", rename_all = "snake_case")]
1226#[ts(export)]
1227pub enum CamillaCaptureDevice {
1228 /// File placeholder compatible with the existing export.
1229 #[default]
1230 File,
1231 /// Windows Audio Session API capture.
1232 Wasapi {
1233 /// Endpoint name; absent selects the system default.
1234 device: Option<String>,
1235 /// Sample representation.
1236 format: CamillaWasapiFormat,
1237 /// Request exclusive access.
1238 exclusive: bool,
1239 /// Capture a playback endpoint in shared mode.
1240 loopback: bool,
1241 },
1242 /// ASIO capture. Requires CamillaDSP's ASIO build (`camilladsp-windows-asio`).
1243 Asio {
1244 /// ASIO driver name.
1245 device: String,
1246 },
1247}
1248
1249/// CamillaDSP playback backend. Channel counts come from the export plan.
1250#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS, Default)]
1251#[serde(tag = "type", rename_all = "snake_case")]
1252#[ts(export)]
1253pub enum CamillaPlaybackDevice {
1254 /// File placeholder compatible with the existing export.
1255 #[default]
1256 File,
1257 /// Windows Audio Session API playback.
1258 Wasapi {
1259 /// Endpoint name; absent selects the system default.
1260 device: Option<String>,
1261 /// Sample representation.
1262 format: CamillaWasapiFormat,
1263 /// Request exclusive access.
1264 exclusive: bool,
1265 },
1266 /// ASIO playback. Requires CamillaDSP's ASIO build (`camilladsp-windows-asio`).
1267 Asio {
1268 /// ASIO driver name.
1269 device: String,
1270 },
1271}
1272
1273/// Capture and playback backends for a CamillaDSP export.
1274#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS, Default)]
1275#[ts(export)]
1276pub struct CamillaDevices {
1277 /// Capture backend.
1278 pub capture: CamillaCaptureDevice,
1279 /// Playback backend.
1280 pub playback: CamillaPlaybackDevice,
1281}
1282
1283/// The export bundle and destination folder.
1284#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1285#[ts(export)]
1286pub struct ExportParams {
1287 /// Export bundle to write.
1288 pub kind: ExportKind,
1289 /// Destination folder path. An empty path uses the project's export folder
1290 /// and applies the upstream timestamped subfolder convention.
1291 pub folder: String,
1292 /// What the REW mdat carries; absent means [`MdatScope::All`]. Only
1293 /// [`ExportKind::BeforeAfterMdat`] takes it; other kinds refuse it.
1294 #[serde(default, skip_serializing_if = "Option::is_none")]
1295 #[ts(optional)]
1296 pub mdat_scope: Option<MdatScope>,
1297 /// Optional stable export name. Named exports replace the matching bundle.
1298 #[serde(default, skip_serializing_if = "Option::is_none")]
1299 #[ts(optional)]
1300 pub design_name: Option<String>,
1301 /// Optional CamillaDSP Windows backends. File placeholders are the default.
1302 #[serde(default, skip_serializing_if = "Option::is_none")]
1303 #[ts(optional)]
1304 pub camilla_devices: Option<CamillaDevices>,
1305}
1306/// Files written by a completed export job.
1307#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1308#[ts(export)]
1309pub struct ExportResult {
1310 /// Paths of all files written by the exporter.
1311 pub files: Vec<String>,
1312 /// Actual export folder written by the job.
1313 pub folder: String,
1314 /// Whether the exported calibration differed from current project inputs.
1315 pub stale: bool,
1316}
1317
1318// P28 host, live-monitor, watch and export-folder types. Keep this block
1319// contiguous so the parallel M5 IPC additions merge without interleaving.
1320/// Optional suggested name for a save dialog.
1321#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS, Default)]
1322#[ts(export)]
1323pub struct DialogSuggestedParams {
1324 /// Suggested file name.
1325 pub suggested: String,
1326}
1327/// Optional starting folder for a folder picker.
1328#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS, Default)]
1329#[ts(export)]
1330pub struct DialogPickFolderParams {
1331 /// Starting folder, when known.
1332 pub start: Option<String>,
1333}
1334/// The kind of file a host save dialog accepts.
1335#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)]
1336#[serde(rename_all = "snake_case")]
1337#[ts(export)]
1338pub enum SaveFileKind {
1339 /// UTF-8 patch-sheet CSV.
1340 PatchSheet,
1341 /// Target-curve JSON.
1342 Target,
1343}
1344/// Typed save-file dialog parameters.
1345#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1346#[ts(export)]
1347pub struct DialogSaveFileParams {
1348 /// File type and filter.
1349 pub kind: SaveFileKind,
1350 /// Suggested file name.
1351 pub suggested: String,
1352}
1353/// The kind of file a host open dialog accepts.
1354#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)]
1355#[serde(rename_all = "snake_case")]
1356#[ts(export)]
1357pub enum OpenFileKind {
1358 /// Target-curve JSON.
1359 Target,
1360 /// Microphone response calibration text (`.txt`, `.cal` or `.frd`).
1361 MicCalibration,
1362}
1363/// Typed open-file dialog parameters.
1364#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1365#[ts(export)]
1366pub struct DialogOpenFileParams {
1367 /// File type and filter.
1368 pub kind: OpenFileKind,
1369}
1370/// Folder to reveal in the operating-system file manager.
1371#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1372#[ts(export)]
1373pub struct OpenFolderParams {
1374 /// Folder path.
1375 pub path: String,
1376}
1377/// Optional role to associate with a level-monitor start.
1378#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS, Default)]
1379#[ts(export)]
1380pub struct LevelMonitorStartParams {
1381 /// Speaker role initially selected for playback.
1382 pub role: Option<String>,
1383}
1384/// Role and output level for one 2.5-second level-check burst.
1385#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
1386#[ts(export)]
1387pub struct LevelPlayParams {
1388 /// Internal speaker role.
1389 pub role: String,
1390 /// Master output level in dBFS.
1391 pub master_dbfs: f64,
1392}
1393/// Export base-folder update.
1394#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1395#[ts(export)]
1396pub struct ExportFolderSetParams {
1397 /// Folder path to store in the open project.
1398 pub folder: String,
1399}
1400/// Current export base folder and whether it came from project storage.
1401#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1402#[ts(export)]
1403pub struct ExportFolderResult {
1404 /// Export base folder.
1405 pub folder: String,
1406 /// True when `extra.export.folder` supplied this value.
1407 pub stored: bool,
1408}
1409
1410// Measurement request and response types.
1411/// A validated microphone calibration file selected for the open project.
1412#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
1413#[ts(export)]
1414pub struct MicCalibrationFile {
1415 /// File name as selected by the user.
1416 pub name: String,
1417 /// Full selected path; the project retains the decoded text separately.
1418 pub path: String,
1419 /// miniDSP serial number from `SERNO`, when present.
1420 pub serial: Option<String>,
1421 /// USB microphone sensitivity factor in dB.
1422 pub sens_factor_db: Option<f64>,
1423 /// Analog input gain from `AGain`, in dB.
1424 pub analog_gain_db: Option<f64>,
1425 /// Analog microphone sensitivity at 94 dB SPL, in dBFS.
1426 pub sensitivity_dbfs: Option<f64>,
1427 /// Quoted metadata lines other than the sensitivity header.
1428 pub notes: Vec<String>,
1429 /// Number of frequency-response points.
1430 pub points: u32,
1431 /// Lowest tabulated frequency in Hz.
1432 pub min_hz: f64,
1433 /// Highest tabulated frequency in Hz.
1434 pub max_hz: f64,
1435 /// Whether any response point includes phase.
1436 pub has_phase: bool,
1437}
1438/// Selected microphone calibration and recorded-measurement attachment counts.
1439#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
1440#[ts(export)]
1441pub struct MicCalibrationStatus {
1442 /// The project's selected file, or absent when none is selected.
1443 pub file: Option<MicCalibrationFile>,
1444 /// Recorded position-role pairs whose sidecar attaches a `meterCal` curve.
1445 pub attached: u32,
1446 /// Sweep measurements without a `meterCal` curve that can receive the file.
1447 pub attachable: u32,
1448}
1449/// A microphone calibration file selected by full path.
1450#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1451#[ts(export)]
1452pub struct MicCalibrationSetParams {
1453 /// Full path selected by the user.
1454 pub path: String,
1455}
1456
1457/// Measurement methods available on this host.
1458#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS, Default)]
1459#[ts(export)]
1460pub struct MeasureMethodsResult {
1461 /// Stable identifiers for available measurement methods.
1462 pub methods: Vec<String>,
1463 /// Native endpoints with their channel counts.
1464 pub native_devices: Vec<MeasureDevice>,
1465 /// Native enumeration failure, without hiding the other methods.
1466 pub native_error: Option<IpcError>,
1467 /// REW process, API, and license status.
1468 pub rew: RewStatusResult,
1469 /// Positions present in the unmodified sample room.
1470 pub mock_positions: Vec<i64>,
1471 /// Roles present in the unmodified sample room.
1472 pub mock_roles: Vec<String>,
1473}
1474/// An enumerated native audio endpoint.
1475#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1476#[ts(export)]
1477pub struct MeasureDevice {
1478 /// Backend endpoint identifier.
1479 pub id: String,
1480 /// Backend endpoint display name.
1481 pub name: String,
1482 /// Maximum number of input channels.
1483 pub input_channels: usize,
1484 /// Maximum number of output channels.
1485 pub output_channels: usize,
1486}
1487/// Measurement method and device settings.
1488#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS, Default)]
1489#[ts(export)]
1490pub struct MeasureConfigureParams {
1491 /// Measurement method identifier.
1492 pub method: String,
1493 /// Optional input device identifier.
1494 pub input_device: Option<String>,
1495 /// Optional output device identifier.
1496 pub output_device: Option<String>,
1497 /// Optional one-based input channel number.
1498 pub input_channel: Option<u16>,
1499 /// One-based output channels in measurement-plan role order; empty preserves wiring.
1500 #[serde(default)]
1501 pub output_channels: Vec<u16>,
1502 /// Optional sweep length in samples.
1503 pub sweep_length: Option<usize>,
1504 /// Optional sweep repetition count; allowed values are 1, 2, 4 and 8.
1505 pub repetitions: Option<u8>,
1506 /// Optional sweep level in dBFS.
1507 pub sweep_level_dbfs: Option<f64>,
1508 /// Optional timing-reference mode.
1509 pub timing: Option<String>,
1510 /// Optional one-based loopback input channel.
1511 pub loopback_input_channel: Option<u16>,
1512 /// Optional acoustic timing-reference output channel.
1513 pub timing_ref_output: Option<u16>,
1514 /// Optional share preference: auto, exclusive, or shared.
1515 pub share_mode: Option<String>,
1516}
1517/// The active measurement settings.
1518#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS, Default)]
1519#[ts(export)]
1520pub struct MeasureConfigResult {
1521 /// Active measurement method identifier.
1522 pub method: String,
1523 /// Active sweep level in dBFS.
1524 pub sweep_level_dbfs: Option<f64>,
1525 /// Active input endpoint identifier; empty selects the default.
1526 pub input_device: String,
1527 /// Active output endpoint identifier; empty selects the default.
1528 pub output_device: String,
1529 /// One-based microphone input channel.
1530 pub input_channel: u16,
1531 /// Roles in the same order as output_channels.
1532 pub roles: Vec<String>,
1533 /// One-based output channels in measurement-plan role order.
1534 pub output_channels: Vec<u16>,
1535 /// Sweep length in samples.
1536 pub sweep_length: usize,
1537 /// Active repetition count after timing-reference normalization.
1538 pub repetitions: u8,
1539 /// Sweep sample rate in Hz.
1540 pub sample_rate: u32,
1541 /// Supported sweep lengths in samples.
1542 pub sweep_lengths: Vec<usize>,
1543 /// Timing-reference mode.
1544 pub timing: String,
1545 /// Optional one-based loopback input channel.
1546 pub loopback_input_channel: Option<u16>,
1547 /// Optional acoustic timing-reference output channel.
1548 pub timing_ref_output: Option<u16>,
1549 /// Share preference: auto, exclusive, or shared.
1550 pub share_mode: String,
1551}
1552/// Recording status and upstream source values for one arranged position.
1553#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1554#[ts(export)]
1555pub struct PositionRecord {
1556 /// One-based arranged position number.
1557 #[ts(type = "number")]
1558 pub position: i64,
1559 /// Whether every planned role is recorded at this position.
1560 pub done: bool,
1561 /// Planned roles that have a recording at this position.
1562 pub roles: Vec<String>,
1563 /// Source file/value by recorded role; native and REW sweeps use `measured`.
1564 pub sources: BTreeMap<String, String>,
1565}
1566/// Recording status for the complete measurement arrangement.
1567#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1568#[ts(export)]
1569pub struct MeasurementRecordsResult {
1570 /// One entry per arranged position.
1571 pub records: Vec<PositionRecord>,
1572}
1573/// Stored level-check readings, keyed by measurement role (upstream `extra['levels']`).
1574#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
1575#[ts(export)]
1576pub struct LevelsResult {
1577 /// Measured input level in dBFS by role: the meter reading while that channel played.
1578 pub levels: BTreeMap<String, f64>,
1579 /// Measured sound-pressure level in dB SPL by role.
1580 pub spl: BTreeMap<String, f64>,
1581}
1582/// The measurement position to record.
1583#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1584#[ts(export)]
1585pub struct MeasurePositionParams {
1586 /// One-based measurement position number.
1587 pub position: i64,
1588}
1589/// The measurement position to clear.
1590#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1591#[ts(export)]
1592pub struct PositionClearParams {
1593 /// One-based measurement position number.
1594 pub position: i64,
1595}
1596/// A service-issued undo token.
1597#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1598#[ts(export)]
1599pub struct UndoParams {
1600 /// Opaque service-issued undo token.
1601 pub token: String,
1602}
1603/// An undo token and the project state it restores.
1604#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1605#[ts(export)]
1606pub struct UndoResult {
1607 /// Opaque token for the inverse action.
1608 pub token: String,
1609 /// Project state after the action.
1610 pub project: ProjectInfo,
1611}
1612/// Whether REW is running, reachable, and licensed as Pro.
1613#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS, Default)]
1614#[ts(export)]
1615pub struct RewStatusResult {
1616 /// Whether an REW process is running.
1617 pub running: bool,
1618 /// Whether the REW API responds.
1619 pub reachable: bool,
1620 /// Observed automatic-sweep license state: unknown until success or rejection.
1621 pub pro: Option<bool>,
1622}
1623/// A requested REW audio driver.
1624#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1625#[ts(export)]
1626pub struct RewDriverSetParams {
1627 /// Driver name as REW reports it (`ASIO` or `Java`).
1628 pub driver: String,
1629}
1630/// Facts from REW, distinct from project-stored measurement preferences.
1631#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS, Default)]
1632#[ts(export)]
1633pub struct RewInfoResult {
1634 pub executable_path: Option<String>,
1635 pub running: bool,
1636 pub reachable: bool,
1637 pub version: Option<String>,
1638 /// REW's current audio driver, or null when it could not be read.
1639 pub driver: Option<String>,
1640 /// Observed automatic-sweep license state: unknown until success or rejection.
1641 pub pro: Option<bool>,
1642 /// REW's current input device, read from its own audio preferences.
1643 pub input_device: Option<String>,
1644 /// REW's current output device, read from its own audio preferences.
1645 pub output_device: Option<String>,
1646 /// REW's current input channel name.
1647 pub input: Option<String>,
1648 pub blocking: Option<bool>,
1649 /// Empty means no file loaded; null means unknown or per-input calibration.
1650 pub input_calibration_file: Option<String>,
1651 pub input_devices: Option<Vec<RewDeviceInfo>>,
1652 pub output_devices: Option<Vec<RewDeviceInfo>>,
1653 pub input_channel_names: Option<Vec<String>>,
1654 pub sample_rate: Option<f64>,
1655 pub sweep: Option<RewSweepInfo>,
1656 pub level_dbfs: Option<f64>,
1657}
1658
1659#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1660#[ts(export)]
1661pub struct RewDeviceInfo {
1662 pub id: String,
1663 pub name: String,
1664 pub channels: usize,
1665}
1666
1667#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
1668#[ts(export)]
1669pub struct RewSweepInfo {
1670 pub start_hz: Option<f64>,
1671 pub end_hz: Option<f64>,
1672 pub length: Option<String>,
1673}
1674
1675/// The REW mdat file to scan.
1676#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1677#[ts(export)]
1678pub struct MdatScanParams {
1679 /// Source `.mdat` file path.
1680 pub path: String,
1681}
1682/// Measurements, suggested assignments, and problem codes found in an mdat file.
1683#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS, Default)]
1684#[ts(export)]
1685pub struct MdatScanResult {
1686 /// Measurements in file order, including assignment guesses.
1687 pub measurements: Vec<MdatScanMeasurement>,
1688 /// Suggested measurement-to-role assignments.
1689 pub suggestions: Vec<MdatAssignment>,
1690 /// Stable problem codes and template parameters.
1691 pub problems: Vec<Notice>,
1692}
1693/// One scanned measurement with an unambiguous row identifier.
1694#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1695#[ts(export)]
1696pub struct MdatScanMeasurement {
1697 /// Zero-based row index, also usable when titles repeat.
1698 pub index: usize,
1699 /// Original measurement title.
1700 pub title: String,
1701 /// Suggested internal role, or no assignment.
1702 pub role: Option<String>,
1703 /// Guessed one-based measurement position.
1704 pub position: i64,
1705 /// Whether the title explicitly names a position, including position 1.
1706 pub position_tagged: bool,
1707 /// Low-band classifier result, absent when no IR is available.
1708 pub looks_like_sub: Option<bool>,
1709}
1710/// An mdat measurement assigned to a speaker role.
1711#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1712#[ts(export)]
1713pub struct MdatAssignment {
1714 /// Measurement title from the mdat file.
1715 pub measurement: String,
1716 /// Internal speaker role.
1717 pub role: String,
1718 /// Optional zero-based row index to disambiguate repeated titles.
1719 pub index: Option<usize>,
1720 /// Optional per-row position overriding the import destination.
1721 pub position: Option<i64>,
1722}
1723/// An mdat file, its assignments, and destination position.
1724#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1725#[ts(export)]
1726pub struct MdatImportParams {
1727 /// Source `.mdat` file path.
1728 pub path: String,
1729 /// Accepted measurement-to-role assignments.
1730 pub assignments: Vec<MdatAssignment>,
1731 /// Destination measurement position number.
1732 pub position: i64,
1733}
1734
1735// Project editing request and response types.
1736/// A filesystem path used by a project editor.
1737#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1738#[ts(export)]
1739pub struct PathParams {
1740 /// File system path chosen by the user.
1741 pub path: String,
1742}
1743/// A path written by an editor.
1744#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1745#[ts(export)]
1746pub struct PathResult {
1747 /// File system path that was written.
1748 pub path: String,
1749}
1750/// A preset change which may need measurement-clearing confirmation.
1751#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1752#[ts(export)]
1753pub struct ConfigPresetParams {
1754 /// 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).
1755 pub preset: String,
1756 /// Clears existing measurements; without it a project with measurements answers `confirm_required` (U5).
1757 #[serde(default)]
1758 pub confirm: bool,
1759}
1760/// A sub-count change which may need measurement-clearing confirmation.
1761#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1762#[ts(export)]
1763pub struct ConfigSubCountParams {
1764 /// Number of subwoofers, 0 to 8.
1765 pub count: i64,
1766 /// Clears existing measurements; without it a project with measurements answers `confirm_required` (U5).
1767 #[serde(default)]
1768 pub confirm: bool,
1769}
1770/// One speaker index.
1771#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1772#[ts(export)]
1773pub struct ConfigIndexParams {
1774 /// Speaker index in `ConfigResult.speakers`.
1775 pub index: usize,
1776}
1777/// Optional edits to one speaker or one of its bands.
1778#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1779#[ts(export)]
1780pub struct ConfigSetSpeakerParams {
1781 /// Speaker index in `ConfigResult.speakers`.
1782 pub index: usize,
1783 /// New role code; a sub role forces a single SUB band (`step2_config.py:988-1002`).
1784 pub role: Option<String>,
1785 /// New size: `small` (crossover applied) or `large` (full range).
1786 pub size: Option<String>,
1787 /// Band the output and polarity edits apply to; absent means the first band.
1788 pub band_index: Option<usize>,
1789 /// New measurement output channel (1-based).
1790 pub out: Option<i64>,
1791 /// New polarity: `+` normal or `-` inverted.
1792 pub pol: Option<String>,
1793}
1794/// One configured output band.
1795#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1796#[ts(export)]
1797pub struct ConfigBand {
1798 /// Band name: `FULL`, `LF`, `MF`, `HF` or `SUB`.
1799 pub band: String,
1800 /// Measurement output channel (1-based).
1801 pub out: i64,
1802 /// Polarity: `+` normal or `-` inverted.
1803 pub pol: String,
1804}
1805/// One configured speaker.
1806#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1807#[ts(export)]
1808pub struct ConfigSpeaker {
1809 /// Role code (`L`, `C`, `SW1`, `Tfl`, ...).
1810 pub role: String,
1811 /// `small` (crossover applied) or `large` (full range); subs are `sub`.
1812 pub size: String,
1813 /// Bands in order; one band unless bi-amped.
1814 pub bands: Vec<ConfigBand>,
1815}
1816/// One upstream speaker group in upstream order.
1817#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1818#[ts(export)]
1819pub struct ConfigGroup {
1820 /// Stable presentation group id: front, center, sub, surround, back, wide, top, height or other.
1821 pub id: String,
1822 /// Upstream group name used as the project and crossover-map key.
1823 pub key: String,
1824 /// Indices into `ConfigResult.speakers` that belong to this group.
1825 pub speaker_indices: Vec<usize>,
1826}
1827/// Values derived from the complete speaker configuration.
1828#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1829#[ts(export)]
1830pub struct ConfigSummary {
1831 /// Number of speakers.
1832 pub speaker_count: usize,
1833 /// Number of measurement outputs in use.
1834 pub outputs: Vec<i64>,
1835 /// Highest output channel in use.
1836 pub max_channel: i64,
1837 /// Number of output channels used twice or more.
1838 pub duplicate_outputs: Vec<i64>,
1839 /// Number of subwoofers.
1840 pub sub_count: usize,
1841}
1842/// Screen-facing editable speaker configuration.
1843#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1844#[ts(export)]
1845pub struct ConfigResult {
1846 /// Speakers in table order.
1847 pub speakers: Vec<ConfigSpeaker>,
1848 /// Preset name, or `custom` after any manual edit.
1849 pub preset: String,
1850 /// Measurement output device name (`config.device`).
1851 pub device: String,
1852 /// Table group header rows in upstream order.
1853 pub groups: Vec<ConfigGroup>,
1854 /// Counts the summary line shows.
1855 pub summary: ConfigSummary,
1856}
1857/// Configuration after a destructive edit and its optional inverse token.
1858#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1859#[ts(export)]
1860pub struct ConfigMutationResult {
1861 /// The configuration after the edit.
1862 pub config: ConfigResult,
1863 /// Token for `undo` when the edit cleared measurements (U5).
1864 pub undo_token: Option<String>,
1865}
1866/// The five manually editable DSP profile fields.
1867#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1868#[ts(export)]
1869pub struct DspFields {
1870 /// FIR taps per channel.
1871 pub fir_taps: i64,
1872 /// Total FIR taps the DSP can run across channels.
1873 pub fir_budget: i64,
1874 /// IIR (PEQ) bands per channel.
1875 pub iir_bands: i64,
1876 /// All-pass bands per channel.
1877 pub allpass_bands: i64,
1878 /// Bass-management path delays the DSP offers.
1879 pub bm_path_delays: i64,
1880}
1881/// One DSP preset in the built-in table.
1882#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1883#[ts(export)]
1884pub struct DspPreset {
1885 /// Preset key (`camilla`, `tesira`, `qsys`, `minidsp`, `custom`).
1886 pub key: String,
1887 /// Device name shown in the device list.
1888 pub name: String,
1889 /// The preset's filter resources.
1890 pub fields: DspFields,
1891}
1892/// A preset selection or a complete manual edit.
1893#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS, Default)]
1894#[ts(export)]
1895pub struct DspSetParams {
1896 /// Selects a preset; absent with `fields` set means a manual edit.
1897 pub preset: Option<String>,
1898 /// Manual values; any manual edit makes the profile `custom`.
1899 pub fields: Option<DspFields>,
1900}
1901/// Editable DSP state and derived resolution values.
1902#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
1903#[ts(export)]
1904pub struct DspResult {
1905 /// Current profile key.
1906 pub profile: String,
1907 /// Current device name.
1908 pub name: String,
1909 /// Current filter resources.
1910 pub fields: DspFields,
1911 /// Every preset, for the device list.
1912 pub presets: Vec<DspPreset>,
1913 /// FIR window length the taps give, in ms (note line).
1914 pub window_ms: f64,
1915 /// Frequency resolution the taps give, in Hz (note line).
1916 pub resolution_hz: f64,
1917}
1918/// Measurement timing-reference settings stored under `extra.devices`.
1919#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
1920#[ts(export)]
1921pub struct LayoutTiming {
1922 /// Timing reference: `acoustic`, `loopback` or `none`.
1923 pub reference: String,
1924 /// Reference speaker role for the acoustic reference (default `R`).
1925 pub role: String,
1926 /// Reference output channel for the loopback reference (1-based).
1927 pub output: i64,
1928}
1929/// One measurement microphone point in centimetres.
1930#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
1931#[ts(export)]
1932pub struct LayoutPoint {
1933 /// Position number; 1 is the listening position.
1934 pub position: usize,
1935 /// Left-right offset from the centre seat, in cm (`layout_map.py`).
1936 pub x_cm: f64,
1937 /// Front-back offset from the centre seat, in cm (`layout_map.py`).
1938 pub y_cm: f64,
1939 /// Height step: 0 at ear height, +1 or -1 for ±20 cm.
1940 pub height: i8,
1941}
1942/// One supported measurement arrangement.
1943#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
1944#[ts(export)]
1945pub struct LayoutArrangement {
1946 /// Number of positions: 1, 9, 13 or 17.
1947 pub count: usize,
1948 /// Microphone points of this arrangement.
1949 pub points: Vec<LayoutPoint>,
1950}
1951/// Editable arrangement and measurement options.
1952#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
1953#[ts(export)]
1954pub struct LayoutResult {
1955 /// Chosen arrangement (`extra['array']`).
1956 pub count: usize,
1957 /// Start delay before each position as stored (`extra['start_delay']`):
1958 /// one of [`crate::rules::START_DELAYS`], `끄기` or `5 초` to `15 초`.
1959 pub start_delay: String,
1960 /// Timing reference and its reference speaker or output.
1961 pub timing: LayoutTiming,
1962 /// Every arrangement with its geometry, for the cards and maps.
1963 pub arrangements: Vec<LayoutArrangement>,
1964}
1965/// Optional arrangement and timing edits.
1966#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS, Default)]
1967#[ts(export)]
1968pub struct LayoutSetParams {
1969 /// New arrangement; changing it clears measurements (U5).
1970 pub count: Option<usize>,
1971 /// New start delay, one of [`crate::rules::START_DELAYS`]; any other
1972 /// value answers `invalid_request` (field `start_delay`).
1973 pub start_delay: Option<String>,
1974 /// New timing reference.
1975 pub timing: Option<LayoutTiming>,
1976 /// Clears existing measurements; without it a project with measurements answers `confirm_required` (U5).
1977 #[serde(default)]
1978 pub confirm: bool,
1979}
1980/// Arrangement after editing and its optional inverse token.
1981#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
1982#[ts(export)]
1983pub struct LayoutMutationResult {
1984 /// The arrangement and options after the edit.
1985 pub layout: LayoutResult,
1986 /// Token for `undo` when the edit cleared measurements (U5).
1987 pub undo_token: Option<String>,
1988}
1989/// Position-duration estimate from upstream step 4 semantics.
1990#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
1991#[ts(export)]
1992pub struct PositionEstimateResult {
1993 /// Number of positions in the arrangement.
1994 pub positions: usize,
1995 /// Estimated time per position, in seconds (`step4_layout.position_seconds`).
1996 pub per_position_seconds: f64,
1997 /// Estimated time for every position, without moving the microphone, in seconds.
1998 pub total_seconds: f64,
1999}
2000/// Target control points.
2001#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
2002#[ts(export)]
2003pub struct TargetPointsParams {
2004 /// Target points as (Hz, dB); clamped to 10–24000 Hz and ±30 dB, at least two.
2005 pub points: Vec<(f64, f64)>,
2006}
2007/// One built-in target preset selection.
2008#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
2009#[ts(export)]
2010pub struct TargetPresetParams {
2011 /// Target preset: `flat`, `harman` or `cinema`.
2012 pub preset: String,
2013}
2014/// Target curve, selected preset, and built-in preset table.
2015#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
2016#[ts(export)]
2017pub struct TargetResult {
2018 /// Target points as (Hz, dB).
2019 pub points: Vec<(f64, f64)>,
2020 /// Preset name, or absent when the points were edited (custom).
2021 pub preset: Option<String>,
2022 /// Preset names the target menu offers.
2023 pub presets: Vec<(String, Vec<(f64, f64)>)>,
2024}
2025/// Complete calibration settings replacement.
2026#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
2027#[ts(export)]
2028pub struct CalibrationSettingsParams {
2029 /// The complete calibration settings to store.
2030 pub settings: CalibrationSettings,
2031}
2032/// Mode whose numeric specification should be returned.
2033#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
2034#[ts(export)]
2035pub struct ModeSpecParams {
2036 /// Correction mode: `zero`, `mixed`, `linear` or `ultra`.
2037 pub mode: Mode,
2038 /// Latency limit for the ultra mode, in ms.
2039 pub ultra_ms: Option<f64>,
2040 /// Whether MIMO support paths use FIR instead of IIR.
2041 #[serde(default, skip_serializing_if = "Option::is_none")]
2042 #[ts(optional)]
2043 pub mimo_fir: Option<bool>,
2044}
2045/// Presentation-neutral values underlying the upstream mode specification line.
2046#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
2047#[ts(export)]
2048pub struct ModeSpecResult {
2049 /// Room-correction filters: `iir` (IIR only) or `iir_fir` (IIR plus FIR).
2050 pub rc_filter: String,
2051 /// FIR taps the mode uses (0 without FIR).
2052 pub fir_taps: usize,
2053 /// PEQ bands per channel, when the mode uses IIR.
2054 pub peq_bands: Option<usize>,
2055 /// MIMO support filter kind (`fir` when requested, otherwise `iir`).
2056 pub mimo_filter: String,
2057 /// Delay the FIR may add ahead of the main arrival (`lift_ms`), in ms.
2058 pub lift_ms: f64,
2059 /// Octave fraction of the excess-phase smoothing (6, or 24 in linear and ultra modes).
2060 pub phase_frac: f64,
2061 /// Upper latency bound of the mode, in ms.
2062 pub max_latency_ms: f64,
2063}
2064/// Application theme preference.
2065#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS, Default)]
2066#[serde(rename_all = "snake_case")]
2067#[ts(export)]
2068pub enum AppTheme {
2069 /// Follow the operating-system preference.
2070 #[default]
2071 System,
2072 /// Always use the light theme.
2073 Light,
2074 /// Always use the dark theme.
2075 Dark,
2076}
2077/// The skin set the screen draws with (docs/skins, ADR 0010).
2078#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS, Default)]
2079#[serde(rename_all = "snake_case")]
2080#[ts(export)]
2081pub enum AppSkin {
2082 /// The instrument skin: the default tokens.
2083 #[default]
2084 Default,
2085 /// Hardware Rack: a 19-inch rack faceplate.
2086 Rack,
2087 /// Signal Matrix: a routing board of cells on a grid.
2088 Matrix,
2089}
2090/// The logo on the rail and the window (docs/DEVIATIONS.md U12). The
2091/// installed program's own icon is always `Expert`.
2092#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS, Default)]
2093#[serde(rename_all = "snake_case")]
2094#[ts(export)]
2095pub enum AppIcon {
2096 /// "전문가 모드": upstream's confirmed logo (`logo_b8.svg`).
2097 #[default]
2098 Expert,
2099 /// "킹반인 모드": SECS 2R's own logo (`apps/secs2r/icons/logo/civilian.svg`),
2100 /// a measured response and its correction on the target.
2101 Civilian,
2102}
2103/// Opt-in loopback API settings.
2104#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)]
2105#[ts(export)]
2106pub struct LocalApiSettings {
2107 /// Whether the app starts the local API server.
2108 pub enabled: bool,
2109 /// Loopback TCP port; zero asks the operating system for a free port.
2110 pub port: u16,
2111}
2112impl Default for LocalApiSettings {
2113 fn default() -> Self {
2114 Self {
2115 enabled: false,
2116 port: crate::local_api::DEFAULT_PORT,
2117 }
2118 }
2119}
2120
2121/// Application-local settings, independent of an open project.
2122#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
2123#[ts(export)]
2124pub struct AppSettings {
2125 /// Application-local compute preference; never stored in an upstream project.
2126 #[serde(default)]
2127 pub compute: ComputePreference,
2128 /// Screen language: `ko` or `en`.
2129 pub language: String,
2130 /// Application theme preference.
2131 #[serde(default)]
2132 pub theme: AppTheme,
2133 /// Logo on the rail and the window.
2134 #[serde(default)]
2135 pub icon: AppIcon,
2136 /// Skin set the screen draws with.
2137 #[serde(default)]
2138 pub skin: AppSkin,
2139 /// Opt-in loopback API server.
2140 #[serde(default)]
2141 pub local_api: LocalApiSettings,
2142 /// Recently opened project paths, newest first (at most 10).
2143 pub recent_projects: Vec<String>,
2144 /// Last window size in logical pixels.
2145 pub last_window_size: Option<(u32, u32)>,
2146}
2147impl Default for AppSettings {
2148 fn default() -> Self {
2149 Self {
2150 language: "ko".into(),
2151 compute: ComputePreference::Auto,
2152 theme: AppTheme::System,
2153 icon: AppIcon::Expert,
2154 skin: AppSkin::Default,
2155 local_api: LocalApiSettings::default(),
2156 recent_projects: Vec::new(),
2157 last_window_size: None,
2158 }
2159 }
2160}
2161/// Complete application-local settings replacement.
2162#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
2163#[ts(export)]
2164pub struct AppSettingsParams {
2165 /// The complete app settings to store.
2166 pub settings: AppSettings,
2167}
2168
2169/// App-facing state of the opt-in local API server.
2170#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
2171#[ts(export)]
2172pub struct LocalApiStatus {
2173 /// Configured enabled state.
2174 pub enabled: bool,
2175 /// Configured port, or the selected free port while running with port zero.
2176 pub port: u16,
2177 /// Whether a listener is currently running.
2178 pub running: bool,
2179 /// Most recent startup failure, when present.
2180 pub error: Option<String>,
2181 /// Loopback base URL.
2182 pub url: String,
2183 /// Current bearer token.
2184 pub token: String,
2185 /// Full token-file path.
2186 pub token_path: String,
2187}
2188
2189/// Why a call failed. The screen maps each code to a written message with the
2190/// cause and the next action.
2191#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)]
2192#[serde(rename_all = "snake_case")]
2193#[ts(export)]
2194pub enum ErrorCode {
2195 /// The request did not parse or named an unknown method.
2196 InvalidRequest,
2197 /// A bug: the service failed in a way it does not expect.
2198 Internal,
2199 /// The method is registered but not connected yet.
2200 NotImplemented,
2201 /// The method opens a window or program and is unavailable to local API clients.
2202 UiOnly,
2203 /// The call requires an open project.
2204 ProjectNotOpen,
2205 /// Opening, reading, or writing project data failed.
2206 ProjectIo,
2207 /// Another job in the same group is running.
2208 JobBusy,
2209 /// The named job does not exist.
2210 JobNotFound,
2211 /// The caller cancelled the operation.
2212 Cancelled,
2213 /// Project measurements or settings are not valid engine inputs.
2214 EngineInput,
2215 /// Explicit GPU execution was requested but no usable GPU backend is available.
2216 ComputeUnavailable,
2217 /// The requested exporter failed.
2218 ExportFailed,
2219 /// The chosen export cannot carry the result (upstream
2220 /// `filter_export_block`); `params.reason` is an [`ExportBlock`] name.
2221 ExportScope,
2222 /// Cached results no longer match project inputs.
2223 StaleInputs,
2224 /// The audio backend or selected endpoint is unavailable.
2225 DeviceUnavailable,
2226 /// Recording completed but was discarded because it had a gap or corrupt samples.
2227 RecordingFailed,
2228 /// The backend refused a pinned audio share mode.
2229 ShareModeRefused,
2230 /// The REW API cannot be reached.
2231 RewUnreachable,
2232 /// REW requires a Pro license for this operation.
2233 RewProRequired,
2234 /// REW returned an HTTP or schema failure.
2235 RewFailed,
2236 /// A measurement setting or plan field is invalid.
2237 MeasurementInput,
2238 /// Tutorial sessions cannot save or export project data.
2239 TutorialBlocked,
2240 /// A destructive edit needs explicit confirmation while measurements exist.
2241 ConfirmRequired,
2242}
2243/// A coded IPC failure with template parameters and diagnostic detail.
2244#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
2245#[ts(export)]
2246pub struct IpcError {
2247 /// Stable error code translated by the screen.
2248 pub code: ErrorCode,
2249 /// Values for the message template of `code` (file names, counts, ...).
2250 pub params: BTreeMap<String, String>,
2251 /// Whether trying the same call again can succeed.
2252 pub retryable: bool,
2253 /// The raw error text, for the details view and the log only.
2254 pub detail: String,
2255}
2256impl IpcError {
2257 /// Creates a non-retryable error without template parameters.
2258 pub fn new(code: ErrorCode, detail: impl Into<String>) -> Self {
2259 Self {
2260 code,
2261 params: BTreeMap::new(),
2262 retryable: false,
2263 detail: detail.into(),
2264 }
2265 }
2266 /// Adds one screen message-template parameter.
2267 pub fn with_param(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
2268 self.params.insert(key.into(), value.into());
2269 self
2270 }
2271}
2272
2273/// The reply to every call: `{"ok": true, "data": ...}` or
2274/// `{"ok": false, "error": ...}`.
2275///
2276/// The TypeScript side declares the same union in `src/ipc/client.ts`
2277/// (`Envelope`); `envelope_wire_shape` below pins the JSON both sides rely on.
2278#[derive(Debug, Clone, PartialEq)]
2279pub enum Envelope {
2280 /// The `Ok` Envelope variant.
2281 Ok(Box<Response>),
2282 /// The `Err` Envelope variant.
2283 Err(IpcError),
2284}
2285impl Serialize for Envelope {
2286 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2287 use serde::ser::SerializeStruct;
2288 let mut state = serializer.serialize_struct("Envelope", 2)?;
2289 match self {
2290 Self::Ok(data) => {
2291 state.serialize_field("ok", &true)?;
2292 state.serialize_field("data", data)?;
2293 }
2294 Self::Err(error) => {
2295 state.serialize_field("ok", &false)?;
2296 state.serialize_field("error", error)?;
2297 }
2298 }
2299 state.end()
2300 }
2301}
2302
2303#[cfg(test)]
2304mod tests {
2305 use super::*;
2306
2307 /// One value of every variant. A new variant fails to compile here until
2308 /// it gets an example, which `method_names_match_the_wire` then checks.
2309 fn examples() -> Vec<Request> {
2310 vec![
2311 Request::Bootstrap,
2312 Request::ProjectNew(ProjectNewParams {
2313 path: "x.secs".into(),
2314 preset: "2.1".into(),
2315 sub_count: 1,
2316 sample: false,
2317 tutorial: false,
2318 }),
2319 Request::ProjectOpen(ProjectPathParams {
2320 path: "x.secs".into(),
2321 }),
2322 Request::ProjectSave(ProjectSaveParams::default()),
2323 Request::ProjectInfo,
2324 Request::SampleLoad(SampleLoadParams::default()),
2325 Request::Calibrate(CalibrateParams::default()),
2326 Request::RecommendXo,
2327 Request::Mimo(MimoParams::default()),
2328 Request::Results,
2329 Request::Export(ExportParams {
2330 kind: ExportKind::CamillaDsp,
2331 folder: ".".into(),
2332 mdat_scope: None,
2333 design_name: None,
2334 camilla_devices: None,
2335 }),
2336 Request::JobCancel(JobCancelParams { job: JobId(1) }),
2337 Request::Jobs,
2338 Request::MeasureMethods,
2339 Request::MeasureConfigure(MeasureConfigureParams::default()),
2340 Request::MeasureConfigGet,
2341 Request::MeasurementRecords,
2342 Request::MicCalibrationGet,
2343 Request::MicCalibrationSet(MicCalibrationSetParams {
2344 path: "mic.txt".into(),
2345 }),
2346 Request::MicCalibrationClear,
2347 Request::MicCalibrationAttach,
2348 Request::LevelsGet,
2349 Request::LevelsFillDefaults,
2350 Request::LevelStop,
2351 Request::MeasurePosition(MeasurePositionParams { position: 1 }),
2352 Request::PositionClear(PositionClearParams { position: 1 }),
2353 Request::Undo(UndoParams { token: "x".into() }),
2354 Request::RewLaunch,
2355 Request::MdatScan(MdatScanParams {
2356 path: "x.mdat".into(),
2357 }),
2358 Request::MdatImport(MdatImportParams {
2359 path: "x.mdat".into(),
2360 assignments: vec![],
2361 position: 1,
2362 }),
2363 Request::ConfigGet,
2364 Request::ConfigApplyPreset(ConfigPresetParams {
2365 preset: "2.1".into(),
2366 confirm: false,
2367 }),
2368 Request::ConfigSetSubCount(ConfigSubCountParams {
2369 count: 2,
2370 confirm: false,
2371 }),
2372 Request::ConfigSetSpeaker(ConfigSetSpeakerParams {
2373 index: 0,
2374 role: Some("L".into()),
2375 size: None,
2376 band_index: Some(0),
2377 out: Some(1),
2378 pol: Some("+".into()),
2379 }),
2380 Request::ConfigAddSpeaker,
2381 Request::ConfigRemoveSpeaker(ConfigIndexParams { index: 0 }),
2382 Request::ConfigRenumber,
2383 Request::ConfigValidate,
2384 Request::PatchSheetCsv(PathParams {
2385 path: "patch.csv".into(),
2386 }),
2387 Request::DspGet,
2388 Request::DspSet(DspSetParams {
2389 preset: Some("tesira".into()),
2390 fields: None,
2391 }),
2392 Request::LayoutGet,
2393 Request::LayoutSet(LayoutSetParams {
2394 count: Some(9),
2395 ..Default::default()
2396 }),
2397 Request::PositionEstimate,
2398 Request::TargetGet,
2399 Request::TargetSetPoints(TargetPointsParams {
2400 points: vec![(20.0, 0.0), (20_000.0, 0.0)],
2401 }),
2402 Request::TargetApplyPreset(TargetPresetParams {
2403 preset: "flat".into(),
2404 }),
2405 Request::TargetSave(PathParams {
2406 path: "target.json".into(),
2407 }),
2408 Request::TargetLoad(PathParams {
2409 path: "target.json".into(),
2410 }),
2411 Request::CalibrationSettingsGet,
2412 Request::CalibrationSettingsEdit(CalibrationSettingsEditParams {
2413 edit: crate::settings_edit::SettingEdit::MimoOn { on: true },
2414 }),
2415 Request::SubWarnings,
2416 Request::RewInfo,
2417 Request::RewDriverSet(RewDriverSetParams {
2418 driver: "ASIO".into(),
2419 }),
2420 Request::CalibrationSettingsSet(CalibrationSettingsParams {
2421 settings: CalibrationSettings::default(),
2422 }),
2423 Request::ModeSpec(ModeSpecParams {
2424 mode: Mode::Mixed,
2425 ultra_ms: None,
2426 mimo_fir: None,
2427 }),
2428 Request::AppSettingsGet,
2429 Request::AppSettingsSet(AppSettingsParams {
2430 settings: AppSettings::default(),
2431 }),
2432 Request::LocalApiStatus,
2433 Request::LocalApiTokenRegenerate,
2434 Request::ComputeStatus,
2435 Request::OpenGpuHelp,
2436 Request::MeasurementCurves(MeasurementCurvesParams::default()),
2437 Request::ResultCurves(ResultCurvesParams {
2438 tab: ResultTab::Spl,
2439 channel: None,
2440 group: None,
2441 xo_hz: None,
2442 corrected: None,
2443 }),
2444 Request::ResultLines,
2445 Request::SubTakeValues,
2446 Request::DialogOpenProject,
2447 Request::DialogSaveProject(DialogSuggestedParams {
2448 suggested: "x.secs".into(),
2449 }),
2450 Request::DialogPickMdat,
2451 Request::DialogPickFolder(DialogPickFolderParams { start: None }),
2452 Request::DialogSaveFile(DialogSaveFileParams {
2453 kind: SaveFileKind::PatchSheet,
2454 suggested: "x.csv".into(),
2455 }),
2456 Request::DialogOpenFile(DialogOpenFileParams {
2457 kind: OpenFileKind::Target,
2458 }),
2459 Request::OpenFolder(OpenFolderParams { path: ".".into() }),
2460 Request::LevelMonitorStart(LevelMonitorStartParams::default()),
2461 Request::LevelMonitorStop,
2462 Request::LevelPlay(LevelPlayParams {
2463 role: "L".into(),
2464 master_dbfs: -20.0,
2465 }),
2466 Request::StatusWatchStart,
2467 Request::StatusWatchStop,
2468 Request::ExportFolderGet,
2469 Request::ExportFolderSet(ExportFolderSetParams { folder: ".".into() }),
2470 ]
2471 }
2472 #[test]
2473 fn method_names_match_the_wire() {
2474 let examples = examples();
2475 assert_eq!(examples.len(), Request::METHODS.len());
2476 for request in examples {
2477 let wire = serde_json::to_value(&request).unwrap();
2478 assert_eq!(wire["method"], request.method());
2479 }
2480 }
2481 #[test]
2482 fn every_method_name_is_listed_and_parses() {
2483 for request in examples() {
2484 let wire = serde_json::to_value(&request).unwrap();
2485 let parsed: Request = serde_json::from_value(wire).unwrap();
2486 assert_eq!(parsed.method(), request.method());
2487 }
2488 }
2489 #[test]
2490 fn old_app_settings_without_local_api_use_the_disabled_default() {
2491 let settings: AppSettings = serde_json::from_str(
2492 r#"{"language":"ko","recent_projects":[],"last_window_size":null}"#,
2493 )
2494 .unwrap();
2495 assert_eq!(settings.local_api, LocalApiSettings::default());
2496 assert!(!settings.local_api.enabled);
2497 assert_eq!(settings.local_api.port, crate::local_api::DEFAULT_PORT);
2498 }
2499
2500 #[test]
2501 fn mdat_scope_is_optional_on_the_wire() {
2502 let old: ExportParams =
2503 serde_json::from_str(r#"{"kind":"before_after_mdat","folder":""}"#).unwrap();
2504 assert_eq!(old.mdat_scope, None);
2505 assert_eq!(old.mdat_scope.unwrap_or_default(), MdatScope::All);
2506 let wire = serde_json::to_value(&old).unwrap();
2507 assert!(wire.get("mdat_scope").is_none());
2508 let scoped = ExportParams {
2509 mdat_scope: Some(MdatScope::Measurements),
2510 design_name: None,
2511 camilla_devices: None,
2512 ..old
2513 };
2514 let wire = serde_json::to_value(&scoped).unwrap();
2515 assert_eq!(wire["mdat_scope"], "measurements");
2516 assert_eq!(
2517 serde_json::from_value::<ExportParams>(wire).unwrap(),
2518 scoped
2519 );
2520 }
2521 #[test]
2522 fn envelope_wire_shape() {
2523 let ok = Envelope::Ok(Box::new(Response::Bootstrap(BootstrapInfo {
2524 app_name: "SECS 2R".into(),
2525 version: "0.0.1".into(),
2526 prerelease: true,
2527 upstream: UpstreamBase {
2528 name: "SECS".into(),
2529 repository: "https://example.invalid/secs".into(),
2530 version: "Beta".into(),
2531 commit: "0".repeat(40),
2532 date: "2026-10-12".into(),
2533 },
2534 platform: Platform {
2535 os: "linux".into(),
2536 arch: "x86_64".into(),
2537 },
2538 })));
2539 let value = serde_json::to_value(ok).unwrap();
2540 assert_eq!(value["ok"], true);
2541 assert_eq!(value["data"]["method"], "bootstrap");
2542 let value = serde_json::to_value(Envelope::Err(IpcError::new(
2543 ErrorCode::InvalidRequest,
2544 "bad",
2545 )))
2546 .unwrap();
2547 assert_eq!(value["ok"], false);
2548 assert_eq!(value["error"]["code"], "invalid_request");
2549 }
2550
2551 #[test]
2552 fn compute_completion_events_are_payload_free_markers() {
2553 for (result, kind) in [
2554 (JobResult::Calibration, "calibration"),
2555 (JobResult::Mimo, "mimo"),
2556 ] {
2557 let event = JobEvent::Finished {
2558 job: JobId(1),
2559 status: JobStatus::Succeeded,
2560 result: Some(Box::new(result)),
2561 error: None,
2562 };
2563 let wire = serde_json::to_vec(&event).unwrap();
2564 let value: serde_json::Value = serde_json::from_slice(&wire).unwrap();
2565 assert_eq!(value["result"], serde_json::json!({ "kind": kind }));
2566 assert!(wire.len() < 128, "completion must not carry a design");
2567 assert_eq!(serde_json::from_slice::<JobEvent>(&wire).unwrap(), event);
2568 }
2569 }
2570
2571 #[test]
2572 fn job_snapshot_is_metadata_without_a_result_payload() {
2573 let snapshot = JobSnapshot {
2574 job: JobId(42),
2575 kind: JobKind::Mimo,
2576 status: JobStatus::Succeeded,
2577 cancellable: false,
2578 progress: None,
2579 error: None,
2580 };
2581 let wire = serde_json::to_value(&snapshot).unwrap();
2582 assert!(wire.get("result").is_none());
2583 assert_eq!(
2584 serde_json::from_value::<JobSnapshot>(wire).unwrap(),
2585 snapshot
2586 );
2587 }
2588
2589 #[test]
2590 fn filter_export_scope_follows_upstream_but_eqapo_carries_mimo_f12() {
2591 use ExportKind::*;
2592 for kind in [
2593 CamillaDsp,
2594 StagedMdat,
2595 BeforeAfterMdat,
2596 EqualizerApo,
2597 Convolution,
2598 ] {
2599 for (subs, mimo) in [(false, false), (true, false), (false, true), (true, true)] {
2600 assert_eq!(filter_export_block(kind, subs, mimo), None, "{kind:?}");
2601 }
2602 }
2603 assert_eq!(filter_export_block(FilterText, false, false), None);
2604 assert_eq!(
2605 export_block(StagedMdat, false, false, true),
2606 Some(ExportBlock::StaleCache)
2607 );
2608 assert_eq!(
2609 export_block(BeforeAfterMdat, true, true, true),
2610 Some(ExportBlock::StaleCache)
2611 );
2612 assert_eq!(export_block(CamillaDsp, true, true, true), None);
2613 assert_eq!(
2614 filter_export_block(FilterText, true, false),
2615 Some(ExportBlock::Subs)
2616 );
2617 assert_eq!(
2618 filter_export_block(FilterText, true, true),
2619 Some(ExportBlock::Subs)
2620 );
2621 assert_eq!(
2622 filter_export_block(FilterText, false, true),
2623 Some(ExportBlock::Mimo)
2624 );
2625 assert_eq!(serde_json::to_value(FilterText).unwrap(), "filter_text");
2626 assert_eq!(serde_json::to_value(EqualizerApo).unwrap(), "equalizer_apo");
2627 assert_eq!(serde_json::to_value(Convolution).unwrap(), "convolution");
2628 assert_eq!(
2629 serde_json::to_value(ExportBlock::Subs).unwrap(),
2630 ExportBlock::Subs.name()
2631 );
2632 assert_eq!(
2633 serde_json::to_value(ExportBlock::Mimo).unwrap(),
2634 ExportBlock::Mimo.name()
2635 );
2636 assert_eq!(
2637 serde_json::to_value(ErrorCode::ExportScope).unwrap(),
2638 "export_scope"
2639 );
2640 }
2641}