firewire-bebob-protocols 0.2.0

Implementation of protocols defined by BridgeCo. AG and application vendors for DM1000, DM1100, and DM1500 ASICs with its BridgeCo. Enhanced Break Out Box (BeBoB) solution.
Documentation
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright (c) 2021 Takashi Sakamoto

//! Protocol implementation for Terratec Phase 88 FW.
//!
//! The module includes structure, enumeration, and trait and its implementation for protocol
//! defined by Terratec for Phase 88 FW.
//!
//! ## Diagram of internal signal flow
//!
//! ```text
//!                        analog-input-1/2 ------+------------> stream-output-1/2
//!                        analog-input-3/4 ------|------------> stream-output-3/4
//!                        analog-input-5/6 ------|-|-+--------> stream-output-5/6
//! line-input-7/8 ---or-> analog-input-7/8 ------|-|-|-+------> stream-output-7/8
//! mic-input-7/8  ---+                           | | | |
//!                        digital-input-1/2 -----|-|-|-|-+----> stream-output-9/10
//!                                               | | | | |
//!                                               v v v v v
//!                                             ++=========++
//!                                             ||         ||
//!                        stream-source-1/2 -> || 12 x  2 ||
//!                        (one source only)    ||  mixer  ||
//!                            ^ ^ ^ ^ ^        ||         ||
//!                            | | | | |        ++---------++
//!                            | | | | |              v
//!                            | | | | |       mixer-output-1/2
//!                            | | | | |              v
//!                            | | | | |     (one destination only)
//!                            | | | | |          | | | | |
//! stream-input-1/2 ----------+-|-|-|-|---------or-|-|-|-|----> analog-output-1/2
//! stream-input-3/4 ------------+-|-|-|-----------or-|-|-|----> analog-output-3/4
//! stream-input-5/6 --------------+-|-|-------------or-|-|----> analog-output-5/6
//! stream-input-7/8 ----------------+-|---------------or-|----> analog-output-7/8
//! stream-input-9/10 -----------------+-----------------or----> digital-output-1/2
//! ```

use super::*;

/// The protocol implementation for media and sampling clock.
#[derive(Default, Debug)]
pub struct Phase88ClkProtocol;

impl MediaClockFrequencyOperation for Phase88ClkProtocol {
    const FREQ_LIST: &'static [u32] = &[32000, 44100, 48000, 88200, 96000];
}

const CLK_SRC_EXT_FB_ID: u8 = 0x08;
const CLK_SRC_EXT_WORD_FB_ID: u8 = 0x09;

impl SamplingClockSourceOperation for Phase88ClkProtocol {
    const DST: SignalAddr = SignalAddr::Subunit(SignalSubunitAddr {
        subunit: MUSIC_SUBUNIT_0,
        plug_id: 0x03,
    });

    const SRC_LIST: &'static [SignalAddr] = &[
        // Internal.
        SignalAddr::Subunit(SignalSubunitAddr {
            subunit: MUSIC_SUBUNIT_0,
            plug_id: 0x05,
        }),
        // S/PDIF input in optical interface.
        SignalAddr::Unit(SignalUnitAddr::Ext(0x04)),
        // Word clock input in BNC interface.
        SignalAddr::Unit(SignalUnitAddr::Ext(0x07)),
    ];

    fn cache_src(
        avc: &BebobAvc,
        params: &mut SamplingClockParameters,
        timeout_ms: u32,
    ) -> Result<(), Error> {
        let mut op = AudioSelector::new(CLK_SRC_EXT_FB_ID, CtlAttr::Current, 0xff);
        avc.status(&AUDIO_SUBUNIT_0_ADDR, &mut op, timeout_ms)?;
        params.src_idx = if op.input_plug_id == 0x00 {
            // Internal.
            0
        } else {
            let mut op = AudioSelector::new(CLK_SRC_EXT_WORD_FB_ID, CtlAttr::Current, 0xff);
            avc.status(&AUDIO_SUBUNIT_0_ADDR, &mut op, timeout_ms)?;
            if op.input_plug_id == 0x00 {
                // S/PDIF.
                1
            } else {
                // Word clock.
                2
            }
        };
        Ok(())
    }

    fn update_src(
        avc: &BebobAvc,
        params: &SamplingClockParameters,
        old: &mut SamplingClockParameters,
        timeout_ms: u32,
    ) -> Result<(), Error> {
        let (is_ext, ext_is_word) = match params.src_idx {
            0 => (0u8, 0u8),
            1 => (0u8, 1u8),
            2 => (1u8, 1u8),
            _ => {
                let msg = format!("Invalid index of source of clock: {}", params.src_idx);
                Err(Error::new(FileError::Inval, &msg))?
            }
        };

        let mut op = AudioSelector::new(CLK_SRC_EXT_WORD_FB_ID, CtlAttr::Current, ext_is_word);
        avc.status(&AUDIO_SUBUNIT_0_ADDR, &mut op, timeout_ms)?;

        let mut op = AudioSelector::new(CLK_SRC_EXT_FB_ID, CtlAttr::Current, is_ext);
        avc.status(&AUDIO_SUBUNIT_0_ADDR, &mut op, timeout_ms)?;

        *old = *params;

        Ok(())
    }
}

