tune 0.36.0

Explore musical tunings and create synthesizer tuning files for microtonal scales.
Documentation
use std::collections::HashMap;

use crate::midi::ChannelMessage;
use crate::midi::ChannelMessageType;
use crate::mts;
use crate::mts::ScaleOctaveTuning;
use crate::mts::ScaleOctaveTuningFormat;
use crate::mts::ScaleOctaveTuningMessage;
use crate::mts::ScaleOctaveTuningOptions;
use crate::mts::SingleNoteTuningChange;
use crate::mts::SingleNoteTuningChangeMessage;
use crate::mts::SingleNoteTuningChangeOptions;
use crate::note::Note;
use crate::pitch::Pitched;
use crate::pitch::Ratio;
use crate::tuner::GroupBy;
use crate::tuner::TunableSynth;

pub struct TunableMidi<H> {
    midi_target: MidiTarget<H>,
    midi_tuning_creator: MidiTuningCreator,
}

impl<H> TunableMidi<H> {
    pub fn single_note_tuning_change(
        midi_target: MidiTarget<H>,
        realtime: bool,
        device_id: u8,
        first_tuning_program: u8,
    ) -> Self {
        Self {
            midi_target,
            midi_tuning_creator: MidiTuningCreator::SingleNoteTuningChange {
                realtime,
                device_id,
                first_tuning_program,
            },
        }
    }

    pub fn scale_octave_tuning(
        midi_target: MidiTarget<H>,
        realtime: bool,
        device_id: u8,
        format: ScaleOctaveTuningFormat,
    ) -> Self {
        Self {
            midi_target,
            midi_tuning_creator: MidiTuningCreator::ScaleOctaveTuning {
                realtime,
                device_id,
                format,
                octave_tunings: HashMap::new(),
            },
        }
    }

    pub fn channel_fine_tuning(midi_target: MidiTarget<H>) -> Self {
        Self {
            midi_target,
            midi_tuning_creator: MidiTuningCreator::ChannelFineTuning,
        }
    }

    pub fn pitch_bend(midi_target: MidiTarget<H>) -> Self {
        Self {
            midi_target,
            midi_tuning_creator: MidiTuningCreator::PitchBend,
        }
    }
}

impl<H: MidiTunerMessageHandler> TunableSynth for TunableMidi<H> {
    type Result = ();
    type NoteAttr = u8;
    type GlobalAttr = ChannelMessageType;

    fn num_channels(&self) -> usize {
        self.midi_target.channels.len()
    }

    fn group_by(&self) -> GroupBy {
        self.midi_tuning_creator.group_by()
    }

    fn notes_detune(&mut self, channel: usize, detuned_notes: &[(Note, Ratio)]) {
        self.midi_tuning_creator
            .create(&mut self.midi_target, channel, detuned_notes)
    }

    fn note_on(&mut self, channel: usize, started_note: Note, velocity: u8) {
        if let Some(started_note) = started_note.checked_midi_number() {
            self.midi_target.send(
                ChannelMessageType::NoteOn {
                    key: started_note,
                    velocity,
                },
                channel,
            );
        }
    }

    fn note_off(&mut self, channel: usize, stopped_note: Note, velocity: u8) {
        if let Some(stopped_note) = stopped_note.checked_midi_number() {
            self.midi_target.send(
                ChannelMessageType::NoteOff {
                    key: stopped_note,
                    velocity,
                },
                channel,
            );
        }
    }

    fn note_attr(&mut self, channel: usize, affected_note: Note, pressure: u8) {
        if let Some(affected_note) = affected_note.checked_midi_number() {
            self.midi_target.send(
                ChannelMessageType::PolyphonicKeyPressure {
                    key: affected_note,
                    pressure,
                },
                channel,
            );
        }
    }

    fn global_attr(&mut self, message_type: ChannelMessageType) {
        for channel in 0..self.num_channels() {
            if self.midi_tuning_creator.allow_pitch_bend()
                || !matches!(message_type, ChannelMessageType::PitchBendChange { .. })
            {
                self.midi_target.send(message_type, channel);
            }
        }
    }
}

pub struct MidiTarget<H> {
    pub handler: H,
    pub channels: Vec<u8>,
}

impl<H: MidiTunerMessageHandler> MidiTarget<H> {
    fn send(&mut self, message: ChannelMessageType, tuner_channel: usize) {
        self.handler
            .handle_channel_message(message, self.midi_channel(tuner_channel));
    }

    fn midi_channel(&self, tuner_channel: usize) -> u8 {
        self.channels[tuner_channel]
    }

    fn tuning_program(&self, tuner_channel: usize, first_tuning_program: u8) -> u8 {
        (u8::try_from(tuner_channel).unwrap() + first_tuning_program) % 128
    }
}

enum MidiTuningCreator {
    SingleNoteTuningChange {
        device_id: u8,
        realtime: bool,
        first_tuning_program: u8,
    },
    ScaleOctaveTuning {
        device_id: u8,
        realtime: bool,
        format: ScaleOctaveTuningFormat,
        octave_tunings: HashMap<usize, ScaleOctaveTuning>,
    },
    ChannelFineTuning,
    PitchBend,
}

