1pub mod fw1082;
10pub mod fw1804;
11pub mod fw1884;
12
13use super::*;
14
15pub trait TascamIsochWhollyCachableParamsOperation<T> {
17 fn cache_wholly(
19 req: &mut FwReq,
20 node: &mut FwNode,
21 states: &mut T,
22 timeout_ms: u32,
23 ) -> Result<(), Error>;
24}
25
26pub trait TascamIsochWhollyUpdatableParamsOperation<T> {
28 fn update_wholly(
30 req: &mut FwReq,
31 node: &mut FwNode,
32 states: &T,
33 timeout_ms: u32,
34 ) -> Result<(), Error>;
35}
36
37pub trait TascamIsochPartiallyUpdatableParamsOperation<T> {
39 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
49pub trait TascamIsochImageParamsOperation<T> {
51 fn parse_image(params: &mut T, image: &[u32]);
53}
54
55#[derive(Debug, Copy, Clone, PartialEq, Eq)]
57pub enum ClkSrc {
58 Internal,
60 Wordclock,
62 Spdif,
64 Adat,
66}
67
68impl Default for ClkSrc {
69 fn default() -> Self {
70 Self::Internal
71 }
72}
73
74#[derive(Debug, Copy, Clone, PartialEq, Eq)]
76pub enum ClkRate {
77 R44100,
79 R48000,
81 R88200,
83 R96000,
85}
86
87impl Default for ClkRate {
88 fn default() -> Self {
89 Self::R44100
90 }
91}
92
93#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
95pub struct TascamClockParameters {
96 pub sampling_clock_source: ClkSrc,
98 pub media_clock_rate: ClkRate,
100}
101
102pub 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 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#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
211pub struct TascamInputDetectionThreshold {
212 pub signal: u16,
214 pub over_level: u16,
216}
217
218pub trait TascamIsochInputDetectionSpecification {
220 const INPUT_SIGNAL_THRESHOLD_MIN: u16 = 1;
222 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#[derive(Debug, Copy, Clone, PartialEq, Eq)]
306pub enum MixerMode {
307 Computer,
309 Inputs,
311 Both,
313}
314
315impl Default for MixerMode {
316 fn default() -> Self {
317 Self::Computer
318 }
319}
320
321#[derive(Default, Debug, Clone, PartialEq, Eq)]
323pub struct IsochMeterState {
324 pub monitor: i16,
326 pub solo: Option<i16>,
328 pub inputs: Vec<i32>,
330 pub outputs: Vec<i32>,
332 pub rate: Option<ClkRate>,
334 pub src: Option<ClkSrc>,
336 pub mixer_meters: [i32; 2],
338 pub monitor_meters: [i32; 2],
340 pub mixer_mode: MixerMode,
342}
343
344pub 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
525pub enum CoaxialOutputSource {
526 StreamInputPair,
528 AnalogOutputPair0,
530}
531
532impl Default for CoaxialOutputSource {
533 fn default() -> Self {
534 Self::StreamInputPair
535 }
536}
537
538pub 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#[derive(Debug, Copy, Clone, PartialEq, Eq)]
586pub enum SpdifCaptureSource {
587 Coaxial,
589 Optical,
591}
592
593impl Default for SpdifCaptureSource {
594 fn default() -> Self {
595 Self::Coaxial
596 }
597}
598
599#[derive(Debug, Copy, Clone, PartialEq, Eq)]
601pub enum OpticalOutputSource {
602 StreamInputPairs,
604 CoaxialOutputPair0,
606 AnalogInputPair0,
608 AnalogOutputPairs,
610}
611
612impl Default for OpticalOutputSource {
613 fn default() -> Self {
614 Self::StreamInputPairs
615 }
616}
617
618#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
620pub struct TascamOpticalIfaceParameters {
621 pub capture_source: SpdifCaptureSource,
623 pub output_source: OpticalOutputSource,
625}
626
627pub 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 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#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
683pub struct IsochConsoleState {
684 pub host_mode: bool,
686
687 pub master_fader_assign: bool,
689}
690
691pub 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#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
751pub struct IsochRackInputParameters {
752 pub gains: [i16; 18],
754 pub balances: [u8; 18],
756 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
786pub trait TascamIsochRackInputSpecification {
788 const CHANNEL_COUNT: usize = 18;
790
791 const INPUT_GAIN_MIN: i16 = 0;
793 const INPUT_GAIN_MAX: i16 = 0x7fff;
795 const INPUT_GAIN_STEP: i16 = 0x100;
797
798 const INPUT_BALANCE_MIN: u8 = 0;
800 const INPUT_BALANCE_MAX: u8 = 255;
802 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#[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
890pub 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
936pub 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
1026trait 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
1110trait 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(¶m, &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}