Skip to main content

vibelang_http/
models.rs

1//! Request and response models for the HTTP API.
2//!
3//! These models match the TypeScript types expected by the VSCode extension.
4
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7
8// =============================================================================
9// Source Location (for navigation to code)
10// =============================================================================
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct SourceLocation {
14    pub file: Option<String>,
15    pub line: Option<u32>,
16    pub column: Option<u32>,
17}
18
19// =============================================================================
20// Transport
21// =============================================================================
22
23#[derive(Debug, Serialize)]
24pub struct TransportState {
25    pub bpm: f64,
26    pub time_signature: TimeSignature,
27    pub running: bool,
28    pub current_beat: f64,
29    pub quantization_beats: f64,
30    /// Loop length in beats from longest active sequence (null if no sequences).
31    #[serde(skip_serializing_if = "Option::is_none")]
32    pub loop_beats: Option<f64>,
33    /// Current beat position within the loop (current_beat % loop_beats).
34    #[serde(skip_serializing_if = "Option::is_none")]
35    pub loop_beat: Option<f64>,
36    /// Server timestamp when this state was captured (milliseconds since Unix epoch).
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub server_time_ms: Option<u64>,
39}
40
41#[derive(Debug, Serialize, Deserialize)]
42pub struct TimeSignature {
43    pub numerator: u8,
44    pub denominator: u8,
45}
46
47#[derive(Debug, Deserialize)]
48pub struct TransportUpdate {
49    pub bpm: Option<f64>,
50    pub time_signature: Option<TimeSignature>,
51    pub quantization_beats: Option<f64>,
52}
53
54#[derive(Debug, Deserialize)]
55pub struct SeekRequest {
56    pub beat: f64,
57}
58
59// =============================================================================
60// Groups
61// =============================================================================
62
63#[derive(Debug, Serialize)]
64pub struct Group {
65    pub name: String,
66    pub path: String,
67    #[serde(skip_serializing_if = "Option::is_none")]
68    pub parent_path: Option<String>,
69    pub children: Vec<String>,
70    pub node_id: i32,
71    pub audio_bus: i32,
72    #[serde(skip_serializing_if = "Option::is_none")]
73    pub link_synth_node_id: Option<i32>,
74    pub muted: bool,
75    pub soloed: bool,
76    pub params: HashMap<String, f32>,
77    #[serde(skip_serializing_if = "Option::is_none")]
78    pub synth_node_ids: Option<Vec<i32>>,
79    #[serde(skip_serializing_if = "Option::is_none")]
80    pub source_location: Option<SourceLocation>,
81}
82
83#[derive(Debug, Deserialize)]
84pub struct GroupCreate {
85    pub name: String,
86    #[serde(skip_serializing_if = "Option::is_none")]
87    pub parent_path: Option<String>,
88    #[serde(default)]
89    pub params: HashMap<String, f32>,
90}
91
92#[derive(Debug, Deserialize)]
93pub struct GroupUpdate {
94    #[serde(default)]
95    pub params: HashMap<String, f32>,
96}
97
98#[derive(Debug, Deserialize)]
99pub struct ParamSet {
100    pub value: f32,
101    pub fade_beats: Option<f64>,
102}
103
104// =============================================================================
105// Voices
106// =============================================================================
107
108#[derive(Debug, Serialize)]
109pub struct Voice {
110    pub name: String,
111    pub synth_name: String,
112    pub polyphony: u8,
113    pub gain: f32,
114    pub group_path: String,
115    #[serde(skip_serializing_if = "Option::is_none")]
116    pub group_name: Option<String>,
117    #[serde(skip_serializing_if = "Option::is_none")]
118    pub output_bus: Option<i32>,
119    pub muted: bool,
120    pub soloed: bool,
121    pub params: HashMap<String, f32>,
122    #[serde(skip_serializing_if = "Option::is_none")]
123    pub sfz_instrument: Option<String>,
124    #[serde(skip_serializing_if = "Option::is_none")]
125    pub vst_instrument: Option<String>,
126    #[serde(skip_serializing_if = "Option::is_none")]
127    pub active_notes: Option<Vec<u8>>,
128    #[serde(skip_serializing_if = "Option::is_none")]
129    pub sustained_notes: Option<Vec<u8>>,
130    pub running: bool,
131    #[serde(skip_serializing_if = "Option::is_none")]
132    pub running_node_id: Option<i32>,
133    #[serde(skip_serializing_if = "Option::is_none")]
134    pub source_location: Option<SourceLocation>,
135}
136
137#[derive(Debug, Deserialize)]
138pub struct VoiceCreate {
139    pub name: Option<String>,
140    #[serde(alias = "synthdef")]
141    pub synth_name: Option<String>,
142    pub polyphony: Option<u8>,
143    pub gain: Option<f32>,
144    #[serde(alias = "group_id")]
145    pub group_path: Option<String>,
146    #[serde(default)]
147    pub params: HashMap<String, f32>,
148    pub sample: Option<String>,
149    pub sfz: Option<String>,
150}
151
152#[derive(Debug, Deserialize)]
153pub struct VoiceUpdate {
154    pub synth_name: Option<String>,
155    pub polyphony: Option<u8>,
156    pub gain: Option<f32>,
157    #[serde(default)]
158    pub params: HashMap<String, f32>,
159}
160
161#[derive(Debug, Deserialize)]
162pub struct TriggerRequest {
163    #[serde(default)]
164    pub params: HashMap<String, f32>,
165}
166
167#[derive(Debug, Deserialize)]
168pub struct NoteOnRequest {
169    pub note: u8,
170    #[serde(default = "default_velocity")]
171    pub velocity: f32,
172}
173
174fn default_velocity() -> f32 {
175    0.8
176}
177
178#[derive(Debug, Deserialize)]
179pub struct NoteOffRequest {
180    pub note: u8,
181}
182
183// =============================================================================
184// Loop Status (for patterns and melodies)
185// =============================================================================
186
187#[derive(Debug, Clone, Serialize, Deserialize)]
188#[serde(rename_all = "snake_case")]
189pub enum LoopState {
190    Stopped,
191    Queued,
192    Playing,
193    QueuedStop,
194}
195
196#[derive(Debug, Clone, Serialize)]
197pub struct LoopStatus {
198    pub state: LoopState,
199    #[serde(skip_serializing_if = "Option::is_none")]
200    pub start_beat: Option<f64>,
201    #[serde(skip_serializing_if = "Option::is_none")]
202    pub stop_beat: Option<f64>,
203}
204
205// =============================================================================
206// Patterns
207// =============================================================================
208
209#[derive(Debug, Serialize)]
210pub struct Pattern {
211    pub name: String,
212    pub voice_name: String,
213    pub group_path: String,
214    pub loop_beats: f64,
215    pub events: Vec<PatternEvent>,
216    #[serde(skip_serializing_if = "Option::is_none")]
217    pub params: Option<HashMap<String, f32>>,
218    pub status: LoopStatus,
219    pub is_looping: bool,
220    #[serde(skip_serializing_if = "Option::is_none")]
221    pub source_location: Option<SourceLocation>,
222    /// Original step pattern string (e.g., "x..x..x.|x.x.x.x.") for visual editing.
223    #[serde(skip_serializing_if = "Option::is_none")]
224    pub step_pattern: Option<String>,
225}
226
227#[derive(Debug, Serialize, Deserialize)]
228pub struct PatternEvent {
229    pub beat: f64,
230    #[serde(default, skip_serializing_if = "Option::is_none")]
231    pub params: Option<HashMap<String, f32>>,
232}
233
234#[derive(Debug, Deserialize)]
235pub struct PatternUpdate {
236    pub events: Option<Vec<PatternEvent>>,
237    pub pattern_string: Option<String>,
238    pub loop_beats: Option<f64>,
239    #[serde(default)]
240    pub params: HashMap<String, f32>,
241}
242
243#[derive(Debug, Deserialize)]
244pub struct PatternCreate {
245    /// Pattern name/ID.
246    pub name: String,
247    /// Voice name to trigger.
248    pub voice_name: String,
249    /// Loop length in beats.
250    #[serde(default = "default_loop_beats")]
251    pub loop_beats: f64,
252    /// Pattern events.
253    #[serde(default)]
254    pub events: Vec<PatternEvent>,
255    /// Step pattern string (e.g., "x..x..x.").
256    pub pattern_string: Option<String>,
257    /// Default parameters for all events.
258    #[serde(default)]
259    pub params: HashMap<String, f32>,
260    /// Swing amount (0.0 to 1.0).
261    #[serde(default)]
262    pub swing: f32,
263}
264
265fn default_loop_beats() -> f64 {
266    4.0
267}
268
269#[derive(Debug, Deserialize)]
270pub struct StartRequest {
271    pub quantize_beats: Option<f64>,
272}
273
274#[derive(Debug, Deserialize)]
275pub struct StopRequest {
276    pub quantize_beats: Option<f64>,
277}
278
279// =============================================================================
280// Melodies
281// =============================================================================
282
283#[derive(Debug, Serialize)]
284pub struct Melody {
285    pub name: String,
286    pub voice_name: String,
287    pub group_path: String,
288    pub loop_beats: f64,
289    pub events: Vec<MelodyEvent>,
290    #[serde(skip_serializing_if = "Option::is_none")]
291    pub params: Option<HashMap<String, f32>>,
292    pub status: LoopStatus,
293    pub is_looping: bool,
294    #[serde(skip_serializing_if = "Option::is_none")]
295    pub source_location: Option<SourceLocation>,
296    /// Notes pattern strings for visual editing (one per lane).
297    #[serde(skip_serializing_if = "Option::is_none")]
298    pub notes_patterns: Option<Vec<String>>,
299}
300
301#[derive(Debug, Serialize, Deserialize)]
302pub struct MelodyEvent {
303    pub beat: f64,
304    pub note: String,
305    #[serde(skip_serializing_if = "Option::is_none")]
306    pub frequency: Option<f64>,
307    #[serde(skip_serializing_if = "Option::is_none")]
308    pub duration: Option<f64>,
309    #[serde(skip_serializing_if = "Option::is_none")]
310    pub velocity: Option<f32>,
311    #[serde(default, skip_serializing_if = "Option::is_none")]
312    pub params: Option<HashMap<String, f32>>,
313}
314
315#[derive(Debug, Deserialize)]
316pub struct MelodyCreate {
317    /// Melody name/ID.
318    pub name: String,
319    /// Voice name to trigger notes on.
320    pub voice_name: String,
321    /// Loop length in beats.
322    #[serde(default = "default_loop_beats")]
323    pub loop_beats: f64,
324    /// Melody events (notes with beat positions).
325    #[serde(default)]
326    pub events: Vec<MelodyEvent>,
327    /// Melody string notation (e.g., "C4 D4 E4").
328    pub melody_string: Option<String>,
329    /// Default parameters for all notes.
330    #[serde(default)]
331    pub params: HashMap<String, f32>,
332}
333
334#[derive(Debug, Deserialize)]
335pub struct MelodyUpdate {
336    pub events: Option<Vec<MelodyEvent>>,
337    pub melody_string: Option<String>,
338    pub lanes: Option<Vec<String>>,
339    pub loop_beats: Option<f64>,
340    #[serde(default)]
341    pub params: HashMap<String, f32>,
342}
343
344// =============================================================================
345// Sequences
346// =============================================================================
347
348#[derive(Debug, Serialize)]
349pub struct Sequence {
350    pub name: String,
351    pub loop_beats: f64,
352    pub clips: Vec<SequenceClip>,
353    #[serde(skip_serializing_if = "Option::is_none")]
354    pub play_once: Option<bool>,
355    #[serde(skip_serializing_if = "Option::is_none")]
356    pub active: Option<bool>,
357    #[serde(skip_serializing_if = "Option::is_none")]
358    pub source_location: Option<SourceLocation>,
359}
360
361#[derive(Debug, Serialize, Deserialize)]
362pub struct SequenceClip {
363    #[serde(rename = "type")]
364    pub clip_type: String,
365    pub name: String,
366    pub start_beat: f64,
367    #[serde(skip_serializing_if = "Option::is_none")]
368    pub end_beat: Option<f64>,
369    #[serde(skip_serializing_if = "Option::is_none")]
370    pub duration_beats: Option<f64>,
371    #[serde(skip_serializing_if = "Option::is_none")]
372    pub once: Option<bool>,
373}
374
375#[derive(Debug, Deserialize)]
376pub struct SequenceCreate {
377    /// Sequence name/ID.
378    pub name: String,
379    /// Loop length in beats.
380    #[serde(default = "default_sequence_length")]
381    pub loop_beats: f64,
382    /// Clips in the sequence (patterns, melodies, fades, nested sequences).
383    #[serde(default)]
384    pub clips: Vec<SequenceClip>,
385}
386
387fn default_sequence_length() -> f64 {
388    16.0
389}
390
391#[derive(Debug, Deserialize)]
392pub struct SequenceUpdate {
393    pub loop_beats: Option<f64>,
394    pub clips: Option<Vec<SequenceClip>>,
395}
396
397#[derive(Debug, Deserialize)]
398pub struct SequenceStartRequest {
399    #[serde(default)]
400    pub play_once: bool,
401}
402
403// =============================================================================
404// Effects
405// =============================================================================
406
407#[derive(Debug, Serialize)]
408pub struct Effect {
409    pub id: String,
410    pub synthdef_name: String,
411    pub group_path: String,
412    #[serde(skip_serializing_if = "Option::is_none")]
413    pub node_id: Option<i32>,
414    #[serde(skip_serializing_if = "Option::is_none")]
415    pub bus_in: Option<i32>,
416    #[serde(skip_serializing_if = "Option::is_none")]
417    pub bus_out: Option<i32>,
418    pub params: HashMap<String, f32>,
419    #[serde(skip_serializing_if = "Option::is_none")]
420    pub position: Option<i32>,
421    #[serde(skip_serializing_if = "Option::is_none")]
422    pub vst_plugin: Option<String>,
423    #[serde(skip_serializing_if = "Option::is_none")]
424    pub source_location: Option<SourceLocation>,
425}
426
427#[derive(Debug, Deserialize)]
428pub struct EffectCreate {
429    pub id: Option<String>,
430    pub synthdef_name: String,
431    pub group_path: String,
432    #[serde(default)]
433    pub params: HashMap<String, f32>,
434    pub position: Option<i32>,
435}
436
437#[derive(Debug, Deserialize)]
438pub struct EffectUpdate {
439    #[serde(default)]
440    pub params: HashMap<String, f32>,
441}
442
443// =============================================================================
444// Samples
445// =============================================================================
446
447#[derive(Debug, Serialize)]
448pub struct SampleSlice {
449    pub name: String,
450    pub start_frame: i64,
451    pub end_frame: i64,
452}
453
454#[derive(Debug, Serialize)]
455pub struct Sample {
456    pub id: String,
457    pub path: String,
458    pub buffer_id: i32,
459    pub num_channels: i32,
460    pub num_frames: i64,
461    pub sample_rate: f32,
462    pub synthdef_name: String,
463    #[serde(skip_serializing_if = "Option::is_none")]
464    pub slices: Option<Vec<SampleSlice>>,
465}
466
467#[derive(Debug, Deserialize)]
468pub struct SampleLoad {
469    /// Path to the audio file.
470    pub path: String,
471    /// Optional sample ID (auto-generated if not provided).
472    pub id: Option<String>,
473}
474
475// =============================================================================
476// SynthDefs
477// =============================================================================
478
479#[derive(Debug, Serialize)]
480pub struct SynthDefParam {
481    pub name: String,
482    pub default_value: f32,
483    #[serde(skip_serializing_if = "Option::is_none")]
484    pub min_value: Option<f32>,
485    #[serde(skip_serializing_if = "Option::is_none")]
486    pub max_value: Option<f32>,
487}
488
489#[derive(Debug, Clone, Serialize, Deserialize)]
490#[serde(rename_all = "snake_case")]
491pub enum SynthDefSource {
492    Builtin,
493    User,
494    Stdlib,
495}
496
497#[derive(Debug, Serialize)]
498pub struct SynthDefInfo {
499    pub name: String,
500    pub params: Vec<SynthDefParam>,
501    #[serde(skip_serializing_if = "Option::is_none")]
502    pub source: Option<SynthDefSource>,
503}
504
505// =============================================================================
506// Fades
507// =============================================================================
508
509#[derive(Debug, Clone, Serialize, Deserialize)]
510#[serde(rename_all = "snake_case")]
511pub enum FadeTargetType {
512    Group,
513    Voice,
514    Effect,
515}
516
517#[derive(Debug, Serialize)]
518pub struct ActiveFade {
519    pub id: String,
520    #[serde(skip_serializing_if = "Option::is_none")]
521    pub name: Option<String>,
522    pub target_type: FadeTargetType,
523    pub target_name: String,
524    pub param_name: String,
525    pub start_value: f32,
526    pub target_value: f32,
527    #[serde(skip_serializing_if = "Option::is_none")]
528    pub current_value: Option<f32>,
529    pub duration_beats: f64,
530    #[serde(skip_serializing_if = "Option::is_none")]
531    pub start_beat: Option<f64>,
532    pub progress: f64,
533}
534
535#[derive(Debug, Deserialize)]
536pub struct FadeCreate {
537    pub target_type: FadeTargetType,
538    pub target_name: String,
539    pub param_name: String,
540    pub start_value: Option<f32>,
541    pub target_value: f32,
542    pub duration_beats: f64,
543}
544
545// =============================================================================
546// Live State
547// =============================================================================
548
549#[derive(Debug, Serialize)]
550pub struct ActiveSynth {
551    pub node_id: i32,
552    pub synthdef_name: String,
553    #[serde(skip_serializing_if = "Option::is_none")]
554    pub voice_name: Option<String>,
555    #[serde(skip_serializing_if = "Option::is_none")]
556    pub group_path: Option<String>,
557    #[serde(skip_serializing_if = "Option::is_none")]
558    pub created_at_beat: Option<f64>,
559}
560
561#[derive(Debug, Serialize)]
562pub struct ActiveSequence {
563    pub name: String,
564    pub start_beat: f64,
565    pub current_position: f64,
566    pub loop_beats: f64,
567    #[serde(skip_serializing_if = "Option::is_none")]
568    pub iteration: Option<u32>,
569    #[serde(skip_serializing_if = "Option::is_none")]
570    pub play_once: Option<bool>,
571}
572
573#[derive(Debug, Serialize)]
574pub struct LiveState {
575    pub transport: TransportState,
576    pub active_synths: Vec<ActiveSynth>,
577    pub active_sequences: Vec<ActiveSequence>,
578    pub active_fades: Vec<ActiveFade>,
579    #[serde(skip_serializing_if = "Option::is_none")]
580    pub active_notes: Option<HashMap<String, Vec<u8>>>,
581    #[serde(skip_serializing_if = "Option::is_none")]
582    pub patterns_status: Option<HashMap<String, LoopStatus>>,
583    #[serde(skip_serializing_if = "Option::is_none")]
584    pub melodies_status: Option<HashMap<String, LoopStatus>>,
585}
586
587// =============================================================================
588// Metering
589// =============================================================================
590
591#[derive(Debug, Serialize)]
592pub struct MeterLevel {
593    pub peak_left: f32,
594    pub peak_right: f32,
595    pub rms_left: f32,
596    pub rms_right: f32,
597}
598
599/// MeterLevels is a map from group path to meter level.
600pub type MeterLevels = HashMap<String, MeterLevel>;
601
602// =============================================================================
603// Error Response
604// =============================================================================
605
606#[derive(Debug, Serialize)]
607pub struct ErrorResponse {
608    pub error: String,
609    pub message: String,
610}
611
612impl ErrorResponse {
613    pub fn new(error: &str, message: &str) -> Self {
614        Self {
615            error: error.to_string(),
616            message: message.to_string(),
617        }
618    }
619
620    pub fn not_found(message: &str) -> Self {
621        Self::new("not_found", message)
622    }
623
624    pub fn bad_request(message: &str) -> Self {
625        Self::new("bad_request", message)
626    }
627
628    pub fn internal(message: &str) -> Self {
629        Self::new("internal_error", message)
630    }
631}