|
| 1 | +use log::info; |
| 2 | +use std::collections::HashMap; |
| 3 | + |
| 4 | +use util::constants::TIMBRECC; |
| 5 | +use util::midi_message_type::MidiMessageType; |
| 6 | + |
| 7 | +use crate::note::{CCIndex, Note, NoteIndex}; |
| 8 | +use util::messages::CC; |
| 9 | +use crate::timed_event::TimedEvent; |
| 10 | +use std::cmp::Ordering; |
| 11 | +use util::midi_message_with_delta::MidiMessageWithDelta; |
| 12 | + |
| 13 | +pub struct Device { |
| 14 | + pub notes: HashMap<NoteIndex, Note>, |
| 15 | + pub cc: HashMap<CCIndex, u8>, |
| 16 | + pub channels: [Channel; 16], |
| 17 | + pub note_index: usize, |
| 18 | +} |
| 19 | + |
| 20 | +impl Default for Device { |
| 21 | + fn default() -> Self { |
| 22 | + Device { |
| 23 | + notes: Default::default(), |
| 24 | + cc: Default::default(), |
| 25 | + channels: [Channel { |
| 26 | + pressure: 0, |
| 27 | + pitchbend: 0, |
| 28 | + timbre: 0, |
| 29 | + }; 16], |
| 30 | + note_index: 0, |
| 31 | + } |
| 32 | + } |
| 33 | +} |
| 34 | + |
| 35 | +#[derive(Copy, Clone, Debug)] |
| 36 | +pub struct Channel { |
| 37 | + pub pressure: u8, |
| 38 | + // in millisemitones |
| 39 | + pub pitchbend: i32, |
| 40 | + pub timbre: u8, |
| 41 | +} |
| 42 | + |
| 43 | +pub enum Expression { |
| 44 | + Timbre, |
| 45 | + Pressure, |
| 46 | + PitchBend, |
| 47 | +} |
| 48 | + |
| 49 | +pub enum DeviceChange { |
| 50 | + AddNote { time: usize, note: Note }, |
| 51 | + RemoveNote { time: usize, note: Note }, |
| 52 | + NoteExpressionChange { time: usize, expression: Expression, note: Note }, |
| 53 | + ReplaceNote { time: usize, old_note: Note, new_note: Note }, |
| 54 | + CCChange { time: usize, cc: CC }, |
| 55 | + None { time: usize }, |
| 56 | +} |
| 57 | + |
| 58 | + |
| 59 | +impl TimedEvent for DeviceChange { |
| 60 | + fn timestamp(&self) -> usize { |
| 61 | + match self { |
| 62 | + DeviceChange::AddNote { time, .. } => *time, |
| 63 | + DeviceChange::RemoveNote { time, .. } => *time, |
| 64 | + DeviceChange::NoteExpressionChange { time, .. } => *time, |
| 65 | + DeviceChange::ReplaceNote { time, .. } => *time, |
| 66 | + DeviceChange::CCChange { time, .. } => *time, |
| 67 | + DeviceChange::None { time, .. } => *time |
| 68 | + } |
| 69 | + } |
| 70 | + |
| 71 | + fn id(&self) -> usize { |
| 72 | + // used to order events that happen at the same time. Doesn't matter on CCs, in any case they'll be sorted |
| 73 | + // by time already |
| 74 | + match self { |
| 75 | + DeviceChange::AddNote { note, .. } => note.id, |
| 76 | + DeviceChange::RemoveNote { note, .. } => note.id, |
| 77 | + DeviceChange::NoteExpressionChange { note, .. } => note.id, |
| 78 | + DeviceChange::ReplaceNote { new_note: note, .. } => note.id, |
| 79 | + DeviceChange::CCChange { .. } => 0, |
| 80 | + DeviceChange::None { .. } => 0 |
| 81 | + } |
| 82 | + } |
| 83 | +} |
| 84 | + |
| 85 | +impl PartialOrd for DeviceChange { |
| 86 | + fn partial_cmp(&self, other: &Self) -> Option<Ordering> { |
| 87 | + let timestamp_cmp = self.timestamp().cmp(&other.timestamp()); |
| 88 | + if timestamp_cmp == Ordering::Equal { |
| 89 | + Some(self.id().cmp(&other.id())) |
| 90 | + } else { |
| 91 | + Some(timestamp_cmp) |
| 92 | + } |
| 93 | + } |
| 94 | +} |
| 95 | + |
| 96 | +impl PartialEq for DeviceChange { |
| 97 | + fn eq(&self, other: &Self) -> bool { |
| 98 | + self.id() == other.id() |
| 99 | + } |
| 100 | +} |
| 101 | + |
| 102 | + |
| 103 | +impl Device { |
| 104 | + pub fn update(&mut self, |
| 105 | + midi_message: MidiMessageWithDelta, current_time: usize, id: Option<usize>) -> DeviceChange { |
| 106 | + let time = current_time + midi_message.delta_frames as usize; |
| 107 | + |
| 108 | + match MidiMessageType::from(&midi_message.data) { |
| 109 | + MidiMessageType::NoteOnMessage(note) => { |
| 110 | + let note_id = match id { |
| 111 | + None => { |
| 112 | + let note_id = self.note_index; |
| 113 | + self.note_index += 1; |
| 114 | + note_id |
| 115 | + } |
| 116 | + Some(id) => id |
| 117 | + }; |
| 118 | + let index = NoteIndex { channel: note.channel, pitch: note.pitch }; |
| 119 | + let new_note = Note { |
| 120 | + id: note_id, |
| 121 | + pressed_at: time, |
| 122 | + released_at: 0, |
| 123 | + channel: note.channel, |
| 124 | + pitch: note.pitch, |
| 125 | + velocity: note.velocity, |
| 126 | + velocity_off: 0, |
| 127 | + pressure: self.channels[note.channel as usize].pressure, |
| 128 | + timbre: self.channels[note.channel as usize].timbre, |
| 129 | + pitchbend: self.channels[note.channel as usize].pitchbend, |
| 130 | + }; |
| 131 | + |
| 132 | + match self.notes.insert(index, new_note) { |
| 133 | + None => { |
| 134 | + DeviceChange::AddNote { time, note: new_note } |
| 135 | + } |
| 136 | + Some(old_note) => { |
| 137 | + DeviceChange::ReplaceNote { time, old_note, new_note } |
| 138 | + } |
| 139 | + } |
| 140 | + } |
| 141 | + MidiMessageType::NoteOffMessage(note) => { |
| 142 | + let index = NoteIndex { channel: note.channel, pitch: note.pitch }; |
| 143 | + |
| 144 | + match self.notes.remove(&index) { |
| 145 | + None => { |
| 146 | + info!("Attempt to remove note, but it was not found {:02X?}", index); |
| 147 | + DeviceChange::None { time } |
| 148 | + } |
| 149 | + Some(mut old_note) => { |
| 150 | + //info!("Removed note {:02X?}", index); |
| 151 | + old_note.released_at = time; |
| 152 | + old_note.velocity_off = note.velocity; |
| 153 | + DeviceChange::RemoveNote { time, note: old_note } |
| 154 | + } |
| 155 | + } |
| 156 | + } |
| 157 | + MidiMessageType::CCMessage(cc) => { |
| 158 | + self.cc.insert(CCIndex { channel: cc.channel, index: cc.cc }, cc.value); |
| 159 | + if cc.cc == TIMBRECC { |
| 160 | + self.channels[cc.channel as usize].timbre = cc.value; |
| 161 | + for (_, note) in self.notes.iter_mut() { |
| 162 | + if note.channel == cc.channel { |
| 163 | + note.timbre = cc.value; |
| 164 | + // note: per design simplification, having several notes running on the same channel |
| 165 | + // is not supported. only the first note found on the channel is updated |
| 166 | + return DeviceChange::NoteExpressionChange { |
| 167 | + time, |
| 168 | + expression: Expression::Timbre, |
| 169 | + note: *note, |
| 170 | + }; |
| 171 | + } |
| 172 | + } |
| 173 | + } |
| 174 | + DeviceChange::CCChange { time, cc } |
| 175 | + } |
| 176 | + MidiMessageType::PressureMessage(message) => { |
| 177 | + self.channels[message.channel as usize].pressure = message.value; |
| 178 | + for (_, note) in self.notes.iter_mut() { |
| 179 | + if note.channel == message.channel { |
| 180 | + // note: per design simplification, having several notes running on the same channel |
| 181 | + // is not supported. only the first note found on the channel is updated |
| 182 | + note.pressure = message.value; |
| 183 | + return DeviceChange::NoteExpressionChange { |
| 184 | + time, |
| 185 | + expression: Expression::Pressure, |
| 186 | + note: *note, |
| 187 | + }; |
| 188 | + } |
| 189 | + } |
| 190 | + DeviceChange::None { time } |
| 191 | + } |
| 192 | + MidiMessageType::PitchBendMessage(message) => { |
| 193 | + self.channels[message.channel as usize].pitchbend = message.millisemitones; |
| 194 | + for (_, note) in self.notes.iter_mut() { |
| 195 | + if note.channel == message.channel { |
| 196 | + // note: per design simplification, having several notes running on the same channel |
| 197 | + // is not supported. only the first note found on the channel is updated |
| 198 | + note.pitchbend = message.millisemitones; |
| 199 | + return DeviceChange::NoteExpressionChange { |
| 200 | + time, |
| 201 | + expression: Expression::PitchBend, |
| 202 | + note: *note, |
| 203 | + }; |
| 204 | + } |
| 205 | + } |
| 206 | + DeviceChange::None { time } |
| 207 | + } |
| 208 | + MidiMessageType::UnsupportedChannelMessage(_) => DeviceChange::None { time }, |
| 209 | + MidiMessageType::Unsupported => DeviceChange::None { time } |
| 210 | + } |
| 211 | + } |
| 212 | +} |
0 commit comments