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 #[serde(skip_serializing_if = "Option::is_none")]
182 pub variants: Option<Vec<String>>,
183}
184
185#[derive(Debug, Clone, Serialize, Deserialize)]
187pub struct ProcessorInfo {
188 pub id: String,
190}
191
192#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
194#[serde(rename_all = "lowercase")]
195pub enum ParameterType {
196 Float,
197 Bool,
198 Enum,
199}
200
201#[derive(Debug, Clone, Serialize, Deserialize)]
207pub struct ParameterChangedNotification {
208 pub id: String,
210 pub value: f32,
212}
213
214pub const METHOD_GET_PARAMETER: &str = "getParameter";
220pub const METHOD_SET_PARAMETER: &str = "setParameter";
222pub const METHOD_GET_ALL_PARAMETERS: &str = "getAllParameters";
224pub const METHOD_GET_METER_FRAME: &str = "getMeterFrame";
226pub const METHOD_GET_OSCILLOSCOPE_FRAME: &str = "getOscilloscopeFrame";
228pub const METHOD_GET_AUDIO_STATUS: &str = "getAudioStatus";
230pub const METHOD_REQUEST_RESIZE: &str = "requestResize";
232pub const METHOD_REGISTER_AUDIO: &str = "registerAudio";
234pub const NOTIFICATION_PARAMETER_CHANGED: &str = "parameterChanged";
236pub const NOTIFICATION_METER_UPDATE: &str = "meterUpdate";
238pub const NOTIFICATION_AUDIO_STATUS_CHANGED: &str = "audioStatusChanged";
240
241impl IpcRequest {
246 pub fn new(
248 id: RequestId,
249 method: impl Into<String>,
250 params: Option<serde_json::Value>,
251 ) -> Self {
252 Self {
253 jsonrpc: "2.0".to_string(),
254 id,
255 method: method.into(),
256 params,
257 }
258 }
259}
260
261impl IpcResponse {
262 pub fn success(id: RequestId, result: impl Serialize) -> Self {
264 Self {
265 jsonrpc: "2.0".to_string(),
266 id,
267 result: Some(serde_json::to_value(result).unwrap()),
268 error: None,
269 }
270 }
271
272 pub fn error(id: RequestId, error: IpcError) -> Self {
274 Self {
275 jsonrpc: "2.0".to_string(),
276 id,
277 result: None,
278 error: Some(error),
279 }
280 }
281}
282
283impl IpcNotification {
284 pub fn new(method: impl Into<String>, params: impl Serialize) -> Self {
286 Self {
287 jsonrpc: "2.0".to_string(),
288 method: method.into(),
289 params: Some(serde_json::to_value(params).unwrap()),
290 }
291 }
292}
293
294impl IpcError {
295 pub fn new(code: i32, message: impl Into<String>) -> Self {
297 Self {
298 code,
299 message: message.into(),
300 data: None,
301 }
302 }
303
304 pub fn with_data(code: i32, message: impl Into<String>, data: impl Serialize) -> Self {
306 Self {
307 code,
308 message: message.into(),
309 data: Some(serde_json::to_value(data).unwrap()),
310 }
311 }
312
313 pub fn parse_error() -> Self {
315 Self::new(ERROR_PARSE, "Parse error")
316 }
317
318 pub fn invalid_request(reason: impl Into<String>) -> Self {
320 Self::new(
321 ERROR_INVALID_REQUEST,
322 format!("Invalid request: {}", reason.into()),
323 )
324 }
325
326 pub fn method_not_found(method: impl AsRef<str>) -> Self {
328 Self::new(
329 ERROR_METHOD_NOT_FOUND,
330 format!("Method not found: {}", method.as_ref()),
331 )
332 }
333
334 pub fn invalid_params(reason: impl Into<String>) -> Self {
336 Self::new(
337 ERROR_INVALID_PARAMS,
338 format!("Invalid params: {}", reason.into()),
339 )
340 }
341
342 pub fn internal_error(reason: impl Into<String>) -> Self {
344 Self::new(ERROR_INTERNAL, format!("Internal error: {}", reason.into()))
345 }
346
347 pub fn param_not_found(id: impl AsRef<str>) -> Self {
349 Self::new(
350 ERROR_PARAM_NOT_FOUND,
351 format!("Parameter not found: {}", id.as_ref()),
352 )
353 }
354
355 pub fn param_out_of_range(id: impl AsRef<str>, value: f32) -> Self {
357 Self::new(
358 ERROR_PARAM_OUT_OF_RANGE,
359 format!("Parameter '{}' value {} out of range", id.as_ref(), value),
360 )
361 }
362}
363
364#[cfg(test)]
365mod tests {
366 use super::*;
367
368 #[test]
369 fn test_request_serialization() {
370 let req = IpcRequest::new(
371 RequestId::Number(1),
372 METHOD_GET_PARAMETER,
373 Some(serde_json::json!({"id": "gain"})),
374 );
375
376 let json = serde_json::to_string(&req).unwrap();
377 assert!(json.contains("\"jsonrpc\":\"2.0\""));
378 assert!(json.contains("\"method\":\"getParameter\""));
379 }
380
381 #[test]
382 fn test_response_serialization() {
383 let resp = IpcResponse::success(
384 RequestId::Number(1),
385 GetParameterResult {
386 id: "gain".to_string(),
387 value: 0.5,
388 },
389 );
390
391 let json = serde_json::to_string(&resp).unwrap();
392 assert!(json.contains("\"jsonrpc\":\"2.0\""));
393 assert!(json.contains("\"result\""));
394 assert!(!json.contains("\"error\""));
395 }
396
397 #[test]
398 fn test_error_response() {
399 let resp = IpcResponse::error(
400 RequestId::String("test".to_string()),
401 IpcError::method_not_found("unknownMethod"),
402 );
403
404 let json = serde_json::to_string(&resp).unwrap();
405 assert!(json.contains("\"error\""));
406 assert!(!json.contains("\"result\""));
407 }
408
409 #[test]
410 fn test_notification_serialization() {
411 let notif = IpcNotification::new(
412 NOTIFICATION_PARAMETER_CHANGED,
413 ParameterChangedNotification {
414 id: "gain".to_string(),
415 value: 0.8,
416 },
417 );
418
419 let json = serde_json::to_string(¬if).unwrap();
420 println!("Notification JSON: {}", json);
421 assert!(json.contains("\"jsonrpc\":\"2.0\""));
422 assert!(json.contains("\"method\":\"parameterChanged\""));
423 }
426
427 #[test]
428 fn test_register_audio_serialization() {
429 let req = IpcRequest::new(
430 RequestId::String("audio-1".to_string()),
431 METHOD_REGISTER_AUDIO,
432 Some(serde_json::json!({
433 "client_id": "dev-audio",
434 "sample_rate": 44100.0,
435 "buffer_size": 512
436 })),
437 );
438
439 let json = serde_json::to_string(&req).unwrap();
440 assert!(json.contains("\"method\":\"registerAudio\""));
441 assert!(json.contains("\"sample_rate\":44100"));
442 }
443
444 #[test]
445 fn test_meter_update_notification() {
446 let notif = IpcNotification::new(
447 NOTIFICATION_METER_UPDATE,
448 MeterUpdateNotification {
449 timestamp_us: 1000,
450 left_peak: 0.5,
451 left_rms: 0.3,
452 right_peak: 0.6,
453 right_rms: 0.4,
454 },
455 );
456
457 let json = serde_json::to_string(¬if).unwrap();
458 assert!(json.contains("\"method\":\"meterUpdate\""));
459 assert!(json.contains("\"left_peak\":0.5"));
460 }
461
462 #[test]
463 fn test_audio_status_serialization() {
464 let result = GetAudioStatusResult {
465 status: Some(AudioRuntimeStatus {
466 phase: AudioRuntimePhase::RunningFullDuplex,
467 diagnostic: None,
468 sample_rate: Some(44100.0),
469 buffer_size: Some(512),
470 updated_at_ms: 123,
471 }),
472 };
473
474 let json = serde_json::to_string(&result).expect("status result should serialize");
475 assert!(json.contains("\"phase\":\"runningFullDuplex\""));
476 assert!(json.contains("\"sample_rate\":44100"));
477 }
478
479 #[test]
480 fn test_oscilloscope_frame_serialization() {
481 let result = GetOscilloscopeFrameResult {
482 frame: Some(OscilloscopeFrame {
483 points_l: vec![0.0; 1024],
484 points_r: vec![0.0; 1024],
485 sample_rate: 44100.0,
486 timestamp: 7,
487 no_signal: true,
488 trigger_mode: OscilloscopeTriggerMode::RisingZeroCrossing,
489 }),
490 };
491
492 let json = serde_json::to_string(&result).expect("oscilloscope result should serialize");
493 assert!(json.contains("\"sample_rate\":44100"));
494 assert!(json.contains("\"trigger_mode\":\"risingZeroCrossing\""));
495 }
496
497 #[test]
498 fn parameter_info_with_variants_serializes_correctly() {
499 let info = ParameterInfo {
500 id: "osc_waveform".to_string(),
501 name: "Waveform".to_string(),
502 param_type: ParameterType::Enum,
503 value: 0.0,
504 default: 0.0,
505 min: 0.0,
506 max: 3.0,
507 unit: None,
508 group: None,
509 variants: Some(vec![
510 "Sine".to_string(),
511 "Square".to_string(),
512 "Saw".to_string(),
513 "Triangle".to_string(),
514 ]),
515 };
516
517 let json = serde_json::to_string(&info).expect("parameter info should serialize");
518 assert!(json.contains("\"variants\""));
519
520 let deserialized: ParameterInfo =
521 serde_json::from_str(&json).expect("parameter info should deserialize");
522 assert_eq!(
523 deserialized.variants.expect("variants should exist").len(),
524 4
525 );
526 }
527
528 #[test]
529 fn parameter_info_without_variants_omits_field() {
530 let info = ParameterInfo {
531 id: "gain".to_string(),
532 name: "Gain".to_string(),
533 param_type: ParameterType::Float,
534 value: 0.5,
535 default: 0.5,
536 min: 0.0,
537 max: 1.0,
538 unit: Some("dB".to_string()),
539 group: None,
540 variants: None,
541 };
542
543 let json = serde_json::to_string(&info).expect("parameter info should serialize");
544 assert!(!json.contains("\"variants\""));
545 }
546}
547
548#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
556pub struct MeterFrame {
557 pub peak_l: f32,
559 pub peak_r: f32,
561 pub rms_l: f32,
563 pub rms_r: f32,
565 pub timestamp: u64,
567}
568
569#[derive(Debug, Clone, Serialize, Deserialize)]
571pub struct GetMeterFrameResult {
572 pub frame: Option<MeterFrame>,
574}
575
576#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
582#[serde(rename_all = "camelCase")]
583pub enum OscilloscopeTriggerMode {
584 RisingZeroCrossing,
585}
586
587#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
589#[serde(rename_all = "camelCase")]
590pub enum OscilloscopeChannelView {
591 Overlay,
592 Left,
593 Right,
594}
595
596#[derive(Debug, Clone, Serialize, Deserialize)]
598pub struct OscilloscopeFrame {
599 pub points_l: Vec<f32>,
601 pub points_r: Vec<f32>,
603 pub sample_rate: f32,
605 pub timestamp: u64,
607 pub no_signal: bool,
609 pub trigger_mode: OscilloscopeTriggerMode,
611}
612
613#[derive(Debug, Clone, Serialize, Deserialize)]
615pub struct GetOscilloscopeFrameResult {
616 pub frame: Option<OscilloscopeFrame>,
618}
619
620#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
626#[serde(rename_all = "camelCase")]
627pub enum AudioRuntimePhase {
628 Disabled,
629 Initializing,
630 RunningFullDuplex,
631 RunningInputOnly,
632 Degraded,
633 Failed,
634}
635
636#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
638#[serde(rename_all = "camelCase")]
639pub enum AudioDiagnosticCode {
640 LoaderUnavailable,
641 VtableMissing,
642 ProcessorCreateFailed,
643 NoInputDevice,
644 InputPermissionDenied,
645 NoOutputDevice,
646 StreamStartFailed,
647 Unknown,
648}
649
650#[derive(Debug, Clone, Serialize, Deserialize)]
652pub struct AudioDiagnostic {
653 pub code: AudioDiagnosticCode,
655 pub message: String,
657 #[serde(skip_serializing_if = "Option::is_none")]
659 pub hint: Option<String>,
660}
661
662#[derive(Debug, Clone, Serialize, Deserialize)]
664pub struct AudioRuntimeStatus {
665 pub phase: AudioRuntimePhase,
667 #[serde(skip_serializing_if = "Option::is_none")]
669 pub diagnostic: Option<AudioDiagnostic>,
670 #[serde(skip_serializing_if = "Option::is_none")]
672 pub sample_rate: Option<f32>,
673 #[serde(skip_serializing_if = "Option::is_none")]
675 pub buffer_size: Option<u32>,
676 pub updated_at_ms: u64,
678}
679
680#[derive(Debug, Clone, Serialize, Deserialize)]
682pub struct GetAudioStatusResult {
683 pub status: Option<AudioRuntimeStatus>,
685}
686
687#[derive(Debug, Clone, Serialize, Deserialize)]
693pub struct RequestResizeParams {
694 pub width: u32,
696 pub height: u32,
698}
699
700#[derive(Debug, Clone, Serialize, Deserialize)]
702pub struct RequestResizeResult {
703 pub accepted: bool,
705}
706
707#[derive(Debug, Clone, Serialize, Deserialize)]
713pub struct RegisterAudioParams {
714 pub client_id: String,
716 pub sample_rate: f32,
718 pub buffer_size: u32,
720}
721
722#[derive(Debug, Clone, Serialize, Deserialize)]
724pub struct RegisterAudioResult {
725 pub status: String,
727}
728
729#[derive(Debug, Clone, Serialize, Deserialize)]
735pub struct MeterUpdateNotification {
736 pub timestamp_us: u64,
738 pub left_peak: f32,
740 pub left_rms: f32,
742 pub right_peak: f32,
744 pub right_rms: f32,
746}