1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
//! Generate tuning maps to enhance the capabilities of synthesizers with limited tuning support.

mod aot;
mod jit;
mod midi;

use std::hash::Hash;

use crate::{
    note::{Note, NoteLetter},
    pitch::Ratio,
};

pub use self::{aot::*, jit::*, midi::*};

/// A note-based multichannel synthesizer with note detuning capabilities.
pub trait TunableSynth {
    type Result: IsErr;
    type NoteAttr: Clone + Default;
    type GlobalAttr;

    fn num_channels(&self) -> usize;

    fn group_by(&self) -> GroupBy;

    fn notes_detune(&mut self, channel: usize, detuned_notes: &[(Note, Ratio)]) -> Self::Result;

    fn note_on(&mut self, channel: usize, started_note: Note, attr: Self::NoteAttr)
        -> Self::Result;

    fn note_off(
        &mut self,
        channel: usize,
        stopped_note: Note,
        attr: Self::NoteAttr,
    ) -> Self::Result;

    fn note_attr(
        &mut self,
        channel: usize,
        affected_note: Note,
        attr: Self::NoteAttr,
    ) -> Self::Result;

    fn global_attr(&mut self, attr: Self::GlobalAttr) -> Self::Result;
}

pub trait IsErr {
    fn is_err(&self) -> bool;

    fn ok() -> Self;
}

impl<T: Default, E> IsErr for Result<T, E> {
    fn is_err(&self) -> bool {
        self.is_err()
    }

    fn ok() -> Self {
        Ok(T::default())
    }
}

impl IsErr for () {
    fn is_err(&self) -> bool {
        false
    }

    fn ok() -> Self {}
}

/// Defines the tuning group that is affected by a tuning change.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum GroupBy {
    /// Tuning changes are applied per [`Note`].
    ///
    /// Example: C4 and C5 are different [`Note`]s which means they can be detuned independently within a single channel.
    Note,
    /// Tuning changes are applied per [`NoteLetter`].
    ///
    /// Example: C4 and C5 share the same [`NoteLetter`] which means they cannot be detuned independently within a single channel.
    /// In order to detune them independently, at least two channels are required.
    NoteLetter,
    /// Tuning changes always affect the whole channel.
    ///
    /// For *n* keys, at least *n* channels are required.
    Channel,
}

impl GroupBy {
    pub fn group(self, note: Note) -> Group {
        match self {
            GroupBy::Note => Group::Note(note),
            GroupBy::NoteLetter => Group::NoteLetter(note.letter_and_octave().0),
            GroupBy::Channel => Group::Channel,
        }
    }
}

/// A tuning group which is not known at compile time.
#[derive(Copy, Clone, Eq, Hash, PartialEq)]
pub enum Group {
    Note(Note),
    NoteLetter(NoteLetter),
    Channel,
}

impl Group {
    pub fn ungroup(self) -> Note {
        match self {
            Group::Note(note) => note,
            Group::NoteLetter(note_letter) => note_letter.in_octave(0),
            Group::Channel => Note::from_midi_number(0),
        }
    }
}