use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
use ts_rs::TS;
use crate::calibration::{CalibrationSettings, Mode};
use crate::compute::{ComputePreference, ComputeReport, ComputeStatus};
pub use crate::settings_edit::CalibrationSettingsEditParams;
#[derive(
Debug, Clone, PartialEq, Serialize, Deserialize, TS, strum::VariantNames, strum::IntoStaticStr,
)]
#[serde(tag = "method", content = "params", rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
#[ts(export)]
pub enum Request {
Bootstrap,
ProjectNew(ProjectNewParams),
ProjectOpen(ProjectPathParams),
ProjectSave(ProjectSaveParams),
ProjectInfo,
SampleLoad(SampleLoadParams),
Calibrate(CalibrateParams),
RecommendXo,
Mimo(MimoParams),
Results,
Export(ExportParams),
JobCancel(JobCancelParams),
Jobs,
MeasureMethods,
MeasureConfigure(MeasureConfigureParams),
MeasureConfigGet,
MeasurementRecords,
MicCalibrationGet,
MicCalibrationSet(MicCalibrationSetParams),
MicCalibrationClear,
MicCalibrationAttach,
LevelsGet,
LevelsFillDefaults,
LevelStop,
MeasurePosition(MeasurePositionParams),
PositionClear(PositionClearParams),
Undo(UndoParams),
RewLaunch,
MdatScan(MdatScanParams),
MdatImport(MdatImportParams),
ConfigGet,
ConfigApplyPreset(ConfigPresetParams),
ConfigSetSubCount(ConfigSubCountParams),
ConfigSetSpeaker(ConfigSetSpeakerParams),
ConfigAddSpeaker,
ConfigRemoveSpeaker(ConfigIndexParams),
ConfigRenumber,
ConfigValidate,
PatchSheetCsv(PathParams),
DspGet,
DspSet(DspSetParams),
LayoutGet,
LayoutSet(LayoutSetParams),
PositionEstimate,
TargetGet,
TargetSetPoints(TargetPointsParams),
TargetApplyPreset(TargetPresetParams),
TargetSave(PathParams),
TargetLoad(PathParams),
CalibrationSettingsGet,
CalibrationSettingsSet(CalibrationSettingsParams),
CalibrationSettingsEdit(CalibrationSettingsEditParams),
SubWarnings,
RewInfo,
RewDriverSet(RewDriverSetParams),
ModeSpec(ModeSpecParams),
AppSettingsGet,
AppSettingsSet(AppSettingsParams),
ApplyStatus,
ApplyCamilla(ApplyCamillaParams),
ApplyEqapo,
RevertEqapo,
ApplySettingsSet(ApplySettings),
HostInstall,
HostUninstall,
HostStopManaging,
UpdateStatus,
UpdateCheck,
UpdateDownload,
UpdateRestart,
UpdateNoticeDismiss,
UpdateOpenRelease(UpdateOpenReleaseParams),
LocalApiStatus,
LocalApiTokenRegenerate,
ComputeStatus,
OpenGpuHelp,
MeasurementCurves(MeasurementCurvesParams),
ResultCurves(ResultCurvesParams),
ResultLines,
SubTakeValues,
DialogOpenProject,
DialogSaveProject(DialogSuggestedParams),
DialogPickMdat,
DialogPickFolder(DialogPickFolderParams),
DialogSaveFile(DialogSaveFileParams),
DialogOpenFile(DialogOpenFileParams),
OpenFolder(OpenFolderParams),
LevelMonitorStart(LevelMonitorStartParams),
LevelMonitorStop,
LevelPlay(LevelPlayParams),
StatusWatchStart,
StatusWatchStop,
ExportFolderGet,
ExportFolderSet(ExportFolderSetParams),
}
impl Request {
pub const METHODS: &'static [&'static str] = <Self as strum::VariantNames>::VARIANTS;
pub fn method(&self) -> &'static str {
self.into()
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
#[serde(tag = "method", content = "data", rename_all = "snake_case")]
#[ts(export)]
pub enum Response {
Bootstrap(BootstrapInfo),
ProjectNew(ProjectInfo),
ProjectOpen(ProjectInfo),
ProjectSave(ProjectInfo),
ProjectInfo(ProjectInfo),
SampleLoad(JobStarted),
Calibrate(JobStarted),
RecommendXo(JobStarted),
Mimo(JobStarted),
Results(ResultsSummary),
Export(JobStarted),
JobCancel(JobSnapshot),
Jobs(Vec<JobSnapshot>),
MeasureMethods(MeasureMethodsResult),
MeasureConfigure(MeasureConfigResult),
MeasureConfigGet(MeasureConfigResult),
MeasurementRecords(MeasurementRecordsResult),
MicCalibrationGet(MicCalibrationStatus),
MicCalibrationSet(MicCalibrationStatus),
MicCalibrationClear(MicCalibrationStatus),
MicCalibrationAttach(MicCalibrationStatus),
LevelsGet(LevelsResult),
LevelsFillDefaults(LevelsResult),
LevelStop(()),
MeasurePosition(JobStarted),
PositionClear(UndoResult),
Undo(ProjectInfo),
RewLaunch(RewStatusResult),
MdatScan(MdatScanResult),
MdatImport(JobStarted),
ConfigGet(ConfigResult),
ConfigApplyPreset(ConfigMutationResult),
ConfigSetSubCount(ConfigMutationResult),
ConfigSetSpeaker(ConfigResult),
ConfigAddSpeaker(ConfigResult),
ConfigRemoveSpeaker(ConfigResult),
ConfigRenumber(ConfigResult),
ConfigValidate(Vec<Notice>),
PatchSheetCsv(PathResult),
DspGet(DspResult),
DspSet(DspResult),
LayoutGet(LayoutResult),
LayoutSet(LayoutMutationResult),
PositionEstimate(PositionEstimateResult),
TargetGet(TargetResult),
TargetSetPoints(TargetResult),
TargetApplyPreset(TargetResult),
TargetSave(PathResult),
TargetLoad(TargetResult),
CalibrationSettingsGet(CalibrationSettings),
CalibrationSettingsSet(CalibrationSettings),
CalibrationSettingsEdit(CalibrationSettings),
SubWarnings(Vec<Notice>),
RewInfo(RewInfoResult),
RewDriverSet(RewInfoResult),
ModeSpec(ModeSpecResult),
AppSettingsGet(AppSettings),
AppSettingsSet(AppSettings),
ApplyStatus(ApplyStatus),
ApplyCamilla(JobStarted),
ApplyEqapo(JobStarted),
RevertEqapo(()),
ApplySettingsSet(ApplySettings),
HostInstall(()),
HostUninstall(()),
HostStopManaging(()),
UpdateStatus(UpdateStatus),
UpdateCheck(UpdateStatus),
UpdateDownload(UpdateStatus),
UpdateRestart(()),
UpdateNoticeDismiss(UpdateStatus),
UpdateOpenRelease(()),
LocalApiStatus(LocalApiStatus),
LocalApiTokenRegenerate(LocalApiStatus),
ComputeStatus(ComputeStatus),
OpenGpuHelp(()),
MeasurementCurves(MeasurementCurvesResult),
ResultCurves(ResultCurvesResult),
ResultLines(ResultLinesResult),
SubTakeValues(CalibrationSettings),
DialogOpenProject(Option<String>),
DialogSaveProject(Option<String>),
DialogPickMdat(Option<String>),
DialogPickFolder(Option<String>),
DialogSaveFile(Option<String>),
DialogOpenFile(Option<String>),
OpenFolder(()),
LevelMonitorStart(JobStarted),
LevelMonitorStop(Option<JobSnapshot>),
LevelPlay(()),
StatusWatchStart(JobStarted),
StatusWatchStop(Option<JobSnapshot>),
ExportFolderGet(ExportFolderResult),
ExportFolderSet(ExportFolderResult),
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS, Default)]
#[ts(export)]
pub struct MeasurementCurvesParams {
#[ts(type = "number[] | null")]
pub positions: Option<Vec<i64>>,
pub roles: Option<Vec<String>>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct MeasurementCurve {
pub role: String,
#[ts(type = "number")]
pub position: i64,
pub kind: String,
pub values: Vec<f64>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct CurveAxes {
pub frequency_hz: [f64; 2],
pub value: [f64; 2],
pub unit: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)]
#[serde(rename_all = "snake_case")]
#[ts(export)]
pub enum CurveReferenceBand {
Mid,
Sub,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct CurveReference {
pub role: String,
#[ts(type = "number")]
pub position: i64,
pub band: CurveReferenceBand,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct MeasurementCurvesResult {
pub grid: Vec<f64>,
pub curves: Vec<MeasurementCurve>,
pub reference: Option<CurveReference>,
pub axes: CurveAxes,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)]
#[serde(rename_all = "snake_case")]
#[ts(export)]
pub enum ResultTab {
Spl,
Gd,
Xo,
Ba,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct ResultCurvesParams {
pub tab: ResultTab,
pub channel: Option<String>,
pub group: Option<String>,
pub xo_hz: Option<f64>,
pub corrected: Option<bool>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct ResultCurve {
pub kind: String,
pub role: Option<String>,
pub values: Vec<f64>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct ResultCurvesResult {
pub tab: ResultTab,
pub grid: Vec<f64>,
pub curves: Vec<ResultCurve>,
pub axes: CurveAxes,
pub stale: bool,
pub subs_only: bool,
pub rc_band_hz: Option<(f64, Option<f64>)>,
#[serde(default)]
pub rc_bands_hz: Vec<(f64, f64)>,
pub xo_hz: Option<f64>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct ChannelResultLine {
pub role: String,
pub within_1db_pct: [f64; 2],
pub gd_spread_ms: [f64; 2],
pub peq_count: usize,
pub fir_taps: Option<usize>,
pub xo_hz: Option<f64>,
pub mimo_support_count: Option<usize>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct SubResultRow {
pub role: String,
pub mode: String,
pub gain_db: f64,
pub delay_ms: f64,
pub polarity: i8,
pub allpass: Vec<(f64, f64)>,
pub high_pass: (f64, String),
pub low_pass: (f64, String),
pub peq: Vec<(f64, f64, f64)>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct SubLayoutLine {
pub bundle: String,
pub kind: String,
pub members: Vec<String>,
pub front: Vec<String>,
pub rear: Vec<String>,
pub rear_ms: f64,
pub auto_ms: Option<f64>,
pub trim_ms: f64,
pub peq: Vec<(f64, f64, f64)>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct SubResultLines {
pub stale: bool,
pub subs_only: bool,
pub band_hz: [f64; 2],
pub flatness_db: [f64; 2],
pub seat_spread_db: Option<[f64; 2]>,
pub positions: usize,
pub rows: Vec<SubResultRow>,
pub layouts: Vec<SubLayoutLine>,
pub warnings: Vec<Notice>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct ResultLinesResult {
pub stale: bool,
pub channels: Vec<ChannelResultLine>,
pub total_delay_ms: Option<f64>,
pub notes: Vec<Notice>,
pub rc: Vec<Notice>,
pub ba: Option<SubResultLines>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct BootstrapInfo {
pub app_name: String,
pub version: String,
pub prerelease: bool,
pub upstream: UpstreamBase,
pub platform: Platform,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct UpstreamBase {
pub name: String,
pub repository: String,
pub version: String,
pub commit: String,
pub date: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct Platform {
pub os: String,
pub arch: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct ProjectNewParams {
pub path: String,
pub preset: String,
pub sub_count: u8,
pub sample: bool,
#[serde(default)]
pub tutorial: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS, Default)]
#[ts(export)]
pub struct SampleLoadParams {
pub positions: Option<u32>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct ProjectPathParams {
pub path: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS, Default)]
#[ts(export)]
pub struct ProjectSaveParams {
pub path: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct ProjectInfo {
pub path: String,
pub name: String,
pub preset: String,
pub sub_count: usize,
pub positions: Vec<i64>,
pub dirty: bool,
pub tutorial: bool,
pub migration_warnings: Vec<MigrationWarning>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct MigrationWarning {
pub code: String,
pub entry: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS, Default)]
#[ts(export)]
pub struct CalibrateParams {
pub settings: Option<CalibrationSettings>,
#[serde(default)]
pub subs_only: bool,
#[serde(default)]
#[ts(as = "Option<bool>", optional)]
pub mimo: bool,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS, Default)]
#[ts(export)]
pub struct MimoParams {
pub settings: Option<CalibrationSettings>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, TS)]
#[serde(transparent)]
#[ts(export)]
pub struct JobId(#[ts(type = "number")] pub u64);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, TS)]
#[serde(rename_all = "snake_case")]
#[ts(export)]
pub enum JobKind {
Measure,
SampleLoad,
MdatImport,
Calibrate,
RecommendXo,
Mimo,
Export,
Apply,
LevelMonitor,
StatusWatch,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)]
#[serde(rename_all = "snake_case")]
#[ts(export)]
pub enum JobStatus {
Running,
CancelRequested,
Succeeded,
Failed,
Cancelled,
}
impl JobStatus {
pub fn is_terminal(self) -> bool {
matches!(self, Self::Succeeded | Self::Failed | Self::Cancelled)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct JobStarted {
pub job: JobId,
pub kind: JobKind,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct JobSnapshot {
pub job: JobId,
pub kind: JobKind,
pub status: JobStatus,
pub cancellable: bool,
pub progress: Option<JobProgress>,
pub error: Option<IpcError>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct JobProgress {
pub stage: String,
#[ts(type = "number")]
pub done: u64,
#[ts(type = "number")]
pub total: u64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
#[serde(tag = "event", rename_all = "snake_case")]
#[ts(export)]
pub enum JobEvent {
Started { job: JobId, kind: JobKind },
Progress {
job: JobId,
stage: String,
#[ts(type = "number")]
done: u64,
#[ts(type = "number")]
total: u64,
},
Log {
job: JobId,
code: String,
params: BTreeMap<String, String>,
},
Finished {
job: JobId,
status: JobStatus,
result: Option<Box<JobResult>>,
error: Option<IpcError>,
},
Meter {
job: JobId,
rms_dbfs: f64,
peak_dbfs: f64,
spl_db: Option<f64>,
clipping: bool,
},
MeasureStatus {
job: JobId,
status: MeasureMethodsResult,
},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
#[serde(tag = "kind", content = "data", rename_all = "snake_case")]
#[ts(export)]
pub enum JobResult {
Calibration,
Xo(XoRecommendation),
Mimo,
Export(ExportResult),
Apply(ApplyResult),
Measurement(ProjectInfo),
MdatImport(ProjectInfo),
Stopped,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct JobCancelParams {
pub job: JobId,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct ResultsSummary {
pub calibration: Option<CalibrationSummary>,
pub mimo: Option<MimoSummary>,
pub mimo_export: MimoExportDecision,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)]
#[serde(rename_all = "snake_case")]
#[ts(export)]
pub enum MimoExportDecision {
Included,
Off,
None,
Stale,
TooFewPositions,
NoSupports,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct Notice {
pub code: String,
pub params: BTreeMap<String, String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct CalibrationSummary {
#[serde(default)]
pub cached: bool,
pub stale: bool,
pub stale_reason: Option<String>,
pub mode: Mode,
pub ultra_ms: f64,
pub verdict: VerdictSummary,
pub scorecard: Vec<ChannelScore>,
#[serde(default)]
pub late_energy: Vec<LateEnergy>,
pub latency_ms: f64,
pub notes: Vec<Notice>,
pub main_roles: Vec<String>,
pub sub_roles: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct VerdictSummary {
pub within_1db_before_pct: f64,
pub within_1db_after_pct: f64,
pub low_band_time_spread_before_ms: f64,
pub low_band_time_spread_after_ms: f64,
pub added_delay_ms: f64,
pub position_spread_db: f64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct ChannelScore {
pub role: String,
pub before: MetricsSummary,
pub after: MetricsSummary,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct LateEnergy {
pub role: String,
pub position: u32,
pub before_db: f64,
pub after_db: f64,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct MetricsSummary {
pub level: f64,
pub in1_20: f64,
pub in2_20: f64,
pub in1_200: f64,
pub in2_200: f64,
pub in1_2000: f64,
pub in2_2000: f64,
pub turns: f64,
pub gd_20: f64,
pub gd_100: f64,
pub gd_500: f64,
pub gd24_abs: f64,
pub gd24_abs_hz: f64,
pub gd24_max: f64,
pub gd24_span: f64,
pub gd_30: f64,
pub pre_db: f64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct MimoSummary {
#[serde(default)]
pub compute: ComputeReport,
pub stale: bool,
pub kind: String,
pub fsiso: f64,
pub support_count: usize,
pub fir_taps_total: usize,
pub notes: Vec<Notice>,
pub input_count: usize,
#[serde(default)]
pub scorecard: Vec<MimoChannelScore>,
#[serde(default)]
pub late_energy: Vec<MimoLateEnergy>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct MimoChannelScore {
pub role: String,
pub after_mimo: MetricsSummary,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct MimoLateEnergy {
pub role: String,
pub position: u32,
pub after_mimo_db: f64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct XoRecommendation {
pub best: Vec<(String, f64)>,
pub candidates: Vec<f64>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)]
#[serde(rename_all = "snake_case")]
#[ts(export)]
pub enum ExportKind {
CamillaDsp,
StagedMdat,
BeforeAfterMdat,
FilterText,
EqualizerApo,
Convolution,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)]
#[serde(rename_all = "snake_case")]
#[ts(export)]
pub enum ExportBlock {
Subs,
Mimo,
StaleCache,
}
impl ExportBlock {
pub fn name(self) -> &'static str {
match self {
Self::Subs => "subs",
Self::Mimo => "mimo",
Self::StaleCache => "stale_cache",
}
}
}
pub fn filter_export_block(
kind: ExportKind,
has_subs: bool,
has_mimo: bool,
) -> Option<ExportBlock> {
match kind {
ExportKind::FilterText if has_subs => Some(ExportBlock::Subs),
ExportKind::FilterText if has_mimo => Some(ExportBlock::Mimo),
_ => None,
}
}
pub fn export_block(
kind: ExportKind,
has_subs: bool,
has_mimo: bool,
stale_cache: bool,
) -> Option<ExportBlock> {
match kind {
ExportKind::StagedMdat | ExportKind::BeforeAfterMdat if stale_cache => {
Some(ExportBlock::StaleCache)
}
_ => filter_export_block(kind, has_subs, has_mimo),
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, TS)]
#[serde(rename_all = "snake_case")]
#[ts(export)]
pub enum MdatScope {
#[default]
All,
Measurements,
Results,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub enum CamillaWasapiFormat {
#[serde(rename = "S32")]
S32,
#[serde(rename = "S24")]
S24,
#[serde(rename = "S16")]
S16,
#[serde(rename = "F32")]
F32,
}
impl CamillaWasapiFormat {
pub fn name(self) -> &'static str {
match self {
Self::S32 => "S32",
Self::S24 => "S24",
Self::S16 => "S16",
Self::F32 => "F32",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS, Default)]
#[serde(tag = "type", rename_all = "snake_case")]
#[ts(export)]
pub enum CamillaCaptureDevice {
#[default]
File,
Wasapi {
device: Option<String>,
format: CamillaWasapiFormat,
exclusive: bool,
loopback: bool,
},
Asio {
device: String,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS, Default)]
#[serde(tag = "type", rename_all = "snake_case")]
#[ts(export)]
pub enum CamillaPlaybackDevice {
#[default]
File,
Wasapi {
device: Option<String>,
format: CamillaWasapiFormat,
exclusive: bool,
},
Asio {
device: String,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS, Default)]
#[ts(export)]
pub struct CamillaDevices {
pub capture: CamillaCaptureDevice,
pub playback: CamillaPlaybackDevice,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct ExportParams {
pub kind: ExportKind,
pub folder: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub mdat_scope: Option<MdatScope>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub design_name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub camilla_devices: Option<CamillaDevices>,
#[serde(default)]
#[ts(optional = nullable)]
pub extra_rates: Vec<u32>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct ExportResult {
pub files: Vec<String>,
pub output_rate: u32,
#[serde(default)]
pub notes: Vec<Notice>,
pub folder: String,
pub stale: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS, Default)]
#[ts(export)]
pub struct DialogSuggestedParams {
pub suggested: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS, Default)]
#[ts(export)]
pub struct DialogPickFolderParams {
pub start: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)]
#[serde(rename_all = "snake_case")]
#[ts(export)]
pub enum SaveFileKind {
PatchSheet,
Target,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct DialogSaveFileParams {
pub kind: SaveFileKind,
pub suggested: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)]
#[serde(rename_all = "snake_case")]
#[ts(export)]
pub enum OpenFileKind {
Target,
MicCalibration,
CamillaExe,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct DialogOpenFileParams {
pub kind: OpenFileKind,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct OpenFolderParams {
pub path: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS, Default)]
#[ts(export)]
pub struct LevelMonitorStartParams {
pub role: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct LevelPlayParams {
pub role: String,
pub master_dbfs: f64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct ExportFolderSetParams {
pub folder: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct ExportFolderResult {
pub folder: String,
pub stored: bool,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct MicCalibrationFile {
pub name: String,
pub path: String,
pub serial: Option<String>,
pub sens_factor_db: Option<f64>,
pub analog_gain_db: Option<f64>,
pub sensitivity_dbfs: Option<f64>,
pub notes: Vec<String>,
pub points: u32,
pub min_hz: f64,
pub max_hz: f64,
pub has_phase: bool,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct MicCalibrationStatus {
pub file: Option<MicCalibrationFile>,
pub attached: u32,
pub attachable: u32,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct MicCalibrationSetParams {
pub path: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS, Default)]
#[ts(export)]
pub struct MeasureMethodsResult {
pub methods: Vec<String>,
pub native_devices: Vec<MeasureDevice>,
pub native_error: Option<IpcError>,
pub rew: RewStatusResult,
pub mock_positions: Vec<i64>,
pub mock_roles: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct MeasureDevice {
pub id: String,
pub name: String,
pub input_channels: usize,
pub output_channels: usize,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS, Default)]
#[ts(export)]
pub struct MeasureConfigureParams {
pub method: String,
pub input_device: Option<String>,
pub output_device: Option<String>,
pub input_channel: Option<u16>,
#[serde(default)]
pub output_channels: Vec<u16>,
pub sweep_length: Option<usize>,
pub repetitions: Option<u8>,
pub sweep_level_dbfs: Option<f64>,
pub timing: Option<String>,
pub loopback_input_channel: Option<u16>,
pub timing_ref_output: Option<u16>,
pub share_mode: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS, Default)]
#[ts(export)]
pub struct MeasureConfigResult {
pub method: String,
pub sweep_level_dbfs: Option<f64>,
pub input_device: String,
pub output_device: String,
pub input_channel: u16,
pub roles: Vec<String>,
pub output_channels: Vec<u16>,
pub sweep_length: usize,
pub repetitions: u8,
pub sample_rate: u32,
pub sweep_lengths: Vec<usize>,
pub timing: String,
pub loopback_input_channel: Option<u16>,
pub timing_ref_output: Option<u16>,
pub share_mode: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct PositionRecord {
#[ts(type = "number")]
pub position: i64,
pub done: bool,
pub roles: Vec<String>,
pub sources: BTreeMap<String, String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct MeasurementRecordsResult {
pub records: Vec<PositionRecord>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct LevelsResult {
pub levels: BTreeMap<String, f64>,
pub spl: BTreeMap<String, f64>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct MeasurePositionParams {
pub position: i64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct PositionClearParams {
pub position: i64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct UndoParams {
pub token: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct UndoResult {
pub token: String,
pub project: ProjectInfo,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS, Default)]
#[ts(export)]
pub struct RewStatusResult {
pub running: bool,
pub reachable: bool,
pub pro: Option<bool>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct RewDriverSetParams {
pub driver: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS, Default)]
#[ts(export)]
pub struct RewInfoResult {
pub executable_path: Option<String>,
pub running: bool,
pub reachable: bool,
pub version: Option<String>,
pub driver: Option<String>,
pub pro: Option<bool>,
pub input_device: Option<String>,
pub output_device: Option<String>,
pub input: Option<String>,
pub blocking: Option<bool>,
pub input_calibration_file: Option<String>,
pub input_devices: Option<Vec<RewDeviceInfo>>,
pub output_devices: Option<Vec<RewDeviceInfo>>,
pub input_channel_names: Option<Vec<String>>,
pub sample_rate: Option<f64>,
pub sweep: Option<RewSweepInfo>,
pub level_dbfs: Option<f64>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct RewDeviceInfo {
pub id: String,
pub name: String,
pub channels: usize,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct RewSweepInfo {
pub start_hz: Option<f64>,
pub end_hz: Option<f64>,
pub length: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct MdatScanParams {
pub path: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS, Default)]
#[ts(export)]
pub struct MdatScanResult {
pub measurements: Vec<MdatScanMeasurement>,
pub suggestions: Vec<MdatAssignment>,
pub problems: Vec<Notice>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct MdatScanMeasurement {
pub index: usize,
pub title: String,
pub role: Option<String>,
pub position: i64,
pub position_tagged: bool,
pub looks_like_sub: Option<bool>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct MdatAssignment {
pub measurement: String,
pub role: String,
pub index: Option<usize>,
pub position: Option<i64>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct MdatImportParams {
pub path: String,
pub assignments: Vec<MdatAssignment>,
pub position: i64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct PathParams {
pub path: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct PathResult {
pub path: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct ConfigPresetParams {
pub preset: String,
#[serde(default)]
pub confirm: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct ConfigSubCountParams {
pub count: i64,
#[serde(default)]
pub confirm: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct ConfigIndexParams {
pub index: usize,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct ConfigSetSpeakerParams {
pub index: usize,
pub role: Option<String>,
pub size: Option<String>,
pub band_index: Option<usize>,
pub out: Option<i64>,
pub pol: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct ConfigBand {
pub band: String,
pub out: i64,
pub pol: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct ConfigSpeaker {
pub role: String,
pub size: String,
pub bands: Vec<ConfigBand>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct ConfigGroup {
pub id: String,
pub key: String,
pub speaker_indices: Vec<usize>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct ConfigSummary {
pub speaker_count: usize,
pub outputs: Vec<i64>,
pub max_channel: i64,
pub duplicate_outputs: Vec<i64>,
pub sub_count: usize,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct ConfigResult {
pub speakers: Vec<ConfigSpeaker>,
pub preset: String,
pub device: String,
pub groups: Vec<ConfigGroup>,
pub summary: ConfigSummary,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct ConfigMutationResult {
pub config: ConfigResult,
pub undo_token: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)]
#[serde(rename_all = "snake_case")]
#[ts(export)]
pub enum OutputRateChoice {
Auto,
Fixed(u32),
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct DspFields {
pub output_rate: OutputRateChoice,
pub output_rate_editable: bool,
pub fir_taps: i64,
pub fir_budget: i64,
pub iir_bands: i64,
pub allpass_bands: i64,
pub bm_path_delays: i64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct DspPreset {
pub key: String,
pub name: String,
pub fields: DspFields,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS, Default)]
#[ts(export)]
pub struct DspSetParams {
pub preset: Option<String>,
pub fields: Option<DspFields>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct DspResult {
pub profile: String,
pub name: String,
pub fields: DspFields,
pub presets: Vec<DspPreset>,
pub window_ms: f64,
pub resolution_hz: f64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct LayoutTiming {
pub reference: String,
pub role: String,
pub output: i64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct LayoutPoint {
pub position: usize,
pub x_cm: f64,
pub y_cm: f64,
pub height: i8,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct LayoutArrangement {
pub count: usize,
pub points: Vec<LayoutPoint>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct LayoutResult {
pub count: usize,
pub start_delay: String,
pub timing: LayoutTiming,
pub arrangements: Vec<LayoutArrangement>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS, Default)]
#[ts(export)]
pub struct LayoutSetParams {
pub count: Option<usize>,
pub start_delay: Option<String>,
pub timing: Option<LayoutTiming>,
#[serde(default)]
pub confirm: bool,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct LayoutMutationResult {
pub layout: LayoutResult,
pub undo_token: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct PositionEstimateResult {
pub positions: usize,
pub per_position_seconds: f64,
pub total_seconds: f64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct TargetPointsParams {
pub points: Vec<(f64, f64)>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct TargetPresetParams {
pub preset: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct TargetResult {
pub points: Vec<(f64, f64)>,
pub preset: Option<String>,
pub presets: Vec<(String, Vec<(f64, f64)>)>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct CalibrationSettingsParams {
pub settings: CalibrationSettings,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct ModeSpecParams {
pub mode: Mode,
pub ultra_ms: Option<f64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub mimo_fir: Option<bool>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct ModeSpecResult {
pub rc_filter: String,
pub fir_taps: usize,
pub peq_bands: Option<usize>,
pub mimo_filter: String,
pub lift_ms: f64,
pub phase_frac: f64,
pub max_latency_ms: f64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS, Default)]
#[serde(rename_all = "snake_case")]
#[ts(export)]
pub enum AppTheme {
#[default]
System,
Light,
Dark,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS, Default)]
#[serde(rename_all = "snake_case")]
#[ts(export)]
pub enum AppSkin {
#[default]
Default,
Rack,
Matrix,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS, Default)]
#[serde(rename_all = "snake_case")]
#[ts(export)]
pub enum AppIcon {
#[default]
Expert,
Civilian,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct LocalApiSettings {
pub enabled: bool,
pub port: u16,
}
impl Default for LocalApiSettings {
fn default() -> Self {
Self {
enabled: false,
port: crate::local_api::DEFAULT_PORT,
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, TS)]
#[serde(rename_all = "snake_case")]
#[ts(export)]
pub enum ApplyLevel {
#[default]
Files,
Apply,
Manage,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct CamillaApply {
pub level: ApplyLevel,
pub address: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub exe_path: Option<String>,
}
impl Default for CamillaApply {
fn default() -> Self {
Self {
level: ApplyLevel::Files,
address: "127.0.0.1:1234".into(),
exe_path: None,
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct EqApoApply {
pub level: ApplyLevel,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct ApplySettings {
pub camilla: CamillaApply,
pub eqapo: EqApoApply,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct ApplyCamillaParams {
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub camilla_devices: Option<CamillaDevices>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)]
#[serde(rename_all = "snake_case")]
#[ts(export)]
pub enum EqApoVariant {
Official,
Xt,
Unknown,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct EqApoInstall {
pub config_path: String,
pub install_path: String,
pub variant: EqApoVariant,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)]
#[serde(rename_all = "snake_case")]
#[ts(export)]
pub enum IncludeState {
Absent,
Present,
Damaged,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct ManagedCamilla {
pub exe_path: String,
pub config_path: String,
pub port: u16,
pub design_id: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct ManagedEqApo {
pub config_path: String,
pub design_id: String,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct HostManaged {
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub camilla: Option<ManagedCamilla>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub eqapo: Option<ManagedEqApo>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(skip)]
pub owner_session_id: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(skip)]
pub owner_user_sid: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[serde(tag = "method", content = "params", rename_all = "snake_case")]
#[ts(export)]
pub enum HostRequest {
Status,
SetManaged(HostManaged),
StopManaging,
Log {
lines: u32,
},
UpdateSelf {
from_dir: String,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct HostLogLine {
pub timestamp: String,
pub event: String,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub fields: BTreeMap<String, String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[serde(tag = "kind", rename_all = "snake_case")]
#[ts(export)]
pub enum ManagedProcessState {
Running,
BackingOff { until: String },
GivenUp { reason: String },
Stopped,
NoSession,
OtherUser,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct ManagedProcess {
pub state: ManagedProcessState,
#[ts(type = "number | null")]
pub pid: Option<u32>,
pub restarts_in_window: u32,
}
impl Default for ManagedProcess {
fn default() -> Self {
Self {
state: ManagedProcessState::Stopped,
pid: None,
restarts_in_window: 0,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct HostStatus {
pub version: String,
pub managed: HostManaged,
pub camilla: ManagedProcess,
pub eqapo_include: IncludeState,
pub last_repair: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[serde(tag = "kind", content = "data", rename_all = "snake_case")]
#[ts(export)]
pub enum HostResponse {
Status(Box<HostStatus>),
Ok,
Log(Vec<HostLogLine>),
Error(IpcError),
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, TS)]
#[serde(rename_all = "snake_case")]
#[ts(export)]
pub enum ServiceState {
#[default]
NotInstalled,
Stopped,
Running,
Unreachable,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[serde(tag = "kind", rename_all = "snake_case")]
#[ts(export)]
pub enum DesignMatch {
This { design_id: String },
Other { title: String },
Unknown,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct CamillaStatus {
pub version: String,
pub state: String,
pub stop_reason: Option<String>,
pub title: Option<String>,
pub description: Option<String>,
pub config_path: Option<String>,
pub capture_rate: Option<u32>,
#[ts(type = "number | null")]
pub clipped_samples: Option<u64>,
pub processing_load_pct: Option<f64>,
pub playback_peak_db: Option<Vec<f64>>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct CamillaApplyStatus {
pub reachable: bool,
pub error: Option<IpcError>,
pub status: Option<CamillaStatus>,
pub design: DesignMatch,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct EqApoApplyStatus {
pub installed: bool,
pub variant: EqApoVariant,
pub config_path: Option<String>,
pub include: IncludeState,
pub applied_design_id: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct ApplyStatus {
pub camilla: CamillaApplyStatus,
pub eqapo: EqApoApplyStatus,
#[serde(default)]
pub host: ServiceState,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub host_status: Option<Box<HostStatus>>,
#[serde(default)]
pub current_design_id: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[serde(tag = "kind", rename_all = "snake_case")]
#[ts(export)]
pub enum ApplyOutcome {
Applied,
HandedOver,
RolledBack { error: IpcError },
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct ApplyResult {
pub outcome: ApplyOutcome,
pub design_id: String,
pub folder: String,
pub stale: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct AppSettings {
#[serde(default)]
pub compute: ComputePreference,
pub language: String,
#[serde(default)]
pub theme: AppTheme,
#[serde(default)]
pub icon: AppIcon,
#[serde(default)]
pub skin: AppSkin,
#[serde(default)]
pub local_api: LocalApiSettings,
#[serde(default)]
pub apply: ApplySettings,
#[serde(default)]
pub updates: UpdatePolicy,
pub recent_projects: Vec<String>,
pub last_window_size: Option<(u32, u32)>,
}
impl Default for AppSettings {
fn default() -> Self {
Self {
language: "ko".into(),
compute: ComputePreference::Auto,
theme: AppTheme::System,
icon: AppIcon::Expert,
skin: AppSkin::Default,
local_api: LocalApiSettings::default(),
apply: ApplySettings::default(),
updates: UpdatePolicy::Auto,
recent_projects: Vec::new(),
last_window_size: None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct AppSettingsParams {
pub settings: AppSettings,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, TS)]
#[serde(rename_all = "snake_case")]
#[ts(export)]
pub enum UpdatePolicy {
#[default]
Auto,
Notify,
Off,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)]
#[serde(rename_all = "snake_case")]
#[ts(export)]
pub enum UpdateInstall {
OnExit,
OnRequest,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[serde(tag = "kind", rename_all = "snake_case")]
#[ts(export)]
pub enum UpdatePhase {
Off,
Idle,
Checking,
UpToDate,
Available { version: String },
Downloading {
version: String,
#[ts(type = "number")]
received: u64,
#[ts(type = "number | null")]
total: Option<u64>,
},
Ready {
version: String,
install: UpdateInstall,
},
Installing { version: String },
Portable { version: String, url: String },
Failed { reason: String },
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct UpdateNotice {
pub from: String,
pub to: String,
pub notes: String,
pub skipped: bool,
pub release_url: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct UpdateOpenReleaseParams {
pub version: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct UpdateStatus {
pub current: String,
pub policy: UpdatePolicy,
pub phase: UpdatePhase,
pub checked_at: Option<String>,
pub notice: Option<UpdateNotice>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PendingUpdate {
pub from: String,
pub to: String,
pub notes: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub previous: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct LocalApiStatus {
pub enabled: bool,
pub port: u16,
pub running: bool,
pub error: Option<String>,
pub url: String,
pub token: String,
pub token_path: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)]
#[serde(rename_all = "snake_case")]
#[ts(export)]
pub enum ErrorCode {
InvalidRequest,
Internal,
NotImplemented,
UiOnly,
Unsupported,
PermissionDenied,
ApplyFailed,
ProjectNotOpen,
ProjectIo,
JobBusy,
JobNotFound,
Cancelled,
EngineInput,
ComputeUnavailable,
ExportFailed,
ExportScope,
StaleInputs,
DeviceUnavailable,
RecordingFailed,
ShareModeRefused,
RewUnreachable,
RewProRequired,
RewFailed,
MeasurementInput,
TutorialBlocked,
ConfirmRequired,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct IpcError {
pub code: ErrorCode,
pub params: BTreeMap<String, String>,
pub retryable: bool,
pub detail: String,
}
impl IpcError {
pub fn new(code: ErrorCode, detail: impl Into<String>) -> Self {
Self {
code,
params: BTreeMap::new(),
retryable: false,
detail: detail.into(),
}
}
pub fn with_param(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.params.insert(key.into(), value.into());
self
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Envelope {
Ok(Box<Response>),
Err(IpcError),
}
impl Serialize for Envelope {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
use serde::ser::SerializeStruct;
let mut state = serializer.serialize_struct("Envelope", 2)?;
match self {
Self::Ok(data) => {
state.serialize_field("ok", &true)?;
state.serialize_field("data", data)?;
}
Self::Err(error) => {
state.serialize_field("ok", &false)?;
state.serialize_field("error", error)?;
}
}
state.end()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn examples() -> Vec<Request> {
vec![
Request::Bootstrap,
Request::ProjectNew(ProjectNewParams {
path: "x.secs".into(),
preset: "2.1".into(),
sub_count: 1,
sample: false,
tutorial: false,
}),
Request::ProjectOpen(ProjectPathParams {
path: "x.secs".into(),
}),
Request::ProjectSave(ProjectSaveParams::default()),
Request::ProjectInfo,
Request::SampleLoad(SampleLoadParams::default()),
Request::Calibrate(CalibrateParams::default()),
Request::RecommendXo,
Request::Mimo(MimoParams::default()),
Request::Results,
Request::Export(ExportParams {
kind: ExportKind::CamillaDsp,
folder: ".".into(),
mdat_scope: None,
design_name: None,
camilla_devices: None,
extra_rates: Vec::new(),
}),
Request::JobCancel(JobCancelParams { job: JobId(1) }),
Request::Jobs,
Request::MeasureMethods,
Request::MeasureConfigure(MeasureConfigureParams::default()),
Request::MeasureConfigGet,
Request::MeasurementRecords,
Request::MicCalibrationGet,
Request::MicCalibrationSet(MicCalibrationSetParams {
path: "mic.txt".into(),
}),
Request::MicCalibrationClear,
Request::MicCalibrationAttach,
Request::LevelsGet,
Request::LevelsFillDefaults,
Request::LevelStop,
Request::MeasurePosition(MeasurePositionParams { position: 1 }),
Request::PositionClear(PositionClearParams { position: 1 }),
Request::Undo(UndoParams { token: "x".into() }),
Request::RewLaunch,
Request::MdatScan(MdatScanParams {
path: "x.mdat".into(),
}),
Request::MdatImport(MdatImportParams {
path: "x.mdat".into(),
assignments: vec![],
position: 1,
}),
Request::ConfigGet,
Request::ConfigApplyPreset(ConfigPresetParams {
preset: "2.1".into(),
confirm: false,
}),
Request::ConfigSetSubCount(ConfigSubCountParams {
count: 2,
confirm: false,
}),
Request::ConfigSetSpeaker(ConfigSetSpeakerParams {
index: 0,
role: Some("L".into()),
size: None,
band_index: Some(0),
out: Some(1),
pol: Some("+".into()),
}),
Request::ConfigAddSpeaker,
Request::ConfigRemoveSpeaker(ConfigIndexParams { index: 0 }),
Request::ConfigRenumber,
Request::ConfigValidate,
Request::PatchSheetCsv(PathParams {
path: "patch.csv".into(),
}),
Request::DspGet,
Request::DspSet(DspSetParams {
preset: Some("tesira".into()),
fields: None,
}),
Request::LayoutGet,
Request::LayoutSet(LayoutSetParams {
count: Some(9),
..Default::default()
}),
Request::PositionEstimate,
Request::TargetGet,
Request::TargetSetPoints(TargetPointsParams {
points: vec![(20.0, 0.0), (20_000.0, 0.0)],
}),
Request::TargetApplyPreset(TargetPresetParams {
preset: "flat".into(),
}),
Request::TargetSave(PathParams {
path: "target.json".into(),
}),
Request::TargetLoad(PathParams {
path: "target.json".into(),
}),
Request::CalibrationSettingsGet,
Request::CalibrationSettingsEdit(CalibrationSettingsEditParams {
edit: crate::settings_edit::SettingEdit::MimoOn { on: true },
}),
Request::SubWarnings,
Request::RewInfo,
Request::RewDriverSet(RewDriverSetParams {
driver: "ASIO".into(),
}),
Request::CalibrationSettingsSet(CalibrationSettingsParams {
settings: CalibrationSettings::default(),
}),
Request::ModeSpec(ModeSpecParams {
mode: Mode::Mixed,
ultra_ms: None,
mimo_fir: None,
}),
Request::AppSettingsGet,
Request::AppSettingsSet(AppSettingsParams {
settings: AppSettings::default(),
}),
Request::ApplyStatus,
Request::ApplyCamilla(ApplyCamillaParams::default()),
Request::ApplyEqapo,
Request::RevertEqapo,
Request::ApplySettingsSet(ApplySettings::default()),
Request::HostInstall,
Request::HostUninstall,
Request::HostStopManaging,
Request::UpdateStatus,
Request::UpdateCheck,
Request::UpdateDownload,
Request::UpdateRestart,
Request::UpdateNoticeDismiss,
Request::UpdateOpenRelease(UpdateOpenReleaseParams {
version: "0.36.0".into(),
}),
Request::LocalApiStatus,
Request::LocalApiTokenRegenerate,
Request::ComputeStatus,
Request::OpenGpuHelp,
Request::MeasurementCurves(MeasurementCurvesParams::default()),
Request::ResultCurves(ResultCurvesParams {
tab: ResultTab::Spl,
channel: None,
group: None,
xo_hz: None,
corrected: None,
}),
Request::ResultLines,
Request::SubTakeValues,
Request::DialogOpenProject,
Request::DialogSaveProject(DialogSuggestedParams {
suggested: "x.secs".into(),
}),
Request::DialogPickMdat,
Request::DialogPickFolder(DialogPickFolderParams { start: None }),
Request::DialogSaveFile(DialogSaveFileParams {
kind: SaveFileKind::PatchSheet,
suggested: "x.csv".into(),
}),
Request::DialogOpenFile(DialogOpenFileParams {
kind: OpenFileKind::Target,
}),
Request::OpenFolder(OpenFolderParams { path: ".".into() }),
Request::LevelMonitorStart(LevelMonitorStartParams::default()),
Request::LevelMonitorStop,
Request::LevelPlay(LevelPlayParams {
role: "L".into(),
master_dbfs: -20.0,
}),
Request::StatusWatchStart,
Request::StatusWatchStop,
Request::ExportFolderGet,
Request::ExportFolderSet(ExportFolderSetParams { folder: ".".into() }),
]
}
#[test]
fn method_names_match_the_wire() {
let examples = examples();
assert_eq!(examples.len(), Request::METHODS.len());
for request in examples {
let wire = serde_json::to_value(&request).unwrap();
assert_eq!(wire["method"], request.method());
}
}
#[test]
fn every_method_name_is_listed_and_parses() {
for request in examples() {
let wire = serde_json::to_value(&request).unwrap();
let parsed: Request = serde_json::from_value(wire).unwrap();
assert_eq!(parsed.method(), request.method());
}
}
#[test]
fn old_app_settings_without_local_api_use_the_disabled_default() {
let settings: AppSettings = serde_json::from_str(
r#"{"language":"ko","recent_projects":[],"last_window_size":null}"#,
)
.unwrap();
assert_eq!(settings.local_api, LocalApiSettings::default());
assert!(!settings.local_api.enabled);
assert_eq!(settings.local_api.port, crate::local_api::DEFAULT_PORT);
}
#[test]
fn calibrate_mimo_defaults_false_on_the_wire() {
let old: CalibrateParams =
serde_json::from_str(r#"{"settings":null,"subs_only":false}"#).unwrap();
assert!(!old.mimo);
assert_eq!(CalibrateParams::default(), old);
let wire = serde_json::to_value(CalibrateParams {
mimo: true,
..Default::default()
})
.unwrap();
assert_eq!(wire["mimo"], true);
}
#[test]
fn mdat_scope_is_optional_on_the_wire() {
let old: ExportParams =
serde_json::from_str(r#"{"kind":"before_after_mdat","folder":""}"#).unwrap();
assert_eq!(old.mdat_scope, None);
assert!(old.extra_rates.is_empty());
assert_eq!(old.mdat_scope.unwrap_or_default(), MdatScope::All);
let wire = serde_json::to_value(&old).unwrap();
assert!(wire.get("mdat_scope").is_none());
let scoped = ExportParams {
mdat_scope: Some(MdatScope::Measurements),
design_name: None,
camilla_devices: None,
..old
};
let wire = serde_json::to_value(&scoped).unwrap();
assert_eq!(wire["mdat_scope"], "measurements");
assert_eq!(
serde_json::from_value::<ExportParams>(wire).unwrap(),
scoped
);
}
#[test]
fn envelope_wire_shape() {
let ok = Envelope::Ok(Box::new(Response::Bootstrap(BootstrapInfo {
app_name: "SECS 2R".into(),
version: "0.0.1".into(),
prerelease: true,
upstream: UpstreamBase {
name: "SECS".into(),
repository: "https://example.invalid/secs".into(),
version: "Beta".into(),
commit: "0".repeat(40),
date: "2026-10-12".into(),
},
platform: Platform {
os: "linux".into(),
arch: "x86_64".into(),
},
})));
let value = serde_json::to_value(ok).unwrap();
assert_eq!(value["ok"], true);
assert_eq!(value["data"]["method"], "bootstrap");
let value = serde_json::to_value(Envelope::Err(IpcError::new(
ErrorCode::InvalidRequest,
"bad",
)))
.unwrap();
assert_eq!(value["ok"], false);
assert_eq!(value["error"]["code"], "invalid_request");
}
#[test]
fn compute_completion_events_are_payload_free_markers() {
for (result, kind) in [
(JobResult::Calibration, "calibration"),
(JobResult::Mimo, "mimo"),
] {
let event = JobEvent::Finished {
job: JobId(1),
status: JobStatus::Succeeded,
result: Some(Box::new(result)),
error: None,
};
let wire = serde_json::to_vec(&event).unwrap();
let value: serde_json::Value = serde_json::from_slice(&wire).unwrap();
assert_eq!(value["result"], serde_json::json!({ "kind": kind }));
assert!(wire.len() < 128, "completion must not carry a design");
assert_eq!(serde_json::from_slice::<JobEvent>(&wire).unwrap(), event);
}
}
#[test]
fn job_snapshot_is_metadata_without_a_result_payload() {
let snapshot = JobSnapshot {
job: JobId(42),
kind: JobKind::Mimo,
status: JobStatus::Succeeded,
cancellable: false,
progress: None,
error: None,
};
let wire = serde_json::to_value(&snapshot).unwrap();
assert!(wire.get("result").is_none());
assert_eq!(
serde_json::from_value::<JobSnapshot>(wire).unwrap(),
snapshot
);
}
#[test]
fn filter_export_scope_follows_upstream_but_eqapo_carries_mimo_f12() {
use ExportKind::*;
for kind in [
CamillaDsp,
StagedMdat,
BeforeAfterMdat,
EqualizerApo,
Convolution,
] {
for (subs, mimo) in [(false, false), (true, false), (false, true), (true, true)] {
assert_eq!(filter_export_block(kind, subs, mimo), None, "{kind:?}");
}
}
assert_eq!(filter_export_block(FilterText, false, false), None);
assert_eq!(
export_block(StagedMdat, false, false, true),
Some(ExportBlock::StaleCache)
);
assert_eq!(
export_block(BeforeAfterMdat, true, true, true),
Some(ExportBlock::StaleCache)
);
assert_eq!(export_block(CamillaDsp, true, true, true), None);
assert_eq!(
filter_export_block(FilterText, true, false),
Some(ExportBlock::Subs)
);
assert_eq!(
filter_export_block(FilterText, true, true),
Some(ExportBlock::Subs)
);
assert_eq!(
filter_export_block(FilterText, false, true),
Some(ExportBlock::Mimo)
);
assert_eq!(serde_json::to_value(FilterText).unwrap(), "filter_text");
assert_eq!(serde_json::to_value(EqualizerApo).unwrap(), "equalizer_apo");
assert_eq!(serde_json::to_value(Convolution).unwrap(), "convolution");
assert_eq!(
serde_json::to_value(ExportBlock::Subs).unwrap(),
ExportBlock::Subs.name()
);
assert_eq!(
serde_json::to_value(ExportBlock::Mimo).unwrap(),
ExportBlock::Mimo.name()
);
assert_eq!(
serde_json::to_value(ErrorCode::ExportScope).unwrap(),
"export_scope"
);
}
}