firewire_dice_protocols/
tcelectronic.rs

1// SPDX-License-Identifier: LGPL-3.0-or-later
2// Copyright (c) 2020 Takashi Sakamoto
3
4//! Protocol defined by TC Electronic.
5//!
6//! The module includes structure, enumeration, and trait and its implementation for protocol
7//! defined by TC Electronic.
8//!
9//! In Konnekt series, the accessible memory space is separated to several segments.
10
11pub mod desktop;
12pub mod shell;
13pub mod studio;
14
15pub mod ch_strip;
16pub mod reverb;
17
18use {
19    super::{tcat::*, *},
20    ta1394_avc_general::{general::*, *},
21};
22
23// The base offset to operate functions in Konnekt series.
24const BASE_OFFSET: usize = 0x00a01000;
25
26/// The generic structure for segment. In Konnekt series, the accessible memory space is separated
27/// to several segments. This structure expresses them.
28#[derive(Debug, Clone, PartialEq, Eq)]
29pub struct TcKonnektSegment<U> {
30    /// Intermediate structured data for parameters.
31    pub data: U,
32    /// Raw byte data for memory layout in hardware.
33    raw: Vec<u8>,
34}
35
36/// Serialize and deserialize for segment in TC Konnekt protocol.
37pub trait TcKonnektSegmentSerdes<T> {
38    /// The name of segment.
39    const NAME: &'static str;
40
41    /// The offset of segment.
42    const OFFSET: usize;
43
44    /// The size of segment.
45    const SIZE: usize;
46
47    /// Serialize for parameter.
48    fn serialize(params: &T, raw: &mut [u8]) -> Result<(), String>;
49
50    /// Deserialize for parameter.
51    fn deserialize(params: &mut T, raw: &[u8]) -> Result<(), String>;
52}
53
54fn generate_error(segment_name: &str, cause: &str, raw: &[u8]) -> Error {
55    let msg = format!(
56        "segment: {}, cause: '{}', raw: {:02x?}",
57        segment_name, cause, raw
58    );
59    Error::new(GeneralProtocolError::VendorDependent, &msg)
60}
61
62/// Operation to cache content of segment in TC Electronic Konnekt series.
63pub trait TcKonnektSegmentOperation<T>: TcatOperation + TcKonnektSegmentSerdes<T> {
64    /// Cache whole segment and deserialize for parameters.
65    fn cache_whole_segment(
66        req: &FwReq,
67        node: &FwNode,
68        segment: &mut TcKonnektSegment<T>,
69        timeout_ms: u32,
70    ) -> Result<(), Error> {
71        // NOTE: Something wrong to implement Default trait.
72        assert_eq!(segment.raw.len(), Self::SIZE);
73
74        Self::read(
75            req,
76            node,
77            BASE_OFFSET + Self::OFFSET,
78            &mut segment.raw,
79            timeout_ms,
80        )?;
81
82        Self::deserialize(&mut segment.data, &segment.raw)
83            .map_err(|cause| generate_error(Self::NAME, &cause, &segment.raw))
84    }
85}
86
87impl<O: TcatOperation + TcKonnektSegmentSerdes<T>, T> TcKonnektSegmentOperation<T> for O {}
88
89/// Operation to update content of segment in TC Electronic Konnekt series.
90pub trait TcKonnektMutableSegmentOperation<T>: TcatOperation + TcKonnektSegmentSerdes<T> {
91    /// Update part of segment for any change at the parameters.
92    fn update_partial_segment(
93        req: &FwReq,
94        node: &FwNode,
95        params: &T,
96        segment: &mut TcKonnektSegment<T>,
97        timeout_ms: u32,
98    ) -> Result<(), Error> {
99        // NOTE: Something wrong to implement Default trait.
100        assert_eq!(segment.raw.len(), Self::SIZE);
101
102        let mut raw = segment.raw.clone();
103        Self::serialize(params, &mut raw)
104            .map_err(|cause| generate_error(Self::NAME, &cause, &segment.raw))?;
105
106        (0..Self::SIZE).step_by(4).try_for_each(|pos| {
107            let new = &mut raw[pos..(pos + 4)];
108            if new != &segment.raw[pos..(pos + 4)] {
109                Self::write(req, node, BASE_OFFSET + Self::OFFSET + pos, new, timeout_ms)
110                    .map(|_| segment.raw[pos..(pos + 4)].copy_from_slice(new))
111            } else {
112                Ok(())
113            }
114        })?;
115
116        Self::deserialize(&mut segment.data, &raw)
117            .map_err(|cause| generate_error(Self::NAME, &cause, &segment.raw))
118    }
119
120    /// Update whole segment by the parameters.
121    fn update_whole_segment(
122        req: &FwReq,
123        node: &FwNode,
124        params: &T,
125        segment: &mut TcKonnektSegment<T>,
126        timeout_ms: u32,
127    ) -> Result<(), Error> {
128        // NOTE: Something wrong to implement Default trait.
129        assert_eq!(segment.raw.len(), Self::SIZE);
130
131        let mut raw = segment.raw.clone();
132        Self::serialize(&params, &mut raw)
133            .map_err(|cause| generate_error(Self::NAME, &cause, &segment.raw))?;
134
135        Self::write(req, node, BASE_OFFSET + Self::OFFSET, &mut raw, timeout_ms)?;
136
137        segment.raw.copy_from_slice(&raw);
138        Self::deserialize(&mut segment.data, &segment.raw)
139            .map_err(|cause| generate_error(Self::NAME, &cause, &segment.raw))
140    }
141}
142
143/// Operation for segment in which any change is notified in TC Electronic Konnekt series.
144pub trait TcKonnektNotifiedSegmentOperation<T> {
145    const NOTIFY_FLAG: u32;
146
147    /// Check message to be notified or not.
148    fn is_notified_segment(_: &TcKonnektSegment<T>, msg: u32) -> bool {
149        msg & Self::NOTIFY_FLAG > 0
150    }
151}
152
153fn serialize_position<T: Eq + std::fmt::Debug>(
154    entries: &[T],
155    entry: &T,
156    raw: &mut [u8],
157    label: &str,
158) -> Result<(), String> {
159    assert!(raw.len() >= 4);
160
161    entries
162        .iter()
163        .position(|t| entry.eq(t))
164        .ok_or_else(|| format!("{} {:?} is not supported", label, entry))
165        .map(|pos| serialize_usize(&pos, raw))
166}
167
168fn deserialize_position<T: Copy + Eq + std::fmt::Debug>(
169    entries: &[T],
170    entry: &mut T,
171    raw: &[u8],
172    label: &str,
173) -> Result<(), String> {
174    assert!(raw.len() >= 4);
175
176    let mut val = 0usize;
177    deserialize_usize(&mut val, raw);
178
179    entries
180        .iter()
181        .nth(val as usize)
182        .ok_or_else(|| format!("{} not found for index {}", label, val))
183        .map(|&e| *entry = e)
184}
185
186/// The state of FireWire LED in TC Konnekt Protocol.
187#[derive(Debug, Copy, Clone, PartialEq, Eq)]
188pub enum FireWireLedState {
189    /// Off.
190    Off,
191    /// On.
192    On,
193    /// Blinking fastly.
194    BlinkFast,
195    /// Blinking slowly.
196    BlinkSlow,
197}
198
199impl Default for FireWireLedState {
200    fn default() -> Self {
201        Self::Off
202    }
203}
204
205const FW_LED_STATES: &[FireWireLedState] = &[
206    FireWireLedState::Off,
207    FireWireLedState::On,
208    FireWireLedState::BlinkSlow,
209    FireWireLedState::BlinkFast,
210];
211
212const FW_LED_STATE_LABEL: &str = "FireWire LED state";
213
214fn serialize_fw_led_state(state: &FireWireLedState, raw: &mut [u8]) -> Result<(), String> {
215    serialize_position(FW_LED_STATES, state, raw, FW_LED_STATE_LABEL)
216}
217
218fn deserialize_fw_led_state(state: &mut FireWireLedState, raw: &[u8]) -> Result<(), String> {
219    deserialize_position(FW_LED_STATES, state, raw, FW_LED_STATE_LABEL)
220}
221
222/// Available rate for sampling clock in standalone mode in TC Konnekt protocol.
223#[derive(Debug, Copy, Clone, PartialEq, Eq)]
224pub enum TcKonnektStandaloneClockRate {
225    /// At 44.1 kHz.
226    R44100,
227    /// At 48.0 kHz.
228    R48000,
229    /// At 88.2 kHz.
230    R88200,
231    /// At 96.0 kHz.
232    R96000,
233}
234
235impl Default for TcKonnektStandaloneClockRate {
236    fn default() -> Self {
237        Self::R44100
238    }
239}
240
241fn serialize_standalone_clock_rate(
242    rate: &TcKonnektStandaloneClockRate,
243    raw: &mut [u8],
244) -> Result<(), String> {
245    assert!(raw.len() >= 4);
246
247    let val = match rate {
248        TcKonnektStandaloneClockRate::R96000 => 4,
249        TcKonnektStandaloneClockRate::R88200 => 3,
250        TcKonnektStandaloneClockRate::R48000 => 2,
251        TcKonnektStandaloneClockRate::R44100 => 1,
252    };
253
254    serialize_u32(&val, raw);
255
256    Ok(())
257}
258
259fn deserialize_standalone_clock_rate(
260    rate: &mut TcKonnektStandaloneClockRate,
261    raw: &[u8],
262) -> Result<(), String> {
263    assert!(raw.len() >= 4);
264
265    let mut val = 0u32;
266    deserialize_u32(&mut val, raw);
267
268    *rate = match val {
269        4 => TcKonnektStandaloneClockRate::R96000,
270        3 => TcKonnektStandaloneClockRate::R88200,
271        2 => TcKonnektStandaloneClockRate::R48000,
272        1 => TcKonnektStandaloneClockRate::R44100,
273        _ => Err(format!(
274            "Unexpected value for standalone clock rate: {}",
275            val
276        ))?,
277    };
278
279    Ok(())
280}
281
282/// Channel and control code of MIDI event in TC Konnekt protocol.
283#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
284pub struct TcKonnektMidiMsgParams {
285    /// The channel for MIDI message.
286    pub ch: u8,
287    /// The control code for MIDI message.
288    pub cc: u8,
289}
290
291/// MIDI sender settings in TC Konnekt protocol.
292#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
293pub struct TcKonnektMidiSender {
294    /// The parameter of MIDI message generated normally.
295    pub normal: TcKonnektMidiMsgParams,
296    /// The parameter of MIDI message generated when knob is pushed.
297    pub pushed: TcKonnektMidiMsgParams,
298    /// Whether to send MIDI message to physical MIDI port.
299    pub send_to_port: bool,
300    /// Whether to deliver MIDI message by tx stream.
301    pub send_to_stream: bool,
302}
303
304impl TcKonnektMidiSender {
305    pub(crate) const SIZE: usize = 36;
306}
307
308fn serialize_midi_sender(sender: &TcKonnektMidiSender, raw: &mut [u8]) -> Result<(), String> {
309    assert!(raw.len() >= TcKonnektMidiSender::SIZE);
310
311    serialize_u8(&sender.normal.ch, &mut raw[..4]);
312    serialize_u8(&sender.normal.cc, &mut raw[4..8]);
313    serialize_u8(&sender.pushed.ch, &mut raw[12..16]);
314    serialize_u8(&sender.pushed.cc, &mut raw[16..20]);
315    serialize_bool(&sender.send_to_port, &mut raw[24..28]);
316    serialize_bool(&sender.send_to_stream, &mut raw[28..32]);
317
318    Ok(())
319}
320
321fn deserialize_midi_sender(sender: &mut TcKonnektMidiSender, raw: &[u8]) -> Result<(), String> {
322    assert!(raw.len() >= TcKonnektMidiSender::SIZE);
323
324    deserialize_u8(&mut sender.normal.ch, &raw[..4]);
325    deserialize_u8(&mut sender.normal.cc, &raw[4..8]);
326    deserialize_u8(&mut sender.pushed.ch, &raw[12..16]);
327    deserialize_u8(&mut sender.pushed.cc, &raw[16..20]);
328    deserialize_bool(&mut sender.send_to_port, &raw[24..28]);
329    deserialize_bool(&mut sender.send_to_stream, &raw[28..32]);
330
331    Ok(())
332}
333
334/// Loaded program in TC Konnekt series.
335#[derive(Debug, Copy, Clone, PartialEq, Eq)]
336pub enum TcKonnektLoadedProgram {
337    /// Program 1.
338    P0,
339    /// Program 2.
340    P1,
341    /// Program 3.
342    P2,
343}
344
345impl Default for TcKonnektLoadedProgram {
346    fn default() -> Self {
347        Self::P0
348    }
349}
350
351const LOADED_PROGRAMS: &[TcKonnektLoadedProgram] = &[
352    TcKonnektLoadedProgram::P0,
353    TcKonnektLoadedProgram::P1,
354    TcKonnektLoadedProgram::P2,
355];
356
357const LOADED_PROGRAM_LABEL: &str = "loaded program";
358
359fn serialize_loaded_program(prog: &TcKonnektLoadedProgram, raw: &mut [u8]) -> Result<(), String> {
360    serialize_position(LOADED_PROGRAMS, prog, raw, LOADED_PROGRAM_LABEL)
361}
362
363fn deserialize_loaded_program(prog: &mut TcKonnektLoadedProgram, raw: &[u8]) -> Result<(), String> {
364    deserialize_position(LOADED_PROGRAMS, prog, raw, LOADED_PROGRAM_LABEL)
365}
366
367/// AV/C operation defined by TC Electronic. This is not used in TC Konnekt series.
368#[derive(Default, Debug)]
369pub struct TcAvcCmd {
370    pub class_id: u8,
371    pub sequence_id: u8,
372    pub command_id: u16,
373    pub arguments: Vec<u8>,
374    op: VendorDependent,
375}
376
377// From open source by Weiss engineering for ALSA dice driver.
378// class_id: 0 -> common
379//   command_id: 6 -> squawk
380//   command_id: 7 -> self identify
381//   command_id: 8 -> codeload
382// class_id: 1 -> general
383//   command_id: 1 -> program identify
384//   command_id: 2 -> tuner frequency
385//   command_id: 3 -> tuner preset
386//   command_id: 4 -> tuner scan mode
387//   command_id: 5 -> tuner tuner output
388//   command_id: 10 -> tuner raw serial
389
390fn tc_avc_cmd_prepare_vendor_dependent_data(cmd: &mut TcAvcCmd) {
391    cmd.op.data.resize(4 + cmd.arguments.len(), 0);
392
393    cmd.op.data[0] = cmd.class_id;
394    cmd.op.data[1] = 0xff;
395    cmd.op.data[2] = (0xff & (cmd.command_id >> 8)) as u8;
396    cmd.op.data[3] = (0xff & cmd.command_id) as u8;
397    cmd.op.data[4..].copy_from_slice(&cmd.arguments);
398}
399
400fn tc_avc_cmd_parse_vendor_dependent_data(cmd: &mut TcAvcCmd) {
401    cmd.class_id = cmd.op.data[0];
402    cmd.sequence_id = cmd.op.data[1];
403    cmd.command_id = ((cmd.op.data[2] as u16) << 8) | (cmd.op.data[3] as u16);
404    cmd.arguments = cmd.op.data[4..].to_owned();
405}
406
407impl TcAvcCmd {
408    pub fn new(company_id: &[u8; 3]) -> Self {
409        Self {
410            class_id: Default::default(),
411            sequence_id: Default::default(),
412            command_id: Default::default(),
413            arguments: Default::default(),
414            op: VendorDependent {
415                company_id: company_id.clone(),
416                // 4 elements at least.
417                data: vec![0; 4],
418            },
419        }
420    }
421}
422
423impl AvcOp for TcAvcCmd {
424    const OPCODE: u8 = VendorDependent::OPCODE;
425}
426
427impl AvcStatus for TcAvcCmd {
428    fn build_operands(&mut self, addr: &AvcAddr) -> Result<Vec<u8>, AvcCmdBuildError> {
429        tc_avc_cmd_prepare_vendor_dependent_data(self);
430        AvcStatus::build_operands(&mut self.op, addr)
431    }
432
433    fn parse_operands(&mut self, addr: &AvcAddr, operands: &[u8]) -> Result<(), AvcRespParseError> {
434        AvcStatus::parse_operands(&mut self.op, addr, operands)
435            .map(|_| tc_avc_cmd_parse_vendor_dependent_data(self))
436    }
437}
438
439impl AvcControl for TcAvcCmd {
440    fn build_operands(&mut self, addr: &AvcAddr) -> Result<Vec<u8>, AvcCmdBuildError> {
441        tc_avc_cmd_prepare_vendor_dependent_data(self);
442        AvcControl::build_operands(&mut self.op, addr)
443    }
444
445    fn parse_operands(&mut self, addr: &AvcAddr, operands: &[u8]) -> Result<(), AvcRespParseError> {
446        AvcControl::parse_operands(&mut self.op, addr, operands)
447            .map(|_| tc_avc_cmd_parse_vendor_dependent_data(self))
448    }
449}
450
451#[cfg(test)]
452mod test {
453    use super::*;
454
455    #[test]
456    fn tc_avc_operation_operands() {
457        let company_id = [0xfe, 0xdc, 0xba];
458        let operands = [0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10];
459        let mut op = TcAvcCmd::new(&company_id);
460        AvcStatus::parse_operands(&mut op, &AvcAddr::Unit, &operands).unwrap();
461        assert_eq!(op.op.company_id, company_id);
462        assert_eq!(op.class_id, operands[3]);
463        assert_eq!(op.sequence_id, operands[4]);
464        assert_eq!(
465            op.command_id,
466            ((operands[5] as u16) << 8) | (operands[6] as u16)
467        );
468        assert_eq!(op.arguments, operands[7..]);
469
470        let target = AvcStatus::build_operands(&mut op, &AvcAddr::Unit).unwrap();
471        assert_eq!(&target[..4], &operands[..4]);
472        // The value of sequence_id field is never matched.
473        assert_eq!(&target[5..], &operands[5..]);
474
475        let target = AvcControl::build_operands(&mut op, &AvcAddr::Unit).unwrap();
476        assert_eq!(&target[..4], &operands[..4]);
477        // The value of sequence_id field is never matched.
478        assert_eq!(&target[5..], &operands[5..]);
479
480        let mut op = TcAvcCmd::new(&company_id);
481        AvcControl::parse_operands(&mut op, &AvcAddr::Unit, &operands).unwrap();
482        assert_eq!(op.op.company_id, company_id);
483        assert_eq!(op.class_id, operands[3]);
484        assert_eq!(op.sequence_id, operands[4]);
485        assert_eq!(
486            op.command_id,
487            ((operands[5] as u16) << 8) | (operands[6] as u16)
488        );
489        assert_eq!(op.arguments, operands[7..]);
490    }
491}