firewire_dice_protocols/focusrite/
spro24dsp.rs

1// SPDX-License-Identifier: LGPL-3.0-or-later
2// Copyright (c) 2021 Takashi Sakamoto
3
4//! Protocol specific to Focusrite Saffire Pro 24 DSP.
5//!
6//! The module includes structure, enumeration, and trait and its implementation for protocol
7//! defined by Focusrite for Saffire Pro 24 DSP.
8//!
9//! ## Diagram of internal signal flow for Saffire Pro 24 DSP.
10//!
11//! I note that optical input interface is available exclusively for ADAT input and S/PDIF input.
12//!
13//! ```text
14//!
15//! XLR input 1 ------+---------+
16//! Phone input 1-----+         |
17//!                             |
18//! XLR input 2 ------+---------+
19//! Phone input 2 ----+         |
20//!                             +------------------> analog-input-1/2
21//! Phone input 3/4 -------------------------------> analog-input-3/4
22//! Phone input 5/6 -------------------------------> analog-input-5/6
23//! Coaxial input 1/2 -----------------------------> spdif-input-1/2
24//! Optical input --------------or-----------------> spdif-input-3/4
25//!                             +------------------> adat-input-1..8
26//!
27//!                          ++=============++
28//! analog-input-1/2 ------> ||   46 x 46   || ----> analog-output-1/2
29//! analog-input-3/4 ------> ||   router    || ----> analog-output-3/4
30//! analog-input-5/6 ------> ||   up to     || ----> analog-output-5/6
31//! spdif-input-1/2 -------> || 128 entries || ----> spdif-output-1/2
32//! spdif-input-3/4 -------> ||             ||
33//! adat-input-1/2 --------> ||             ||
34//! adat-input-3/4 --------> ||             ||
35//! adat-input-5/6 --------> ||             ||
36//! adat-input-7/8 --------> ||             ||
37//!                          ||             ||
38//! stream-input-1/2 ------> ||             || ----> stream-output-1/2
39//! stream-input-3/4 ------> ||             || ----> stream-output-3/4
40//! stream-input-5/6 ------> ||             || ----> stream-output-5/6
41//! stream-input-7/8 ------> ||             || ----> stream-output-7/8
42//!                          ||             || ----> stream-output-9/10
43//!                          ||             || ----> stream-output-11/12
44//!                          ||             || ----> stream-output-13/14
45//!                          ||             || ----> stream-output-15/16
46//!                          ||             ||
47//! mixer-output-1/2 ------> ||             || ----> mixer-input-1/2
48//! mixer-output-3/4 ------> ||             || ----> mixer-input-3/4
49//! mixer-output-5/6 ------> ||             || ----> mixer-input-5/6
50//! mixer-output-7/8 ------> ||             || ----> mixer-input-7/8
51//! mixer-output-9/10 -----> ||             || ----> mixer-input-9/10
52//! mixer-output-11/12 ----> ||             || ----> mixer-input-11/12
53//! mixer-output-13/14 ----> ||             || ----> mixer-input-13/14
54//! mixer-output-15/16 ----> ||             || ----> mixer-input-15/16
55//!                          ||             || ----> mixer-input-17/18
56//!                          ||             ||
57//! ch-strip-output-1/2 ---> ||             || ----> ch-strip-input-1/2
58//! reverb-output-1/2 -----> ||             || ----> reverb-input-1/2
59//!                          ++=============++
60//!
61//!                          ++=============++
62//! mixer-input-1/2 -------> ||             || ----> mixer-output-1/2
63//! mixer-input-3/4 -------> ||             || ----> mixer-output-3/4
64//! mixer-input-5/6 -------> ||             || ----> mixer-output-5/6
65//! mixer-input-7/8 -------> ||    mixer    || ----> mixer-output-7/8
66//! mixer-input-9/10 ------> ||             || ----> mixer-output-9/10
67//! mixer-input-11/12 -----> ||   18 x 16   || ----> mixer-output-10/12
68//! mixer-input-13/14 -----> ||             || ----> mixer-output-12/14
69//! mixer-input-15/16 -----> ||             || ----> mixer-output-14/16
70//! mixer-input-17/18 -----> ||             ||
71//!                          ++=============++
72//!
73//!                          ++=============++
74//!                          ||             || ----> Phone output 1/2
75//!                          ||             ||
76//! analog-output-1/2 -----> ||   output    || --+-> Phone output 3/4
77//! analog-output-3/4 -----> ||             ||   +-> Headphone output 1/2
78//! analog-output-5/6 -----> ||   group     ||
79//!                          ||             || --+-> Phone output 5/6
80//!                          ||             ||   +-> Headphone output 3/4
81//!                          ++=============++
82//!
83//!                          ++=============++
84//!                          ||  equalizer  ||
85//! ch-strip-input-1/2 ----> ||      &      || ----> ch-strip-output-1/2
86//!                          || compressor  ||
87//!                          ++=============++
88//!
89//!                          ++=============++
90//! reverb-input-1/2 ------> ||   reverb    || ----> reverb-output-1/2
91//!                          ++=============++
92//!
93//! ```
94//!
95//! ## Data layout in TCAT application section for DSP effect
96//!
97//! The offset of TCAT application section is 0x6dd4. Any change by write transaction is firstly
98//! effective when software message is written to 0x5ec.
99//!
100//! ### Data Layout for DSP effect
101//!
102//! * 0x000: DSP enable/disable (sw msg: 0x1c)
103//! * 0x008: flags for channel strip effects (sw msg: 0x05)
104//!     * 0x00000001: ch 0 equalizer enable
105//!     * 0x00010000: ch 1 equalizer enable
106//!     * 0x00000002: ch 0 compressor enable
107//!     * 0x00020000: ch 1 compressor enable
108//!     * 0x00000004: ch 0 equalizer after compressor
109//!     * 0x00040000: ch 1 equalizer after compressor
110//!
111//! blk 0 | blk 2 | blk 4 | blk 6 | count   | purpose                         | sw msg |
112//! ----- | ----- | ----- | ----- | --------| ------------------------------- | ------ |
113//! 0x120 | 0x230 | 0x340 | 0x450 | 6 quads | ch 0 comp                       |   0x06 |
114//! 0x138 | 0x248 | 0x358 | 0x468 | 2 quads | ch 0/ch 1 eq output             |   0z09 |
115//! 0x140 | 0x250 | 0x360 | 0x470 | 5 quads | ch 0 eq low freq filter         |   0x0c |
116//! 0x154 | 0x264 | 0x374 | 0x484 | 5 quads | ch 0 eq low-middle freq filter  |   0x0f |
117//! 0x168 | 0x278 | 0x388 | 0x498 | 5 quads | ch 0 eq high-middle freq filter |   0x12 |
118//! 0x17c | 0x28c | 0x39c | 0x4ac | 5 quads | ch 0 eq high freq filter        |   0x15 |
119//! 0x190 | 0x2a0 | 0x3b0 | 0x4c0 | 6 quads | ch 0 reverb                     |   0x1a |
120//!
121//! blk 1 | blk 3 | blk 5 | blk 7 | count   | purpose                         | sw msg |
122//! ----- | ----- | ----- | ----- | --------| ------------------------------- | ------ |
123//! 0x1a8 | 0x2b8 | 0x3c8 | 0x4d8 | 6 quads | ch 1 comp                       |   0x07 |
124//! 0x1c0 | 0x2d0 | 0x3e0 | 0x4f0 | 2 quads | ch 0/ch 1 eq output             |   0x0a |
125//! 0x1c8 | 0x2d8 | 0x3e8 | 0x4f8 | 5 quads | ch 1 eq low freq filter         |   0x0d |
126//! 0x1dc | 0x2ec | 0x3fc | 0x50c | 5 quads | ch 1 eq low-middle freq filter  |   0x10 |
127//! 0x1f0 | 0x300 | 0x410 | 0x520 | 5 quads | ch 1 eq high-middle freq filter |   0x13 |
128//! 0x204 | 0x314 | 0x424 | 0x534 | 5 quads | ch 1 eq high freq filter        |   0x16 |
129//! 0x218 | 0x328 | 0x438 | 0x548 | 6 quads | ch 1 reverb                     |   0x1a |
130//!
131//! ### Compressor coefficients (6 quadlets)
132//!
133//! Actually change to block 2 is effective.
134//!
135//! quad | purpose          | min value  | max value  | min repr | max repr |
136//! ---- | -------------    | ---------- | ---------- | -------- | -------- |
137//!    0 | unknown          | 0x3f800000 | 0x3f800000 |    -     |    -     |
138//!    1 | output volume    | 0x00000000 | 0x42800000 | -36.0 dB | +36.0 dB |
139//!    2 | threshold        | 0xbfa00000 | 0x00000000 | -80.0 dB |   0.0 dB |
140//!    3 | ratio            | 0x3d000000 | 0x3f000000 |  1.1:1   |  inf:1   |
141//!    4 | attack           | 0xbf800000 | 0xbf700000 |   2ms    |  100ms   |
142//!    5 | release          | 0x3f700000 | 0x3f800000 |  100ms   |   3s     |
143//!
144//! ### Equalizer output coefficients (2 quadlets)
145//!
146//! Actually change to block 2 is effective.
147//!
148//! quad | purpose          | min value  | max value  | min repr | max repr |
149//! ---- | ---------------- | ---------- | ---------- | -------- | -------- |
150//!    0 | left volume      | 0x00000000 | 0x3f800000 | -36.0 dB | +36.0 dB |
151//!    1 | right volume     | 0x00000000 | 0x3f800000 | -36.0 dB | +36.0 dB |
152//!
153//! ### Equalizer coefficients (5 quadlets)
154//!
155//! Actually change to block 2 is effective.
156//!
157//! quad | purpose          | min value  | max value  | min repr | max repr |
158//! ---- | ---------------- | ---------- | ---------- | -------- | -------- |
159//!    0 | unknown          |     -      |     -      |    -     |    -     |
160//!    1 | unknown          |     -      |     -      |    -     |    -     |
161//!    2 | unknown          |     -      |     -      |    -     |    -     |
162//!    3 | unknown          |     -      |     -      |    -     |    -     |
163//!    4 | unknown          |     -      |     -      |    -     |    -     |
164//!
165//! ### Reverb coefficients (6 quadlets)
166//!
167//! Actually change to block 3 is effective.
168//!
169//! quad | purpose          | min value  | max value  | min repr | max repr |
170//! ---- | ---------------- | ---------- | ---------- | -------- | -------- |
171//!    0 | room size        | 0x00000000 | 0x3f800000 | 0 %      | 100 %    |
172//!    1 | air              | 0x00000000 | 0x3f800000 | 100 %    | 0 %      |
173//!    2 | enabled          | 0x00000000 | 0x3f800000 | false    | true     |
174//!    3 | disabled         | 0x00000000 | 0x3f800000 | false    | true     |
175//!    4 | pre filter value | 0x00000000 | 0x3f800000 | 5.0      | 0.0      |
176//!    5 | pre filter sign  | 0x00000000 | 0x3f800000 | negative | positive |
177
178use super::{tcat::tcd22xx_spec::*, *};
179
180/// Protocol implementation specific to Saffire Pro 24 DSP.
181#[derive(Default, Debug)]
182pub struct SPro24DspProtocol;
183
184impl TcatOperation for SPro24DspProtocol {}
185
186impl TcatGlobalSectionSpecification for SPro24DspProtocol {}
187
188impl TcatExtensionOperation for SPro24DspProtocol {}
189
190impl Tcd22xxSpecification for SPro24DspProtocol {
191    const INPUTS: &'static [Input] = &[
192        Input {
193            id: SrcBlkId::Ins0,
194            offset: 2,
195            count: 2,
196            label: Some("Mic"),
197        },
198        Input {
199            id: SrcBlkId::Ins0,
200            offset: 0,
201            count: 2,
202            label: Some("Line"),
203        },
204        Input {
205            id: SrcBlkId::Ins0,
206            offset: 8,
207            count: 2,
208            label: Some("Ch-strip"),
209        },
210        // Input{id: SrcBlkId::Ins0, offset: 4, count: 2, label: Some("Ch-strip")}, at 88.2/96.0 kHz.
211        Input {
212            id: SrcBlkId::Ins0,
213            offset: 14,
214            count: 2,
215            label: Some("Reverb"),
216        },
217        // Input{id: SrcBlkId::Ins0, offset: 6, count: 2, label: Some("Reverb")}, at 88.2/96.0 kHz.
218        Input {
219            id: SrcBlkId::Aes,
220            offset: 6,
221            count: 2,
222            label: Some("S/PDIF-coax"),
223        },
224        // NOTE: share the same optical interface.
225        Input {
226            id: SrcBlkId::Adat,
227            offset: 0,
228            count: 8,
229            label: None,
230        },
231        Input {
232            id: SrcBlkId::Aes,
233            offset: 4,
234            count: 2,
235            label: Some("S/PDIF-opt"),
236        },
237    ];
238
239    const OUTPUTS: &'static [Output] = &[
240        Output {
241            id: DstBlkId::Ins0,
242            offset: 0,
243            count: 6,
244            label: None,
245        },
246        Output {
247            id: DstBlkId::Aes,
248            offset: 6,
249            count: 2,
250            label: Some("S/PDIF-coax"),
251        },
252        Output {
253            id: DstBlkId::Ins0,
254            offset: 8,
255            count: 2,
256            label: Some("Ch-strip"),
257        },
258        // Output{id: DstBlkId::Ins0, offset: 4, count: 2, label: Some("Ch-strip")}, at 88.2/96.0 kHz.
259        Output {
260            id: DstBlkId::Ins0,
261            offset: 14,
262            count: 2,
263            label: Some("Reverb"),
264        },
265        // Output{id: DstBlkId::Ins0, offset: 6, count: 2, label: Some("Reverb")}, at 88.2/96.0 kHz.
266    ];
267
268    // NOTE: The first 4 entries in router section are used to display hardware metering.
269    const FIXED: &'static [SrcBlk] = &[
270        SrcBlk {
271            id: SrcBlkId::Ins0,
272            ch: 2,
273        },
274        SrcBlk {
275            id: SrcBlkId::Ins0,
276            ch: 3,
277        },
278        SrcBlk {
279            id: SrcBlkId::Ins0,
280            ch: 0,
281        },
282        SrcBlk {
283            id: SrcBlkId::Ins0,
284            ch: 1,
285        },
286    ];
287}
288
289impl SaffireproSwNoticeOperation for SPro24DspProtocol {
290    const SW_NOTICE_OFFSET: usize = 0x05ec;
291}
292
293impl SaffireproOutGroupSpecification for SPro24DspProtocol {
294    const OUT_GROUP_STATE_OFFSET: usize = 0x000c;
295
296    const ENTRY_COUNT: usize = 6;
297    const HAS_VOL_HWCTL: bool = false;
298
299    const SRC_NOTICE: u32 = 0x00000001;
300    const DIM_MUTE_NOTICE: u32 = 0x00000002;
301}
302
303impl SaffireproInputSpecification for SPro24DspProtocol {
304    const INPUT_PARAMS_OFFSET: usize = 0x0058;
305}
306
307// When VRM mode is enabled, write 0x00000001 to the offset
308#[allow(dead_code)]
309const DSP_ENABLE_OFFSET: usize = 0x0070; // sw notice: 0x1c.
310const CH_STRIP_FLAG_OFFSET: usize = 0x0078;
311const CH_STRIP_FLAG_EQ_ENABLE: u16 = 0x0001;
312const CH_STRIP_FLAG_COMP_ENABLE: u16 = 0x0002;
313const CH_STRIP_FLAG_EQ_AFTER_COMP: u16 = 0x0004;
314
315const CH_STRIP_FLAG_SW_NOTICE: u32 = 0x00000005;
316
317const COEF_OFFSET: usize = 0x0190;
318const COEF_BLOCK_SIZE: usize = 0x88;
319//const COEF_BLOCK_COUNT: usize = 8;
320
321const EQ_COEF_COUNT: usize = 5;
322
323const COMP_OUTPUT_OFFSET: usize = 0x04;
324const COMP_THRESHOLD_OFFSET: usize = 0x08;
325const COMP_RATIO_OFFSET: usize = 0x0c;
326const COMP_ATTACK_OFFSET: usize = 0x10;
327const COMP_RELEASE_OFFSET: usize = 0x14;
328
329const COMP_CH0_SW_NOTICE: u32 = 0x00000006;
330const COMP_CH1_SW_NOTICE: u32 = 0x00000007;
331
332const EQ_OUTPUT_OFFSET: usize = 0x18;
333const EQ_LOW_FREQ_OFFSET: usize = 0x20;
334//const EQ_LOW_MIDDLE_FREQ_OFFSET: usize = 0x34;
335//const EQ_HIGH_MIDDLE_FREQ_OFFSET: usize = 0x48;
336//const EQ_HIGH_FREQ_OFFSET: usize = 0x5c;
337
338const EQ_OUTPUT_CH0_SW_NOTICE: u32 = 0x09;
339const EQ_OUTPUT_CH1_SW_NOTICE: u32 = 0x0a;
340const EQ_LOW_FREQ_CH0_SW_NOTICE: u32 = 0x0c;
341const EQ_LOW_FREQ_CH1_SW_NOTICE: u32 = 0x0c;
342const EQ_LOW_MIDDLE_FREQ_CH0_SW_NOTICE: u32 = 0x0f;
343const EQ_LOW_MIDDLE_FREQ_CH1_SW_NOTICE: u32 = 0x10;
344const EQ_HIGH_MIDDLE_FREQ_CH0_SW_NOTICE: u32 = 0x12;
345const EQ_HIGH_MIDDLE_FREQ_CH1_SW_NOTICE: u32 = 0x13;
346const EQ_HIGH_FREQ_CH0_SW_NOTICE: u32 = 0x15;
347const EQ_HIGH_FREQ_CH1_SW_NOTICE: u32 = 0x16;
348
349const REVERB_SIZE_OFFSET: usize = 0x70;
350const REVERB_AIR_OFFSET: usize = 0x74;
351const REVERB_ENABLE_OFFSET: usize = 0x78;
352const REVERB_DISABLE_OFFSET: usize = 0x7c;
353const REVERB_PRE_FILTER_VALUE_OFFSET: usize = 0x80;
354const REVERB_PRE_FILTER_SIGN_OFFSET: usize = 0x84;
355
356const REVERB_SW_NOTICE: u32 = 0x0000001a;
357
358fn serialize_f32(val: &f32, raw: &mut [u8]) -> Result<(), String> {
359    assert!(raw.len() >= 4);
360
361    raw[..4].copy_from_slice(&val.to_be_bytes());
362
363    Ok(())
364}
365
366fn deserialize_f32(val: &mut f32, raw: &[u8]) -> Result<(), String> {
367    assert!(raw.len() >= 4);
368
369    let mut quadlet = [0; 4];
370    quadlet.copy_from_slice(&raw[..4]);
371    *val = f32::from_be_bytes(quadlet);
372
373    Ok(())
374}
375
376/// State of compressor effect.
377#[derive(Default, Debug, Copy, Clone, PartialEq)]
378pub struct Spro24DspCompressorState {
379    /// The volume of output, between 0.0 to 64.0.
380    pub output: [f32; 2],
381    /// The threshold, between -1.25 to 0.0.
382    pub threshold: [f32; 2],
383    /// The ratio, between 0.03125 to 0.5.
384    pub ratio: [f32; 2],
385    /// The attack, between -0.9375 to -1.0.
386    pub attack: [f32; 2],
387    /// The release, between 0.9375 to 1.0.
388    pub release: [f32; 2],
389}
390
391/// Coefficients per frequency band.
392#[derive(Default, Debug, Copy, Clone, PartialEq)]
393pub struct Spro24DspEqualizerFrequencyBandState([f32; EQ_COEF_COUNT]);
394
395/// State of equalizer effect.
396#[derive(Default, Debug, Copy, Clone, PartialEq)]
397pub struct Spro24DspEqualizerState {
398    /// The volume of output, between 0.0 to 1.0.
399    pub output: [f32; 2],
400    // TODO: how to convert these coefficients to friendly parameter.
401    pub low_coef: [Spro24DspEqualizerFrequencyBandState; 2],
402    pub low_middle_coef: [Spro24DspEqualizerFrequencyBandState; 2],
403    pub high_middle_coef: [Spro24DspEqualizerFrequencyBandState; 2],
404    pub high_coef: [Spro24DspEqualizerFrequencyBandState; 2],
405}
406
407/// State of reverb effect.
408#[derive(Default, Debug, Copy, Clone, PartialEq)]
409pub struct Spro24DspReverbState {
410    /// The size of room, between 0.0 to 1.0.
411    pub size: f32,
412    /// The amount to reduce dumping, between 0.0 to 1.0.
413    pub air: f32,
414    /// Whether the reverb effect is enabled or not.
415    pub enabled: bool,
416    /// The ratio of high-pass/low-pass filter, between -1.0 to 1.0.
417    pub pre_filter: f32,
418}
419
420/// General parameters of DSP effects.
421#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
422pub struct Spro24DspEffectGeneralParams {
423    /// Use equalizer after compressor.
424    pub eq_after_comp: [bool; 2],
425    /// Whether to enable compressor.
426    pub comp_enable: [bool; 2],
427    /// Whether to enable equalizer.
428    pub eq_enable: [bool; 2],
429}
430
431const COEF_BLOCK_COMP: usize = 2;
432const COEF_BLOCK_EQ: usize = 2;
433const COEF_BLOCK_REVERB: usize = 3;
434
435// Serialize to a pair of coefficient block.
436fn serialize_compressor_state(
437    state: &Spro24DspCompressorState,
438    raw: &mut [u8],
439) -> Result<(), String> {
440    assert!(raw.len() >= COEF_BLOCK_SIZE * 2);
441
442    (0..2).try_for_each(|ch| {
443        let base_offset = COEF_BLOCK_SIZE * ch;
444
445        let pos = base_offset + COMP_OUTPUT_OFFSET;
446        serialize_f32(&state.output[ch], &mut raw[pos..(pos + 4)])?;
447
448        let pos = base_offset + COMP_THRESHOLD_OFFSET;
449        serialize_f32(&state.threshold[ch], &mut raw[pos..(pos + 4)])?;
450
451        let pos = base_offset + COMP_RATIO_OFFSET;
452        serialize_f32(&state.ratio[ch], &mut raw[pos..(pos + 4)])?;
453
454        let pos = base_offset + COMP_ATTACK_OFFSET;
455        serialize_f32(&state.attack[ch], &mut raw[pos..(pos + 4)])?;
456
457        let pos = base_offset + COMP_RELEASE_OFFSET;
458        serialize_f32(&state.release[ch], &mut raw[pos..(pos + 4)])
459    })
460}
461
462// Deserialize from a pair of coefficient block.
463fn deserialize_compressor_state(
464    state: &mut Spro24DspCompressorState,
465    raw: &[u8],
466) -> Result<(), String> {
467    assert!(raw.len() >= COEF_BLOCK_SIZE * 2);
468
469    (0..2).try_for_each(|ch| {
470        let base_offset = COEF_BLOCK_SIZE * ch;
471
472        let pos = base_offset + COMP_OUTPUT_OFFSET;
473        deserialize_f32(&mut state.output[ch], &raw[pos..(pos + 4)])?;
474
475        let pos = base_offset + COMP_THRESHOLD_OFFSET;
476        deserialize_f32(&mut state.threshold[ch], &raw[pos..(pos + 4)])?;
477
478        let pos = base_offset + COMP_RATIO_OFFSET;
479        deserialize_f32(&mut state.ratio[ch], &raw[pos..(pos + 4)])?;
480
481        let pos = base_offset + COMP_ATTACK_OFFSET;
482        deserialize_f32(&mut state.attack[ch], &raw[pos..(pos + 4)])?;
483
484        let pos = base_offset + COMP_RELEASE_OFFSET;
485        deserialize_f32(&mut state.release[ch], &raw[pos..(pos + 4)])
486    })
487}
488
489// Serialize to a pair of coefficient block.
490fn serialize_equalizer_state(
491    state: &Spro24DspEqualizerState,
492    raw: &mut [u8],
493) -> Result<(), String> {
494    assert!(raw.len() >= COEF_BLOCK_SIZE * 2);
495
496    (0..2).try_for_each(|ch| {
497        let base_offset = COEF_BLOCK_SIZE * ch;
498
499        let pos = base_offset + EQ_OUTPUT_OFFSET;
500        serialize_f32(&state.output[ch], &mut raw[pos..(pos + 4)])?;
501
502        state.low_coef[ch]
503            .0
504            .iter()
505            .chain(state.low_middle_coef[ch].0.iter())
506            .chain(state.high_middle_coef[ch].0.iter())
507            .chain(state.high_coef[ch].0.iter())
508            .enumerate()
509            .try_for_each(|(i, coef)| {
510                let pos = base_offset + EQ_LOW_FREQ_OFFSET + i * 4;
511                serialize_f32(coef, &mut raw[pos..(pos + 4)])
512            })
513    })
514}
515
516// Deserialize from a pair of coefficient block.
517fn deserialize_equalizer_state(
518    state: &mut Spro24DspEqualizerState,
519    raw: &[u8],
520) -> Result<(), String> {
521    assert!(raw.len() >= COEF_BLOCK_SIZE * 2);
522
523    (0..2).try_for_each(|ch| {
524        let base_offset = COEF_BLOCK_SIZE * ch;
525
526        let pos = base_offset + EQ_OUTPUT_OFFSET;
527        deserialize_f32(&mut state.output[ch], &raw[pos..(pos + 4)])?;
528
529        state.low_coef[ch]
530            .0
531            .iter_mut()
532            .chain(state.low_middle_coef[ch].0.iter_mut())
533            .chain(state.high_middle_coef[ch].0.iter_mut())
534            .chain(state.high_coef[ch].0.iter_mut())
535            .enumerate()
536            .try_for_each(|(i, coef)| {
537                let pos = base_offset + EQ_LOW_FREQ_OFFSET + i * 4;
538                deserialize_f32(coef, &raw[pos..(pos + 4)])
539            })
540    })
541}
542
543// Serialize to a coefficient block.
544fn serialize_reverb_state(state: &Spro24DspReverbState, raw: &mut [u8]) -> Result<(), String> {
545    assert!(raw.len() >= COEF_BLOCK_SIZE);
546
547    let pos = REVERB_SIZE_OFFSET;
548    serialize_f32(&state.size, &mut raw[pos..(pos + 4)])?;
549
550    let pos = REVERB_AIR_OFFSET;
551    serialize_f32(&state.air, &mut raw[pos..(pos + 4)])?;
552
553    let enabled_pos = REVERB_ENABLE_OFFSET;
554    let disabled_pos = REVERB_DISABLE_OFFSET;
555    let vals = if state.enabled {
556        [1.0, 0.0]
557    } else {
558        [0.0, 1.0]
559    };
560    serialize_f32(&vals[0], &mut raw[enabled_pos..(enabled_pos + 4)])?;
561    serialize_f32(&vals[1], &mut raw[disabled_pos..(disabled_pos + 4)])?;
562
563    let pos = REVERB_ENABLE_OFFSET;
564    let val = if state.enabled { 1.0 } else { 0.0 };
565    serialize_f32(&val, &mut raw[pos..(pos + 4)])?;
566
567    let pos = REVERB_PRE_FILTER_VALUE_OFFSET;
568    let val = state.pre_filter.abs();
569    serialize_f32(&val, &mut raw[pos..(pos + 4)])?;
570
571    let pos = REVERB_PRE_FILTER_SIGN_OFFSET;
572    let sign = if state.pre_filter > 0.0 { 1.0 } else { 0.0 };
573    serialize_f32(&sign, &mut raw[pos..(pos + 4)])?;
574
575    Ok(())
576}
577
578// Deserialize from a coefficient block.
579fn deserialize_reverb_state(state: &mut Spro24DspReverbState, raw: &[u8]) -> Result<(), String> {
580    assert!(raw.len() >= COEF_BLOCK_SIZE);
581
582    let pos = REVERB_SIZE_OFFSET;
583    deserialize_f32(&mut state.size, &raw[pos..(pos + 4)])?;
584
585    let pos = REVERB_AIR_OFFSET;
586    deserialize_f32(&mut state.air, &raw[pos..(pos + 4)])?;
587
588    let mut val = 0.0;
589    let pos = REVERB_ENABLE_OFFSET;
590    deserialize_f32(&mut val, &raw[pos..(pos + 4)])?;
591    state.enabled = val > 0.0;
592
593    let mut val = 0.0;
594    let pos = REVERB_PRE_FILTER_VALUE_OFFSET;
595    deserialize_f32(&mut val, &raw[pos..(pos + 4)])?;
596
597    let mut sign = 0.0;
598    let pos = REVERB_PRE_FILTER_SIGN_OFFSET;
599    deserialize_f32(&mut sign, &raw[pos..(pos + 4)])?;
600    if sign == 0.0 {
601        val *= -1.0;
602    }
603    state.pre_filter = val;
604
605    Ok(())
606}
607
608fn serialize_effect_general_params(
609    params: &Spro24DspEffectGeneralParams,
610    raw: &mut [u8],
611) -> Result<(), String> {
612    assert!(raw.len() >= 4);
613
614    let mut val = 0u32;
615
616    (0..2).for_each(|i| {
617        let mut flags = 0u16;
618        if params.eq_after_comp[i] {
619            flags |= CH_STRIP_FLAG_EQ_AFTER_COMP;
620        }
621        if params.comp_enable[i] {
622            flags |= CH_STRIP_FLAG_COMP_ENABLE;
623        }
624        if params.eq_enable[i] {
625            flags |= CH_STRIP_FLAG_EQ_ENABLE;
626        }
627
628        val |= (flags as u32) << (16 * i);
629    });
630
631    serialize_u32(&val, raw);
632
633    Ok(())
634}
635
636fn deserialize_effect_general_params(
637    params: &mut Spro24DspEffectGeneralParams,
638    raw: &[u8],
639) -> Result<(), String> {
640    assert!(raw.len() >= 4);
641
642    let mut val = 0u32;
643    deserialize_u32(&mut val, raw);
644    (0..2).for_each(|i| {
645        let flags = (val >> (i * 16)) as u16;
646        params.eq_after_comp[i] = flags & CH_STRIP_FLAG_EQ_AFTER_COMP > 0;
647        params.comp_enable[i] = flags & CH_STRIP_FLAG_COMP_ENABLE > 0;
648        params.eq_enable[i] = flags & CH_STRIP_FLAG_EQ_ENABLE > 0;
649    });
650
651    Ok(())
652}
653
654impl TcatExtensionSectionParamsOperation<Spro24DspEffectGeneralParams> for SPro24DspProtocol {
655    fn cache_extension_whole_params(
656        req: &FwReq,
657        node: &FwNode,
658        sections: &ExtensionSections,
659        _: &ExtensionCaps,
660        params: &mut Spro24DspEffectGeneralParams,
661        timeout_ms: u32,
662    ) -> Result<(), Error> {
663        let mut raw = vec![0u8; 4];
664        Self::read_extension(
665            req,
666            node,
667            &sections.application,
668            CH_STRIP_FLAG_OFFSET,
669            &mut raw,
670            timeout_ms,
671        )?;
672        deserialize_effect_general_params(params, &raw)
673            .map_err(|cause| Error::new(ProtocolExtensionError::Appl, &cause))
674    }
675}
676
677impl TcatExtensionSectionPartialMutableParamsOperation<Spro24DspEffectGeneralParams>
678    for SPro24DspProtocol
679{
680    fn update_extension_partial_params(
681        req: &FwReq,
682        node: &FwNode,
683        sections: &ExtensionSections,
684        _: &ExtensionCaps,
685        params: &Spro24DspEffectGeneralParams,
686        prev: &mut Spro24DspEffectGeneralParams,
687        timeout_ms: u32,
688    ) -> Result<(), Error> {
689        let mut new = vec![0u8; 4];
690        serialize_effect_general_params(params, &mut new)
691            .map_err(|cause| Error::new(ProtocolExtensionError::Appl, &cause))?;
692
693        let mut old = vec![0u8; 4];
694        serialize_effect_general_params(prev, &mut old)
695            .map_err(|cause| Error::new(ProtocolExtensionError::Appl, &cause))?;
696
697        if new != old {
698            Self::write_extension(
699                req,
700                node,
701                &sections.application,
702                CH_STRIP_FLAG_OFFSET,
703                &mut new,
704                timeout_ms,
705            )?;
706            Self::write_sw_notice(req, node, sections, CH_STRIP_FLAG_SW_NOTICE, timeout_ms)?;
707        }
708        deserialize_effect_general_params(prev, &new)
709            .map_err(|cause| Error::new(ProtocolExtensionError::Appl, &cause))
710    }
711}
712
713impl TcatExtensionSectionParamsOperation<Spro24DspCompressorState> for SPro24DspProtocol {
714    fn cache_extension_whole_params(
715        req: &FwReq,
716        node: &FwNode,
717        sections: &ExtensionSections,
718        _: &ExtensionCaps,
719        params: &mut Spro24DspCompressorState,
720        timeout_ms: u32,
721    ) -> Result<(), Error> {
722        let mut raw = vec![0u8; COEF_BLOCK_SIZE * 2];
723        Self::read_extension(
724            req,
725            node,
726            &sections.application,
727            COEF_OFFSET + COEF_BLOCK_SIZE * COEF_BLOCK_COMP,
728            &mut raw,
729            timeout_ms,
730        )?;
731        deserialize_compressor_state(params, &raw)
732            .map_err(|cause| Error::new(ProtocolExtensionError::Appl, &cause))
733    }
734}
735
736impl TcatExtensionSectionPartialMutableParamsOperation<Spro24DspCompressorState>
737    for SPro24DspProtocol
738{
739    fn update_extension_partial_params(
740        req: &FwReq,
741        node: &FwNode,
742        sections: &ExtensionSections,
743        _: &ExtensionCaps,
744        params: &Spro24DspCompressorState,
745        prev: &mut Spro24DspCompressorState,
746        timeout_ms: u32,
747    ) -> Result<(), Error> {
748        let mut new = vec![0u8; COEF_BLOCK_SIZE * 2];
749        serialize_compressor_state(params, &mut new)
750            .map_err(|cause| Error::new(ProtocolExtensionError::Appl, &cause))?;
751
752        let mut old = vec![0u8; COEF_BLOCK_SIZE * 2];
753        serialize_compressor_state(prev, &mut old)
754            .map_err(|cause| Error::new(ProtocolExtensionError::Appl, &cause))?;
755
756        (0..(COEF_BLOCK_SIZE * 2)).step_by(4).try_for_each(|pos| {
757            if new[pos..(pos + 4)] != old[pos..(pos + 4)] {
758                Self::write_extension(
759                    req,
760                    node,
761                    &sections.application,
762                    COEF_OFFSET + COEF_BLOCK_SIZE * COEF_BLOCK_COMP + pos,
763                    &mut new[pos..(pos + 4)],
764                    timeout_ms,
765                )
766            } else {
767                Ok(())
768            }
769        })?;
770        Self::write_sw_notice(req, node, sections, COMP_CH0_SW_NOTICE, timeout_ms)?;
771        Self::write_sw_notice(req, node, sections, COMP_CH1_SW_NOTICE, timeout_ms)?;
772
773        deserialize_compressor_state(prev, &new)
774            .map_err(|cause| Error::new(ProtocolExtensionError::Appl, &cause))
775    }
776}
777
778impl TcatExtensionSectionParamsOperation<Spro24DspEqualizerState> for SPro24DspProtocol {
779    fn cache_extension_whole_params(
780        req: &FwReq,
781        node: &FwNode,
782        sections: &ExtensionSections,
783        _: &ExtensionCaps,
784        params: &mut Spro24DspEqualizerState,
785        timeout_ms: u32,
786    ) -> Result<(), Error> {
787        let mut raw = vec![0u8; COEF_BLOCK_SIZE * 2];
788        Self::read_extension(
789            req,
790            node,
791            &sections.application,
792            COEF_OFFSET + COEF_BLOCK_SIZE * COEF_BLOCK_EQ,
793            &mut raw,
794            timeout_ms,
795        )?;
796        deserialize_equalizer_state(params, &raw)
797            .map_err(|cause| Error::new(ProtocolExtensionError::Appl, &cause))
798    }
799}
800
801impl TcatExtensionSectionPartialMutableParamsOperation<Spro24DspEqualizerState>
802    for SPro24DspProtocol
803{
804    fn update_extension_partial_params(
805        req: &FwReq,
806        node: &FwNode,
807        sections: &ExtensionSections,
808        _: &ExtensionCaps,
809        params: &Spro24DspEqualizerState,
810        prev: &mut Spro24DspEqualizerState,
811        timeout_ms: u32,
812    ) -> Result<(), Error> {
813        let mut new = vec![0u8; COEF_BLOCK_SIZE * 2];
814        serialize_equalizer_state(params, &mut new)
815            .map_err(|cause| Error::new(ProtocolExtensionError::Appl, &cause))?;
816
817        let mut old = vec![0u8; COEF_BLOCK_SIZE * 2];
818        serialize_equalizer_state(prev, &mut old)
819            .map_err(|cause| Error::new(ProtocolExtensionError::Appl, &cause))?;
820
821        (0..(COEF_BLOCK_SIZE * 2)).step_by(4).try_for_each(|pos| {
822            if new[pos..(pos + 4)] != old[pos..(pos + 4)] {
823                Self::write_extension(
824                    req,
825                    node,
826                    &sections.application,
827                    COEF_OFFSET + COEF_BLOCK_SIZE * COEF_BLOCK_EQ + pos,
828                    &mut new[pos..(pos + 4)],
829                    timeout_ms,
830                )
831            } else {
832                Ok(())
833            }
834        })?;
835        Self::write_sw_notice(req, node, sections, EQ_OUTPUT_CH0_SW_NOTICE, timeout_ms)?;
836        Self::write_sw_notice(req, node, sections, EQ_OUTPUT_CH1_SW_NOTICE, timeout_ms)?;
837        Self::write_sw_notice(req, node, sections, EQ_LOW_FREQ_CH0_SW_NOTICE, timeout_ms)?;
838        Self::write_sw_notice(req, node, sections, EQ_LOW_FREQ_CH1_SW_NOTICE, timeout_ms)?;
839        Self::write_sw_notice(
840            req,
841            node,
842            sections,
843            EQ_LOW_MIDDLE_FREQ_CH0_SW_NOTICE,
844            timeout_ms,
845        )?;
846        Self::write_sw_notice(
847            req,
848            node,
849            sections,
850            EQ_LOW_MIDDLE_FREQ_CH1_SW_NOTICE,
851            timeout_ms,
852        )?;
853        Self::write_sw_notice(
854            req,
855            node,
856            sections,
857            EQ_HIGH_MIDDLE_FREQ_CH0_SW_NOTICE,
858            timeout_ms,
859        )?;
860        Self::write_sw_notice(
861            req,
862            node,
863            sections,
864            EQ_HIGH_MIDDLE_FREQ_CH1_SW_NOTICE,
865            timeout_ms,
866        )?;
867        Self::write_sw_notice(req, node, sections, EQ_HIGH_FREQ_CH0_SW_NOTICE, timeout_ms)?;
868        Self::write_sw_notice(req, node, sections, EQ_HIGH_FREQ_CH1_SW_NOTICE, timeout_ms)?;
869
870        deserialize_equalizer_state(prev, &new)
871            .map_err(|cause| Error::new(ProtocolExtensionError::Appl, &cause))
872    }
873}
874
875impl TcatExtensionSectionParamsOperation<Spro24DspReverbState> for SPro24DspProtocol {
876    fn cache_extension_whole_params(
877        req: &FwReq,
878        node: &FwNode,
879        sections: &ExtensionSections,
880        _: &ExtensionCaps,
881        params: &mut Spro24DspReverbState,
882        timeout_ms: u32,
883    ) -> Result<(), Error> {
884        let mut raw = vec![0u8; COEF_BLOCK_SIZE];
885        Self::read_extension(
886            req,
887            node,
888            &sections.application,
889            COEF_OFFSET + COEF_BLOCK_SIZE * COEF_BLOCK_REVERB,
890            &mut raw,
891            timeout_ms,
892        )?;
893        deserialize_reverb_state(params, &raw)
894            .map_err(|cause| Error::new(ProtocolExtensionError::Appl, &cause))
895    }
896}
897
898impl TcatExtensionSectionPartialMutableParamsOperation<Spro24DspReverbState> for SPro24DspProtocol {
899    fn update_extension_partial_params(
900        req: &FwReq,
901        node: &FwNode,
902        sections: &ExtensionSections,
903        _: &ExtensionCaps,
904        params: &Spro24DspReverbState,
905        prev: &mut Spro24DspReverbState,
906        timeout_ms: u32,
907    ) -> Result<(), Error> {
908        let mut new = vec![0u8; COEF_BLOCK_SIZE];
909        serialize_reverb_state(params, &mut new)
910            .map_err(|cause| Error::new(ProtocolExtensionError::Appl, &cause))?;
911
912        let mut old = vec![0u8; COEF_BLOCK_SIZE];
913        serialize_reverb_state(prev, &mut old)
914            .map_err(|cause| Error::new(ProtocolExtensionError::Appl, &cause))?;
915
916        (0..(COEF_BLOCK_SIZE * 2)).step_by(4).try_for_each(|pos| {
917            if new[pos..(pos + 4)] != old[pos..(pos + 4)] {
918                Self::write_extension(
919                    req,
920                    node,
921                    &sections.application,
922                    COEF_OFFSET + COEF_BLOCK_SIZE * COEF_BLOCK_REVERB + pos,
923                    &mut new[pos..(pos + 4)],
924                    timeout_ms,
925                )
926            } else {
927                Ok(())
928            }
929        })?;
930        Self::write_sw_notice(req, node, sections, REVERB_SW_NOTICE, timeout_ms)?;
931        deserialize_reverb_state(prev, &new)
932            .map_err(|cause| Error::new(ProtocolExtensionError::Appl, &cause))
933    }
934}
935
936impl SPro24DspProtocol {
937    pub const COMPRESSOR_OUTPUT_MIN: f32 = 0.0;
938    pub const COMPRESSOR_OUTPUT_MAX: f32 = 64.0;
939
940    pub const COMPRESSOR_THRESHOLD_MIN: f32 = -1.25;
941    pub const COMPRESSOR_THRESHOLD_MAX: f32 = 0.0;
942
943    pub const COMPRESSOR_RATIO_MIN: f32 = 0.03125;
944    pub const COMPRESSOR_RATIO_MAX: f32 = 0.5;
945
946    pub const COMPRESSOR_ATTACK_MIN: f32 = -1.0;
947    pub const COMPRESSOR_ATTACK_MAX: f32 = -0.9375;
948
949    pub const COMPRESSOR_RELEASE_MIN: f32 = 0.9375;
950    pub const COMPRESSOR_RELEASE_MAX: f32 = 1.0;
951
952    pub const EQUALIZER_OUTPUT_MIN: f32 = 0.0;
953    pub const EQUALIZER_OUTPUT_MAX: f32 = 1.0;
954
955    pub const REVERB_SIZE_MIN: f32 = 0.0;
956    pub const REVERB_SIZE_MAX: f32 = 1.0;
957
958    pub const REVERB_AIR_MIN: f32 = 0.0;
959    pub const REVERB_AIR_MAX: f32 = 1.0;
960
961    pub const REVERB_PRE_FILTER_MIN: f32 = -1.0;
962    pub const REVERB_PRE_FILTER_MAX: f32 = 1.0;
963}
964
965#[cfg(test)]
966mod test {
967    use super::*;
968
969    #[test]
970    fn compressor_state_serdes() {
971        let state = Spro24DspCompressorState {
972            output: [0.04, 0.05],
973            threshold: [0.16, 0.17],
974            ratio: [0.20, 0.21],
975            attack: [0.32, 0.33],
976            release: [0.44, 0.45],
977        };
978
979        let mut raw = [0u8; COEF_BLOCK_SIZE * 2];
980        serialize_compressor_state(&state, &mut raw).unwrap();
981
982        let mut s = Spro24DspCompressorState::default();
983        deserialize_compressor_state(&mut s, &raw).unwrap();
984
985        assert_eq!(state, s);
986    }
987
988    #[test]
989    fn equalizer_state_serdes() {
990        let state = Spro24DspEqualizerState {
991            output: [0.06, 0.07],
992            low_coef: [
993                Spro24DspEqualizerFrequencyBandState([0.00, 0.01, 0.02, 0.03, 0.04]),
994                Spro24DspEqualizerFrequencyBandState([0.10, 0.11, 0.12, 0.13, 0.14]),
995            ],
996            low_middle_coef: [
997                Spro24DspEqualizerFrequencyBandState([0.20, 0.21, 0.22, 0.23, 0.24]),
998                Spro24DspEqualizerFrequencyBandState([0.30, 0.31, 0.32, 0.33, 0.34]),
999            ],
1000            high_middle_coef: [
1001                Spro24DspEqualizerFrequencyBandState([0.40, 0.41, 0.42, 0.43, 0.44]),
1002                Spro24DspEqualizerFrequencyBandState([0.50, 0.51, 0.52, 0.53, 0.54]),
1003            ],
1004            high_coef: [
1005                Spro24DspEqualizerFrequencyBandState([0.60, 0.61, 0.62, 0.63, 0.64]),
1006                Spro24DspEqualizerFrequencyBandState([0.70, 0.71, 0.72, 0.73, 0.74]),
1007            ],
1008        };
1009
1010        let mut raw = [0u8; COEF_BLOCK_SIZE * 2];
1011        serialize_equalizer_state(&state, &mut raw).unwrap();
1012
1013        let mut s = Spro24DspEqualizerState::default();
1014        deserialize_equalizer_state(&mut s, &raw).unwrap();
1015
1016        assert_eq!(state, s);
1017    }
1018
1019    #[test]
1020    fn reverb_state_serdes() {
1021        let state = Spro24DspReverbState {
1022            size: 0.04,
1023            air: 0.14,
1024            enabled: false,
1025            pre_filter: -0.1,
1026        };
1027        let mut raw = [0u8; COEF_BLOCK_SIZE];
1028        serialize_reverb_state(&state, &mut raw).unwrap();
1029
1030        let mut s = Spro24DspReverbState::default();
1031        deserialize_reverb_state(&mut s, &raw).unwrap();
1032
1033        assert_eq!(state, s);
1034    }
1035
1036    #[test]
1037    fn effect_general_params_serdes() {
1038        let params = Spro24DspEffectGeneralParams {
1039            eq_after_comp: [false, true],
1040            comp_enable: [true, false],
1041            eq_enable: [false, true],
1042        };
1043        let mut raw = [0u8; 4];
1044        serialize_effect_general_params(&params, &mut raw).unwrap();
1045
1046        let mut p = Spro24DspEffectGeneralParams::default();
1047        deserialize_effect_general_params(&mut p, &raw).unwrap();
1048
1049        assert_eq!(params, p);
1050    }
1051}