1pub mod master_trigger;
2pub mod settings;
3pub mod constants;
4pub mod thread_control;
5pub mod sine_fitter;
6
7use constants::{
8 DEFAULT_LTB_ID,
9};
10
11use std::thread;
12use std::time::Duration;
13use std::os::raw::c_int;
14use std::sync::{
15 Arc,
16 Mutex,
17};
18
19use chrono::Utc;
20
21#[cfg(feature="database")]
22use core::f32::consts::PI;
23
24#[cfg(feature="database")]
25use half::f16;
26
27pub use master_trigger::{
28 master_trigger,
29 MTBSettings,
30};
31
32pub use settings::{
33 LiftofSettings,
34 AnalysisEngineSettings,
35};
36
37use std::fmt;
38
39use std::path::PathBuf;
40use std::fs::read_to_string;
41use std::io::{
42 Write,
43};
44
45use std::collections::HashMap;
46use colored::{
47 Colorize,
48 ColoredString
49};
50
51use serde_json::Value;
52
53use log::Level;
54
55#[macro_use] extern crate log;
56extern crate env_logger;
57
58use signal_hook::iterator::Signals;
59use signal_hook::consts::signal::{
60 SIGTERM,
61 SIGINT
62};
63
64use tof_dataclasses::DsiLtbRBMapping;
65#[cfg(feature="database")]
66use tof_dataclasses::database::ReadoutBoard;
67
68#[cfg(feature="database")]
69use tof_dataclasses::constants::NWORDS;
70#[cfg(feature="database")]
71use tof_dataclasses::errors::AnalysisError;
72use tof_dataclasses::errors::SetError;
73#[cfg(feature="database")]
74use tof_dataclasses::events::{
75 RBEvent,
76 TofHit,
77};
78
79#[cfg(feature="database")]
80use tof_dataclasses::analysis::{
81 calculate_pedestal,
82 integrate,
83 cfd_simple,
84 find_peaks,
85};
86
87use tof_dataclasses::RBChannelPaddleEndIDMap;
88
89use crate::thread_control::ThreadControl;
90
91use clap::{arg,
92 Args,
93};
94
95pub const MT_MAX_PACKSIZE : usize = 512;
96pub const DATAPORT : u32 = 42000;
97pub const ASSET_DIR : &str = "/home/gaps/assets/";
98pub const LIFTOF_LOGO_SHOW : &str = "
99 ___ ___ ___
100 /\\__\\ /\\ \\ /\\__\\
101 ___ /:/ _/_ ___ /::\\ \\ /:/ _/_
102 /\\__\\ /:/ /\\__\\ /\\__\\ /:/\\:\\ \\ /:/ /\\__\\
103 ___ ___ /:/__/ /:/ /:/ / /:/ / /:/ \\:\\ \\ /:/ /:/ /
104 /\\ \\ /\\__\\ /::\\ \\ /:/_/:/ / /:/__/ /:/__/ \\:\\__\\ /:/_/:/ /
105 \\:\\ \\ /:/ / \\/\\:\\ \\__ \\:\\/:/ / /::\\ \\ \\:\\ \\ /:/ / \\:\\/:/ /
106 \\:\\ /:/ / ~~\\:\\/\\__\\ \\::/__/ /:/\\:\\ \\ \\:\\ /:/ / \\::/__/
107 \\:\\/:/ / \\::/ / \\:\\ \\ \\/__\\:\\ \\ \\:\\/:/ / \\:\\ \\
108 \\::/ / /:/ / \\:\\__\\ \\:\\__\\ \\::/ / \\:\\__\\
109 \\/__/ \\/__/ \\/__/ \\/__/ \\/__/ \\/__/
110
111 (LIFTOF - liftof is for tof, Version 0.10 'LELEWAA', Mar 2024)
112 >> with support from the Hawaiian islands \u{1f30a}\u{1f308}\u{1f965}\u{1f334}
113
114 * Documentation
115 ==> GitHub https://github.com/GAPS-Collab/gaps-online-software/tree/LELEWAA-0.10
116 ==> API docs https://gaps-collab.github.io/gaps-online-software/
117
118 ";
119
120pub fn signal_handler(thread_control : Arc<Mutex<ThreadControl>>) {
148 let sleep_time = Duration::from_millis(300);
149 let mut signals = Signals::new(&[SIGTERM, SIGINT]).expect("Unknown signals");
150 'main: loop {
151 thread::sleep(sleep_time);
152
153 for signal in signals.pending() {
156 match signal as c_int {
157 SIGTERM | SIGINT => {
158 println!("=> {}", String::from("SIGTERM or SIGINT received. Maybe Ctrl+C has been pressed! Commencing program shutdown!").red().bold());
159 match thread_control.lock() {
160 Ok(mut tc) => {
161 tc.sigint_recvd = true;
162 }
163 Err(err) => {
164 error!("Can't acquire lock for ThreadControl! {err}");
165 },
166 }
167 break 'main; }
169 _ => {
170 error!("Received signal, but I don't have instructions what to do about it!");
171 }
172 }
173 }
174 }
175}
176
177
178pub fn color_log(level : &Level) -> ColoredString {
180 match level {
181 Level::Error => String::from(" ERROR!").red(),
182 Level::Warn => String::from(" WARN ").yellow(),
183 Level::Info => String::from(" Info ").green(),
184 Level::Debug => String::from(" debug ").blue(),
185 Level::Trace => String::from(" trace ").cyan(),
186 }
187}
188
189pub fn init_env_logger() {
195 env_logger::builder()
196 .format(|buf, record| {
197 writeln!( buf, "[{ts} - {level}][{module_path}:{line}] {args}",
198 ts = Utc::now().format("%Y/%m/%d-%H:%M:%SUTC"),
199 level = color_log(&record.level()),
200 module_path = record.module_path().unwrap_or("<unknown>"),
201 line = record.line().unwrap_or(0),
202 args = record.args()
203 )
204 }).init();
205}
206
207#[derive(Debug, Copy, Clone)]
209pub struct RunStatistics {
210 pub n_events_rec : usize,
212 pub evproc_npack : usize,
215 pub first_evid : u32,
217 pub last_evid : u32,
219 pub n_err_deser : usize,
222 pub n_err_zmq_send : usize,
225 pub n_err_chid_wrong : usize,
228 pub n_err_tail_wrong : usize,
231 pub n_err_crc32_wrong : usize,
233}
234
235impl RunStatistics {
236
237 pub fn new() -> Self {
238 Self {
239 n_events_rec : 0,
240 evproc_npack : 0,
241 first_evid : 0,
242 last_evid : 0,
243 n_err_deser : 0,
244 n_err_zmq_send : 0,
245 n_err_chid_wrong : 0,
246 n_err_tail_wrong : 0,
247 n_err_crc32_wrong : 0,
248 }
249 }
250
251 pub fn get_n_anticipated(&self) -> i32 {
252 self.last_evid as i32 - self.first_evid as i32
253 }
254}
255
256impl fmt::Display for RunStatistics {
257 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
258 let mut resp = String::from("<RunStatistics:\n");
259 resp += &(format!(" first event id : {}\n", self.first_evid));
260 resp += &(format!(" last event id : {}\n", self.last_evid));
261 resp += &(format!(" --> expected {} event (ids)\n", self.get_n_anticipated()));
262 resp += &(format!(" event_processing #packets : {}\n", self.evproc_npack));
263 if self.get_n_anticipated() != self.evproc_npack as i32 {
264 resp += &(format!(" --> discrepancy of {} event (ids)\n", self.get_n_anticipated() - self.evproc_npack as i32))
265 }
266 resp += &(format!(" event_processing n tail err : {}\n", self.n_err_tail_wrong));
267 resp += &(format!(" event_processing n chid err : {}\n", self.n_err_chid_wrong));
268 write!(f, "{}", resp)
269 }
270}
271
272#[cfg(feature="database")]
273pub fn fit_sine_sydney(volts: &Vec<f32>, times: &Vec<f32>) -> (f32, f32, f32) {
275 let start_bin = 20;
276 let size_bin = 900;
277 let pi = PI;
278 let mut data_size = 0;
279
280 let mut xi_yi = 0.0;
281 let mut xi_zi = 0.0;
282 let mut yi_zi = 0.0;
283 let mut xi_xi = 0.0;
284 let mut yi_yi = 0.0;
285 let mut xi_sum = 0.0;
286 let mut yi_sum = 0.0;
287 let mut zi_sum = 0.0;
288
289 for i in start_bin..(start_bin + size_bin) {
290 let xi = (2.0 * pi * 0.02 * times[i]).cos();
291 let yi = (2.0 * pi * 0.02 * times[i]).sin();
292 let zi = volts[i];
293
294 xi_yi += xi * yi;
295 xi_zi += xi * zi;
296 yi_zi += yi * zi;
297 xi_xi += xi * xi;
298 yi_yi += yi * yi;
299 xi_sum += xi;
300 yi_sum += yi;
301 zi_sum += zi;
302
303 data_size += 1;
304 }
305
306 let mut a_matrix = [[0.0; 3]; 3];
307 a_matrix[0][0] = xi_xi;
308 a_matrix[0][1] = xi_yi;
309 a_matrix[0][2] = xi_sum;
310 a_matrix[1][0] = xi_yi;
311 a_matrix[1][1] = yi_yi;
312 a_matrix[1][2] = yi_sum;
313 a_matrix[2][0] = xi_sum;
314 a_matrix[2][1] = yi_sum;
315 a_matrix[2][2] = data_size as f32;
316
317 let determinant = a_matrix[0][0] * a_matrix[1][1] * a_matrix[2][2]
318 + a_matrix[0][1] * a_matrix[1][2] * a_matrix[2][0]
319 + a_matrix[0][2] * a_matrix[1][0] * a_matrix[2][1]
320 - a_matrix[0][0] * a_matrix[1][2] * a_matrix[2][1]
321 - a_matrix[0][1] * a_matrix[1][0] * a_matrix[2][2]
322 - a_matrix[0][2] * a_matrix[1][1] * a_matrix[2][0];
323
324 let inverse_factor = 1.0 / determinant;
325
326 let mut cofactor_matrix = [[0.0; 3]; 3];
327 cofactor_matrix[0][0] = a_matrix[1][1] * a_matrix[2][2] - a_matrix[2][1] * a_matrix[1][2];
328 cofactor_matrix[0][1] = (a_matrix[1][0] * a_matrix[2][2] - a_matrix[2][0] * a_matrix[1][2]) * -1.0;
329 cofactor_matrix[0][2] = a_matrix[1][0] * a_matrix[2][1] - a_matrix[2][0] * a_matrix[1][1];
330 cofactor_matrix[1][0] = (a_matrix[0][1] * a_matrix[2][2] - a_matrix[2][1] * a_matrix[0][2]) * -1.0;
331 cofactor_matrix[1][1] = a_matrix[0][0] * a_matrix[2][2] - a_matrix[2][0] * a_matrix[0][2];
332 cofactor_matrix[1][2] = (a_matrix[0][0] * a_matrix[2][1] - a_matrix[2][0] * a_matrix[0][1]) * -1.0;
333 cofactor_matrix[2][0] = a_matrix[0][1] * a_matrix[1][2] - a_matrix[1][1] * a_matrix[0][2];
334 cofactor_matrix[2][1] = (a_matrix[0][0] * a_matrix[1][2] - a_matrix[1][0] * a_matrix[0][2]) * -1.0;
335 cofactor_matrix[2][2] = a_matrix[0][0] * a_matrix[1][1] - a_matrix[1][0] * a_matrix[0][1];
336
337 let mut inverse_matrix = [[0.0; 3]; 3];
338 for i in 0..3 {
339 for j in 0..3 {
340 inverse_matrix[i][j] = cofactor_matrix[j][i] * inverse_factor;
341 }
342 }
343
344 let p = [xi_zi, yi_zi, zi_sum];
345 let a = inverse_matrix[0][0] * p[0] + inverse_matrix[1][0] * p[1] + inverse_matrix[2][0] * p[2];
346 let b = inverse_matrix[0][1] * p[0] + inverse_matrix[1][1] * p[1] + inverse_matrix[2][1] * p[2];
347
348 let phi = a.atan2(b);
349 let amp = (a*a + b*b).sqrt();
350 let freq = 0.02 as f32;
351
352 (amp, freq, phi)
353}
354
355pub fn build_tcp_from_ip(ip: String, port: String) -> String {
375 format!("tcp://{}:{}", ip, port)
377}
378
379
380#[cfg(feature="database")]
405pub fn waveform_analysis(event : &mut RBEvent,
406 rb : &ReadoutBoard,
407 settings : AnalysisEngineSettings)
408-> Result<(), AnalysisError> {
409 if event.has_any_mangling_flag() {
411 warn!("Event for RB {} has data mangling! Not doing analysis!", rb.rb_id);
412 return Err(AnalysisError::DataMangling);
413 }
414 match event.self_check() {
415 Err(_err) => {
416 },
418 Ok(_) => ()
419 }
420 let active_channels = event.header.get_channels();
421 let fit_sinus = true;
423 let mut voltages : Vec<f32>= vec![0.0; NWORDS];
425 let mut times : Vec<f32>= vec![0.0; NWORDS];
426
427 let mut fit_result = (0.0f32, 0.0f32, 0.0f32);
429 if fit_sinus {
430 if !active_channels.contains(&8) {
431 warn!("RB {} does not have ch9 data!", rb.rb_id);
432 return Err(AnalysisError::NoChannel9);
434 }
435 rb.calibration.voltages(9,
436 event.header.stop_cell as usize,
437 &event.adc[8],
438 &mut voltages);
439 rb.calibration.nanoseconds(9,
448 event.header.stop_cell as usize,
449 &mut times);
450 fit_result = fit_sine_sydney(&voltages, ×);
451
452 }
455
456 let mut paddles = HashMap::<u8, TofHit>::new();
460 for pid in rb.get_paddle_ids() {
462 let ch_a = rb.get_pid_rbchA(pid).unwrap() as usize;
464 let ch_b = rb.get_pid_rbchB(pid).unwrap() as usize;
465 let mut hit = TofHit::new();
466 hit.paddle_id = pid;
467 for (k, ch) in [ch_a, ch_b].iter().enumerate() {
469 if !active_channels.contains(&(*ch as u8 -1)) {
474 trace!("Skipping channel {} because it is not marked to be readout in the event header channel mask!", ch);
475 continue;
476 }
477 rb.calibration.voltages(*ch,
479 event.header.stop_cell as usize,
480 &event.adc[*ch as usize -1],
481 &mut voltages);
482 rb.calibration.nanoseconds(*ch,
491 event.header.stop_cell as usize,
492 &mut times);
493 let (ped, ped_err) = calculate_pedestal(&voltages,
495 settings.pedestal_thresh,
496 settings.pedestal_begin_bin,
497 settings.pedestal_win_bins);
498 trace!("Calculated pedestal of {} +- {}", ped, ped_err);
499 for n in 0..voltages.len() {
500 voltages[n] -= ped;
501 }
502 let mut charge : f32 = 0.0;
503 let mut cfd_times = Vec::<f32>::new();
505 let mut max_volts = 0.0f32;
506 match find_peaks(&voltages ,
510 × ,
511 settings.find_pks_t_start ,
512 settings.find_pks_t_window,
513 settings.min_peak_size ,
514 settings.find_pks_thresh ,
515 settings.max_peaks ) {
516 Err(err) => {
517 debug!("Unable to find peaks for RB{:02} ch {ch}! Ignoring this channel!", rb.rb_id);
519 debug!("We won't be able to calculate timing information for this channel! Err {err}");
520 },
521 Ok(peaks) => {
522 for pk in peaks.iter() {
526 match cfd_simple(&voltages,
527 ×,
528 settings.cfd_fraction,
529 pk.0, pk.1) {
530 Err(err) => {
531 debug!("Unable to calculate cfd for peak {} {}! {}", pk.0, pk.1, err);
532 }
533 Ok(cfd) => {
534 cfd_times.push(cfd);
535 }
536 }
537 let pk_height = voltages[pk.0..pk.1].iter().max_by(|a,b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Less)).unwrap();
538 max_volts = *pk_height;
539 let max_index = voltages.iter().position(|element| *element == max_volts).unwrap();
540
541 let (start_q_int, stop_q_int) = if max_index - 40 < 10 {
542 (10, 210)
543 } else {
544 (max_index - 40, max_index + 160)
545 };
546
547
548 match integrate(&voltages,
552 ×,
553 start_q_int,
558 stop_q_int,
559 50.0) {
560 Err(err) => {
561 error!("Integration failed! Err {err}");
562 }
563 Ok(chrg) => {
564 charge = chrg;
565 }
566 }
567 break;
585 }
586 }} let mut tdc : f32 = 0.0;
589 if cfd_times.len() > 0 {
590 tdc = cfd_times[0];
591 }
592 if k == 0 {
595 hit.ftime_a = tdc;
596 hit.fpeak_a = max_volts;
597 hit.set_time_a(tdc);
598 hit.set_charge_a(charge);
599 hit.set_peak_a(max_volts);
600 hit.baseline_a = f16::from_f32(ped);
601 hit.baseline_a_rms = f16::from_f32(ped_err);
602 } else {
603 hit.ftime_b = tdc;
604 hit.fpeak_b = max_volts;
605 hit.set_time_b(tdc);
606 hit.set_charge_b(charge);
607 hit.set_peak_b(max_volts);
608 hit.baseline_b = f16::from_f32(ped);
609 hit.baseline_b_rms = f16::from_f32(ped_err);
610 hit.phase = f16::from_f32(fit_result.2);
613 paddles.insert(pid, hit);
614 }
615 }
616 }
617 let result = paddles.into_values().collect();
618 event.hits = result;
619 Ok(())
621}
622
623pub fn get_rb_ch_pid_map(map_file : PathBuf, rb_id : u8) -> RBChannelPaddleEndIDMap {
631 let mut mapping = RBChannelPaddleEndIDMap::new();
632 let json_content : String;
633 match read_to_string(&map_file) {
634 Ok(_json_content) => {
635 json_content = _json_content;
636 },
637 Err(err) => {
638 error!("Unable to parse json file {}. Error {err}", map_file.display());
639 return mapping;
640 }
641 }
642 let json : Value;
643 match serde_json::from_str(&json_content) {
644 Ok(_json) => {
645 json = _json;
646 },
647 Err(err) => {
648 error!("Unable to parse json file {}. Error {err}", map_file.display());
649 return mapping;
650 }
651 }
652 for ch in 0..8 {
653 let tmp_val = &json[rb_id.to_string()][(ch +1).to_string()];
654 let val = tmp_val.to_string().parse::<u16>().unwrap_or(0);
655 mapping.insert(ch as u8 + 1, val);
656 }
657 mapping
658}
659
660pub fn get_ltb_dsi_j_ch_mapping(mapping_file : PathBuf) -> DsiLtbRBMapping {
661 let mut mapping = HashMap::<u8,HashMap::<u8,HashMap::<u8,(u8,u8)>>>::new();
662 for dsi in 1..6 {
663 mapping.insert(dsi, HashMap::<u8,HashMap::<u8, (u8, u8)>>::new());
664 for j in 1..6 {
665 mapping.get_mut(&dsi).unwrap().insert(j, HashMap::<u8,(u8, u8)>::new());
666 for ch in 1..17 {
667 mapping.get_mut(&dsi).unwrap().get_mut(&j).unwrap().insert(ch, (0,0));
668 }
669 }
670 }
671 let json_content : String;
672 match read_to_string(&mapping_file) {
673 Ok(_json_content) => {
674 json_content = _json_content;
675 },
676 Err(err) => {
677 error!("Unable to parse json file {}. Error {err}", mapping_file.display());
678 return mapping;
679 }
680 }
681 let json : Value;
682 match serde_json::from_str(&json_content) {
683 Ok(_json) => {
684 json = _json;
685 },
686 Err(err) => {
687 error!("Unable to parse json file {}. Error {err}", mapping_file.display());
688 return mapping;
689 }
690 }
691 for dsi in 1..6 {
692 for j in 1..6 {
693 for ch in 1..17 {
694 let val = mapping.get_mut(&dsi).unwrap().get_mut(&j).unwrap().get_mut(&ch).unwrap();
695 let tmp_val = &json[dsi.to_string()][j.to_string()][ch.to_string()];
697 *val = (tmp_val[0].to_string().parse::<u8>().unwrap_or(0), tmp_val[1].to_string().parse::<u8>().unwrap_or(0));
698 }
699 }
700 }
701 debug!("Mapping {:?}", mapping);
702 mapping
703}
704
705pub fn to_board_id_string(rb_id: u32) -> String {
707
708 format!("RB{:02}", rb_id)
710}
711
712#[derive(Debug, Clone, Args, PartialEq)]
715pub struct LtbThresholdOpts {
716 #[arg(short, long, default_value_t = DEFAULT_LTB_ID)]
718 pub id: u8,
719 #[arg(required = true)]
721 pub name: LTBThresholdName,
722 #[arg(required = true)]
724 pub level: u16
725}
726
727impl LtbThresholdOpts {
728 pub fn new(id: u8, name: LTBThresholdName, level: u16) -> Self {
729 Self {
730 id,
731 name,
732 level
733 }
734 }
735}
736
737#[derive(Debug, Copy, Clone, PartialEq, serde::Deserialize, serde::Serialize, clap::ValueEnum)]
739#[repr(u8)]
740pub enum LTBThresholdName {
741 Unknown = 0u8,
742 Hit = 10u8,
743 Beta = 20u8,
744 Veto = 30u8,
745}
746
747impl LTBThresholdName {
748 pub fn get_ch_number(threshold_name: LTBThresholdName) -> Result<u8, SetError> {
749 match threshold_name {
750 LTBThresholdName::Hit => Ok(0u8),
751 LTBThresholdName::Beta => Ok(1u8),
752 LTBThresholdName::Veto => Ok(2u8),
753 LTBThresholdName::Unknown => {
754 error!("Not able to get a LTB threshold from Unknown");
755 Err(SetError::EmptyInputData)
756 }
757 }
758 }
759}
760
761impl fmt::Display for LTBThresholdName {
762 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
763 let r = serde_json::to_string(self).unwrap_or(
764 String::from("Error: cannot unwrap this PowerStatusEnum"));
765 write!(f, "<PowerStatusEnum: {}>", r)
766 }
767}
768
769impl From<u8> for LTBThresholdName {
770 fn from(value: u8) -> Self {
771 match value {
772 0u8 => LTBThresholdName::Unknown,
773 10u8 => LTBThresholdName::Hit,
774 20u8 => LTBThresholdName::Beta,
775 30u8 => LTBThresholdName::Veto,
776 _ => LTBThresholdName::Unknown
777 }
778 }
779}
780
781#[derive(Debug, Copy, Clone, PartialEq, serde::Deserialize, serde::Serialize, clap::ValueEnum)]
782#[repr(u8)]
783pub enum TofComponent {
784 Unknown = 0u8,
785 All = 1u8,
787 AllButMT = 2u8,
789 TofCpu = 3u8,
791 MT = 10u8,
793 RB = 20u8,
795 PB = 30u8,
797 LTB = 40u8,
799 Preamp = 50u8
801}
802
803impl fmt::Display for TofComponent {
804 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
805 let r = serde_json::to_string(self).unwrap_or(
806 String::from("Error: cannot unwrap this TofComponent"));
807 write!(f, "<TofComponent: {}>", r)
808 }
809}
810
811impl From<u8> for TofComponent {
812 fn from(value: u8) -> Self {
813 match value {
814 0u8 => TofComponent::Unknown,
815 1u8 => TofComponent::All,
816 2u8 => TofComponent::AllButMT,
817 3u8 => TofComponent::TofCpu,
818 10u8 => TofComponent::MT,
819 20u8 => TofComponent::RB,
820 30u8 => TofComponent::PB,
821 40u8 => TofComponent::LTB,
822 50u8 => TofComponent::Preamp,
823 _ => TofComponent::Unknown
824 }
825 }
826}
827
828impl From<TofComponent> for clap::builder::Str {
829 fn from(value: TofComponent) -> Self {
830 match value {
831 TofComponent::Unknown => clap::builder::Str::from("Unknown"),
832 TofComponent::All => clap::builder::Str::from("All"),
833 TofComponent::AllButMT => clap::builder::Str::from("AllButMT"),
834 TofComponent::TofCpu => clap::builder::Str::from("TofCpu"),
835 TofComponent::MT => clap::builder::Str::from("MT"),
836 TofComponent::RB => clap::builder::Str::from("RB"),
837 TofComponent::PB => clap::builder::Str::from("PB"),
838 TofComponent::LTB => clap::builder::Str::from("LTB"),
839 TofComponent::Preamp => clap::builder::Str::from("Preamp")
840 }
841 }
842}
843