1#![warn(missing_docs)]
2
3use std::{error::Error, fmt::Display, fs};
14
15pub const MIDI_0_FREQ: f64 = 8.17579891564371;
17
18#[derive(Debug)]
20pub enum TuningError {
21 ParseError(String),
23 InvalidTone,
25 InvalidNoteCount,
27 TooFewNotes,
29 TuningUnmappedKey,
31 MappingLongerThanScale,
33 FileError,
35 ZeroSpan,
37 NonPositiveCents,
39 ZeroSteps,
41 IncompleteKBM,
43 IncompleteSCL,
45 InvalidKeyCount,
47}
48
49impl Display for TuningError {
50 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51 match self {
52 TuningError::ParseError(error) => write!(f, "Error parsing the file: {error}"),
53 TuningError::InvalidTone => write!(f, "Value is an invalid tone."),
54 TuningError::InvalidNoteCount => write!(f, "Invalid number of notes."),
55 TuningError::TooFewNotes => write!(f, "Too few notes."),
56 TuningError::TuningUnmappedKey => write!(f, "Attempted to tune unmapped key."),
57 TuningError::MappingLongerThanScale => {
58 write!(f, "Keyboard mapping is longer than the scale.")
59 }
60 TuningError::FileError => write!(f, "Error reading the file."),
61 TuningError::ZeroSpan => write!(f, "Cannot divide zero span."),
62 TuningError::NonPositiveCents => {
63 write!(f, "Cannot divide by non-positive cents amount.")
64 }
65 TuningError::ZeroSteps => write!(f, "Cannot divide by zero steps."),
66 TuningError::IncompleteKBM => write!(f, "KBM file is incomplete."),
67 TuningError::IncompleteSCL => write!(f, "SCL file is incomplete."),
68 TuningError::InvalidKeyCount => write!(f, "Invalid number of keys."),
69 }
70 }
71}
72
73impl Error for TuningError {}
74
75#[doc(hidden)]
76pub struct AllowTuningOnUnmapped(pub bool);
77
78#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
82#[derive(Copy, Clone, Debug, PartialEq)]
83pub enum ToneValue {
84 Cents(f64),
91
92 Ratio(i64, i64),
99}
100
101impl ToneValue {
102 fn cents(&self) -> f64 {
103 match self {
104 Self::Cents(value) => *value,
105 Self::Ratio(n, d) => 1200.0 * (*n as f64 / *d as f64).log2() / 2f64.log2(),
106 }
107 }
108
109 fn float_value(&self) -> f64 {
110 self.cents() / 1200.0 + 1.0
111 }
112}
113
114impl Default for ToneValue {
115 fn default() -> Self {
116 ToneValue::Ratio(1, 1)
117 }
118}
119
120impl Eq for ToneValue {}
121
122impl PartialOrd for ToneValue {
123 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
124 self.cents().partial_cmp(&other.cents())
125 }
126}
127
128impl Ord for ToneValue {
129 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
130 self.partial_cmp(other).unwrap()
131 }
132}
133
134impl Display for ToneValue {
135 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
136 match self {
137 ToneValue::Cents(value) => write!(f, "{value}c"),
138 ToneValue::Ratio(n, d) => write!(f, "{n}/{d}"),
139 }
140 }
141}
142
143#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
148#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
149pub struct Tone {
150 pub value: ToneValue,
152
153 pub string_rep: String,
155
156 pub lineno: Option<usize>,
158}
159
160impl Tone {
161 pub fn new() -> Self {
163 Tone::default()
164 }
165
166 pub fn cents(&self) -> f64 {
168 self.value.cents()
169 }
170
171 pub fn float_value(&self) -> f64 {
173 self.value.float_value()
174 }
175
176 pub fn from_string(line: &str, lineno: Option<usize>) -> Result<Self, TuningError> {
185 let mut t = Tone::new();
186 t.string_rep = line.to_string();
187 t.lineno = lineno;
188
189 if line.find('.').is_some() {
190 t.value = ToneValue::Cents(match line.parse() {
191 Ok(x) => x,
192 Err(_) => {
193 return Err(TuningError::ParseError(format!(
194 "Line {} contains . but is not numeric.",
195 lineno.unwrap_or_default()
196 )))
197 }
198 });
199
200 return Ok(t);
201 }
202
203 let parts: Vec<&str> = line.split('/').collect();
204 match parts[..] {
205 [one] => {
206 t.value = ToneValue::Ratio(
207 match one.parse() {
208 Ok(x) => x,
209 Err(_) => {
210 return Err(TuningError::ParseError(format!(
211 "Numerator on line {} is not numeric.",
212 lineno.unwrap_or_default()
213 )))
214 }
215 },
216 1,
217 )
218 }
219 [one, two] => {
220 t.value = ToneValue::Ratio(
221 match one.parse() {
222 Ok(x) => x,
223 Err(_) => {
224 return Err(TuningError::ParseError(format!(
225 "Numerator on line {} is not numeric.",
226 lineno.unwrap_or_default()
227 )))
228 }
229 },
230 match two.parse() {
231 Ok(x) => x,
232 Err(_) => {
233 return Err(TuningError::ParseError(format!(
234 "Denominator on line {} is not numeric.",
235 lineno.unwrap_or_default()
236 )))
237 }
238 },
239 )
240 }
241 _ => {
242 return Err(TuningError::ParseError(format!(
243 "Value on line {} is not a valid fraction.",
244 lineno.unwrap_or_default()
245 )))
246 }
247 }
248
249 if let ToneValue::Ratio(d, n) = t.value {
250 if d == 0 || n == 0 {
251 return Err(TuningError::InvalidTone);
252 }
253 }
254
255 Ok(t)
256 }
257}
258
259impl Display for Tone {
260 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
261 write!(f, "{}", self.string_rep)
262 }
263}
264
265impl Default for Tone {
266 fn default() -> Self {
267 Tone {
268 value: ToneValue::default(),
269 string_rep: String::from("1/1"),
270 lineno: None,
271 }
272 }
273}
274
275#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
285#[derive(Default, Clone, Debug)]
286pub struct Scale {
287 pub name: String,
289
290 pub description: String,
292
293 pub raw_text: String,
295
296 count: usize,
298
299 pub tones: Vec<Tone>,
301}
302
303impl Scale {
304 pub fn new() -> Self {
306 Scale {
307 name: String::from("empty scale"),
308 ..Default::default()
309 }
310 }
311
312 pub fn count(&self) -> usize {
314 self.count
315 }
316
317 pub fn read_scl_file<P>(fname: P) -> Result<Self, TuningError>
319 where
320 P: AsRef<std::path::Path>,
321 {
322 let content = match fs::read_to_string(fname) {
323 Ok(lines) => lines,
324 Err(_) => return Err(TuningError::FileError),
325 };
326
327 Scale::parse_scl_data(&content)
328 }
329
330 pub fn parse_scl_data(scl_contents: &str) -> Result<Self, TuningError> {
332 enum State {
333 ReadHeader,
334 ReadCount,
335 ReadNote,
336 Trailing,
337 }
338 let mut state = State::ReadHeader;
339
340 let mut res = Scale::new();
341
342 for (lineno, line) in scl_contents.split('\n').map(|x| x.trim()).enumerate() {
343 if (matches!(state, State::ReadNote) && line.is_empty()) || line.starts_with('!') {
344 continue;
345 }
346
347 match state {
348 State::ReadHeader => {
349 res.description = line.to_string();
350 state = State::ReadCount
351 }
352 State::ReadCount => {
353 res.count = match line.parse() {
354 Ok(value) if value >= 1 => value,
355 Ok(_) => return Err(TuningError::InvalidNoteCount),
356 Err(_) => {
357 return Err(TuningError::ParseError(format!(
358 "Error parsing note count on line {lineno}."
359 )))
360 }
361 };
362
363 state = State::ReadNote;
364 }
365 State::ReadNote => {
366 let t = Tone::from_string(line, Some(lineno))?;
367 res.tones.push(t);
368
369 if res.tones.len() == res.count {
370 state = State::Trailing;
371 }
372 }
373 State::Trailing => (),
374 }
375 }
376
377 if !matches!(state, State::ReadNote | State::Trailing) {
378 return Err(TuningError::IncompleteSCL);
379 }
380
381 if res.tones.len() != res.count {
382 return Err(TuningError::InvalidNoteCount);
383 }
384
385 res.raw_text = scl_contents.to_string();
386 Ok(res)
387 }
388
389 pub fn even_temperament_12_note_scale() -> Self {
391 let data = "! 12 Tone Equal Temperament.scl
392 !
393 12 Tone Equal Temperament | ED2-12 - Equal division of harmonic 2 into 12 parts
394 12
395 !
396 100.00000
397 200.00000
398 300.00000
399 400.00000
400 500.00000
401 600.00000
402 700.00000
403 800.00000
404 900.00000
405 1000.00000
406 1100.00000
407 2/1";
408
409 Scale::parse_scl_data(data).expect("This shouldn't fail")
410 }
411
412 pub fn even_division_of_span_by_m(span: u32, m: u32) -> Result<Self, TuningError> {
423 if span == 0 {
424 return Err(TuningError::ZeroSpan);
425 }
426
427 if m == 0 {
428 return Err(TuningError::ZeroSteps);
429 }
430
431 let mut data = String::new();
432 data += &format!("! Automatically generated ED{span}-{m} scale\n");
433 data += &format!("Automatically generated ED{span}-{m} scale\n");
434 data += &format!("{m}\n");
435 data += "!\n";
436
437 let top_cents = 1200.0 * (span as f64).log2() / 2f64.log2();
438 let d_cents = top_cents / m as f64;
439 data += &(1..m)
440 .map(|i| format!("{:.32}\n", d_cents * i as f64))
441 .collect::<String>();
442 data += &format!("{span}/1\n");
443
444 Scale::parse_scl_data(&data)
445 }
446
447 pub fn even_division_of_cents_by_m(
451 cents: f64,
452 m: u32,
453 last_label: &str,
454 ) -> Result<Self, TuningError> {
455 if cents <= 0.0 {
456 return Err(TuningError::NonPositiveCents);
457 }
458
459 if m == 0 {
460 return Err(TuningError::ZeroSteps);
461 }
462
463 let mut data = String::new();
464 data += &format!("! Automatically generated Even Division of {cents} ct into {m} scale\n");
465 data += &format!("Automatically generated Even Division of {cents} ct into {m} scale\n");
466 data += &format!("{m}\n");
467 data += "!\n";
468
469 let top_cents = cents;
470 let d_cents = top_cents / m as f64;
471 data += &(1..m)
472 .map(|i| format!("{}\n", d_cents * i as f64))
473 .collect::<String>();
474
475 data += &match last_label {
476 "" => format!("{cents}\n"),
477 label => format!("{label}\n"),
478 };
479
480 Scale::parse_scl_data(&data)
481 }
482}
483
484#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
494#[derive(Default, Clone, Debug)]
495pub struct KeyboardMapping {
496 count: usize,
498
499 pub first_midi: i32,
501
502 pub last_midi: i32,
504
505 pub middle_note: i32,
507
508 pub tuning_constant_note: i32,
510
511 pub tuning_frequency: f64,
513
514 pub tuning_pitch: f64,
516
517 pub octave_degrees: i32,
519
520 pub keys: Vec<i32>, pub raw_text: String,
526
527 pub name: String,
529}
530
531impl KeyboardMapping {
532 pub fn new() -> Self {
534 let mut k = KeyboardMapping {
535 count: 0,
536 first_midi: 0,
537 last_midi: 127,
538 middle_note: 60,
539 tuning_constant_note: 60,
540 tuning_frequency: MIDI_0_FREQ * 32.0,
541 tuning_pitch: 32.0,
542 octave_degrees: 0,
543 raw_text: String::new(),
544 ..Default::default()
545 };
546
547 k.raw_text = format!(
548 "! Default KBM file\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n",
549 k.count,
550 k.first_midi,
551 k.last_midi,
552 k.middle_note,
553 k.tuning_constant_note,
554 k.tuning_frequency,
555 k.octave_degrees
556 );
557
558 k
559 }
560
561 pub fn count(&self) -> usize {
563 self.count
564 }
565
566 pub fn read_kbm_file<P>(fname: P) -> Result<Self, TuningError>
568 where
569 P: AsRef<std::path::Path>,
570 {
571 let content = match fs::read_to_string(&fname) {
572 Ok(lines) => lines,
573 Err(_) => return Err(TuningError::FileError),
574 };
575
576 let mut res = KeyboardMapping::parse_kbm_data(&content)?;
577 res.name = fname.as_ref().to_str().unwrap_or_default().to_string();
578 Ok(res)
579 }
580
581 pub fn parse_kbm_data(kbm_contents: &str) -> Result<Self, TuningError> {
583 enum ParsePosition {
584 MapSize,
585 FirstMidi,
586 LastMidi,
587 Middle,
588 Reference,
589 Freq,
590 Degree,
591 Keys,
592 Trailing,
593 }
594
595 impl ParsePosition {
596 fn next(&self) -> ParsePosition {
597 match self {
598 ParsePosition::MapSize => Self::FirstMidi,
599 ParsePosition::FirstMidi => Self::LastMidi,
600 ParsePosition::LastMidi => Self::Middle,
601 ParsePosition::Middle => Self::Reference,
602 ParsePosition::Reference => Self::Freq,
603 ParsePosition::Freq => Self::Degree,
604 ParsePosition::Degree => Self::Keys,
605 ParsePosition::Keys => Self::Trailing,
606 ParsePosition::Trailing => panic!("this should not happen"),
607 }
608 }
609 }
610 let mut state = ParsePosition::MapSize;
611
612 let mut res = KeyboardMapping::new();
613
614 for (lineno, mut line) in kbm_contents.split('\n').map(|x| x.trim()).enumerate() {
615 if line.starts_with('!') {
616 continue;
617 }
618
619 if line == "x" {
620 line = "-1";
621 } else if !matches!(state, ParsePosition::Trailing) {
622 let lc = line.chars();
623 let mut valid_line = !line.is_empty();
624 let mut bad_char = '\0';
625
626 for val in lc {
627 if !valid_line || val == '\0' {
628 break;
629 }
630
631 if !(val == ' '
632 || val.is_ascii_digit()
633 || val == '.'
634 || val == 13 as char
635 || val == '\n')
636 {
637 valid_line = false;
638 bad_char = val;
639 }
640 }
641
642 if !valid_line {
643 return Err(TuningError::ParseError(format!(
644 "Bad character {bad_char} on line {lineno}"
645 )));
646 }
647 }
648
649 let i = match line.parse::<i32>() {
650 Err(_) => Err(TuningError::ParseError(format!(
651 "Value on line {lineno} is not integer value."
652 ))),
653 Ok(x) => Ok(x),
654 };
655 let v = match line.parse::<f64>() {
656 Err(_) => Err(TuningError::ParseError(format!(
657 "Value on line {lineno} is not float value."
658 ))),
659 Ok(x) => Ok(x),
660 };
661
662 match state {
663 ParsePosition::MapSize => res.count = i? as usize,
664 ParsePosition::FirstMidi => res.first_midi = i?,
665 ParsePosition::LastMidi => res.last_midi = i?,
666 ParsePosition::Middle => res.middle_note = i?,
667 ParsePosition::Reference => res.tuning_constant_note = i?,
668 ParsePosition::Freq => {
669 res.tuning_frequency = v?;
670 res.tuning_pitch = res.tuning_frequency / MIDI_0_FREQ;
671 }
672 ParsePosition::Degree => res.octave_degrees = i?,
673 ParsePosition::Keys => {
674 res.keys.push(i?);
675 if res.keys.len() == res.count {
676 state = ParsePosition::Trailing;
677 }
678 }
679 ParsePosition::Trailing => {}
680 }
681
682 if !(matches!(state, ParsePosition::Keys | ParsePosition::Trailing)) {
683 state = state.next();
684 }
685 if matches!(state, ParsePosition::Keys) && res.count == 0 {
686 state = ParsePosition::Trailing;
687 }
688 }
689
690 if !matches!(state, ParsePosition::Keys | ParsePosition::Trailing) {
691 return Err(TuningError::IncompleteKBM);
692 }
693
694 if res.keys.len() != res.count {
695 return Err(TuningError::InvalidKeyCount);
696 }
697
698 res.raw_text = kbm_contents.to_string();
699 Ok(res)
700 }
701
702 pub fn tune_a69_to(freq: f64) -> Self {
705 KeyboardMapping::tune_note_to(69, freq)
706 }
707
708 pub fn tune_note_to(midi_note: i32, freq: f64) -> Self {
711 KeyboardMapping::start_scale_on_and_tune_note_to(60, midi_note, freq)
712 }
713
714 pub fn start_scale_on_and_tune_note_to(scale_start: i32, midi_note: i32, freq: f64) -> Self {
717 let data = format!(
718 "! Automatically generated mapping, tuning note {midi_note} to {freq} Hz
719 !
720 ! Size of map
721 0
722 ! First and last MIDI notes to map - map the entire keyboard
723 0
724 127
725 ! Middle note where the first entry in the scale is mapped.
726 {scale_start}
727 ! Reference note where frequency is fixed
728 {midi_note}
729 ! Frequency for MIDI note {midi_note}
730 {freq}
731 ! Scale degree for formal octave. This is an empty mapping, so:
732 0
733 ! Mapping. This is an empty mapping so list no keys"
734 );
735
736 KeyboardMapping::parse_kbm_data(&data).expect("this should not fail")
737 }
738}
739
740const N: usize = 512;
741
742#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
763#[derive(Clone, Debug)]
764pub struct Tuning {
765 pub scale: Scale,
767
768 pub keyboard_mapping: KeyboardMapping,
770
771 ptable: Vec<f64>,
772 lptable: Vec<f64>,
773 scale_position_table: Vec<i32>,
774 allow_tuning_center_on_unmapped: bool,
775}
776
777impl Default for Tuning {
778 fn default() -> Self {
779 Self::new()
780 }
781}
782
783impl Tuning {
784 pub fn new() -> Self {
786 Tuning::from_scale_and_keyboard_mapping(
787 Scale::even_temperament_12_note_scale(),
788 KeyboardMapping::new(),
789 AllowTuningOnUnmapped(false),
790 )
791 .unwrap()
792 }
793
794 pub fn from_scale(scale: Scale) -> Result<Self, TuningError> {
796 Tuning::from_scale_and_keyboard_mapping(
797 scale,
798 KeyboardMapping::new(),
799 AllowTuningOnUnmapped(false),
800 )
801 }
802
803 pub fn from_keyboard_mapping(keyboard_mapping: KeyboardMapping) -> Result<Self, TuningError> {
805 Tuning::from_scale_and_keyboard_mapping(
806 Scale::even_temperament_12_note_scale(),
807 keyboard_mapping,
808 AllowTuningOnUnmapped(false),
809 )
810 }
811
812 pub fn from_scale_and_keyboard_mapping(
814 scale: Scale,
815 keyboard_mapping: KeyboardMapping,
816 allow_tuning_center_on_unmapped: AllowTuningOnUnmapped,
817 ) -> Result<Self, TuningError> {
818 let mut tun = Tuning {
819 scale,
820 keyboard_mapping,
821 allow_tuning_center_on_unmapped: allow_tuning_center_on_unmapped.0,
822 ptable: vec![0f64; N],
823 lptable: vec![0f64; N],
824 scale_position_table: vec![0; N],
825 };
826
827 let mut o_sp = 0;
828 if tun.scale.count == 0 {
829 return Err(TuningError::TooFewNotes);
830 }
831
832 let kbm_rotations = tun
833 .keyboard_mapping
834 .keys
835 .iter()
836 .map(|x| (*x as f64 / tun.scale.count as f64).ceil() as i32)
837 .max()
838 .unwrap_or(1);
839
840 if kbm_rotations > 1 {
841 let mut new_s = tun.scale.clone();
842 new_s.count = tun.scale.count * kbm_rotations as usize;
843 let back_cents = tun.scale.tones.last().unwrap().value.cents();
844 let mut push_off = back_cents;
845
846 for _ in 1..kbm_rotations {
847 for t in &tun.scale.tones {
848 let mut t_copy = t.clone();
849 t_copy.value = ToneValue::Cents(t.value.cents() + push_off);
850 new_s.tones.push(t_copy);
851 }
852 push_off += back_cents;
853 }
854
855 tun.scale = new_s;
856 tun.keyboard_mapping.octave_degrees *= kbm_rotations;
857 if tun.keyboard_mapping.octave_degrees == 0 {
858 tun.keyboard_mapping.octave_degrees = tun.scale.count as i32;
859 }
860 }
861
862 if tun.keyboard_mapping.octave_degrees > tun.scale.count as i32 {
863 return Err(TuningError::MappingLongerThanScale);
864 }
865
866 let mut pitches = [0.0; N];
867
868 let pos_pitch_0 = 256 + tun.keyboard_mapping.tuning_constant_note;
869 let pos_scale_0 = 256 + tun.keyboard_mapping.middle_note;
870
871 let pitch_mod = tun.keyboard_mapping.tuning_pitch.log2() / 2f64.log2() - 1.0;
872
873 let mut scale_position_of_tuning_note =
874 tun.keyboard_mapping.tuning_constant_note - tun.keyboard_mapping.middle_note;
875
876 if tun.keyboard_mapping.count > 0 {
877 while scale_position_of_tuning_note >= tun.keyboard_mapping.count as i32 {
878 scale_position_of_tuning_note -= tun.keyboard_mapping.count as i32;
879 }
880
881 while scale_position_of_tuning_note < 0 {
882 scale_position_of_tuning_note += tun.keyboard_mapping.count as i32;
883 }
884
885 o_sp = scale_position_of_tuning_note;
886 scale_position_of_tuning_note =
887 tun.keyboard_mapping.keys[scale_position_of_tuning_note as usize];
888
889 if scale_position_of_tuning_note == -1 && !tun.allow_tuning_center_on_unmapped {
890 return Err(TuningError::TuningUnmappedKey);
891 }
892 }
893
894 let tuning_center_pitch_offset;
895 if scale_position_of_tuning_note == 0 {
896 tuning_center_pitch_offset = 0.0;
897 } else if scale_position_of_tuning_note == -1 && tun.allow_tuning_center_on_unmapped {
898 let mut low = 0;
899 let mut high = 0;
900 let mut octave_up = false;
901 let mut octave_down = false;
902
903 let mut i = o_sp as usize - 1;
904 while i != o_sp as usize {
905 if tun.keyboard_mapping.keys[i] != -1 {
906 low = tun.keyboard_mapping.keys[i];
907 break;
908 }
909
910 if i > o_sp as usize {
911 octave_down = true;
912 }
913
914 i = (i - 1) % tun.keyboard_mapping.count;
915 }
916
917 i = o_sp as usize + 1;
918 while i != o_sp as usize {
919 if tun.keyboard_mapping.keys[i] != -1 {
920 high = tun.keyboard_mapping.keys[i];
921 break;
922 }
923
924 if i < o_sp as usize {
925 octave_up = true;
926 }
927
928 i = (i + 1) % tun.keyboard_mapping.count;
929 }
930
931 let dt = tun.scale.tones[tun.scale.count - 1].value.cents();
932 let pitch_low = if octave_down {
933 tun.scale.tones[low as usize - 1].value.cents() - dt
934 } else {
935 tun.scale.tones[low as usize - 1].value.float_value() - 1.0
936 };
937 let pitch_high = if octave_up {
938 tun.scale.tones[high as usize - 1].value.cents() + dt
939 } else {
940 tun.scale.tones[high as usize - 1].value.float_value() - 1.0
941 };
942 tuning_center_pitch_offset = (pitch_high + pitch_low) / 2.0;
943 } else {
944 let mut tshift = 0.0;
945 let dt = tun.scale.tones[tun.scale.count - 1].value.float_value() - 1.0;
946
947 while scale_position_of_tuning_note < 0 {
948 scale_position_of_tuning_note += tun.scale.count as i32;
949 tshift += dt;
950 }
951 while scale_position_of_tuning_note > tun.scale.count as i32 {
952 scale_position_of_tuning_note -= tun.scale.count as i32;
953 tshift -= dt;
954 }
955
956 if scale_position_of_tuning_note == 0 {
957 tuning_center_pitch_offset = -tshift;
958 } else {
959 tuning_center_pitch_offset = tun.scale.tones
960 [scale_position_of_tuning_note as usize - 1]
961 .value
962 .float_value()
963 - 1.0
964 - tshift;
965 }
966 }
967
968 for (i, pitch) in pitches.iter_mut().enumerate().take(N) {
969 let distance_from_pitch_0 = i as i32 - pos_pitch_0;
970 let distance_from_scale_0 = i as i32 - pos_scale_0;
971
972 if distance_from_pitch_0 == 0 {
973 *pitch = 1.0;
974 tun.lptable[i] = *pitch + pitch_mod;
975 tun.ptable[i] = 2f64.powf(tun.lptable[i]);
976
977 if tun.keyboard_mapping.count > 0 {
978 let mut mapping_key = distance_from_scale_0 % tun.keyboard_mapping.count as i32;
979 if mapping_key < 0 {
980 mapping_key += tun.keyboard_mapping.count as i32;
981 }
982
983 let cm = tun.keyboard_mapping.keys[mapping_key as usize];
984 if !tun.allow_tuning_center_on_unmapped && cm < 0 {
985 return Err(TuningError::TuningUnmappedKey);
986 }
987 }
988
989 tun.scale_position_table[i] =
990 scale_position_of_tuning_note % tun.scale.count as i32;
991 } else {
992 let mut rounds;
993 let mut this_round;
994 let mut disable = false;
995 if tun.keyboard_mapping.count == 0 {
996 rounds = (distance_from_scale_0 - 1) / tun.scale.count as i32;
997 this_round = (distance_from_scale_0 - 1) % tun.scale.count as i32;
998 } else {
999 let mut mapping_key = distance_from_scale_0 % tun.keyboard_mapping.count as i32;
1000 if mapping_key < 0 {
1001 mapping_key += tun.keyboard_mapping.count as i32;
1002 }
1003
1004 let mut rotations = 0;
1005 let mut dt = distance_from_scale_0;
1006 if dt > 0 {
1007 while dt >= tun.keyboard_mapping.count as i32 {
1008 dt -= tun.keyboard_mapping.count as i32;
1009 rotations += 1;
1010 }
1011 } else {
1012 while dt < 0 {
1013 dt += tun.keyboard_mapping.count as i32;
1014 rotations -= 1;
1015 }
1016 }
1017
1018 let cm = tun.keyboard_mapping.keys[mapping_key as usize];
1019
1020 let mut push = 0;
1021 if cm < 0 {
1022 disable = true;
1023 } else {
1024 if cm > tun.scale.count as i32 {
1025 return Err(TuningError::MappingLongerThanScale);
1026 }
1027 push = mapping_key - cm;
1028 }
1029
1030 if tun.keyboard_mapping.octave_degrees > 0
1031 && tun.keyboard_mapping.octave_degrees != tun.keyboard_mapping.count as i32
1032 {
1033 rounds = rotations;
1034 this_round = cm - 1;
1035 if this_round < 0 {
1036 this_round = tun.keyboard_mapping.octave_degrees - 1;
1037 rounds -= 1;
1038 }
1039 } else {
1040 rounds = (distance_from_scale_0 - push - 1) / tun.scale.count as i32;
1041 this_round = (distance_from_scale_0 - push - 1) % tun.scale.count as i32;
1042 }
1043 }
1044
1045 if this_round < 0 {
1046 this_round += tun.scale.count as i32;
1047 rounds -= 1;
1048 }
1049
1050 if disable {
1051 *pitch = 0.0;
1052 tun.scale_position_table[i] = -1;
1053 } else {
1054 *pitch = tun.scale.tones[this_round as usize].value.float_value()
1055 + rounds as f64
1056 * (tun.scale.tones[tun.scale.count - 1].value.float_value() - 1.0)
1057 - tuning_center_pitch_offset;
1058 tun.scale_position_table[i] = (this_round + 1) % tun.scale.count as i32;
1059 }
1060
1061 tun.lptable[i] = *pitch + pitch_mod;
1062 tun.ptable[i] = 2f64.powf(*pitch + pitch_mod);
1063 }
1064 }
1065
1066 Ok(tun)
1067 }
1068
1069 pub fn with_skipped_notes_interpolated(&self) -> Self {
1071 let mut res = self.clone();
1072
1073 for i in 1..N - 1 {
1074 if self.scale_position_table[i] >= 0 {
1075 continue;
1076 }
1077
1078 let mut nxt = i + 1;
1079 let mut prv = i - 1;
1080 while self.scale_position_table[prv] < 0 {
1081 prv -= 1;
1082 }
1083 while nxt < N && self.scale_position_table[nxt] < 0 {
1084 nxt += 1;
1085 }
1086 let dist = (nxt - prv) as f64;
1087 let frac = (i - prv) as f64 / dist;
1088 res.lptable[i] = (1.0 - frac) * self.lptable[prv] + frac * self.lptable[nxt];
1089 res.ptable[i] = 2f64.powf(res.lptable[i]);
1090 }
1091
1092 res
1093 }
1094
1095 pub fn frequency_for_midi_note(&self, midi_note: i32) -> f64 {
1103 let mni = (N as i32 - 1).min(0.max(midi_note + 256));
1104 self.ptable[mni as usize] * MIDI_0_FREQ
1105 }
1106
1107 pub fn frequency_for_midi_note_scaled_by_midi0(&self, midi_note: i32) -> f64 {
1115 let mni = (N as i32 - 1).min(0.max(midi_note + 256));
1116 self.ptable[mni as usize]
1117 }
1118
1119 pub fn log_scaled_frequency_for_midi_note(&self, midi_note: i32) -> f64 {
1128 let mni = (N as i32 - 1).min(0.max(midi_note + 256));
1129 self.lptable[mni as usize]
1130 }
1131
1132 pub fn scale_position_for_midi_note(&self, midi_note: i32) -> i32 {
1136 let mni = (N as i32 - 1).min(0.max(midi_note + 256));
1137 self.scale_position_table[mni as usize]
1138 }
1139
1140 pub fn is_midi_note_mapped(&self, midi_note: i32) -> bool {
1142 let mni = (N as i32 - 1).min(0.max(midi_note + 256));
1143 self.scale_position_table[mni as usize] >= 0
1144 }
1145}