1use serde::{Deserialize, Serialize};
20
21#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct IpcRequest {
24 pub jsonrpc: String,
26 pub id: RequestId,
28 pub method: String,
30 #[serde(skip_serializing_if = "Option::is_none")]
32 pub params: Option<serde_json::Value>,
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize)]
37pub struct IpcResponse {
38 pub jsonrpc: String,
40 pub id: RequestId,
42 #[serde(skip_serializing_if = "Option::is_none")]
44 pub result: Option<serde_json::Value>,
45 #[serde(skip_serializing_if = "Option::is_none")]
47 pub error: Option<IpcError>,
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct IpcNotification {
53 pub jsonrpc: String,
55 pub method: String,
57 #[serde(skip_serializing_if = "Option::is_none")]
59 pub params: Option<serde_json::Value>,
60}
61
62#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
64#[serde(untagged)]
65pub enum RequestId {
66 String(String),
67 Number(i64),
68}
69
70#[derive(Debug, Clone, Serialize, Deserialize)]
72pub struct IpcError {
73 pub code: i32,
75 pub message: String,
77 #[serde(skip_serializing_if = "Option::is_none")]
79 pub data: Option<serde_json::Value>,
80}
81
82pub const ERROR_PARSE: i32 = -32700;
88pub const ERROR_INVALID_REQUEST: i32 = -32600;
90pub const ERROR_METHOD_NOT_FOUND: i32 = -32601;
92pub const ERROR_INVALID_PARAMS: i32 = -32602;
94pub const ERROR_INTERNAL: i32 = -32603;
96
97pub const ERROR_PARAM_NOT_FOUND: i32 = -32000;
100pub const ERROR_PARAM_OUT_OF_RANGE: i32 = -32001;
102
103#[derive(Debug, Clone, Serialize, Deserialize)]
113pub struct GetParameterParams {
114 pub id: String,
116}
117
118#[derive(Debug, Clone, Serialize, Deserialize)]
120pub struct GetParameterResult {
121 pub id: String,
123 pub value: f32,
125}
126
127#[derive(Debug, Clone, Serialize, Deserialize)]
133pub struct SetParameterParams {
134 pub id: String,
136 pub value: f32,
138}
139
140#[derive(Debug, Clone, Serialize, Deserialize)]
142pub struct SetParameterResult {}
143
144#[derive(Debug, Clone, Serialize, Deserialize)]
150pub struct GetAllParametersResult {
151 pub parameters: Vec<ParameterInfo>,
153}
154
155#[derive(Debug, Clone, Serialize, Deserialize)]
157pub struct ParameterInfo {
158 pub id: String,
160 pub name: String,
162 #[serde(rename = "type")]
164 pub param_type: ParameterType,
165 pub value: f32,
167 pub default: f32,
169 pub min: f32,
171 pub max: f32,
173 #[serde(skip_serializing_if = "Option::is_none")]
175 pub unit: Option<String>,
176 #[serde(skip_serializing_if = "Option::is_none")]
178 pub group: Option<String>,
179}
180
181#[derive(Debug, Clone, Serialize, Deserialize)]
183pub struct ProcessorInfo {
184 pub id: String,
186}
187
188#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
190#[serde(rename_all = "lowercase")]
191pub enum ParameterType {
192 Float,
193 Bool,
194 Enum,
195}
196
197#[derive(Debug, Clone, Serialize, Deserialize)]
203pub struct ParameterChangedNotification {
204 pub id: String,
206 pub value: f32,
208}
209
210pub const METHOD_GET_PARAMETER: &str = "getParameter";
216pub const METHOD_SET_PARAMETER: &str = "setParameter";
218pub const METHOD_GET_ALL_PARAMETERS: &str = "getAllParameters";
220pub const METHOD_GET_METER_FRAME: &str = "getMeterFrame";
222pub const METHOD_GET_OSCILLOSCOPE_FRAME: &str = "getOscilloscopeFrame";
224pub const METHOD_GET_AUDIO_STATUS: &str = "getAudioStatus";
226pub const METHOD_REQUEST_RESIZE: &str = "requestResize";
228pub const METHOD_REGISTER_AUDIO: &str = "registerAudio";
230pub const NOTIFICATION_PARAMETER_CHANGED: &str = "parameterChanged";
232pub const NOTIFICATION_METER_UPDATE: &str = "meterUpdate";
234pub const NOTIFICATION_AUDIO_STATUS_CHANGED: &str = "audioStatusChanged";
236
237impl IpcRequest {
242 pub fn new(
244 id: RequestId,
245 method: impl Into<String>,
246 params: Option<serde_json::Value>,
247 ) -> Self {
248 Self {
249 jsonrpc: "2.0".to_string(),
250 id,
251 method: method.into(),
252 params,
253 }
254 }
255}
256
257impl IpcResponse {
258 pub fn success(id: RequestId, result: impl Serialize) -> Self {
260 Self {
261 jsonrpc: "2.0".to_string(),
262 id,
263 result: Some(serde_json::to_value(result).unwrap()),
264 error: None,
265 }
266 }
267
268 pub fn error(id: RequestId, error: IpcError) -> Self {
270 Self {
271 jsonrpc: "2.0".to_string(),
272 id,
273 result: None,
274 error: Some(error),
275 }
276 }
277}
278
279impl IpcNotification {
280 pub fn new(method: impl Into<String>, params: impl Serialize) -> Self {
282 Self {
283 jsonrpc: "2.0".to_string(),
284 method: method.into(),
285 params: Some(serde_json::to_value(params).unwrap()),
286 }
287 }
288}
289
290impl IpcError {
291 pub fn new(code: i32, message: impl Into<String>) -> Self {
293 Self {
294 code,
295 message: message.into(),
296 data: None,
297 }
298 }
299
300 pub fn with_data(code: i32, message: impl Into<String>, data: impl Serialize) -> Self {
302 Self {
303 code,
304 message: message.into(),
305 data: Some(serde_json::to_value(data).unwrap()),
306 }
307 }
308
309 pub fn parse_error() -> Self {
311 Self::new(ERROR_PARSE, "Parse error")
312 }
313
314 pub fn invalid_request(reason: impl Into<String>) -> Self {
316 Self::new(
317 ERROR_INVALID_REQUEST,
318 format!("Invalid request: {}", reason.into()),
319 )
320 }
321
322 pub fn method_not_found(method: impl AsRef<str>) -> Self {
324 Self::new(
325 ERROR_METHOD_NOT_FOUND,
326 format!("Method not found: {}", method.as_ref()),
327 )
328 }
329
330 pub fn invalid_params(reason: impl Into<String>) -> Self {
332 Self::new(
333 ERROR_INVALID_PARAMS,
334 format!("Invalid params: {}", reason.into()),
335 )
336 }
337
338 pub fn internal_error(reason: impl Into<String>) -> Self {
340 Self::new(ERROR_INTERNAL, format!("Internal error: {}", reason.into()))
341 }
342
343 pub fn param_not_found(id: impl AsRef<str>) -> Self {
345 Self::new(
346 ERROR_PARAM_NOT_FOUND,
347 format!("Parameter not found: {}", id.as_ref()),
348 )
349 }
350
351 pub fn param_out_of_range(id: impl AsRef<str>, value: f32) -> Self {
353 Self::new(
354 ERROR_PARAM_OUT_OF_RANGE,
355 format!("Parameter '{}' value {} out of range", id.as_ref(), value),
356 )
357 }
358}
359
360#[cfg(test)]
361mod tests {
362 use super::*;
363
364 #[test]
365 fn test_request_serialization() {
366 let req = IpcRequest::new(
367 RequestId::Number(1),
368 METHOD_GET_PARAMETER,
369 Some(serde_json::json!({"id": "gain"})),
370 );
371
372 let json = serde_json::to_string(&req).unwrap();
373 assert!(json.contains("\"jsonrpc\":\"2.0\""));
374 assert!(json.contains("\"method\":\"getParameter\""));
375 }
376
377 #[test]
378 fn test_response_serialization() {
379 let resp = IpcResponse::success(
380 RequestId::Number(1),
381 GetParameterResult {
382 id: "gain".to_string(),
383 value: 0.5,
384 },
385 );
386
387 let json = serde_json::to_string(&resp).unwrap();
388 assert!(json.contains("\"jsonrpc\":\"2.0\""));
389 assert!(json.contains("\"result\""));
390 assert!(!json.contains("\"error\""));
391 }
392
393 #[test]
394 fn test_error_response() {
395 let resp = IpcResponse::error(
396 RequestId::String("test".to_string()),
397 IpcError::method_not_found("unknownMethod"),
398 );
399
400 let json = serde_json::to_string(&resp).unwrap();
401 assert!(json.contains("\"error\""));
402 assert!(!json.contains("\"result\""));
403 }
404
405 #[test]
406 fn test_notification_serialization() {
407 let notif = IpcNotification::new(
408 NOTIFICATION_PARAMETER_CHANGED,
409 ParameterChangedNotification {
410 id: "gain".to_string(),
411 value: 0.8,
412 },
413 );
414
415 let json = serde_json::to_string(¬if).unwrap();
416 println!("Notification JSON: {}", json);
417 assert!(json.contains("\"jsonrpc\":\"2.0\""));
418 assert!(json.contains("\"method\":\"parameterChanged\""));
419 }
422
423 #[test]
424 fn test_register_audio_serialization() {
425 let req = IpcRequest::new(
426 RequestId::String("audio-1".to_string()),
427 METHOD_REGISTER_AUDIO,
428 Some(serde_json::json!({
429 "client_id": "dev-audio",
430 "sample_rate": 44100.0,
431 "buffer_size": 512
432 })),
433 );
434
435 let json = serde_json::to_string(&req).unwrap();
436 assert!(json.contains("\"method\":\"registerAudio\""));
437 assert!(json.contains("\"sample_rate\":44100"));
438 }
439
440 #[test]
441 fn test_meter_update_notification() {
442 let notif = IpcNotification::new(
443 NOTIFICATION_METER_UPDATE,
444 MeterUpdateNotification {
445 timestamp_us: 1000,
446 left_peak: 0.5,
447 left_rms: 0.3,
448 right_peak: 0.6,
449 right_rms: 0.4,
450 },
451 );
452
453 let json = serde_json::to_string(¬if).unwrap();
454 assert!(json.contains("\"method\":\"meterUpdate\""));
455 assert!(json.contains("\"left_peak\":0.5"));
456 }
457
458 #[test]
459 fn test_audio_status_serialization() {
460 let result = GetAudioStatusResult {
461 status: Some(AudioRuntimeStatus {
462 phase: AudioRuntimePhase::RunningFullDuplex,
463 diagnostic: None,
464 sample_rate: Some(44100.0),
465 buffer_size: Some(512),
466 updated_at_ms: 123,
467 }),
468 };
469
470 let json = serde_json::to_string(&result).expect("status result should serialize");
471 assert!(json.contains("\"phase\":\"runningFullDuplex\""));
472 assert!(json.contains("\"sample_rate\":44100"));
473 }
474
475 #[test]
476 fn test_oscilloscope_frame_serialization() {
477 let result = GetOscilloscopeFrameResult {
478 frame: Some(OscilloscopeFrame {
479 points_l: vec![0.0; 1024],
480 points_r: vec![0.0; 1024],
481 sample_rate: 44100.0,
482 timestamp: 7,
483 no_signal: true,
484 trigger_mode: OscilloscopeTriggerMode::RisingZeroCrossing,
485 }),
486 };
487
488 let json = serde_json::to_string(&result).expect("oscilloscope result should serialize");
489 assert!(json.contains("\"sample_rate\":44100"));
490 assert!(json.contains("\"trigger_mode\":\"risingZeroCrossing\""));
491 }
492}
493
494#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
502pub struct MeterFrame {
503 pub peak_l: f32,
505 pub peak_r: f32,
507 pub rms_l: f32,
509 pub rms_r: f32,
511 pub timestamp: u64,
513}
514
515#[derive(Debug, Clone, Serialize, Deserialize)]
517pub struct GetMeterFrameResult {
518 pub frame: Option<MeterFrame>,
520}
521
522#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
528#[serde(rename_all = "camelCase")]
529pub enum OscilloscopeTriggerMode {
530 RisingZeroCrossing,
531}
532
533#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
535#[serde(rename_all = "camelCase")]
536pub enum OscilloscopeChannelView {
537 Overlay,
538 Left,
539 Right,
540}
541
542#[derive(Debug, Clone, Serialize, Deserialize)]
544pub struct OscilloscopeFrame {
545 pub points_l: Vec<f32>,
547 pub points_r: Vec<f32>,
549 pub sample_rate: f32,
551 pub timestamp: u64,
553 pub no_signal: bool,
555 pub trigger_mode: OscilloscopeTriggerMode,
557}
558
559#[derive(Debug, Clone, Serialize, Deserialize)]
561pub struct GetOscilloscopeFrameResult {
562 pub frame: Option<OscilloscopeFrame>,
564}
565
566#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
572#[serde(rename_all = "camelCase")]
573pub enum AudioRuntimePhase {
574 Disabled,
575 Initializing,
576 RunningFullDuplex,
577 RunningInputOnly,
578 Degraded,
579 Failed,
580}
581
582#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
584#[serde(rename_all = "camelCase")]
585pub enum AudioDiagnosticCode {
586 LoaderUnavailable,
587 VtableMissing,
588 ProcessorCreateFailed,
589 NoInputDevice,
590 InputPermissionDenied,
591 NoOutputDevice,
592 StreamStartFailed,
593 Unknown,
594}
595
596#[derive(Debug, Clone, Serialize, Deserialize)]
598pub struct AudioDiagnostic {
599 pub code: AudioDiagnosticCode,
601 pub message: String,
603 #[serde(skip_serializing_if = "Option::is_none")]
605 pub hint: Option<String>,
606}
607
608#[derive(Debug, Clone, Serialize, Deserialize)]
610pub struct AudioRuntimeStatus {
611 pub phase: AudioRuntimePhase,
613 #[serde(skip_serializing_if = "Option::is_none")]
615 pub diagnostic: Option<AudioDiagnostic>,
616 #[serde(skip_serializing_if = "Option::is_none")]
618 pub sample_rate: Option<f32>,
619 #[serde(skip_serializing_if = "Option::is_none")]
621 pub buffer_size: Option<u32>,
622 pub updated_at_ms: u64,
624}
625
626#[derive(Debug, Clone, Serialize, Deserialize)]
628pub struct GetAudioStatusResult {
629 pub status: Option<AudioRuntimeStatus>,
631}
632
633#[derive(Debug, Clone, Serialize, Deserialize)]
639pub struct RequestResizeParams {
640 pub width: u32,
642 pub height: u32,
644}
645
646#[derive(Debug, Clone, Serialize, Deserialize)]
648pub struct RequestResizeResult {
649 pub accepted: bool,
651}
652
653#[derive(Debug, Clone, Serialize, Deserialize)]
659pub struct RegisterAudioParams {
660 pub client_id: String,
662 pub sample_rate: f32,
664 pub buffer_size: u32,
666}
667
668#[derive(Debug, Clone, Serialize, Deserialize)]
670pub struct RegisterAudioResult {
671 pub status: String,
673}
674
675#[derive(Debug, Clone, Serialize, Deserialize)]
681pub struct MeterUpdateNotification {
682 pub timestamp_us: u64,
684 pub left_peak: f32,
686 pub left_rms: f32,
688 pub right_peak: f32,
690 pub right_rms: f32,
692}