firewire_motu_protocols/
lib.rs

1// SPDX-License-Identifier: LGPL-3.0-or-later
2// Copyright (c) 2021 Takashi Sakamoto
3
4#![doc = include_str!("../README.md")]
5
6pub mod command_dsp;
7pub mod config_rom;
8pub mod register_dsp;
9pub mod version_1;
10pub mod version_2;
11pub mod version_3;
12
13use {
14    glib::{Error, FileError},
15    hinawa::{
16        prelude::{FwNodeExt, FwReqExtManual},
17        FwNode, FwReq, FwTcode,
18    },
19    std::{thread, time},
20};
21
22/// The trait to operate cacheable parameters at once.
23pub trait MotuWhollyCacheableParamsOperation<T> {
24    /// Cache whole parameters.
25    fn cache_wholly(
26        req: &mut FwReq,
27        node: &mut FwNode,
28        params: &mut T,
29        timeout_ms: u32,
30    ) -> Result<(), Error>;
31}
32
33/// The trait to operate updatable parameters at once.
34pub trait MotuWhollyUpdatableParamsOperation<T> {
35    /// Update whole parameters.
36    fn update_wholly(
37        req: &mut FwReq,
38        node: &mut FwNode,
39        params: &T,
40        timeout_ms: u32,
41    ) -> Result<(), Error>;
42}
43
44/// The trait to operate updatable parameters partially.
45pub trait MotuPartiallyUpdatableParamsOperation<T> {
46    /// Update the part of parameters.
47    fn update_partially(
48        req: &mut FwReq,
49        node: &mut FwNode,
50        params: &mut T,
51        updates: T,
52        timeout_ms: u32,
53    ) -> Result<(), Error>;
54}
55
56const BASE_OFFSET: u64 = 0xfffff0000000;
57const OFFSET_CLK: u32 = 0x0b14;
58const OFFSET_PORT: u32 = 0x0c04;
59const OFFSET_CLK_DISPLAY: u32 = 0x0c60;
60
61fn read_quad(req: &FwReq, node: &mut FwNode, offset: u32, timeout_ms: u32) -> Result<u32, Error> {
62    let mut frame = [0; 4];
63    req.transaction_sync(
64        node,
65        FwTcode::ReadQuadletRequest,
66        BASE_OFFSET + offset as u64,
67        4,
68        &mut frame,
69        timeout_ms,
70    )
71    .map(|_| u32::from_be_bytes(frame))
72}
73
74// AudioExpress sometimes transfers response subaction with non-standard rcode. This causes
75// Linux firewire subsystem to report 'unsolicited response' error. In the case, send error
76// is reported to userspace applications. As a workaround, the change of register is ensured
77// by following read transaction in failure of write transaction.
78fn write_quad(
79    req: &FwReq,
80    node: &mut FwNode,
81    offset: u32,
82    quad: u32,
83    timeout_ms: u32,
84) -> Result<(), Error> {
85    let mut frame = [0; 4];
86    frame.copy_from_slice(&quad.to_be_bytes());
87    req.transaction_sync(
88        node,
89        FwTcode::WriteQuadletRequest,
90        BASE_OFFSET + offset as u64,
91        4,
92        &mut frame,
93        timeout_ms,
94    )
95    .or_else(|err| {
96        // For prevention of RCODE_BUSY.
97        thread::sleep(time::Duration::from_millis(BUSY_DURATION));
98        req.transaction_sync(
99            node,
100            FwTcode::WriteQuadletRequest,
101            BASE_OFFSET + offset as u64,
102            4,
103            &mut frame,
104            timeout_ms,
105        )
106        .and_then(|_| {
107            if u32::from_be_bytes(frame) == quad {
108                Ok(())
109            } else {
110                Err(err)
111            }
112        })
113    })
114}
115
116fn serialize_flag<T: Copy + Eq>(
117    flag: &T,
118    quad: &mut u32,
119    mask: u32,
120    shift: usize,
121    flags: &[T],
122    vals: &[u8],
123    label: &str,
124) -> Result<(), Error> {
125    flags
126        .iter()
127        .zip(vals)
128        .find(|(f, _)| flag.eq(f))
129        .ok_or_else(|| {
130            let label = format!(
131                "Invalid argument for {}, 0x{:08x}, 0x{:08x}",
132                label, *quad, mask
133            );
134            Error::new(FileError::Io, &label)
135        })
136        .map(|(_, &val)| {
137            *quad &= !mask;
138            *quad |= (val as u32) << shift;
139        })
140}
141
142fn deserialize_flag<T: Copy + Eq>(
143    flag: &mut T,
144    quad: &u32,
145    mask: u32,
146    shift: usize,
147    flags: &[T],
148    vals: &[u8],
149    label: &str,
150) -> Result<(), Error> {
151    let val = ((*quad & mask) >> shift) as u8;
152    flags
153        .iter()
154        .zip(vals)
155        .find(|(_, v)| val.eq(v))
156        .ok_or_else(|| {
157            let label = format!(
158                "Invalid value for {}, 0x{:08x}, 0x{:08x}",
159                label, quad, mask
160            );
161            Error::new(FileError::Io, &label)
162        })
163        .map(|(&f, _)| *flag = f)
164}
165
166/// Nominal rate of sampling clock.
167#[derive(Debug, Copy, Clone, PartialEq, Eq)]
168pub enum ClkRate {
169    /// 44.1 kHx.
170    R44100,
171    /// 48.0 kHx.
172    R48000,
173    /// 88.2 kHx.
174    R88200,
175    /// 96.0 kHx.
176    R96000,
177    /// 176.4 kHx.
178    R176400,
179    /// 192.2 kHx.
180    R192000,
181}
182
183impl Default for ClkRate {
184    fn default() -> Self {
185        Self::R44100
186    }
187}
188
189const BUSY_DURATION: u64 = 150;
190const DISPLAY_CHARS: usize = 4 * 4;
191
192/// Parameters of clock name in LCD display.
193#[derive(Default, Debug, Clone, PartialEq, Eq)]
194pub struct ClockNameDisplayParameters(pub String);
195
196/// The trait for specification of LCD to display clock name.
197pub trait MotuClockNameDisplaySpecification {}
198
199impl<O> MotuWhollyUpdatableParamsOperation<ClockNameDisplayParameters> for O
200where
201    O: MotuClockNameDisplaySpecification,
202{
203    fn update_wholly(
204        req: &mut FwReq,
205        node: &mut FwNode,
206        params: &ClockNameDisplayParameters,
207        timeout_ms: u32,
208    ) -> Result<(), Error> {
209        let mut chars = [0x20; DISPLAY_CHARS];
210        chars
211            .iter_mut()
212            .zip(params.0.bytes())
213            .for_each(|(c, l)| *c = l);
214
215        (0..(DISPLAY_CHARS / 4)).try_for_each(|i| {
216            let mut frame = [0; 4];
217            frame.copy_from_slice(&chars[(i * 4)..(i * 4 + 4)]);
218            frame.reverse();
219            let quad = u32::from_ne_bytes(frame);
220            let offset = OFFSET_CLK_DISPLAY + 4 * i as u32;
221            write_quad(req, node, offset, quad, timeout_ms)
222        })
223    }
224}
225
226/// The trait for specification of port assignment.
227pub trait MotuPortAssignSpecification {
228    const ASSIGN_PORT_TARGETS: &'static [TargetPort];
229    const ASSIGN_PORT_VALS: &'static [u8];
230}
231
232const PORT_PHONE_LABEL: &str = "phone-assign";
233const PORT_PHONE_MASK: u32 = 0x0000000f;
234const PORT_PHONE_SHIFT: usize = 0;
235
236/// The parameters of phone assignments.
237#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
238pub struct PhoneAssignParameters(pub TargetPort);
239
240impl<O> MotuWhollyCacheableParamsOperation<PhoneAssignParameters> for O
241where
242    O: MotuPortAssignSpecification,
243{
244    fn cache_wholly(
245        req: &mut FwReq,
246        node: &mut FwNode,
247        params: &mut PhoneAssignParameters,
248        timeout_ms: u32,
249    ) -> Result<(), Error> {
250        let quad = read_quad(req, node, OFFSET_PORT, timeout_ms)?;
251
252        deserialize_flag(
253            &mut params.0,
254            &quad,
255            PORT_PHONE_MASK,
256            PORT_PHONE_SHIFT,
257            Self::ASSIGN_PORT_TARGETS,
258            Self::ASSIGN_PORT_VALS,
259            PORT_PHONE_LABEL,
260        )
261    }
262}
263
264impl<O> MotuWhollyUpdatableParamsOperation<PhoneAssignParameters> for O
265where
266    O: MotuPortAssignSpecification,
267{
268    fn update_wholly(
269        req: &mut FwReq,
270        node: &mut FwNode,
271        params: &PhoneAssignParameters,
272        timeout_ms: u32,
273    ) -> Result<(), Error> {
274        let mut quad = read_quad(req, node, OFFSET_PORT, timeout_ms)?;
275
276        serialize_flag(
277            &params.0,
278            &mut quad,
279            PORT_PHONE_MASK,
280            PORT_PHONE_SHIFT,
281            Self::ASSIGN_PORT_TARGETS,
282            Self::ASSIGN_PORT_VALS,
283            PORT_PHONE_LABEL,
284        )?;
285
286        write_quad(req, node, OFFSET_PORT, quad, timeout_ms)
287    }
288}
289
290/// Mode of speed for output signal of word clock on BNC interface.
291#[derive(Debug, Copy, Clone, PartialEq, Eq)]
292pub enum WordClkSpeedMode {
293    /// The speed is forced to be 44.1/48.0 kHz.
294    ForceLowRate,
295    /// The speed is following to system clock.
296    FollowSystemClk,
297}
298
299impl Default for WordClkSpeedMode {
300    fn default() -> Self {
301        Self::FollowSystemClk
302    }
303}
304
305const WORD_OUT_LABEL: &str = "word-out";
306const WORD_OUT_MASK: u32 = 0x08000000;
307const WORD_OUT_SHIFT: usize = 27;
308
309/// The trait for specification of speed of word clock signal in XLR output interface.
310pub trait MotuWordClockOutputSpecification {
311    const WORD_CLOCK_OUTPUT_SPEED_MODES: &'static [WordClkSpeedMode] = &[
312        WordClkSpeedMode::ForceLowRate,
313        WordClkSpeedMode::FollowSystemClk,
314    ];
315}
316
317const WORD_CLOCK_OUTPUT_SPEED_MODE_VALS: &[u8] = &[0x00, 0x01];
318
319impl<O: MotuWordClockOutputSpecification> MotuWhollyCacheableParamsOperation<WordClkSpeedMode>
320    for O
321{
322    fn cache_wholly(
323        req: &mut FwReq,
324        node: &mut FwNode,
325        params: &mut WordClkSpeedMode,
326        timeout_ms: u32,
327    ) -> Result<(), Error> {
328        let quad = read_quad(req, node, OFFSET_CLK, timeout_ms)?;
329
330        deserialize_flag(
331            params,
332            &quad,
333            WORD_OUT_MASK,
334            WORD_OUT_SHIFT,
335            Self::WORD_CLOCK_OUTPUT_SPEED_MODES,
336            WORD_CLOCK_OUTPUT_SPEED_MODE_VALS,
337            WORD_OUT_LABEL,
338        )
339    }
340}
341
342impl<O: MotuWordClockOutputSpecification> MotuWhollyUpdatableParamsOperation<WordClkSpeedMode>
343    for O
344{
345    fn update_wholly(
346        req: &mut FwReq,
347        node: &mut FwNode,
348        params: &WordClkSpeedMode,
349        timeout_ms: u32,
350    ) -> Result<(), Error> {
351        let mut quad = read_quad(req, node, OFFSET_CLK, timeout_ms)?;
352
353        serialize_flag(
354            params,
355            &mut quad,
356            WORD_OUT_MASK,
357            WORD_OUT_SHIFT,
358            Self::WORD_CLOCK_OUTPUT_SPEED_MODES,
359            WORD_CLOCK_OUTPUT_SPEED_MODE_VALS,
360            WORD_OUT_LABEL,
361        )?;
362
363        write_quad(req, node, OFFSET_CLK, quad, timeout_ms)
364    }
365}
366
367/// Mode of rate convert for AES/EBU input/output signals.
368#[derive(Debug, Copy, Clone, PartialEq, Eq)]
369pub enum AesebuRateConvertMode {
370    /// Not available.
371    None,
372    /// The rate of input signal is converted to system rate.
373    InputToSystem,
374    /// The rate of output signal is slave to input, ignoring system rate.
375    OutputDependsInput,
376    /// The rate of output signal is double rate than system rate.
377    OutputDoubleSystem,
378}
379
380impl Default for AesebuRateConvertMode {
381    fn default() -> Self {
382        Self::None
383    }
384}
385
386const AESEBU_RATE_CONVERT_LABEL: &str = "aesebu-rate-convert";
387
388/// The trait for specification of rate convert specific to AES/EBU input/output signals.
389pub trait MotuAesebuRateConvertSpecification {
390    const AESEBU_RATE_CONVERT_MASK: u32;
391    const AESEBU_RATE_CONVERT_SHIFT: usize;
392
393    const AESEBU_RATE_CONVERT_MODES: &'static [AesebuRateConvertMode] = &[
394        AesebuRateConvertMode::None,
395        AesebuRateConvertMode::InputToSystem,
396        AesebuRateConvertMode::OutputDependsInput,
397        AesebuRateConvertMode::OutputDoubleSystem,
398    ];
399}
400
401const AESEBU_RATE_CONVERT_VALS: &[u8] = &[0x00, 0x01, 0x02, 0x03];
402
403impl<O> MotuWhollyCacheableParamsOperation<AesebuRateConvertMode> for O
404where
405    O: MotuAesebuRateConvertSpecification,
406{
407    fn cache_wholly(
408        req: &mut FwReq,
409        node: &mut FwNode,
410        params: &mut AesebuRateConvertMode,
411        timeout_ms: u32,
412    ) -> Result<(), Error> {
413        let quad = read_quad(req, node, OFFSET_CLK, timeout_ms)?;
414
415        deserialize_flag(
416            params,
417            &quad,
418            Self::AESEBU_RATE_CONVERT_MASK,
419            Self::AESEBU_RATE_CONVERT_SHIFT,
420            Self::AESEBU_RATE_CONVERT_MODES,
421            AESEBU_RATE_CONVERT_VALS,
422            AESEBU_RATE_CONVERT_LABEL,
423        )
424    }
425}
426
427impl<O> MotuWhollyUpdatableParamsOperation<AesebuRateConvertMode> for O
428where
429    O: MotuAesebuRateConvertSpecification,
430{
431    fn update_wholly(
432        req: &mut FwReq,
433        node: &mut FwNode,
434        params: &AesebuRateConvertMode,
435        timeout_ms: u32,
436    ) -> Result<(), Error> {
437        let mut quad = read_quad(req, node, OFFSET_CLK, timeout_ms)?;
438
439        serialize_flag(
440            params,
441            &mut quad,
442            Self::AESEBU_RATE_CONVERT_MASK,
443            Self::AESEBU_RATE_CONVERT_SHIFT,
444            Self::AESEBU_RATE_CONVERT_MODES,
445            AESEBU_RATE_CONVERT_VALS,
446            AESEBU_RATE_CONVERT_LABEL,
447        )?;
448
449        write_quad(req, node, OFFSET_CLK, quad, timeout_ms)
450    }
451}
452
453/// Mode of hold time for clip and peak LEDs.
454#[derive(Debug, Copy, Clone, PartialEq, Eq)]
455pub enum LevelMetersHoldTimeMode {
456    /// off.
457    Off,
458    /// 2 seconds.
459    Sec2,
460    /// 4 seconds.
461    Sec4,
462    /// 10 seconds.
463    Sec10,
464    /// 1 minute.
465    Sec60,
466    /// 5 minutes.
467    Sec300,
468    /// 8 minutes.
469    Sec480,
470    /// Infinite.
471    Infinite,
472}
473
474impl Default for LevelMetersHoldTimeMode {
475    fn default() -> Self {
476        Self::Off
477    }
478}
479
480/// Mode of programmable meter display.
481#[derive(Debug, Copy, Clone, PartialEq, Eq)]
482pub enum LevelMetersProgrammableMode {
483    /// For analog outputs.
484    AnalogOutput,
485    /// For ADAT A inputs.
486    AdatAInput,
487    /// For ADAT A outputs.
488    AdatAOutput,
489    /// For ADAT B inputs.
490    AdatBInput,
491    /// For ADAT B outputs.
492    AdatBOutput,
493    /// For AES/EBU inputs and outputs.
494    AesEbuInputOutput,
495}
496
497impl Default for LevelMetersProgrammableMode {
498    fn default() -> Self {
499        Self::AnalogOutput
500    }
501}
502
503/// Mode of AES/EBU meter display.
504#[derive(Debug, Copy, Clone, PartialEq, Eq)]
505pub enum LevelMetersAesebuMode {
506    /// For AES/EBU inputs.
507    Input,
508    /// For AES/EBU outputs.
509    Output,
510}
511
512impl Default for LevelMetersAesebuMode {
513    fn default() -> Self {
514        Self::Input
515    }
516}
517
518/// The parameters of level meters.
519#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
520pub struct LevelMetersParameters {
521    /// The duration to hold peak.
522    pub peak_hold_time: LevelMetersHoldTimeMode,
523    /// The duration to hold clip.
524    pub clip_hold_time: LevelMetersHoldTimeMode,
525    /// The mode to display AES/EBU signal.
526    pub aesebu_mode: LevelMetersAesebuMode,
527    /// The mode of programmable meter.
528    pub programmable_mode: LevelMetersProgrammableMode,
529}
530
531const LEVEL_METERS_OFFSET: u32 = 0x0b24;
532
533const LEVEL_METERS_PEAK_HOLD_TIME_MASK: u32 = 0x00003800;
534const LEVEL_METERS_PEAK_HOLD_TIME_SHIFT: usize = 11;
535
536const LEVEL_METERS_CLIP_HOLD_TIME_MASK: u32 = 0x00000700;
537const LEVEL_METERS_CLIP_HOLD_TIME_SHIFT: usize = 8;
538
539const LEVEL_METERS_HOLD_TIME_VALS: &[u8] = &[0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07];
540
541const LEVEL_METERS_AESEBU_MASK: u32 = 0x00000004;
542const LEVEL_METERS_AESEBU_SHIFT: usize = 2;
543
544const LEVEL_METERS_AESEBU_VALS: &[u8] = &[0x00, 0x01];
545
546const LEVEL_METERS_PROGRAMMABLE_MASK: u32 = 0x00000003;
547const LEVEL_METERS_PROGRAMMABLE_SHIFT: usize = 0;
548const LEVEL_METERS_PROGRAMMABLE_VALS: &[u8] = &[0x00, 0x01, 0x02, 0x03, 0x04, 0x05];
549
550const LEVEL_METERS_PEAK_HOLD_TIME_LABEL: &str = "level-meters-peak-hold-time";
551const LEVEL_METERS_CLIP_HOLD_TIME_LABEL: &str = "level-meters-clip-hold-time";
552const LEVEL_METERS_PROGRAMMABLE_LABEL: &str = "level-meters-programmable";
553const LEVEL_METERS_AESEBU_LABEL: &str = "level-meters-aesebu";
554
555/// The trait for specification of level meter.
556pub trait MotuLevelMetersSpecification {
557    const LEVEL_METERS_HOLD_TIME_MODES: &'static [LevelMetersHoldTimeMode] = &[
558        LevelMetersHoldTimeMode::Off,
559        LevelMetersHoldTimeMode::Sec2,
560        LevelMetersHoldTimeMode::Sec4,
561        LevelMetersHoldTimeMode::Sec10,
562        LevelMetersHoldTimeMode::Sec60,
563        LevelMetersHoldTimeMode::Sec300,
564        LevelMetersHoldTimeMode::Sec480,
565        LevelMetersHoldTimeMode::Infinite,
566    ];
567
568    const LEVEL_METERS_AESEBU_MODES: &'static [LevelMetersAesebuMode] =
569        &[LevelMetersAesebuMode::Output, LevelMetersAesebuMode::Input];
570
571    const LEVEL_METERS_PROGRAMMABLE_MODES: &'static [LevelMetersProgrammableMode];
572}
573
574impl<O> MotuWhollyCacheableParamsOperation<LevelMetersParameters> for O
575where
576    O: MotuLevelMetersSpecification,
577{
578    fn cache_wholly(
579        req: &mut FwReq,
580        node: &mut FwNode,
581        params: &mut LevelMetersParameters,
582        timeout_ms: u32,
583    ) -> Result<(), Error> {
584        let quad = read_quad(req, node, LEVEL_METERS_OFFSET, timeout_ms)?;
585
586        deserialize_flag(
587            &mut params.peak_hold_time,
588            &quad,
589            LEVEL_METERS_PEAK_HOLD_TIME_MASK,
590            LEVEL_METERS_PEAK_HOLD_TIME_SHIFT,
591            Self::LEVEL_METERS_HOLD_TIME_MODES,
592            LEVEL_METERS_HOLD_TIME_VALS,
593            LEVEL_METERS_PEAK_HOLD_TIME_LABEL,
594        )?;
595
596        deserialize_flag(
597            &mut params.clip_hold_time,
598            &quad,
599            LEVEL_METERS_CLIP_HOLD_TIME_MASK,
600            LEVEL_METERS_CLIP_HOLD_TIME_SHIFT,
601            Self::LEVEL_METERS_HOLD_TIME_MODES,
602            LEVEL_METERS_HOLD_TIME_VALS,
603            LEVEL_METERS_CLIP_HOLD_TIME_LABEL,
604        )?;
605
606        deserialize_flag(
607            &mut params.aesebu_mode,
608            &quad,
609            LEVEL_METERS_AESEBU_MASK,
610            LEVEL_METERS_AESEBU_SHIFT,
611            Self::LEVEL_METERS_AESEBU_MODES,
612            LEVEL_METERS_AESEBU_VALS,
613            LEVEL_METERS_AESEBU_LABEL,
614        )?;
615
616        deserialize_flag(
617            &mut params.programmable_mode,
618            &quad,
619            LEVEL_METERS_PROGRAMMABLE_MASK,
620            LEVEL_METERS_PROGRAMMABLE_SHIFT,
621            Self::LEVEL_METERS_PROGRAMMABLE_MODES,
622            LEVEL_METERS_PROGRAMMABLE_VALS,
623            LEVEL_METERS_PROGRAMMABLE_LABEL,
624        )?;
625
626        Ok(())
627    }
628}
629
630impl<O> MotuWhollyUpdatableParamsOperation<LevelMetersParameters> for O
631where
632    O: MotuLevelMetersSpecification,
633{
634    fn update_wholly(
635        req: &mut FwReq,
636        node: &mut FwNode,
637        params: &LevelMetersParameters,
638        timeout_ms: u32,
639    ) -> Result<(), Error> {
640        let mut quad = read_quad(req, node, LEVEL_METERS_OFFSET, timeout_ms)?;
641
642        serialize_flag(
643            &params.peak_hold_time,
644            &mut quad,
645            LEVEL_METERS_PEAK_HOLD_TIME_MASK,
646            LEVEL_METERS_PEAK_HOLD_TIME_SHIFT,
647            Self::LEVEL_METERS_HOLD_TIME_MODES,
648            LEVEL_METERS_HOLD_TIME_VALS,
649            LEVEL_METERS_PEAK_HOLD_TIME_LABEL,
650        )?;
651
652        serialize_flag(
653            &params.clip_hold_time,
654            &mut quad,
655            LEVEL_METERS_CLIP_HOLD_TIME_MASK,
656            LEVEL_METERS_CLIP_HOLD_TIME_SHIFT,
657            Self::LEVEL_METERS_HOLD_TIME_MODES,
658            LEVEL_METERS_HOLD_TIME_VALS,
659            LEVEL_METERS_CLIP_HOLD_TIME_LABEL,
660        )?;
661
662        serialize_flag(
663            &params.aesebu_mode,
664            &mut quad,
665            LEVEL_METERS_AESEBU_MASK,
666            LEVEL_METERS_AESEBU_SHIFT,
667            Self::LEVEL_METERS_AESEBU_MODES,
668            LEVEL_METERS_AESEBU_VALS,
669            LEVEL_METERS_AESEBU_LABEL,
670        )?;
671
672        serialize_flag(
673            &params.programmable_mode,
674            &mut quad,
675            LEVEL_METERS_PROGRAMMABLE_MASK,
676            LEVEL_METERS_PROGRAMMABLE_SHIFT,
677            Self::LEVEL_METERS_PROGRAMMABLE_MODES,
678            LEVEL_METERS_PROGRAMMABLE_VALS,
679            LEVEL_METERS_PROGRAMMABLE_LABEL,
680        )?;
681
682        write_quad(req, node, LEVEL_METERS_OFFSET, quad, timeout_ms)
683    }
684}
685
686/// Port to assign.
687#[derive(Debug, Copy, Clone, PartialEq, Eq)]
688pub enum TargetPort {
689    Disabled,
690    AnalogPair(usize),
691    AesEbuPair,
692    PhonePair,
693    MainPair,
694    SpdifPair,
695    AdatPair(usize),
696    Analog6Pairs,
697    Analog8Pairs,
698    OpticalAPair(usize),
699    OpticalBPair(usize),
700    Analog(usize),
701    AesEbu(usize),
702    Phone(usize),
703    Main(usize),
704    Spdif(usize),
705    Adat(usize),
706    OpticalA(usize),
707    OpticalB(usize),
708}
709
710impl Default for TargetPort {
711    fn default() -> Self {
712        Self::Disabled
713    }
714}
715
716/// Nominal level of audio signal.
717#[derive(Debug, Copy, Clone, PartialEq, Eq)]
718pub enum NominalSignalLevel {
719    /// -10 dBV.
720    Consumer,
721    /// +4 dBu.
722    Professional,
723}
724
725impl Default for NominalSignalLevel {
726    fn default() -> Self {
727        Self::Consumer
728    }
729}
730
731#[cfg(test)]
732mod test {
733    use super::*;
734
735    #[test]
736    fn flag_serdes() {
737        const TEST0_MASK: u32 = 0x000000ff;
738        const TEST0_SHIFT: usize = 0;
739        const TEST0_LABEL: &'static str = "test0";
740        const TEST0_TARGETS: &[TargetPort] = &[
741            TargetPort::AesEbuPair,
742            TargetPort::PhonePair,
743            TargetPort::MainPair,
744            TargetPort::SpdifPair,
745        ];
746        const TEST0_VALS: &[u8] = &[0x01, 0x02, 0x04, 0x08];
747
748        const TEST1_MASK: u32 = 0x0000ff00;
749        const TEST1_SHIFT: usize = 8;
750        const TEST1_LABEL: &'static str = "test1";
751        const TEST1_TARGETS: &[LevelMetersHoldTimeMode] = &[
752            LevelMetersHoldTimeMode::Off,
753            LevelMetersHoldTimeMode::Sec2,
754            LevelMetersHoldTimeMode::Sec4,
755            LevelMetersHoldTimeMode::Sec10,
756            LevelMetersHoldTimeMode::Sec60,
757            LevelMetersHoldTimeMode::Sec300,
758            LevelMetersHoldTimeMode::Sec480,
759        ];
760        const TEST1_VALS: &[u8] = &[0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80];
761
762        let orig0 = TargetPort::SpdifPair;
763        let mut quad = 0;
764        serialize_flag(
765            &orig0,
766            &mut quad,
767            TEST0_MASK,
768            TEST0_SHIFT,
769            TEST0_TARGETS,
770            TEST0_VALS,
771            TEST0_LABEL,
772        )
773        .unwrap();
774        assert_eq!(quad, 0x00000008);
775
776        let orig1 = LevelMetersHoldTimeMode::Off;
777        serialize_flag(
778            &orig1,
779            &mut quad,
780            TEST1_MASK,
781            TEST1_SHIFT,
782            TEST1_TARGETS,
783            TEST1_VALS,
784            TEST1_LABEL,
785        )
786        .unwrap();
787        assert_eq!(quad, 0x00000108);
788
789        let mut target0 = TargetPort::default();
790        deserialize_flag(
791            &mut target0,
792            &quad,
793            TEST0_MASK,
794            TEST0_SHIFT,
795            TEST0_TARGETS,
796            TEST0_VALS,
797            TEST0_LABEL,
798        )
799        .unwrap();
800        assert_eq!(target0, orig0);
801
802        let mut target1 = LevelMetersHoldTimeMode::default();
803        deserialize_flag(
804            &mut target1,
805            &quad,
806            TEST1_MASK,
807            TEST1_SHIFT,
808            TEST1_TARGETS,
809            TEST1_VALS,
810            TEST1_LABEL,
811        )
812        .unwrap();
813        assert_eq!(target1, orig1);
814    }
815}