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}