1use sim_kernel::{Error, Result, Symbol};
2use sim_lib_audio_graph_core::Transport;
3use sim_lib_stream_clock::Clock;
4use sim_lib_stream_core::StreamMedia;
5use sim_lib_stream_host::HostDirection;
6
7#[derive(Clone, Copy, Debug, PartialEq, Eq)]
9pub struct JackTiming {
10 sample_rate_hz: u32,
11 block_frames: usize,
12 input_latency_frames: u32,
13 output_latency_frames: u32,
14}
15
16#[derive(Clone, Copy, Debug, PartialEq)]
18pub struct JackTransportState {
19 rolling: bool,
20 sample_pos: u64,
21 tempo_bpm: f64,
22 ppq_pos: f64,
23}
24
25#[derive(Clone, Debug, PartialEq, Eq)]
27pub struct JackClient {
28 id: Symbol,
29 name: String,
30 timing: JackTiming,
31 audio_inputs: usize,
32 audio_outputs: usize,
33 midi_inputs: usize,
34 midi_outputs: usize,
35}
36
37#[derive(Clone, Debug, PartialEq, Eq)]
39pub struct JackPort {
40 id: Symbol,
41 client: Symbol,
42 name: String,
43 media: StreamMedia,
44 direction: HostDirection,
45 index: usize,
46}
47
48impl JackTiming {
49 pub fn new(
55 sample_rate_hz: u32,
56 block_frames: usize,
57 input_latency_frames: u32,
58 output_latency_frames: u32,
59 ) -> Result<Self> {
60 if sample_rate_hz == 0 {
61 return Err(Error::Eval(
62 "JACK sample rate must be greater than zero".to_owned(),
63 ));
64 }
65 if block_frames == 0 {
66 return Err(Error::Eval(
67 "JACK block size must be greater than zero".to_owned(),
68 ));
69 }
70 Ok(Self {
71 sample_rate_hz,
72 block_frames,
73 input_latency_frames,
74 output_latency_frames,
75 })
76 }
77
78 pub fn pro_audio_default() -> Self {
81 Self::new(48_000, 128, 128, 128).expect("valid JACK timing")
82 }
83
84 pub fn sample_rate_hz(self) -> u32 {
86 self.sample_rate_hz
87 }
88
89 pub fn block_frames(self) -> usize {
91 self.block_frames
92 }
93
94 pub fn input_latency_frames(self) -> u32 {
96 self.input_latency_frames
97 }
98
99 pub fn output_latency_frames(self) -> u32 {
101 self.output_latency_frames
102 }
103
104 pub fn frame_clock(self) -> Result<Clock> {
111 Clock::frame(jack_clock_symbol(), u64::from(self.sample_rate_hz))
112 }
113}
114
115impl JackTransportState {
116 pub fn stopped(sample_pos: u64) -> Self {
119 Self {
120 rolling: false,
121 sample_pos,
122 tempo_bpm: 120.0,
123 ppq_pos: 0.0,
124 }
125 }
126
127 pub fn rolling(sample_pos: u64, tempo_bpm: f64, ppq_pos: f64) -> Result<Self> {
135 if !tempo_bpm.is_finite() || tempo_bpm <= 0.0 {
136 return Err(Error::Eval(
137 "JACK transport tempo must be finite and positive".to_owned(),
138 ));
139 }
140 if !ppq_pos.is_finite() {
141 return Err(Error::Eval(
142 "JACK transport PPQ position must be finite".to_owned(),
143 ));
144 }
145 Ok(Self {
146 rolling: true,
147 sample_pos,
148 tempo_bpm,
149 ppq_pos,
150 })
151 }
152
153 pub fn rolling_flag(self) -> bool {
155 self.rolling
156 }
157
158 pub fn sample_pos(self) -> u64 {
160 self.sample_pos
161 }
162
163 pub fn tempo_bpm(self) -> f64 {
165 self.tempo_bpm
166 }
167
168 pub fn ppq_pos(self) -> f64 {
170 self.ppq_pos
171 }
172
173 pub fn to_graph_transport(self) -> Transport {
176 Transport {
177 playing: self.rolling,
178 sample_pos: self.sample_pos,
179 tempo_bpm: self.tempo_bpm,
180 ppq_pos: self.ppq_pos,
181 }
182 }
183}
184
185impl JackClient {
186 pub fn new(
195 name: impl Into<String>,
196 timing: JackTiming,
197 audio_inputs: usize,
198 audio_outputs: usize,
199 midi_inputs: usize,
200 midi_outputs: usize,
201 ) -> Result<Self> {
202 let name = name.into();
203 if name.is_empty() {
204 return Err(Error::Eval("JACK client name must not be empty".to_owned()));
205 }
206 if audio_inputs == 0 && audio_outputs == 0 {
207 return Err(Error::Eval(
208 "JACK client must register at least one audio port".to_owned(),
209 ));
210 }
211 Ok(Self {
212 id: Symbol::new(format!("jack/{name}/client")),
213 name,
214 timing,
215 audio_inputs,
216 audio_outputs,
217 midi_inputs,
218 midi_outputs,
219 })
220 }
221
222 pub fn sim_default() -> Result<Self> {
230 Self::new("SIM", JackTiming::pro_audio_default(), 2, 2, 1, 1)
231 }
232
233 pub fn id(&self) -> &Symbol {
235 &self.id
236 }
237
238 pub fn name(&self) -> &str {
240 &self.name
241 }
242
243 pub fn timing(&self) -> JackTiming {
245 self.timing
246 }
247
248 pub fn audio_inputs(&self) -> usize {
250 self.audio_inputs
251 }
252
253 pub fn audio_outputs(&self) -> usize {
255 self.audio_outputs
256 }
257
258 pub fn midi_inputs(&self) -> usize {
260 self.midi_inputs
261 }
262
263 pub fn midi_outputs(&self) -> usize {
265 self.midi_outputs
266 }
267
268 pub fn direction(&self) -> HostDirection {
273 match (self.audio_inputs > 0, self.audio_outputs > 0) {
274 (true, true) => HostDirection::Duplex,
275 (true, false) => HostDirection::Input,
276 (false, true) => HostDirection::Output,
277 (false, false) => HostDirection::Duplex,
278 }
279 }
280
281 pub fn is_compatible_with(&self, requested: HostDirection) -> bool {
287 self.direction() == requested || self.direction() == HostDirection::Duplex
288 }
289
290 pub fn ports(&self) -> Vec<JackPort> {
293 let mut ports = Vec::new();
294 ports.extend(self.audio_ports(HostDirection::Input, self.audio_inputs, "audio_in"));
295 ports.extend(self.audio_ports(HostDirection::Output, self.audio_outputs, "audio_out"));
296 ports.extend(self.midi_ports(HostDirection::Input, self.midi_inputs, "midi_in"));
297 ports.extend(self.midi_ports(HostDirection::Output, self.midi_outputs, "midi_out"));
298 ports
299 }
300
301 fn audio_ports(&self, direction: HostDirection, count: usize, stem: &str) -> Vec<JackPort> {
302 self.numbered_ports(StreamMedia::Pcm, direction, count, stem)
303 }
304
305 fn midi_ports(&self, direction: HostDirection, count: usize, stem: &str) -> Vec<JackPort> {
306 self.numbered_ports(StreamMedia::Midi, direction, count, stem)
307 }
308
309 fn numbered_ports(
310 &self,
311 media: StreamMedia,
312 direction: HostDirection,
313 count: usize,
314 stem: &str,
315 ) -> Vec<JackPort> {
316 (0..count)
317 .map(|index| {
318 let name = format!("{stem}_{index}");
319 JackPort::new(
320 Symbol::new(format!("jack/{}/{}", self.name, name)),
321 self.id.clone(),
322 name,
323 media,
324 direction,
325 index,
326 )
327 })
328 .collect()
329 }
330}
331
332impl JackPort {
333 pub fn new(
336 id: Symbol,
337 client: Symbol,
338 name: impl Into<String>,
339 media: StreamMedia,
340 direction: HostDirection,
341 index: usize,
342 ) -> Self {
343 Self {
344 id,
345 client,
346 name: name.into(),
347 media,
348 direction,
349 index,
350 }
351 }
352
353 pub fn id(&self) -> &Symbol {
355 &self.id
356 }
357
358 pub fn client(&self) -> &Symbol {
360 &self.client
361 }
362
363 pub fn name(&self) -> &str {
365 &self.name
366 }
367
368 pub fn media(&self) -> StreamMedia {
370 self.media
371 }
372
373 pub fn direction(&self) -> HostDirection {
375 self.direction
376 }
377
378 pub fn index(&self) -> usize {
380 self.index
381 }
382}
383
384pub fn jack_clock_symbol() -> Symbol {
386 Symbol::qualified("clock", "jack")
387}