/// The protocol implementation of physical input.
#[derive(Default, Debug)]
pub struct Phase88PhysInputProtocol;

impl AvcSelectorOperation for Phase88PhysInputProtocol {
    //  NOTE: source of analog-input-7/8
    const FUNC_BLOCK_ID_LIST: &'static [u8] = &[0x0a];
    const INPUT_PLUG_ID_LIST: &'static [u8] = &[
        0x00, // line
        0x01, // mic
    ];
}

/// The protocol implementation of source of physical input to mixer.
#[derive(Default, Debug)]
pub struct Phase88MixerPhysSourceProtocol;

impl AvcAudioFeatureSpecification for Phase88MixerPhysSourceProtocol {
    const ENTRIES: &'static [(u8, AudioCh)] = &[
        (0x02, AudioCh::Each(0)), // analog-input-1
        (0x02, AudioCh::Each(1)), // analog-input-2
        (0x03, AudioCh::Each(0)), // analog-input-3
        (0x03, AudioCh::Each(1)), // analog-input-4
        (0x04, AudioCh::Each(0)), // analog-input-5
        (0x04, AudioCh::Each(1)), // analog-input-6
        (0x05, AudioCh::Each(0)), // analog-input-7
        (0x05, AudioCh::Each(1)), // analog-input-8
        (0x06, AudioCh::Each(0)), // digital-input-1
        (0x06, AudioCh::Each(1)), // digital-input-2
    ];
}

impl AvcLevelOperation for Phase88MixerPhysSourceProtocol {}

impl AvcMuteOperation for Phase88MixerPhysSourceProtocol {}

/// The protocol implementation of source of stream input to mixer.
#[derive(Default, Debug)]
pub struct Phase88MixerStreamSourceProtocol;

impl AvcAudioFeatureSpecification for Phase88MixerStreamSourceProtocol {
    const ENTRIES: &'static [(u8, AudioCh)] = &[
        (0x07, AudioCh::Each(0)), // stream-source-1
        (0x07, AudioCh::Each(1)), // stream-source-2
    ];
}

impl AvcLevelOperation for Phase88MixerStreamSourceProtocol {}

impl AvcMuteOperation for Phase88MixerStreamSourceProtocol {}

impl AvcSelectorOperation for Phase88MixerStreamSourceProtocol {
    const FUNC_BLOCK_ID_LIST: &'static [u8] = &[0x07];
    const INPUT_PLUG_ID_LIST: &'static [u8] = &[
        0x01, // stream-input-1/2
        0x02, // stream-input-3/4
        0x03, // stream-input-5/6
        0x04, // stream-input-7/8
        0x00, // stream-input-9/10
    ];
}

/// The protocol implementation of mixer output.
#[derive(Default, Debug)]
pub struct Phase88MixerOutputProtocol;

impl AvcAudioFeatureSpecification for Phase88MixerOutputProtocol {
    const ENTRIES: &'static [(u8, AudioCh)] = &[(0x00, AudioCh::Each(0)), (0x01, AudioCh::Each(1))];
}

impl AvcLevelOperation for Phase88MixerOutputProtocol {}