impl MidiTuningCreator {
    fn create(
        &mut self,
        target: &mut MidiTarget<impl MidiTunerMessageHandler>,
        tuner_channel: usize,
        detuned_notes: &[(Note, Ratio)],
    ) {
        let midi_channel = target.midi_channel(tuner_channel);

        match self {
            MidiTuningCreator::SingleNoteTuningChange {
                realtime,
                device_id,
                first_tuning_program,
            } => {
                let tuning_program = target.tuning_program(tuner_channel, *first_tuning_program);

                let options = SingleNoteTuningChangeOptions {
                    realtime: *realtime,
                    device_id: *device_id,
                    tuning_program,
                    with_bank_select: None,
                };

                for channel_message in
                    mts::tuning_program_change(midi_channel, tuning_program).unwrap()
                {
                    target
                        .handler
                        .handle(MidiTunerMessage::new(channel_message));
                }

                if let Ok(tuning_message) = SingleNoteTuningChangeMessage::from_tuning_changes(
                    &options,
                    detuned_notes
                        .iter()
                        .map(|&(note, detuning)| SingleNoteTuningChange {
                            key: note.as_piano_key(),
                            target_pitch: note.pitch() * detuning,
                        }),
                ) {
                    target.handler.handle(MidiTunerMessage::new(tuning_message));
                }
            }
            MidiTuningCreator::ScaleOctaveTuning {
                realtime,
                device_id,
                format,
                octave_tunings,
            } => {
                let octave_tuning = octave_tunings.entry(tuner_channel).or_default();

                for &(note, detuning) in detuned_notes {
                    *octave_tuning.as_mut(note.letter_and_octave().0) = detuning;
                }

                let options = ScaleOctaveTuningOptions {
                    realtime: *realtime,
                    device_id: *device_id,
                    channels: midi_channel.into(),
                    format: *format,
                };

                if let Ok(tuning_message) =
                    ScaleOctaveTuningMessage::from_octave_tuning(&options, octave_tuning)
                {
                    target.handler.handle(MidiTunerMessage::new(tuning_message));
                }
            }
            MidiTuningCreator::ChannelFineTuning => {
                for &(_, detuning) in detuned_notes {
                    for channel_message in mts::channel_fine_tuning(midi_channel, detuning).unwrap()
                    {
                        target
                            .handler
                            .handle(MidiTunerMessage::new(channel_message));
                    }
                }
            }
            MidiTuningCreator::PitchBend => {
                for &(_, detuning) in detuned_notes {
                    let channel_message = pitch_bend_message(detuning)
                        .in_channel(midi_channel)
                        .unwrap();
                    target
                        .handler
                        .handle(MidiTunerMessage::new(channel_message));
                }
            }
        }
    }

    fn group_by(&self) -> GroupBy {
        match self {
            MidiTuningCreator::SingleNoteTuningChange { .. } => GroupBy::Note,
            MidiTuningCreator::ScaleOctaveTuning { .. } => GroupBy::NoteLetter,
            MidiTuningCreator::ChannelFineTuning | MidiTuningCreator::PitchBend => GroupBy::Channel,
        }
    }

    fn allow_pitch_bend(&self) -> bool {
        match self {
            MidiTuningCreator::SingleNoteTuningChange { .. }
            | MidiTuningCreator::ScaleOctaveTuning { .. }
            | MidiTuningCreator::ChannelFineTuning => true,
            MidiTuningCreator::PitchBend => false,
        }
    }
}

#[derive(Debug)]
pub struct MidiTunerMessage {
    variant: MidiTunerMessageVariant,
}

impl MidiTunerMessage {
    fn new<M: Into<MidiTunerMessageVariant>>(variant: M) -> Self {
        Self {
            variant: variant.into(),
        }
    }

    pub fn send_to(&self, mut receiver: impl FnMut(&[u8])) {
        match &self.variant {
            MidiTunerMessageVariant::Channel(channel_message) => {
                receiver(&channel_message.to_raw_message());
            }
            MidiTunerMessageVariant::ScaleOctaveTuning(tuning_message) => {
                receiver(tuning_message.sysex_bytes());
            }
            MidiTunerMessageVariant::SingleNoteTuningChange(tuning_message) => {
                for sysex_bytes in tuning_message.sysex_bytes() {
                    receiver(sysex_bytes);
                }
            }
        }
    }
}

#[derive(Debug)]
enum MidiTunerMessageVariant {
    Channel(ChannelMessage),
    ScaleOctaveTuning(ScaleOctaveTuningMessage),
    SingleNoteTuningChange(SingleNoteTuningChangeMessage),
}

impl From<ChannelMessage> for MidiTunerMessageVariant {
    fn from(v: ChannelMessage) -> Self {
        Self::Channel(v)
    }
}

impl From<ScaleOctaveTuningMessage> for MidiTunerMessageVariant {
    fn from(v: ScaleOctaveTuningMessage) -> Self {
        Self::ScaleOctaveTuning(v)
    }
}

impl From<SingleNoteTuningChangeMessage> for MidiTunerMessageVariant {
    fn from(v: SingleNoteTuningChangeMessage) -> Self {
        Self::SingleNoteTuningChange(v)
    }
}

pub trait MidiTunerMessageHandler {
    fn handle(&mut self, message: MidiTunerMessage);

    fn handle_channel_message(&mut self, message_type: ChannelMessageType, channel: u8) {
        if let Some(message) = message_type.in_channel(channel) {
            self.handle(MidiTunerMessage::new(message));
        }
    }
}

impl<F: FnMut(MidiTunerMessage)> MidiTunerMessageHandler for F {
    fn handle(&mut self, message: MidiTunerMessage) {
        self(message)
    }
}

fn pitch_bend_message(detuning: Ratio) -> ChannelMessageType {
    ChannelMessageType::PitchBendChange {
        value: ((detuning.as_semitones() / 2.0 * 8192.0) as i16)
            .max(-8192)
            .min(8192),
    }
}