tune/tuner/
midi.rs

1use std::collections::HashMap;
2
3use crate::{
4    midi::{ChannelMessage, ChannelMessageType},
5    mts::{
6        self, ScaleOctaveTuning, ScaleOctaveTuningFormat, ScaleOctaveTuningMessage,
7        ScaleOctaveTuningOptions, SingleNoteTuningChange, SingleNoteTuningChangeMessage,
8        SingleNoteTuningChangeOptions,
9    },
10    note::Note,
11    pitch::{Pitched, Ratio},
12};
13
14use super::{GroupBy, TunableSynth};
15
16pub struct TunableMidi<H> {
17    midi_target: MidiTarget<H>,
18    midi_tuning_creator: MidiTuningCreator,
19}
20
21impl<H> TunableMidi<H> {
22    pub fn single_note_tuning_change(
23        midi_target: MidiTarget<H>,
24        realtime: bool,
25        device_id: u8,
26        first_tuning_program: u8,
27    ) -> Self {
28        Self {
29            midi_target,
30            midi_tuning_creator: MidiTuningCreator::SingleNoteTuningChange {
31                realtime,
32                device_id,
33                first_tuning_program,
34            },
35        }
36    }
37
38    pub fn scale_octave_tuning(
39        midi_target: MidiTarget<H>,
40        realtime: bool,
41        device_id: u8,
42        format: ScaleOctaveTuningFormat,
43    ) -> Self {
44        Self {
45            midi_target,
46            midi_tuning_creator: MidiTuningCreator::ScaleOctaveTuning {
47                realtime,
48                device_id,
49                format,
50                octave_tunings: HashMap::new(),
51            },
52        }
53    }
54
55    pub fn channel_fine_tuning(midi_target: MidiTarget<H>) -> Self {
56        Self {
57            midi_target,
58            midi_tuning_creator: MidiTuningCreator::ChannelFineTuning,
59        }
60    }
61
62    pub fn pitch_bend(midi_target: MidiTarget<H>) -> Self {
63        Self {
64            midi_target,
65            midi_tuning_creator: MidiTuningCreator::PitchBend,
66        }
67    }
68}
69
70impl<H: MidiTunerMessageHandler> TunableSynth for TunableMidi<H> {
71    type Result = ();
72    type NoteAttr = u8;
73    type GlobalAttr = ChannelMessageType;
74
75    fn num_channels(&self) -> usize {
76        self.midi_target.channels.len()
77    }
78
79    fn group_by(&self) -> GroupBy {
80        self.midi_tuning_creator.group_by()
81    }
82
83    fn notes_detune(&mut self, channel: usize, detuned_notes: &[(Note, Ratio)]) {
84        self.midi_tuning_creator
85            .create(&mut self.midi_target, channel, detuned_notes)
86    }
87
88    fn note_on(&mut self, channel: usize, started_note: Note, velocity: u8) {
89        if let Some(started_note) = started_note.checked_midi_number() {
90            self.midi_target.send(
91                ChannelMessageType::NoteOn {
92                    key: started_note,
93                    velocity,
94                },
95                channel,
96            );
97        }
98    }
99
100    fn note_off(&mut self, channel: usize, stopped_note: Note, velocity: u8) {
101        if let Some(stopped_note) = stopped_note.checked_midi_number() {
102            self.midi_target.send(
103                ChannelMessageType::NoteOff {
104                    key: stopped_note,
105                    velocity,
106                },
107                channel,
108            );
109        }
110    }
111
112    fn note_attr(&mut self, channel: usize, affected_note: Note, pressure: u8) {
113        if let Some(affected_note) = affected_note.checked_midi_number() {
114            self.midi_target.send(
115                ChannelMessageType::PolyphonicKeyPressure {
116                    key: affected_note,
117                    pressure,
118                },
119                channel,
120            );
121        }
122    }
123
124    fn global_attr(&mut self, message_type: ChannelMessageType) {
125        for channel in 0..self.num_channels() {
126            if self.midi_tuning_creator.allow_pitch_bend()
127                || !matches!(message_type, ChannelMessageType::PitchBendChange { .. })
128            {
129                self.midi_target.send(message_type, channel);
130            }
131        }
132    }
133}
134
135pub struct MidiTarget<H> {
136    pub handler: H,
137    pub channels: Vec<u8>,
138}
139
140impl<H: MidiTunerMessageHandler> MidiTarget<H> {
141    fn send(&mut self, message: ChannelMessageType, tuner_channel: usize) {
142        self.handler
143            .handle_channel_message(message, self.midi_channel(tuner_channel));
144    }
145
146    fn midi_channel(&self, tuner_channel: usize) -> u8 {
147        self.channels[tuner_channel]
148    }
149
150    fn tuning_program(&self, tuner_channel: usize, first_tuning_program: u8) -> u8 {
151        (u8::try_from(tuner_channel).unwrap() + first_tuning_program) % 128
152    }
153}
154
155enum MidiTuningCreator {
156    SingleNoteTuningChange {
157        device_id: u8,
158        realtime: bool,
159        first_tuning_program: u8,
160    },
161    ScaleOctaveTuning {
162        device_id: u8,
163        realtime: bool,
164        format: ScaleOctaveTuningFormat,
165        octave_tunings: HashMap<usize, ScaleOctaveTuning>,
166    },
167    ChannelFineTuning,
168    PitchBend,
169}
170
171impl MidiTuningCreator {
172    fn create(
173        &mut self,
174        target: &mut MidiTarget<impl MidiTunerMessageHandler>,
175        tuner_channel: usize,
176        detuned_notes: &[(Note, Ratio)],
177    ) {
178        let midi_channel = target.midi_channel(tuner_channel);
179
180        match self {
181            MidiTuningCreator::SingleNoteTuningChange {
182                realtime,
183                device_id,
184                first_tuning_program,
185            } => {
186                let tuning_program = target.tuning_program(tuner_channel, *first_tuning_program);
187
188                let options = SingleNoteTuningChangeOptions {
189                    realtime: *realtime,
190                    device_id: *device_id,
191                    tuning_program,
192                    with_bank_select: None,
193                };
194
195                for channel_message in
196                    mts::tuning_program_change(midi_channel, tuning_program).unwrap()
197                {
198                    target
199                        .handler
200                        .handle(MidiTunerMessage::new(channel_message));
201                }
202
203                if let Ok(tuning_message) = SingleNoteTuningChangeMessage::from_tuning_changes(
204                    &options,
205                    detuned_notes
206                        .iter()
207                        .map(|&(note, detuning)| SingleNoteTuningChange {
208                            key: note.as_piano_key(),
209                            target_pitch: note.pitch() * detuning,
210                        }),
211                ) {
212                    target.handler.handle(MidiTunerMessage::new(tuning_message));
213                }
214            }
215            MidiTuningCreator::ScaleOctaveTuning {
216                realtime,
217                device_id,
218                format,
219                octave_tunings,
220            } => {
221                let octave_tuning = octave_tunings.entry(tuner_channel).or_default();
222
223                for &(note, detuning) in detuned_notes {
224                    *octave_tuning.as_mut(note.letter_and_octave().0) = detuning;
225                }
226
227                let options = ScaleOctaveTuningOptions {
228                    realtime: *realtime,
229                    device_id: *device_id,
230                    channels: midi_channel.into(),
231                    format: *format,
232                };
233
234                if let Ok(tuning_message) =
235                    ScaleOctaveTuningMessage::from_octave_tuning(&options, octave_tuning)
236                {
237                    target.handler.handle(MidiTunerMessage::new(tuning_message));
238                }
239            }
240            MidiTuningCreator::ChannelFineTuning => {
241                for &(_, detuning) in detuned_notes {
242                    for channel_message in mts::channel_fine_tuning(midi_channel, detuning).unwrap()
243                    {
244                        target
245                            .handler
246                            .handle(MidiTunerMessage::new(channel_message));
247                    }
248                }
249            }
250            MidiTuningCreator::PitchBend => {
251                for &(_, detuning) in detuned_notes {
252                    let channel_message = pitch_bend_message(detuning)
253                        .in_channel(midi_channel)
254                        .unwrap();
255                    target
256                        .handler
257                        .handle(MidiTunerMessage::new(channel_message));
258                }
259            }
260        }
261    }
262
263    fn group_by(&self) -> GroupBy {
264        match self {
265            MidiTuningCreator::SingleNoteTuningChange { .. } => GroupBy::Note,
266            MidiTuningCreator::ScaleOctaveTuning { .. } => GroupBy::NoteLetter,
267            MidiTuningCreator::ChannelFineTuning | MidiTuningCreator::PitchBend => GroupBy::Channel,
268        }
269    }
270
271    fn allow_pitch_bend(&self) -> bool {
272        match self {
273            MidiTuningCreator::SingleNoteTuningChange { .. }
274            | MidiTuningCreator::ScaleOctaveTuning { .. }
275            | MidiTuningCreator::ChannelFineTuning => true,
276            MidiTuningCreator::PitchBend => false,
277        }
278    }
279}
280
281pub struct MidiTunerMessage {
282    variant: MidiTunerMessageVariant,
283}
284
285impl MidiTunerMessage {
286    fn new<M: Into<MidiTunerMessageVariant>>(variant: M) -> Self {
287        Self {
288            variant: variant.into(),
289        }
290    }
291
292    pub fn send_to(&self, mut receiver: impl FnMut(&[u8])) {
293        match &self.variant {
294            MidiTunerMessageVariant::Channel(channel_message) => {
295                receiver(&channel_message.to_raw_message());
296            }
297            MidiTunerMessageVariant::ScaleOctaveTuning(tuning_message) => {
298                receiver(tuning_message.sysex_bytes());
299            }
300            MidiTunerMessageVariant::SingleNoteTuningChange(tuning_message) => {
301                for sysex_bytes in tuning_message.sysex_bytes() {
302                    receiver(sysex_bytes);
303                }
304            }
305        }
306    }
307}
308
309enum MidiTunerMessageVariant {
310    Channel(ChannelMessage),
311    ScaleOctaveTuning(ScaleOctaveTuningMessage),
312    SingleNoteTuningChange(SingleNoteTuningChangeMessage),
313}
314
315impl From<ChannelMessage> for MidiTunerMessageVariant {
316    fn from(v: ChannelMessage) -> Self {
317        Self::Channel(v)
318    }
319}
320
321impl From<ScaleOctaveTuningMessage> for MidiTunerMessageVariant {
322    fn from(v: ScaleOctaveTuningMessage) -> Self {
323        Self::ScaleOctaveTuning(v)
324    }
325}
326
327impl From<SingleNoteTuningChangeMessage> for MidiTunerMessageVariant {
328    fn from(v: SingleNoteTuningChangeMessage) -> Self {
329        Self::SingleNoteTuningChange(v)
330    }
331}
332
333pub trait MidiTunerMessageHandler {
334    fn handle(&mut self, message: MidiTunerMessage);
335
336    fn handle_channel_message(&mut self, message_type: ChannelMessageType, channel: u8) {
337        if let Some(message) = message_type.in_channel(channel) {
338            self.handle(MidiTunerMessage::new(message));
339        }
340    }
341}
342
343impl<F: FnMut(MidiTunerMessage)> MidiTunerMessageHandler for F {
344    fn handle(&mut self, message: MidiTunerMessage) {
345        self(message)
346    }
347}
348
349fn pitch_bend_message(detuning: Ratio) -> ChannelMessageType {
350    ChannelMessageType::PitchBendChange {
351        value: ((detuning.as_semitones() / 2.0 * 8192.0) as i16)
352            .max(-8192)
353            .min(8192),
354    }
355}