impl AvcMuteOperation for Phase88MixerOutputProtocol {}

const MIXER_OUT_SELECTOR_FB_ID_LIST: [u8; 1] = [0x06];

const MIXER_OUT_SELECTOR_ID_LIST: [u8; 6] = [
    0x01, // analog-output-1/2
    0x02, // analog-output-3/4
    0x03, // analog-output-5/6
    0x04, // analog-output-7/8
    0x00, // digital-output-1/2
    0x05, // no destination
];

const PHYS_OUTPUT_SELECTOR_FB_ID_LIST: [u8; 5] = [
    0x01, // analog-output-1/2
    0x02, // analog-output-3/4
    0x03, // analog-output-5/6
    0x04, // analog-output-7/8
    0x05, // digital-output-1/2
];

// NOTE: "mixer-output-1/2", "stream-input" corresponds to the channel
#[allow(dead_code)]
const PHYS_OUTPUT_SELECTOR_ID_LIST: [u8; 2] = [0x00, 0x01];

// NOTE: destination of mixer output.
impl AvcSelectorOperation for Phase88MixerOutputProtocol {
    const FUNC_BLOCK_ID_LIST: &'static [u8] = &MIXER_OUT_SELECTOR_FB_ID_LIST;
    const INPUT_PLUG_ID_LIST: &'static [u8] = &MIXER_OUT_SELECTOR_ID_LIST;

    fn cache_selectors(
        avc: &BebobAvc,
        params: &mut AvcSelectorParameters,
        timeout_ms: u32,
    ) -> Result<(), Error> {
        assert_eq!(params.selectors.len(), 1);

        let func_block_id = MIXER_OUT_SELECTOR_FB_ID_LIST[0];

        let mut op = AudioSelector::new(func_block_id, CtlAttr::Current, 0xff);
        avc.status(&AUDIO_SUBUNIT_0_ADDR, &mut op, timeout_ms)?;

        MIXER_OUT_SELECTOR_ID_LIST
            .iter()
            .position(|&input_plug_id| input_plug_id == op.input_plug_id)
            .ok_or_else(|| {
                let msg = format!(
                    "Unexpected value for index of input plug number: {}",
                    op.input_plug_id
                );
                Error::new(FileError::Io, &msg)
            })
            .map(|pos| params.selectors[0] = pos)
    }

    fn update_selectors(
        avc: &BebobAvc,
        params: &AvcSelectorParameters,
        old: &mut AvcSelectorParameters,
        timeout_ms: u32,
    ) -> Result<(), Error> {
        let selector = params.selectors[0];

        if selector != old.selectors[0] {
            let func_block_id = MIXER_OUT_SELECTOR_FB_ID_LIST[0];
            let input_plug_id = MIXER_OUT_SELECTOR_ID_LIST
                .iter()
                .nth(selector)
                .ok_or_else(|| {
                    let msg = format!(
                        "Invalid argument for index of input plug number: {}",
                        selector
                    );
                    Error::new(FileError::Inval, &msg)
                })
                .copied()?;

            let mut op = AudioSelector::new(func_block_id, CtlAttr::Current, input_plug_id);
            avc.control(&AUDIO_SUBUNIT_0_ADDR, &mut op, timeout_ms)?;

            PHYS_OUTPUT_SELECTOR_FB_ID_LIST
                .iter()
                .enumerate()
                .try_for_each(|(i, &func_block_id)| {
                    let plug_id_idx = if input_plug_id == 0x05 {
                        // The mixer-output-1/2 is not available. Set corresponding stream-input to
                        // physical outputs as source.
                        0
                    } else {
                        if i == 0 {
                            // The mixer-output-1/2 is configured for source of the physical output.
                            0
                        } else {
                            // The stream-input-1/2 is configured for source of the physical output.
                            1
                        }
                    };

                    let val = PHYS_OUTPUT_SELECTOR_ID_LIST[plug_id_idx];
                    let mut op = AudioSelector::new(func_block_id, CtlAttr::Current, val);
                    avc.control(&AUDIO_SUBUNIT_0_ADDR, &mut op, timeout_ms)
                })?;

            old.selectors[0] = selector;
        }

        Ok(())
    }
}