firewire_tascam_protocols/
isoch.rs

1// SPDX-License-Identifier: LGPL-3.0-or-later
2// Copyright (c) 2021 Takashi Sakamoto
3
4//! Protocols defined for Tascam for FireWire series with isochronous communication.
5//!
6//! The module includes protocol implementation defined by Tascam for FireWire series with
7//! isochronous communication.
8
9pub mod fw1082;
10pub mod fw1804;
11pub mod fw1884;
12
13use super::*;
14
15/// Operation to cache whole parameters at once.
16pub trait TascamIsochWhollyCachableParamsOperation<T> {
17    /// Cache whole parameters.
18    fn cache_wholly(
19        req: &mut FwReq,
20        node: &mut FwNode,
21        states: &mut T,
22        timeout_ms: u32,
23    ) -> Result<(), Error>;
24}
25
26/// Operation to update whole parameters at once.
27pub trait TascamIsochWhollyUpdatableParamsOperation<T> {
28    /// Update whole parameters.
29    fn update_wholly(
30        req: &mut FwReq,
31        node: &mut FwNode,
32        states: &T,
33        timeout_ms: u32,
34    ) -> Result<(), Error>;
35}
36
37/// Operation to update the part of parameters.
38pub trait TascamIsochPartiallyUpdatableParamsOperation<T> {
39    /// Update the part of parameters.
40    fn update_partially(
41        req: &mut FwReq,
42        node: &mut FwNode,
43        params: &mut T,
44        update: T,
45        timeout_ms: u32,
46    ) -> Result<(), Error>;
47}
48
49/// Operation to parse parameters in the image of hardware state.
50pub trait TascamIsochImageParamsOperation<T> {
51    /// Parse the image of hardware state.
52    fn parse_image(params: &mut T, image: &[u32]);
53}
54
55/// Signal source of sampling clock.
56#[derive(Debug, Copy, Clone, PartialEq, Eq)]
57pub enum ClkSrc {
58    /// Internal oscillator.
59    Internal,
60    /// Word clock signal from BNC input interface.
61    Wordclock,
62    /// S/PDIF signal from coaxial input interface.
63    Spdif,
64    /// ADAT signal from optical input interface.
65    Adat,
66}
67
68impl Default for ClkSrc {
69    fn default() -> Self {
70        Self::Internal
71    }
72}
73
74/// Nominal frequency of media clock.
75#[derive(Debug, Copy, Clone, PartialEq, Eq)]
76pub enum ClkRate {
77    /// At 44.1 kHz.
78    R44100,
79    /// At 48.0 kHz.
80    R48000,
81    /// At 88.2 kHz.
82    R88200,
83    /// At 96.0 kHz.
84    R96000,
85}
86
87impl Default for ClkRate {
88    fn default() -> Self {
89        Self::R44100
90    }
91}
92
93/// The parameters of sampling and media clock.
94#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
95pub struct TascamClockParameters {
96    /// The source of sampling clock.
97    pub sampling_clock_source: ClkSrc,
98    /// The rate of media clock.
99    pub media_clock_rate: ClkRate,
100}
101
102/// The specification of sampling and media clocks.
103pub trait TascamIsochClockSpecification {
104    const SAMPLING_CLOCK_SOURCES: &'static [ClkSrc];
105}
106
107const CLOCK_STATUS_OFFSET: u64 = 0x0228;
108
109const CLOCK_SOURCES: [(ClkSrc, u8); 4] = [
110    (ClkSrc::Internal, 0x01),
111    (ClkSrc::Wordclock, 0x02),
112    (ClkSrc::Spdif, 0x03),
113    (ClkSrc::Adat, 0x04),
114];
115
116const CLOCK_RATES: [(ClkRate, u8); 4] = [
117    (ClkRate::R44100, 0x01),
118    (ClkRate::R48000, 0x02),
119    (ClkRate::R88200, 0x03),
120    (ClkRate::R96000, 0x04),
121];
122
123impl<O> TascamIsochWhollyCachableParamsOperation<TascamClockParameters> for O
124where
125    O: TascamIsochClockSpecification,
126{
127    fn cache_wholly(
128        req: &mut FwReq,
129        node: &mut FwNode,
130        states: &mut TascamClockParameters,
131        timeout_ms: u32,
132    ) -> Result<(), Error> {
133        let mut frame = [0; 4];
134        read_quadlet(req, node, CLOCK_STATUS_OFFSET, &mut frame, timeout_ms)?;
135        let src = CLOCK_SOURCES
136            .iter()
137            .find_map(|&(src, val)| if val == frame[3] { Some(src) } else { None })
138            .ok_or_else(|| {
139                let msg = format!("Unexpected value for source of clock: {}", frame[3]);
140                Error::new(FileError::Io, &msg)
141            })?;
142        Self::SAMPLING_CLOCK_SOURCES
143            .iter()
144            .find_map(|s| if src.eq(s) { Some(src) } else { None })
145            .ok_or_else(|| {
146                let msg = "Unsupported source of sampling clock";
147                Error::new(FileError::Inval, &msg)
148            })
149            .map(|src| states.sampling_clock_source = src)?;
150        CLOCK_RATES
151            .iter()
152            .find_map(|&(rate, val)| if val == frame[1] { Some(rate) } else { None })
153            .ok_or_else(|| {
154                let label = format!("Unexpected value for rate of clock: {}", frame[1]);
155                Error::new(FileError::Io, &label)
156            })
157            .map(|rate| states.media_clock_rate = rate)
158    }
159}
160
161impl<O> TascamIsochWhollyUpdatableParamsOperation<TascamClockParameters> for O
162where
163    O: TascamIsochClockSpecification,
164{
165    /// Update whole parameters.
166    fn update_wholly(
167        req: &mut FwReq,
168        node: &mut FwNode,
169        params: &TascamClockParameters,
170        timeout_ms: u32,
171    ) -> Result<(), Error> {
172        let _ = Self::SAMPLING_CLOCK_SOURCES
173            .iter()
174            .find(|s| params.sampling_clock_source.eq(s))
175            .ok_or_else(|| {
176                let msg = "Unsupported source of sampling clock";
177                Error::new(FileError::Inval, &msg)
178            })?;
179        let mut frame = [0; 4];
180        frame[3] = CLOCK_SOURCES
181            .iter()
182            .find_map(|&(s, val)| {
183                if params.sampling_clock_source.eq(&s) {
184                    Some(val)
185                } else {
186                    None
187                }
188            })
189            .unwrap();
190        frame[2] = CLOCK_RATES
191            .iter()
192            .find_map(|&(r, val)| {
193                if params.media_clock_rate.eq(&r) {
194                    Some(val)
195                } else {
196                    None
197                }
198            })
199            .unwrap();
200        write_quadlet(req, node, CLOCK_STATUS_OFFSET, &mut frame, timeout_ms)
201    }
202}
203
204/// The parameters of threshold to detect input signal. The value is between 1 and 0x7fff. The dB
205/// level can be calculated by below formula.
206///
207/// ```text
208/// level = 20 * log10(value / 0x7fff)
209/// ```
210#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
211pub struct TascamInputDetectionThreshold {
212    /// For signal detection itself.
213    pub signal: u16,
214    /// For over-level detection.
215    pub over_level: u16,
216}
217
218/// The specification of input detection.
219pub trait TascamIsochInputDetectionSpecification {
220    /// The minimum value of threshold to detect input signal.
221    const INPUT_SIGNAL_THRESHOLD_MIN: u16 = 1;
222    /// The maximum value of threshold to detect input signal.
223    const INPUT_SIGNAL_THRESHOLD_MAX: u16 = 0x7fff;
224}
225
226const INPUT_THRESHOLD_OFFSET: u64 = 0x0230;
227
228impl<O> TascamIsochWhollyCachableParamsOperation<TascamInputDetectionThreshold> for O
229where
230    O: TascamIsochInputDetectionSpecification,
231{
232    fn cache_wholly(
233        req: &mut FwReq,
234        node: &mut FwNode,
235        states: &mut TascamInputDetectionThreshold,
236        timeout_ms: u32,
237    ) -> Result<(), Error> {
238        let mut quads = [0; 4];
239        read_quadlet(req, node, INPUT_THRESHOLD_OFFSET, &mut quads, timeout_ms).map(|_| {
240            let quad = u32::from_be_bytes(quads);
241            let val = (quad & 0x0000ffff) as u16;
242            states.signal = val.clamp(
243                Self::INPUT_SIGNAL_THRESHOLD_MIN,
244                Self::INPUT_SIGNAL_THRESHOLD_MAX,
245            );
246
247            let val = (quad >> 16) as u16;
248            states.over_level = val.clamp(
249                Self::INPUT_SIGNAL_THRESHOLD_MIN,
250                Self::INPUT_SIGNAL_THRESHOLD_MAX,
251            );
252        })
253    }
254}
255
256impl<O> TascamIsochWhollyUpdatableParamsOperation<TascamInputDetectionThreshold> for O
257where
258    O: TascamIsochInputDetectionSpecification,
259{
260    fn update_wholly(
261        req: &mut FwReq,
262        node: &mut FwNode,
263        params: &TascamInputDetectionThreshold,
264        timeout_ms: u32,
265    ) -> Result<(), Error> {
266        if params.signal > Self::INPUT_SIGNAL_THRESHOLD_MAX
267            || params.signal < Self::INPUT_SIGNAL_THRESHOLD_MIN
268        {
269            let msg = format!(
270                "Argument should be greater than {} and less than {}, but {}",
271                Self::INPUT_SIGNAL_THRESHOLD_MIN,
272                Self::INPUT_SIGNAL_THRESHOLD_MAX,
273                params.signal
274            );
275            Err(Error::new(FileError::Inval, &msg))?;
276        }
277
278        if params.over_level > Self::INPUT_SIGNAL_THRESHOLD_MAX
279            || params.over_level < Self::INPUT_SIGNAL_THRESHOLD_MIN
280        {
281            let msg = format!(
282                "Argument should be greater than {} and less than {}, but {}",
283                Self::INPUT_SIGNAL_THRESHOLD_MIN,
284                Self::INPUT_SIGNAL_THRESHOLD_MAX,
285                params.over_level
286            );
287            Err(Error::new(FileError::Inval, &msg))?;
288        }
289
290        let quad = ((params.over_level as u32) << 16) | (params.signal as u32);
291
292        write_quadlet(
293            req,
294            node,
295            INPUT_THRESHOLD_OFFSET,
296            &mut quad.to_be_bytes(),
297            timeout_ms,
298        )
299    }
300}
301
302const ISOCH_IMAGE_QUADLET_COUNT: usize = 64;
303
304/// Mode of output mixer.
305#[derive(Debug, Copy, Clone, PartialEq, Eq)]
306pub enum MixerMode {
307    /// From stream input 0/1 only.
308    Computer,
309    /// From monitor outputs 0/1 only.
310    Inputs,
311    /// Multiplex signals from stream input 0/1 and monitor output 0/1.
312    Both,
313}
314
315impl Default for MixerMode {
316    fn default() -> Self {
317        Self::Computer
318    }
319}
320
321/// State of meter.
322#[derive(Default, Debug, Clone, PartialEq, Eq)]
323pub struct IsochMeterState {
324    /// The value of monitor knob.
325    pub monitor: i16,
326    /// The value of solo knob.
327    pub solo: Option<i16>,
328    /// Detected signal levels for inputs.
329    pub inputs: Vec<i32>,
330    /// Detected signal levels for outputs.
331    pub outputs: Vec<i32>,
332    /// Detected rate of sampling clock.
333    pub rate: Option<ClkRate>,
334    /// detected source of sampling clock.
335    pub src: Option<ClkSrc>,
336    /// Detected signal levels of mixer outputs.
337    pub mixer_meters: [i32; 2],
338    /// Detected signal levels of monitor outputs.
339    pub monitor_meters: [i32; 2],
340    /// The mode of mixer.
341    pub mixer_mode: MixerMode,
342}
343
344/// The specification of metering.
345pub trait TascamIsochMeterSpecification: TascamHardwareImageSpecification {
346    const INPUT_COUNT: usize;
347    const OUTPUT_COUNT: usize;
348    const HAS_SOLO: bool;
349
350    const ROTARY_MIN: i16 = 0;
351    const ROTARY_MAX: i16 = 1023;
352    const ROTARY_STEP: i16 = 2;
353
354    const LEVEL_MIN: i32 = 0;
355    const LEVEL_MAX: i32 = 0x7fffff00;
356    const LEVEL_STEP: i32 = 0x100;
357
358    fn create_meter_state() -> IsochMeterState {
359        IsochMeterState {
360            monitor: Default::default(),
361            solo: if Self::HAS_SOLO {
362                Some(Default::default())
363            } else {
364                None
365            },
366            inputs: vec![Default::default(); Self::INPUT_COUNT],
367            outputs: vec![Default::default(); Self::OUTPUT_COUNT],
368            rate: Default::default(),
369            src: Default::default(),
370            mixer_meters: Default::default(),
371            monitor_meters: Default::default(),
372            mixer_mode: Default::default(),
373        }
374    }
375}
376
377impl<O> TascamIsochImageParamsOperation<IsochMeterState> for O
378where
379    O: TascamIsochMeterSpecification,
380{
381    fn parse_image(state: &mut IsochMeterState, image: &[u32]) {
382        assert_eq!(image.len(), Self::IMAGE_QUADLET_COUNT);
383
384        let monitor = (image[5] & 0x0000ffff) as i16;
385        if (state.monitor - monitor).abs() > Self::ROTARY_STEP {
386            state.monitor = monitor;
387        }
388
389        if let Some(solo) = &mut state.solo {
390            let val = ((image[4] >> 16) & 0x0000ffff) as i16;
391            if (*solo - val).abs() > Self::ROTARY_STEP {
392                *solo = val;
393            }
394        }
395
396        state
397            .inputs
398            .iter_mut()
399            .take(Self::INPUT_COUNT)
400            .enumerate()
401            .for_each(|(i, input)| {
402                let pos = if Self::INPUT_COUNT == 10 && i >= 8 {
403                    i + 16
404                } else {
405                    i
406                } + 16;
407                *input = image[pos] as i32;
408            });
409
410        state
411            .outputs
412            .iter_mut()
413            .take(Self::OUTPUT_COUNT)
414            .enumerate()
415            .for_each(|(i, output)| {
416                let pos = if Self::OUTPUT_COUNT == 4 && i >= 2 {
417                    i + 16
418                } else {
419                    i
420                } + 34;
421                *output = image[pos] as i32;
422            });
423
424        let bits = (image[52] & 0x0000000f) as u8;
425        state.src = match bits {
426            0x04 => Some(ClkSrc::Adat),
427            0x03 => Some(ClkSrc::Spdif),
428            0x02 => Some(ClkSrc::Wordclock),
429            0x01 => Some(ClkSrc::Internal),
430            _ => None,
431        };
432
433        let bits = ((image[52] >> 8) & 0x000000ff) as u8;
434        state.rate = match bits {
435            0x82 => Some(ClkRate::R96000),
436            0x81 => Some(ClkRate::R88200),
437            0x02 => Some(ClkRate::R48000),
438            0x01 => Some(ClkRate::R44100),
439            _ => None,
440        };
441
442        state
443            .mixer_meters
444            .iter_mut()
445            .enumerate()
446            .for_each(|(i, m)| {
447                *m = image[i + 54] as i32;
448            });
449
450        state
451            .monitor_meters
452            .iter_mut()
453            .enumerate()
454            .for_each(|(i, m)| {
455                *m = image[i + 57] as i32;
456            });
457
458        if image[59] > 0 && image[59] < 4 {
459            state.mixer_mode = match image[59] {
460                2 => MixerMode::Inputs,
461                1 => MixerMode::Computer,
462                _ => MixerMode::Both,
463            };
464        }
465    }
466}
467
468fn serialize_config_flag<T: Copy + Eq>(
469    option: &T,
470    flags: &[(T, u32, u32)],
471    val: &mut u32,
472) -> Result<(), Error> {
473    let mask = flags.iter().fold(0, |mask, (_, _, flag)| mask | flag);
474    *val &= !mask;
475    let (_, _, flag) = flags.iter().find(|(o, _, _)| option.eq(o)).unwrap();
476    *val |= flag;
477    Ok(())
478}
479
480fn deserialize_config_flag<T: Copy + Eq>(
481    option: &mut T,
482    flags: &[(T, u32, u32)],
483    val: u32,
484) -> Result<(), Error> {
485    let mask = flags.iter().fold(0, |mask, (_, flag, _)| mask | flag);
486    flags
487        .iter()
488        .find(|&(_, flag, _)| val & mask == *flag)
489        .ok_or_else(|| {
490            let msg = format!("No flag detected: val: 0x{:08x} mask: 0x{:08x}", val, mask);
491            Error::new(FileError::Nxio, &msg)
492        })
493        .map(|&(opt, _, _)| *option = opt)
494}
495
496fn read_config(
497    req: &mut FwReq,
498    node: &mut FwNode,
499    config: &mut u32,
500    timeout_ms: u32,
501) -> Result<(), Error> {
502    let mut quads = [0; 4];
503    read_quadlet(req, node, CONFIG_FLAG_OFFSET, &mut quads, timeout_ms).map(|_| {
504        *config = u32::from_be_bytes(quads);
505    })
506}
507
508fn write_config(
509    req: &mut FwReq,
510    node: &mut FwNode,
511    config: u32,
512    timeout_ms: u32,
513) -> Result<(), Error> {
514    write_quadlet(
515        req,
516        node,
517        CONFIG_FLAG_OFFSET,
518        &mut config.to_be_bytes(),
519        timeout_ms,
520    )
521}
522
523/// Source of output coaxial interface.
524#[derive(Debug, Clone, Copy, PartialEq, Eq)]
525pub enum CoaxialOutputSource {
526    /// A pair in stream inputs.
527    StreamInputPair,
528    /// Mirror of analog output 0 and 1.
529    AnalogOutputPair0,
530}
531
532impl Default for CoaxialOutputSource {
533    fn default() -> Self {
534        Self::StreamInputPair
535    }
536}
537
538/// The specification of coaxial output interface.
539pub trait TascamIsochCoaxialOutputSpecification {}
540
541const COAXIAL_OUTPUT_SOURCES: [(CoaxialOutputSource, u32, u32); 2] = [
542    (CoaxialOutputSource::StreamInputPair, 0x00000002, 0x00020000),
543    (
544        CoaxialOutputSource::AnalogOutputPair0,
545        0x00000000,
546        0x00000200,
547    ),
548];
549
550impl<O> TascamIsochWhollyCachableParamsOperation<CoaxialOutputSource> for O
551where
552    O: TascamIsochCoaxialOutputSpecification,
553{
554    fn cache_wholly(
555        req: &mut FwReq,
556        node: &mut FwNode,
557        states: &mut CoaxialOutputSource,
558        timeout_ms: u32,
559    ) -> Result<(), Error> {
560        let mut config = 0;
561        read_config(req, node, &mut config, timeout_ms)?;
562        deserialize_config_flag(states, &COAXIAL_OUTPUT_SOURCES, config)
563    }
564}
565
566impl<O> TascamIsochWhollyUpdatableParamsOperation<CoaxialOutputSource> for O
567where
568    O: TascamIsochCoaxialOutputSpecification,
569{
570    fn update_wholly(
571        req: &mut FwReq,
572        node: &mut FwNode,
573        states: &CoaxialOutputSource,
574        timeout_ms: u32,
575    ) -> Result<(), Error> {
576        let mut config = 0;
577        serialize_config_flag(states, &COAXIAL_OUTPUT_SOURCES, &mut config)?;
578        write_config(req, node, config, timeout_ms)
579    }
580}
581
582const CONFIG_FLAG_OFFSET: u64 = 0x022c;
583
584/// Source of S/PDIF input.
585#[derive(Debug, Copy, Clone, PartialEq, Eq)]
586pub enum SpdifCaptureSource {
587    /// To coaxial interface.
588    Coaxial,
589    /// To optical interface.
590    Optical,
591}
592
593impl Default for SpdifCaptureSource {
594    fn default() -> Self {
595        Self::Coaxial
596    }
597}
598
599/// Source of optical output.
600#[derive(Debug, Copy, Clone, PartialEq, Eq)]
601pub enum OpticalOutputSource {
602    /// 4 pairs in stream inputs.
603    StreamInputPairs,
604    /// Mirror of coaxial output 0 and 1.
605    CoaxialOutputPair0,
606    /// Analog input 0 and 1.
607    AnalogInputPair0,
608    /// Mirror of analog output 0, 1, 2, 3, 4, 5, 6, 7, and 8.
609    AnalogOutputPairs,
610}
611
612impl Default for OpticalOutputSource {
613    fn default() -> Self {
614        Self::StreamInputPairs
615    }
616}
617
618/// The parameters of digital input and output interfaces.
619#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
620pub struct TascamOpticalIfaceParameters {
621    /// The input interface from which the S/PDIF signal is captured.
622    pub capture_source: SpdifCaptureSource,
623    /// The source signal of optical output interface.
624    pub output_source: OpticalOutputSource,
625}
626
627/// The specification of digital interfaces.
628pub trait TascamIsochOpticalIfaceSpecification {
629    const OPTICAL_OUTPUT_SOURCES: &'static [(OpticalOutputSource, u32, u32)];
630}
631
632const SPDIF_CAPTURE_SOURCES: &[(SpdifCaptureSource, u32, u32)] = &[
633    (SpdifCaptureSource::Coaxial, 0x00000000, 0x00010000),
634    (SpdifCaptureSource::Optical, 0x00000001, 0x00000100),
635];
636
637impl<O> TascamIsochWhollyCachableParamsOperation<TascamOpticalIfaceParameters> for O
638where
639    O: TascamIsochOpticalIfaceSpecification,
640{
641    fn cache_wholly(
642        req: &mut FwReq,
643        node: &mut FwNode,
644        states: &mut TascamOpticalIfaceParameters,
645        timeout_ms: u32,
646    ) -> Result<(), Error> {
647        let mut config = 0;
648        read_config(req, node, &mut config, timeout_ms)?;
649        deserialize_config_flag(&mut states.capture_source, &SPDIF_CAPTURE_SOURCES, config)?;
650        deserialize_config_flag(
651            &mut states.output_source,
652            &Self::OPTICAL_OUTPUT_SOURCES,
653            config,
654        )?;
655        Ok(())
656    }
657}
658
659impl<O> TascamIsochWhollyUpdatableParamsOperation<TascamOpticalIfaceParameters> for O
660where
661    O: TascamIsochOpticalIfaceSpecification,
662{
663    /// Update whole parameters.
664    fn update_wholly(
665        req: &mut FwReq,
666        node: &mut FwNode,
667        states: &TascamOpticalIfaceParameters,
668        timeout_ms: u32,
669    ) -> Result<(), Error> {
670        let mut config = 0;
671        serialize_config_flag(&states.capture_source, &SPDIF_CAPTURE_SOURCES, &mut config)?;
672        serialize_config_flag(
673            &states.output_source,
674            &Self::OPTICAL_OUTPUT_SOURCES,
675            &mut config,
676        )?;
677        write_config(req, node, config, timeout_ms)
678    }
679}
680
681/// State of console.
682#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
683pub struct IsochConsoleState {
684    /// Whether to enable host mode.
685    pub host_mode: bool,
686
687    /// Whether to assign master fader to analog output 1/2.
688    pub master_fader_assign: bool,
689}
690
691/// The specification of console.
692pub trait TascamIsochConsoleSpecification: TascamHardwareImageSpecification {}
693
694const MASTER_FADER_ASSIGNS: [(bool, u32, u32); 2] = [
695    (false, 0x00000040, 0x00400000),
696    (true, 0x00000000, 0x00004000),
697];
698
699impl<O> TascamIsochWhollyCachableParamsOperation<IsochConsoleState> for O
700where
701    O: TascamIsochConsoleSpecification,
702{
703    fn cache_wholly(
704        req: &mut FwReq,
705        node: &mut FwNode,
706        states: &mut IsochConsoleState,
707        timeout_ms: u32,
708    ) -> Result<(), Error> {
709        let mut config = 0;
710        read_config(req, node, &mut config, timeout_ms)?;
711        deserialize_config_flag(
712            &mut states.master_fader_assign,
713            &MASTER_FADER_ASSIGNS,
714            config,
715        )
716    }
717}
718
719impl<O> TascamIsochWhollyUpdatableParamsOperation<IsochConsoleState> for O
720where
721    O: TascamIsochConsoleSpecification,
722{
723    fn update_wholly(
724        req: &mut FwReq,
725        node: &mut FwNode,
726        states: &IsochConsoleState,
727        timeout_ms: u32,
728    ) -> Result<(), Error> {
729        let mut config = 0;
730        serialize_config_flag(
731            &states.master_fader_assign,
732            &MASTER_FADER_ASSIGNS,
733            &mut config,
734        )?;
735        write_config(req, node, config, timeout_ms)
736    }
737}
738
739impl<O> TascamIsochImageParamsOperation<IsochConsoleState> for O
740where
741    O: TascamIsochConsoleSpecification + TascamHardwareImageSpecification,
742{
743    fn parse_image(params: &mut IsochConsoleState, image: &[u32]) {
744        assert_eq!(image.len(), Self::IMAGE_QUADLET_COUNT);
745        params.host_mode = (image[5] & 0xff000000) != 0xff000000;
746    }
747}
748
749/// The parameters of input.
750#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
751pub struct IsochRackInputParameters {
752    /// Gain of signal to stereo monitor. The value is between 0 and 0x7fff.
753    pub gains: [i16; 18],
754    /// L/R balance to stereo monitor. The value is between 0 and 255.
755    pub balances: [u8; 18],
756    /// Whether to mute.
757    pub mutes: [bool; 18],
758}
759
760const RACK_STATE_SIZE: usize = 72;
761
762fn serialize_input_params(params: &IsochRackInputParameters, raw: &mut [u8; RACK_STATE_SIZE]) {
763    (0..18).for_each(|i| {
764        let val = ((i as u32) << 24) | ((params.mutes[i] as u32) << 16) | (params.gains[i] as u32);
765        let pos = i * 4;
766        raw[pos..(pos + 4)].copy_from_slice(&val.to_be_bytes());
767    });
768}
769
770#[cfg(test)]
771fn deserialize_input_params(params: &mut IsochRackInputParameters, raw: &[u8; RACK_STATE_SIZE]) {
772    (0..RACK_STATE_SIZE).step_by(4).for_each(|pos| {
773        let mut quad = [0; 4];
774        quad.copy_from_slice(&raw[pos..(pos + 4)]);
775        let val = u32::from_be_bytes(quad);
776        let i = (val >> 24) as usize;
777        let muted = (val & 0x00ff0000) > 0;
778        let gain = (val & 0x0000ffff) as i16;
779        if i < params.gains.len() {
780            params.gains[i] = gain;
781            params.mutes[i] = muted;
782        }
783    });
784}
785
786/// The specification of rack input.
787pub trait TascamIsochRackInputSpecification {
788    /// The number of input channels.
789    const CHANNEL_COUNT: usize = 18;
790
791    /// The minimum value of gain.
792    const INPUT_GAIN_MIN: i16 = 0;
793    /// The maximum value of gain.
794    const INPUT_GAIN_MAX: i16 = 0x7fff;
795    /// The step value of gain.
796    const INPUT_GAIN_STEP: i16 = 0x100;
797
798    /// The minimum value of L/R balance.
799    const INPUT_BALANCE_MIN: u8 = 0;
800    /// The maximum value of L/R balance.
801    const INPUT_BALANCE_MAX: u8 = 255;
802    /// The step value of L/R balance.
803    const INPUT_BALANCE_STEP: u8 = 1;
804
805    fn create_input_parameters() -> IsochRackInputParameters {
806        let mut params = IsochRackInputParameters::default();
807        params.gains.fill(Self::INPUT_GAIN_MAX);
808        params
809            .balances
810            .iter_mut()
811            .enumerate()
812            .for_each(|(i, balance)| {
813                *balance = if i % 2 > 0 { u8::MAX } else { u8::MIN };
814            });
815        params.mutes.fill(false);
816        params
817    }
818}
819
820const INPUT_OFFSET: u64 = 0x0408;
821
822fn write_input_quadlet(
823    req: &mut FwReq,
824    node: &mut FwNode,
825    quad: &mut [u8],
826    timeout_ms: u32,
827) -> Result<(), Error> {
828    assert_eq!(quad.len(), 4);
829
830    write_quadlet(req, node, INPUT_OFFSET, quad, timeout_ms)
831}
832
833impl<O> TascamIsochWhollyUpdatableParamsOperation<IsochRackInputParameters> for O
834where
835    O: TascamIsochRackInputSpecification,
836{
837    fn update_wholly(
838        req: &mut FwReq,
839        node: &mut FwNode,
840        states: &IsochRackInputParameters,
841        timeout_ms: u32,
842    ) -> Result<(), Error> {
843        let mut raw = [0; RACK_STATE_SIZE];
844        serialize_input_params(states, &mut raw);
845        (0..RACK_STATE_SIZE).step_by(4).try_for_each(|pos| {
846            write_input_quadlet(req, node, &mut raw[pos..(pos + 4)], timeout_ms)
847        })
848    }
849}
850
851impl<O> TascamIsochPartiallyUpdatableParamsOperation<IsochRackInputParameters> for O
852where
853    O: TascamIsochRackInputSpecification,
854{
855    fn update_partially(
856        req: &mut FwReq,
857        node: &mut FwNode,
858        params: &mut IsochRackInputParameters,
859        update: IsochRackInputParameters,
860        timeout_ms: u32,
861    ) -> Result<(), Error> {
862        let mut old = [0; RACK_STATE_SIZE];
863        serialize_input_params(params, &mut old);
864
865        let mut new = [0; RACK_STATE_SIZE];
866        serialize_input_params(&update, &mut new);
867
868        (0..RACK_STATE_SIZE)
869            .step_by(4)
870            .try_for_each(|pos| {
871                if old[pos..(pos + 4)] != new[pos..(pos + 4)] {
872                    write_input_quadlet(req, node, &mut new[pos..(pos + 4)], timeout_ms)
873                } else {
874                    Ok(())
875                }
876            })
877            .map(|_| *params = update)
878    }
879}
880
881/// State of surface in isoch models.
882#[derive(Default, Debug, Clone, PartialEq, Eq)]
883pub struct TascamSurfaceIsochState {
884    shifted: bool,
885    shifted_items: Vec<bool>,
886    bank: u16,
887    enabled_leds: LedState,
888}
889
890/// The trait to express specification of LEDS for isochronous models.
891pub trait TascamSurfaceLedIsochSpecification {
892    const BANK_LEDS: [&'static [u16]; 4];
893}
894
895impl<O> TascamSurfaceLedOperation<TascamSurfaceIsochState> for O
896where
897    O: TascamSurfaceLedIsochSpecification,
898{
899    fn operate_leds(
900        state: &mut TascamSurfaceIsochState,
901        machine_value: &(MachineItem, ItemValue),
902        req: &mut FwReq,
903        node: &mut FwNode,
904        timeout_ms: u32,
905    ) -> Result<(), Error> {
906        if let (MachineItem::Bank, ItemValue::U16(value)) = machine_value {
907            Self::BANK_LEDS
908                .iter()
909                .enumerate()
910                .try_for_each(|(i, positions)| {
911                    let enable = *value == i as u16;
912                    operate_led_cached(
913                        &mut state.enabled_leds,
914                        req,
915                        node,
916                        positions[0],
917                        enable,
918                        timeout_ms,
919                    )
920                })?;
921        }
922
923        Ok(())
924    }
925
926    fn clear_leds(
927        state: &mut TascamSurfaceIsochState,
928        req: &mut FwReq,
929        node: &mut FwNode,
930        timeout_ms: u32,
931    ) -> Result<(), Error> {
932        clear_leds(&mut state.enabled_leds, req, node, timeout_ms)
933    }
934}
935
936/// The trait to express state of surface specific to isochronous models.
937pub trait TascamSurfaceStateIsochSpecification {
938    const SHIFT_ITEM: SurfaceBoolValue;
939    const SHIFTED_ITEMS: &'static [(SurfaceBoolValue, [MachineItem; 2])];
940    const BANK_CURSORS: [SurfaceBoolValue; 2];
941}
942
943impl<O> TascamSurfaceStateOperation<TascamSurfaceIsochState> for O
944where
945    O: TascamSurfaceStateIsochSpecification,
946{
947    fn init(state: &mut TascamSurfaceIsochState) {
948        state.shifted = false;
949        state.shifted_items = vec![false; Self::SHIFTED_ITEMS.len()];
950        state.bank = 0;
951    }
952
953    fn peek(
954        state: &TascamSurfaceIsochState,
955        _: &[u32],
956        index: u32,
957        before: u32,
958        after: u32,
959    ) -> Vec<(MachineItem, ItemValue)> {
960        let mut machine_values = Vec::new();
961
962        let shifted = if detect_bool_action(&Self::SHIFT_ITEM, index, before, after) {
963            let shifted = detect_bool_value(&Self::SHIFT_ITEM, before);
964            machine_values.push((MachineItem::Shift, ItemValue::Bool(shifted)));
965            shifted
966        } else {
967            state.shifted
968        };
969
970        if shifted != state.shifted {
971            let prev_idx = state.shifted as usize;
972            let curr_idx = shifted as usize;
973
974            Self::SHIFTED_ITEMS
975                .iter()
976                .zip(&state.shifted_items)
977                .filter(|(_, &s)| s)
978                .for_each(|((_, pairs), _)| {
979                    machine_values.push((pairs[prev_idx], ItemValue::Bool(false)));
980                    machine_values.push((pairs[curr_idx], ItemValue::Bool(true)));
981                });
982        }
983
984        Self::SHIFTED_ITEMS
985            .iter()
986            .filter(|(bool_val, _)| detect_bool_action(bool_val, index, before, after))
987            .for_each(|(bool_val, pairs)| {
988                let value = detect_bool_value(bool_val, before);
989                machine_values.push((pairs[shifted as usize], ItemValue::Bool(value)));
990            });
991
992        Self::BANK_CURSORS
993            .iter()
994            .enumerate()
995            .filter(|(_, bool_val)| detect_bool_action(bool_val, index, before, after))
996            .for_each(|(idx, bool_val)| {
997                let is_right = idx > 0;
998                let push_event = detect_bool_value(bool_val, before);
999                if push_event {
1000                    let mut bank = state.bank;
1001
1002                    if !is_right && bank > BANK_MIN {
1003                        bank -= 1;
1004                    } else if is_right && bank < BANK_MAX {
1005                        bank += 1;
1006                    }
1007
1008                    if bank != state.bank {
1009                        machine_values.push((MachineItem::Bank, ItemValue::U16(bank)));
1010                    }
1011                }
1012            });
1013
1014        machine_values
1015    }
1016
1017    fn ack(state: &mut TascamSurfaceIsochState, machine_value: &(MachineItem, ItemValue)) {
1018        match machine_value {
1019            &(MachineItem::Shift, ItemValue::Bool(value)) => state.shifted = value,
1020            &(MachineItem::Bank, ItemValue::U16(value)) => state.bank = value,
1021            _ => (),
1022        }
1023    }
1024}
1025
1026/// The trait for operation specific to isoch models.
1027trait SurfaceImageIsochOperation {
1028    const SHIFT_ITEM: SurfaceBoolValue;
1029    const SHIFTED_ITEMS: &'static [(SurfaceBoolValue, [MachineItem; 2])];
1030    const BANK_CURSORS: [SurfaceBoolValue; 2];
1031
1032    fn initialize_surface_isoch_state(state: &mut TascamSurfaceIsochState) {
1033        state.shifted = false;
1034        state.shifted_items = vec![false; Self::SHIFTED_ITEMS.len()];
1035        state.bank = 0;
1036    }
1037
1038    fn decode_surface_image_isoch(
1039        machine_values: &mut Vec<(MachineItem, ItemValue)>,
1040        state: &TascamSurfaceIsochState,
1041        index: u32,
1042        before: u32,
1043        after: u32,
1044    ) {
1045        let shifted = if detect_bool_action(&Self::SHIFT_ITEM, index, before, after) {
1046            let shifted = detect_bool_value(&Self::SHIFT_ITEM, before);
1047            machine_values.push((MachineItem::Shift, ItemValue::Bool(shifted)));
1048            shifted
1049        } else {
1050            state.shifted
1051        };
1052
1053        if shifted != state.shifted {
1054            let prev_idx = state.shifted as usize;
1055            let curr_idx = shifted as usize;
1056
1057            Self::SHIFTED_ITEMS
1058                .iter()
1059                .zip(&state.shifted_items)
1060                .filter(|(_, &s)| s)
1061                .for_each(|((_, pairs), _)| {
1062                    machine_values.push((pairs[prev_idx], ItemValue::Bool(false)));
1063                    machine_values.push((pairs[curr_idx], ItemValue::Bool(true)));
1064                });
1065        }
1066
1067        Self::SHIFTED_ITEMS
1068            .iter()
1069            .filter(|(bool_val, _)| detect_bool_action(bool_val, index, before, after))
1070            .for_each(|(bool_val, pairs)| {
1071                let value = detect_bool_value(bool_val, before);
1072                machine_values.push((pairs[shifted as usize], ItemValue::Bool(value)));
1073            });
1074
1075        Self::BANK_CURSORS
1076            .iter()
1077            .enumerate()
1078            .filter(|(_, bool_val)| detect_bool_action(bool_val, index, before, after))
1079            .for_each(|(idx, bool_val)| {
1080                let is_right = idx > 0;
1081                let push_event = detect_bool_value(bool_val, before);
1082                if push_event {
1083                    let mut bank = state.bank;
1084
1085                    if !is_right && bank > BANK_MIN {
1086                        bank -= 1;
1087                    } else if is_right && bank < BANK_MAX {
1088                        bank += 1;
1089                    }
1090
1091                    if bank != state.bank {
1092                        machine_values.push((MachineItem::Bank, ItemValue::U16(bank)));
1093                    }
1094                }
1095            });
1096    }
1097
1098    fn feedback_to_surface_isoch(
1099        state: &mut TascamSurfaceIsochState,
1100        machine_value: &(MachineItem, ItemValue),
1101    ) {
1102        match machine_value {
1103            &(MachineItem::Shift, ItemValue::Bool(value)) => state.shifted = value,
1104            &(MachineItem::Bank, ItemValue::U16(value)) => state.bank = value,
1105            _ => (),
1106        }
1107    }
1108}
1109
1110/// The trait for operation of bank LEDs in surface.
1111trait SurfaceBankLedOperation {
1112    const BANK_LEDS: [&'static [u16]; 4];
1113
1114    fn operate_bank_leds(
1115        state: &mut LedState,
1116        req: &mut FwReq,
1117        node: &mut FwNode,
1118        bank: u16,
1119        timeout_ms: u32,
1120    ) -> Result<(), Error> {
1121        Self::BANK_LEDS
1122            .iter()
1123            .enumerate()
1124            .try_for_each(|(i, positions)| {
1125                let enable = bank == i as u16;
1126                let pos = positions[0];
1127                operate_led_cached(state, req, node, pos, enable, timeout_ms)
1128            })
1129    }
1130}
1131
1132#[cfg(test)]
1133mod test {
1134    use super::*;
1135
1136    #[test]
1137    fn config_flag_serdes() {
1138        #[derive(Debug, Copy, Clone, PartialEq, Eq)]
1139        enum Flag {
1140            A,
1141            B,
1142            C,
1143            N,
1144        }
1145        const TABLE: &[(Flag, u32, u32)] = &[
1146            (Flag::A, 0x00000001, 0x10000000),
1147            (Flag::B, 0x00000020, 0x20000000),
1148            (Flag::C, 0x00000004, 0x00040000),
1149        ];
1150
1151        let mut param = Flag::N;
1152        deserialize_config_flag(&mut param, &TABLE, 0x00000001).unwrap();
1153        assert_eq!(param, Flag::A);
1154
1155        deserialize_config_flag(&mut param, &TABLE, 0x00000000).unwrap_err();
1156
1157        let mut val = 0;
1158        serialize_config_flag(&param, &TABLE, &mut val).unwrap();
1159        assert_eq!(val, 0x10000000);
1160    }
1161
1162    #[test]
1163    fn rack_input_params_serdes() {
1164        let orig = IsochRackInputParameters::default();
1165        let mut raw = [0; RACK_STATE_SIZE];
1166        serialize_input_params(&orig, &mut raw);
1167
1168        let mut target = IsochRackInputParameters::default();
1169        deserialize_input_params(&mut target, &raw);
1170
1171        assert_eq!(target, orig);
1172    }
1173}