├── .gitignore ├── Cargo.toml ├── README.md ├── notes └── NOTES.md └── src ├── core ├── control │ ├── mod.rs │ ├── sheet_music.rs │ ├── synth.rs │ └── tools.rs ├── mod.rs ├── music_theory │ ├── diatonic_scale.rs │ ├── mod.rs │ ├── pitch.rs │ ├── pitch_class.rs │ └── rhythm.rs ├── sheet_music │ ├── mod.rs │ ├── playing_music.rs │ └── sheet_music.rs ├── synth │ ├── adsr.rs │ ├── builder.rs │ ├── filter │ │ ├── biquad.rs │ │ └── mod.rs │ ├── instrument.rs │ ├── lfo.rs │ ├── mod.rs │ ├── modulated.rs │ └── oscillator │ │ ├── basic.rs │ │ ├── mix.rs │ │ ├── mod.rs │ │ └── pulse.rs └── tools │ ├── arpeggiator │ ├── builder.rs │ ├── mod.rs │ └── phrase.rs │ ├── loops.rs │ ├── mod.rs │ ├── pulse.rs │ ├── tap_tempo.rs │ └── transposer.rs ├── io ├── audio.rs ├── midi │ ├── meta_events.rs │ ├── mod.rs │ └── patch.rs └── mod.rs ├── lib.rs ├── preset.rs └── util ├── duration.rs ├── mod.rs ├── range_map.rs └── reckless_float.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | 5 | .idea/ 6 | *.iml 7 | 8 | .DS_Store 9 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rust-synth" 3 | version = "0.1.0" 4 | authors = ["Victor Basso "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | cpal = "0.8.2" 9 | rimd = { git = "https://github.com/RustAudio/rimd.git" } 10 | rand = "0.5.5" 11 | num-traits = "0.2.6" 12 | num-derive = "0.2.3" 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | A sound synthesiser built from scratch. It implements oscillators, filters, an arpeggiator and a looper. It's written in Rust, a systems programming language, due to the performance critical nature of producing sound in real time. 2 | 3 | https://user-images.githubusercontent.com/1895014/157036647-2696a3bb-1ed7-4cfc-8a3c-401b13763e3d.mp4 4 | 5 | 6 | It works by receiving commands such as `NoteOn`, `NoteOff`and `SetPatch` which could originate from user interaction or MIDI protocol instructions, for example, 7 | and producing a sound signal that can be sent to an audio output device. 8 | 9 | An example of usage and runnable demo can be found in [rust-synth-gui](https://github.com/vitobasso/rust-synth-gui). 10 | 11 | ## Progress 12 | - Synth 13 | - Oscillators 14 | - [x] Sine, Saw, Square, Pulse 15 | - [x] Mix of detuned oscillators 16 | - [ ] Noise 17 | - Filters 18 | - [x] Biquad LPF, HPF, BPF, Notch 19 | - Modulation 20 | - [x] ADSR 21 | - [x] LFO's 22 | - [x] Wire modulation to parameters 23 | - [x] Polyphony 24 | - Effects 25 | - [ ] Compression 26 | - [ ] Distortion 27 | - [ ] Delay 28 | - Tools 29 | - [x] Arpeggiator 30 | - [x] Tap tempo 31 | - [x] Loop recorder 32 | - [ ] Snap to measures 33 | - [ ] Drums 34 | - [x] Read Midi 35 | - [x] State accessible for visualization 36 | -------------------------------------------------------------------------------- /notes/NOTES.md: -------------------------------------------------------------------------------- 1 | # FIX 2 | - crash when pitch too low: 'attempt to multiply with overflow', src/core/music_theory/pitch.rs:72:10 3 | 4 | # LINKS 5 | - Midi files: https://freemidi.org/ 6 | - Midi documentation 7 | - programs: https://en.wikipedia.org/wiki/General_MIDI#Percussion 8 | - meta events: https://www.csie.ntu.edu.tw/~r92092/ref/midi/ 9 | - MIDI to CSV on the command line: http://www.fourmilab.ch/webtools/midicsv/ 10 | 11 | # INSPIRATION 12 | - [TonicAudio](https://github.com/TonicAudio/Tonic). C++ lib. 13 | - Api design: controllers, generators, proccessors 14 | - combine oscillators with +, *, ... 15 | 16 | # TODO 17 | - newtype instead of type aliases. 18 | - restrictive types for RecklessFloat, range 0-1 (rhythm) 19 | - move midi -> preset mapping out of rust-synth 20 | - From(usize) for PitchClass, Pitch -------------------------------------------------------------------------------- /src/core/control/mod.rs: -------------------------------------------------------------------------------- 1 | 2 | pub mod tools; 3 | pub mod synth; 4 | pub mod sheet_music; 5 | -------------------------------------------------------------------------------- /src/core/control/sheet_music.rs: -------------------------------------------------------------------------------- 1 | use std::sync::mpsc::SyncSender; 2 | use std::collections::HashMap; 3 | use crate::core::{control::synth, music_theory::Hz, synth::Sample}; 4 | use crate::core::sheet_music::{sheet_music::*, playing_music::*}; 5 | 6 | /// 7 | /// Orchestrates synths to play commands from sheet music 8 | /// 9 | 10 | pub fn start(sample_rate: Hz, music: SheetMusic, signal_out: SyncSender) { 11 | let mut state = State::new(sample_rate, music); 12 | loop { 13 | state.tick_music(); 14 | let sample = state.next_sample(); 15 | signal_out.send(sample).expect("Failed to send a sample"); 16 | } 17 | } 18 | 19 | struct State { 20 | synths: HashMap, 21 | music: PlayingMusic, 22 | } 23 | 24 | impl State { 25 | fn new(sample_rate: Hz, sheet_music: SheetMusic) -> State { 26 | State { 27 | synths: sheet_music.voices.iter() 28 | .map(|track| (track.instrument_id, synth::State::new(sample_rate))) 29 | .collect(), 30 | music: PlayingMusic::new(sheet_music), 31 | } 32 | } 33 | 34 | fn interpret(&mut self, (command, channel): TargetedCommand) { 35 | match self.synths.get_mut(&channel) { 36 | Some(player) => player.interpret(command), 37 | None => eprintln!("Player not found for channel: {}", channel), 38 | } 39 | } 40 | 41 | fn tick_music(&mut self) { 42 | self.music.next().commands.into_iter() 43 | .for_each(|cmd| self.interpret(cmd)); 44 | } 45 | 46 | fn next_sample(&mut self) -> Sample { 47 | self.synths.values_mut() 48 | .map(|i| i.next_sample()) 49 | .sum() 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/core/control/synth.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use crate::core::{ 3 | music_theory::{Hz, pitch::Pitch}, 4 | synth::{Sample, Velocity, instrument::{self, Instrument}}, 5 | }; 6 | 7 | /// 8 | /// Interprets commands by translating to synth::instrument method calls 9 | /// 10 | 11 | #[derive(Clone, PartialEq, Debug)] 12 | pub enum Command { 13 | NoteOn(Pitch, Velocity, Id), NoteOff(Id), 14 | ModXY(f64, f64), 15 | SetPatch(instrument::Specs), 16 | } 17 | 18 | pub struct State { 19 | sample_rate: Hz, 20 | instrument: Instrument, 21 | holding_notes: HashMap, 22 | } 23 | 24 | #[derive(Clone, PartialEq, Default, Debug)] 25 | pub struct View { 26 | pub instrument: instrument::View, 27 | pub holding_notes: HashMap, 28 | } 29 | 30 | impl State { 31 | 32 | pub fn new(sample_rate: Hz) -> State { 33 | let specs = instrument::Specs::default(); 34 | State { 35 | sample_rate, 36 | instrument: Instrument::new(specs, sample_rate), 37 | holding_notes: HashMap::new(), 38 | } 39 | } 40 | 41 | pub fn interpret(&mut self, command: Command) { 42 | match command { 43 | Command::NoteOn(pitch, velocity, id) => self.handle_note_on(pitch, velocity, id), 44 | Command::NoteOff(id) => self.handle_note_off(id), 45 | Command::ModXY(x, y) => self.instrument.set_xy_params(x, y), 46 | Command::SetPatch(specs) => self.set_specs(specs), 47 | } 48 | } 49 | 50 | pub fn next_sample(&mut self) -> Sample { 51 | self.instrument.next_sample() 52 | } 53 | 54 | pub fn view(&self) -> View { 55 | View { 56 | instrument: self.instrument.view(), 57 | holding_notes: self.holding_notes.clone(), 58 | } 59 | } 60 | 61 | fn handle_note_on(&mut self, pitch: Pitch, velocity: Velocity, id: Id) { 62 | if self.holding_notes.insert(id, pitch).is_none() { 63 | self.instrument.hold(pitch, velocity) 64 | } 65 | } 66 | 67 | fn handle_note_off(&mut self, id: Id) { 68 | if let Some(remembered_pitch) = self.holding_notes.remove(&id) { 69 | self.instrument.release(remembered_pitch) 70 | } 71 | } 72 | 73 | fn set_specs(&mut self, specs: instrument::Specs) { 74 | let state = self.instrument.get_state(); 75 | self.instrument = Instrument::new(specs, self.sample_rate); 76 | self.instrument.set_state(state); 77 | } 78 | 79 | } 80 | 81 | pub type Discriminator = u8; 82 | 83 | #[derive(Eq, PartialEq, Hash, Clone, Copy)] 84 | pub struct Id { 85 | pub pitch: Pitch, 86 | pub discriminator: Option, 87 | } 88 | 89 | use std::fmt::{Debug, Formatter}; 90 | impl Debug for Id { 91 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 92 | write!(f, "Id( {:?}, {:?} )", self.pitch, self.discriminator) 93 | } 94 | } 95 | 96 | pub const fn id(pitch: Pitch) -> Id { 97 | Id { pitch, discriminator: None } 98 | } 99 | 100 | pub const fn id_discr(pitch: Pitch, discriminator: Discriminator) -> Id { 101 | Id { pitch, discriminator: Some(discriminator) } 102 | } 103 | -------------------------------------------------------------------------------- /src/core/control/tools.rs: -------------------------------------------------------------------------------- 1 | use std::sync::mpsc::{Receiver, SyncSender}; 2 | use std::time::Duration; 3 | use crate::core::{ 4 | control::{synth::{self, Command::*}}, 5 | music_theory::{Hz, pitch_class::PitchClass}, 6 | synth::{instrument, Sample}, 7 | tools::{pulse, transposer, loops, arpeggiator, arpeggiator::phrase::Phrase, tap_tempo, Millis}, 8 | sheet_music::sheet_music::MeasurePosition, 9 | }; 10 | 11 | /// 12 | /// Connects tools and synth together, interprets commands and delegates to them 13 | /// 14 | 15 | pub fn start(sample_rate: Hz, command_in: Receiver, sound_out: SyncSender, view_out: SyncSender) { 16 | let command_rate = 10; //TODO in hz 17 | let view_refresh_rate = 1000; //TODO in hz 18 | let mut state = State::new(sample_rate); 19 | for i in 0.. { 20 | if i % command_rate == 0 { 21 | if let Ok(command) = command_in.try_recv() { 22 | state.interpret(command); 23 | } 24 | state.tick_arpeggiator(); 25 | } 26 | 27 | let new_sample = state.next_sample(); 28 | sound_out.send(new_sample).expect("Failed to send a sample"); 29 | state.loops.write(new_sample); 30 | 31 | if i % view_refresh_rate == 0 { 32 | let view = state.view(); 33 | let _ = view_out.try_send(view); 34 | } 35 | } 36 | } 37 | 38 | const BEATS_PER_MEASURE: u64 = 4; 39 | const PULSES_PER_BEAT: u64 = 32; 40 | const DEFAULT_PULSE: Millis = 12; 41 | 42 | #[derive(Clone)] 43 | pub enum Command { 44 | Instrument(synth::Command), 45 | Transposer(transposer::Command), 46 | SetPatch(Patch), 47 | Loop(loops::Command), 48 | TapTempo, 49 | } 50 | 51 | #[derive(Clone)] 52 | pub enum Patch { 53 | Instrument(instrument::Specs), 54 | ArpeggiatorPhrase(Option), //TODO deprecate 55 | Arpeggiator(Option), 56 | Noop, 57 | } 58 | 59 | pub struct State { 60 | synth: synth::State, 61 | transposer: transposer::State, 62 | pulse: pulse::Pulse, 63 | arpeggiator: Option, 64 | arp_index: f64, 65 | tap_tempo: tap_tempo::TapTempo, 66 | loops: loops::Manager, 67 | } 68 | 69 | #[derive(Clone, PartialEq, Default, Debug)] 70 | pub struct View { 71 | pub synth: synth::View, 72 | pub transposer: transposer::State, 73 | pub pulse: pulse::View, 74 | pub arpeggiator: Option, 75 | pub arp_index: f64, 76 | pub tap_tempo: tap_tempo::TapTempo, 77 | pub loops: loops::View, 78 | } 79 | 80 | impl State { 81 | fn new(sample_rate: Hz) -> State { 82 | State { 83 | synth: synth::State::new(sample_rate), 84 | transposer: transposer::State::new(PitchClass::C), 85 | tap_tempo: Default::default(), 86 | pulse: pulse::Pulse::new_with_millis(DEFAULT_PULSE), 87 | arpeggiator: None, 88 | arp_index: 0., 89 | loops: Default::default(), 90 | } 91 | } 92 | 93 | fn interpret(&mut self, command: Command) { 94 | match command { 95 | Command::Instrument(cmd) => self.play_or_arpeggiate(cmd), 96 | Command::Transposer(cmd) => self.transposer.interpret(cmd), 97 | Command::SetPatch(patch) => self.set_patch(patch), 98 | Command::Loop(cmd) => self.loops.interpret(cmd), 99 | Command::TapTempo => self.tap_tempo(), 100 | } 101 | } 102 | 103 | fn play_or_arpeggiate(&mut self, command: synth::Command) { 104 | match command { 105 | NoteOn(_, _, _) | NoteOff(_) => { 106 | if let Some(arp) = &mut self.arpeggiator { 107 | arp.interpret(command); 108 | } else { 109 | self.play_transposed(command); 110 | } 111 | }, 112 | _ => self.play_transposed(command) 113 | } 114 | } 115 | 116 | fn play_transposed(&mut self, command: synth::Command) { 117 | let changed_command = match command { 118 | NoteOn(pitch, velocity, id) => NoteOn(self.transposer.transpose(pitch), velocity, id), 119 | other => other, 120 | }; 121 | self.synth.interpret(changed_command) 122 | } 123 | 124 | fn set_patch(&mut self, patch: Patch) { 125 | match patch { 126 | Patch::Instrument(specs) => self.synth.interpret(SetPatch(specs)), 127 | Patch::Arpeggiator(specs) => self.set_arpeggiator(specs), 128 | Patch::ArpeggiatorPhrase(seq) => self.set_arpeggiator_phrase(seq), 129 | Patch::Noop => (), 130 | } 131 | } 132 | 133 | fn set_arpeggiator(&mut self, specs: Option) { 134 | let old_state = self.arpeggiator.as_ref().map(|a| a.state()); 135 | self.arpeggiator = specs.map(|s| { 136 | let mut arp = arpeggiator::Arpeggiator::from_specs(s); 137 | if let Some(state) = old_state { 138 | arp.set_state(state); 139 | } 140 | arp 141 | }); 142 | } 143 | 144 | fn set_arpeggiator_phrase(&mut self, phrase: Option) { 145 | let old_state = self.arpeggiator.as_ref().map(|a| a.state()); 146 | self.arpeggiator = phrase.map(|p| { 147 | let mut arp = arpeggiator::Arpeggiator::from_phrase(PitchClass::C, p); 148 | if let Some(state) = old_state { 149 | arp.set_state(state); 150 | } 151 | arp 152 | }); 153 | } 154 | 155 | fn tap_tempo(&mut self) { 156 | self.tap_tempo.tap(); 157 | if let Some(beat) = self.tap_tempo.read() { 158 | let pulse_period = Duration::from_millis(beat / PULSES_PER_BEAT); 159 | self.pulse = self.pulse.with_period(pulse_period); 160 | } 161 | } 162 | 163 | fn tick_arpeggiator(&mut self) { 164 | if let Some(measure_progress) = self.tick_around_measure() { 165 | let from = self.arp_index; 166 | let to = self.arp_index + measure_progress; 167 | self.arp_index = to; 168 | self.arpeggiator.as_mut() 169 | .map(|arp| arp.next(from, to)).unwrap_or_else(Vec::default) 170 | .into_iter().for_each(|cmd| self.play_transposed(cmd)); 171 | } 172 | } 173 | 174 | fn tick_around_measure(&mut self) -> Option { 175 | self.pulse.read().map(|pulse::PulseReading{ missed, .. }| { 176 | let pulses_passed = 1 + missed; 177 | let pulses_per_measure = PULSES_PER_BEAT * BEATS_PER_MEASURE; 178 | f64::from(pulses_passed) / pulses_per_measure as MeasurePosition 179 | }) 180 | } 181 | 182 | fn next_sample(&mut self) -> Sample { 183 | let new_sample = self.synth.next_sample(); 184 | let loop_sample = self.loops.next_sample(); 185 | loop_sample + new_sample 186 | } 187 | 188 | pub fn view(&self) -> View { 189 | View { 190 | synth: self.synth.view(), 191 | transposer: self.transposer.clone(), 192 | pulse: self.pulse.view(), 193 | arpeggiator: self.arpeggiator.as_ref().map(|a| a.view()), 194 | arp_index: self.arp_index, 195 | tap_tempo: self.tap_tempo.clone(), 196 | loops: self.loops.view(), 197 | } 198 | } 199 | 200 | } 201 | -------------------------------------------------------------------------------- /src/core/mod.rs: -------------------------------------------------------------------------------- 1 | 2 | pub mod music_theory; 3 | pub mod synth; 4 | pub mod tools; 5 | pub mod sheet_music; 6 | pub mod control; 7 | -------------------------------------------------------------------------------- /src/core/music_theory/diatonic_scale.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Add, Sub}; 2 | use super::{num_traits::FromPrimitive, Octave, pitch::Pitch, pitch_class::PitchClass::{self, *}}; 3 | use self::ScaleDegree::*; 4 | 5 | #[derive(Clone, Copy, PartialEq, Eq, Debug, FromPrimitive)] 6 | pub enum ScaleDegree { 7 | I1, I2, I3, I4, I5, I6, I7 8 | } 9 | #[derive(Clone, Copy, PartialEq, Eq, Debug, FromPrimitive)] 10 | pub enum OctaveShift { 11 | Down3=-3, Down2=-2, Down1=-1, Same=0, Up1=1, Up2=2, Up3=3 12 | } 13 | impl OctaveShift { 14 | pub fn from_i8(i: i8) -> Option { 15 | FromPrimitive::from_i8(i) 16 | } 17 | } 18 | 19 | pub type RelativePitch = (OctaveShift, ScaleDegree); 20 | 21 | pub fn degree_from(pitch: RelativePitch) -> i32 { 22 | let (octave, degree) = pitch; 23 | octave as i32 * 7 + degree as i32 24 | } 25 | 26 | impl Add for ScaleDegree { 27 | type Output = Self; 28 | fn add(self, rhs: ScaleDegree) -> Self { 29 | let i = (self as u8 + rhs as u8) % 7; 30 | FromPrimitive::from_u8(i) 31 | .unwrap_or_else(|| panic!("Failed to get ScaleDegree for i={}", i)) 32 | } 33 | } 34 | impl Sub for ScaleDegree { 35 | type Output = Self; 36 | fn sub(self, rhs: ScaleDegree) -> Self { 37 | let i = ((((self as i8 - rhs as i8) % 7) + 7) % 7) as u8; 38 | FromPrimitive::from_u8(i) 39 | .unwrap_or_else(|| panic!("Failed to get ScaleDegree for i={}", i)) 40 | } 41 | } 42 | 43 | impl Default for OctaveShift { 44 | fn default() -> Self { 45 | OctaveShift::Same 46 | } 47 | } 48 | 49 | pub type Key = PitchClass; 50 | impl Key { 51 | pub fn pitch_at(self, offset: Pitch, interval: RelativePitch) -> Option { 52 | self.degree_of(offset.class).and_then(|offset_degree: ScaleDegree| { 53 | let (octave_increment, degree_increment) = interval; 54 | let new_degree: ScaleDegree = offset_degree + degree_increment; 55 | let new_class: PitchClass = self.pitch_class_at(new_degree); 56 | let carry_octave: Octave = if (new_class as u8) < offset.class as u8 {1} else {0}; 57 | let new_octave: Octave = offset.octave as i8 + octave_increment as i8 + carry_octave; 58 | if new_octave > 0 { 59 | Some(Pitch::new(new_class, new_octave)) 60 | } else { 61 | None 62 | } 63 | }) 64 | } 65 | fn degree_of(self, pitch_class: PitchClass) -> Option { 66 | let relative_pitch = pitch_class - self; 67 | match relative_pitch { 68 | C => Some(I1), 69 | D => Some(I2), 70 | E => Some(I3), 71 | F => Some(I4), 72 | G => Some(I5), 73 | A => Some(I6), 74 | B => Some(I7), 75 | _ => None 76 | } 77 | } 78 | fn pitch_class_at(self, degree: ScaleDegree) -> PitchClass { 79 | let relative_pitch = match degree { 80 | I1 => C, 81 | I2 => D, 82 | I3 => E, 83 | I4 => F, 84 | I5 => G, 85 | I6 => A, 86 | I7 => B, 87 | }; 88 | self + relative_pitch 89 | } 90 | 91 | pub fn transpose_to(self, other: Key, pitch: Pitch) -> Option { 92 | self.transpose_class_to(other, pitch.class).map(|class| { 93 | let carry_octave = 94 | if pitch.class == PitchClass::C && class == PitchClass::B {-1} else {0}; 95 | Pitch::new(class, pitch.octave + carry_octave) 96 | }) 97 | } 98 | 99 | fn transpose_class_to(self, other: Key, pitch_class: PitchClass) -> Option { 100 | self.degree_of(pitch_class).map(|degree| 101 | match (self.degree_of(other), other.degree_of(self)) { 102 | (Some(key_diff), _) => other.pitch_class_at(degree - key_diff), 103 | (None, Some(reciprocal_key_diff)) => other.pitch_class_at(degree + reciprocal_key_diff), 104 | (None, None) => if degree == I4 { pitch_class } else { pitch_class - 1 } 105 | } 106 | ) 107 | } 108 | 109 | pub fn shift_fifths(self, increment: i8) -> Key { 110 | self + (7 * increment) % 12 111 | } 112 | 113 | pub fn distance_fifths(self, other: Key) -> i8 { 114 | let norm_self = self - C; 115 | let norm_other_usize = (other - norm_self) as usize % 12; 116 | let norm_other = PitchClass::from_index(norm_other_usize) 117 | .unwrap_or_else(|| panic!("Expected int < 12")); 118 | match norm_other { 119 | C => 0, 120 | G => 1, 121 | D => 2, 122 | A => 3, 123 | E => 4, 124 | B => 5, 125 | Gb => 6, 126 | Db => -5, 127 | Ab => -4, 128 | Eb => -3, 129 | Bb => -2, 130 | F => -1, 131 | } 132 | } 133 | 134 | } 135 | 136 | #[cfg(test)] 137 | mod tests { 138 | use super::{Key, OctaveShift::*, Pitch, 139 | PitchClass::{self, *}, RelativePitch, ScaleDegree::{self, *}}; 140 | 141 | #[test] 142 | fn pitch_class_to_scale_degree() { 143 | let cases: &[(Key, PitchClass, Option)] = &[ 144 | (C, C, Some(I1)), 145 | (C, Db, None), 146 | (C, D, Some(I2)), 147 | (C, Eb, None), 148 | (C, E, Some(I3)), 149 | (C, F, Some(I4)), 150 | (C, Gb, None), 151 | (C, G, Some(I5)), 152 | (C, Ab, None), 153 | (C, A, Some(I6)), 154 | (C, B, Some(I7)), 155 | (Db, Db, Some(I1)), 156 | (Db, C, Some(I7)), 157 | (Db, D, None), 158 | (Db, C, Some(I7)), 159 | (Db, D, None), 160 | ]; 161 | for (key, pitch, expected_result) in cases.iter() { 162 | let actual_result: Option = key.degree_of(*pitch); 163 | assert_eq!(actual_result, *expected_result, 164 | "Input was: {:?} {:?} {:?}", key, pitch, *expected_result); 165 | } 166 | } 167 | 168 | #[test] 169 | fn scale_degree_to_pitch_class() { 170 | let cases: &[(Key, ScaleDegree, PitchClass)] = &[ 171 | (C, I1, C), 172 | (C, I2, D), 173 | (C, I3, E), 174 | (C, I4, F), 175 | (C, I5, G), 176 | (C, I6, A), 177 | (C, I7, B), 178 | (Db, I1, Db), 179 | (Db, I2, Eb), 180 | (Db, I3, F), 181 | (Db, I4, Gb), 182 | (Db, I5, Ab), 183 | (Db, I6, Bb), 184 | (Db, I7, C), 185 | ]; 186 | for (key, degree, expected_result) in cases.iter() { 187 | let actual_result = key.pitch_class_at(*degree); 188 | assert_eq!(actual_result, *expected_result, 189 | "Input was: {:?} {:?} {:?}", key, degree, *expected_result); 190 | } 191 | } 192 | 193 | #[test] 194 | fn scale_degree_to_pitch() { 195 | let c4 = Pitch::new(C, 4); 196 | let d4 = Pitch::new(D, 4); 197 | let a4 = Pitch::new(A, 4); 198 | let b4 = Pitch::new(B, 4); 199 | let cases: &[(Key, Pitch, RelativePitch, Option)] = &[ 200 | (C, c4, (Same, I1), Some(c4)), 201 | (C, a4, (Same, I1), Some(a4)), 202 | (C, b4, (Same, I1), Some(b4)), 203 | (Bb, d4, (Same, I1), Some(d4)), 204 | (D, d4, (Same, I1), Some(d4)), 205 | (C, c4, (Up1, I1), Some(Pitch::new(C, 5))), 206 | (C, c4, (Down3, I1), Some(Pitch::new(C, 1))), 207 | (C, c4, (Same, I2), Some(Pitch::new(D, 4))), 208 | (C, c4, (Same, I7), Some(Pitch::new(B, 4))), 209 | (C, c4, (Down1, I7), Some(Pitch::new(B, 3))), 210 | (C, d4, (Same, I2), Some(Pitch::new(E, 4))), 211 | (C, a4, (Same, I3), Some(Pitch::new(C, 5))), 212 | (Bb, d4, (Same, I2), Some(Pitch::new(Eb, 4))), 213 | (D, d4, (Same, I2), Some(Pitch::new(E, 4))), 214 | (D, d4, (Same, I7), Some(Pitch::new(Db, 5))), 215 | (B, c4, (Same, I1), None), //offset is out of key 216 | (C, Pitch::new(C, 0), (Down1, I1), None), //min octave is 0 217 | ]; 218 | for (key, offset, interval, expected_result) in cases.iter() { 219 | let actual_result = key.pitch_at(*offset, *interval); 220 | assert_eq!(actual_result, *expected_result, 221 | "Input was: {:?}, {:?}, {:?}, {:?}", key, *offset, *interval, *expected_result); 222 | } 223 | } 224 | 225 | #[test] 226 | fn shift_fifths() { 227 | let cases: &[(Key, i8, Key)] = &[ 228 | (C, 1, G), 229 | (C, 2, D), 230 | (C, 3, A), 231 | (C, 4, E), 232 | (C, 5, B), 233 | (C, 6, Gb), 234 | (C, 7, Db), 235 | (C, 8, Ab), 236 | (C, 9, Eb), 237 | (C, 10, Bb), 238 | (C, 11, F), 239 | (C, -1, F), 240 | (C, -2, Bb), 241 | ]; 242 | for (key, increment, expected_result) in cases.iter() { 243 | let actual_result = key.shift_fifths(*increment); 244 | assert_eq!(actual_result, *expected_result, 245 | "Input was: {:?}, {:?}, {:?}", *key, *increment, *expected_result); 246 | } 247 | } 248 | 249 | #[test] 250 | fn distance_fifths() { 251 | let cases: &[(Key, Key, i8)] = &[ 252 | (C, C, 0), 253 | (C, G, 1), 254 | (C, D, 2), 255 | (C, A, 3), 256 | (C, E, 4), 257 | (C, B, 5), 258 | (C, Gb, 6), 259 | (C, Db, -5), 260 | (C, Ab, -4), 261 | (C, Eb, -3), 262 | (C, Bb, -2), 263 | (C, F, -1), 264 | (G, C, -1), 265 | (G, D, 1), 266 | ]; 267 | for (key, other, expected_result) in cases.iter() { 268 | let actual_result = key.distance_fifths(*other); 269 | assert_eq!(actual_result, *expected_result, 270 | "Input was: {:?}, {:?}, {:?}", *key, *other, *expected_result); 271 | } 272 | } 273 | 274 | #[test] 275 | fn transpose_class_to() { 276 | let cases: &[(Key, Key, PitchClass, Option)] = &[ 277 | (C, G, C, Some(C)), 278 | (C, G, F, Some(Gb)), 279 | (C, F, B, Some(Bb)), 280 | (C, E, C, Some(Db)), 281 | (C, E, D, Some(Eb)), 282 | (C, E, E, Some(E)), 283 | (C, E, F, Some(Gb)), 284 | (C, E, G, Some(Ab)), 285 | (C, E, A, Some(A)), 286 | (C, E, B, Some(B)), 287 | (C, Bb, E, Some(Eb)), 288 | (C, C, C, Some(C)), 289 | (C, Gb, C, Some(B)), 290 | (C, Gb, F, Some(F)), 291 | (C, G, Db, None), 292 | ]; 293 | for (source_key, target_key, pitch_class, expected_result) in cases.iter() { 294 | let actual_result = source_key.transpose_class_to(*target_key, *pitch_class); 295 | assert_eq!(actual_result, *expected_result, 296 | "Input was: {:?}, {:?}, {:?}, {:?}", *source_key, *target_key, *pitch_class, *expected_result); 297 | } 298 | } 299 | 300 | 301 | #[test] 302 | fn transpose_to() { 303 | let cases: &[(Key, Key, Pitch, Option)] = &[ 304 | (C, G, Pitch{class: C, octave: 4}, Some(Pitch{class: C, octave: 4})), 305 | (C, Gb, Pitch{class: D, octave: 4}, Some(Pitch{class: Db, octave: 4})), 306 | (C, Gb, Pitch{class: C, octave: 4}, Some(Pitch{class: B, octave: 3})), 307 | ]; 308 | for (source_key, target_key, pitch, expected_result) in cases.iter() { 309 | let actual_result = source_key.transpose_to(*target_key, *pitch); 310 | assert_eq!(actual_result, *expected_result, 311 | "Input was: {:?}, {:?}, {:?}, {:?}", *source_key, *target_key, *pitch, *expected_result); 312 | } 313 | } 314 | 315 | } -------------------------------------------------------------------------------- /src/core/music_theory/mod.rs: -------------------------------------------------------------------------------- 1 | use num_traits; 2 | 3 | pub mod pitch_class; 4 | pub mod pitch; 5 | pub mod diatonic_scale; 6 | pub mod rhythm; 7 | 8 | pub type Semitones = i8; 9 | pub type Octave = i8; 10 | pub type Hz = f64; 11 | 12 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 13 | pub enum Modality { 14 | MAJOR, MINOR 15 | } 16 | 17 | impl Default for Modality { 18 | fn default() -> Self { 19 | Modality::MINOR 20 | } 21 | } -------------------------------------------------------------------------------- /src/core/music_theory/pitch.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{Debug, Formatter, Display}; 2 | use std::ops::Add; 3 | use super::{Hz, Semitones, Octave, pitch_class::{PitchClass, NUM_CLASSES}}; 4 | 5 | #[derive(Clone, Copy, PartialEq, Eq, Hash)] 6 | pub struct Pitch { 7 | pub class: PitchClass, 8 | pub octave: Octave, 9 | } 10 | 11 | impl Pitch { 12 | 13 | pub fn new(class: PitchClass, octave: Octave) -> Pitch { 14 | Pitch { class, octave } 15 | } 16 | 17 | /// Frequencies on the equal tempered scale: 18 | /// fn = f0 * (a)^n 19 | /// 20 | /// where 21 | /// f0 = the frequency of one fixed note which must be defined. A common choice is setting the A above middle C (A4) at f0 = 440 Hz. 22 | /// n = the number of half steps away from the fixed note you are. If you are at a higher note, n is positive. If you are on a lower note, n is negative. 23 | /// fn = the frequency of the note n half steps away. 24 | /// a = (2)1/12 = the twelfth root of 2 = the number which when multiplied by itself 12 times equals 2 = 1.059463094359... 25 | /// 26 | /// Source: http://pages.mtu.edu/~suits/NoteFreqCalcs.html 27 | /// 28 | pub fn freq(self) -> Hz { 29 | let f0: Hz = 440.; 30 | let a: f64 = 2_f64.powf(1./12.); 31 | let n: isize = self.index() as isize - 69; 32 | f0 * a.powf(n as f64) 33 | } 34 | 35 | /// Follows the MIDI convention: the index for C4 is 60 36 | /// https://newt.phys.unsw.edu.au/jw/notes.html 37 | pub fn index(self) -> usize{ 38 | (self.octave + 1) as usize * NUM_CLASSES + self.class as usize 39 | } 40 | 41 | /// Follows the MIDI convention: the index for C4 is 60 42 | /// https://newt.phys.unsw.edu.au/jw/notes.html 43 | pub fn from_index(i: usize) -> Pitch { 44 | Pitch { 45 | octave: ((i / NUM_CLASSES) as Octave - 1), 46 | class: PitchClass::from_index(i % NUM_CLASSES) 47 | .unwrap_or_else(|| panic!("Failed to get PitchClass for i={}", i)) 48 | } 49 | } 50 | 51 | } 52 | 53 | impl Default for Pitch { 54 | fn default() -> Self { 55 | Pitch { class: PitchClass::A, octave: 4 } 56 | } 57 | } 58 | 59 | impl Add for Pitch { 60 | type Output = Self; 61 | fn add(self, rhs: Semitones) -> Self { 62 | Pitch::from_index((self.index() as Semitones + rhs) as usize) 63 | } 64 | } 65 | 66 | impl Debug for Pitch { 67 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 68 | write!(f, "{:?}{:?}", self.class, self.octave) 69 | } 70 | } 71 | 72 | impl Display for Pitch { 73 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 74 | write!(f, "{:?}{:?}", self.class, self.octave) 75 | } 76 | } 77 | 78 | #[cfg(test)] 79 | mod tests { 80 | use super::{Pitch, PitchClass::*, Hz}; 81 | 82 | #[test] 83 | fn should_convert_pitch_to_freq() { 84 | let cases: &[(Pitch, Hz)] = &[ 85 | (Pitch{ octave: 0, class: C }, 16.35 ), 86 | (Pitch{ octave: 4, class: C }, 261.63 ), 87 | (Pitch{ octave: 4, class: A }, 440.0 ), 88 | (Pitch{ octave: 5, class: A }, 880.0 ), 89 | (Pitch{ octave: 8, class: B }, 7902.13), 90 | ]; 91 | for (pitch, expected_freq) in cases.iter() { 92 | let err = expected_freq - pitch.freq(); 93 | assert!(err.abs() < 1.0); 94 | } 95 | } 96 | 97 | /// Follows the MIDI convention: the index for C4 is 60 98 | /// https://newt.phys.unsw.edu.au/jw/notes.html 99 | #[test] 100 | fn should_convert_index_to_pitch() { 101 | let cases: &[(usize, Pitch)] = &[ 102 | (21, Pitch{ octave: 0, class: A }), 103 | (60, Pitch{ octave: 4, class: C }), 104 | (69, Pitch{ octave: 4, class: A }), 105 | (60 - 4*12, Pitch{ octave: 0, class: C }), 106 | (69 + 12, Pitch{ octave: 5, class: A }), 107 | (59 + 5*12, Pitch{ octave: 8, class: B }), 108 | ]; 109 | for (index, expected_pitch) in cases.iter() { 110 | assert_eq!(Pitch::from_index(*index), *expected_pitch); 111 | } 112 | } 113 | } -------------------------------------------------------------------------------- /src/core/music_theory/pitch_class.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Add, Sub, AddAssign}; 2 | use super::{Semitones, num_traits::FromPrimitive}; 3 | use std::fmt::{Display, Formatter}; 4 | 5 | #[derive(Clone, Copy, PartialEq, Eq, Hash, FromPrimitive, Debug)] 6 | pub enum PitchClass { 7 | C, Db, D, Eb, E, F, Gb, G, Ab, A, Bb, B 8 | } 9 | 10 | pub const NUM_CLASSES: usize = 12; 11 | 12 | impl PitchClass { 13 | pub fn from_index(i: usize) -> Option { 14 | FromPrimitive::from_usize(i) 15 | } 16 | } 17 | 18 | impl Add for PitchClass { 19 | type Output = Self; 20 | fn add(self, rhs: Semitones) -> Self { 21 | let limit = NUM_CLASSES as i8; 22 | let i = ((((self as Semitones + rhs) % limit) + limit) % limit) as usize; 23 | PitchClass::from_index(i).unwrap_or_else(|| panic!("Failed to get PitchClass for i={}", i)) 24 | } 25 | } 26 | impl Add for PitchClass { 27 | type Output = Self; 28 | fn add(self, rhs: PitchClass) -> Self { 29 | self + rhs as Semitones 30 | } 31 | } 32 | impl Sub for PitchClass { 33 | type Output = Self; 34 | fn sub(self, rhs: Semitones) -> Self { 35 | self + -rhs 36 | } 37 | } 38 | impl Sub for PitchClass { 39 | type Output = Self; 40 | fn sub(self, rhs: PitchClass) -> Self { 41 | self - rhs as Semitones 42 | } 43 | } 44 | impl AddAssign for PitchClass { 45 | fn add_assign(&mut self, rhs: i8) { 46 | *self = *self + rhs 47 | } 48 | } 49 | 50 | impl Default for PitchClass { 51 | fn default() -> Self { 52 | PitchClass::A 53 | } 54 | } 55 | 56 | impl Display for PitchClass { 57 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 58 | write!(f, "{:?}", self) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/core/music_theory/rhythm.rs: -------------------------------------------------------------------------------- 1 | use super::diatonic_scale::{RelativePitch, OctaveShift, ScaleDegree}; 2 | use num_traits::FromPrimitive; 3 | 4 | #[derive(Clone, Copy, PartialEq, Eq, Debug, FromPrimitive)] 5 | pub enum NoteDuration { 6 | Whole=16, Half=8, Quarter=4, Eight=2, Sixteenth=1 7 | } 8 | 9 | impl NoteDuration { 10 | 11 | pub fn half(&self) -> Option { 12 | FromPrimitive::from_u8(*self as u8 / 2) 13 | } 14 | 15 | pub fn double(&self) -> Option { 16 | FromPrimitive::from_u8(*self as u8 * 2) 17 | } 18 | } 19 | 20 | #[derive(Clone, Copy, PartialEq, Eq, Debug)] 21 | pub struct Note { 22 | pub duration: NoteDuration, 23 | pub pitch: RelativePitch, 24 | } 25 | 26 | impl Note { 27 | pub fn new(duration: NoteDuration, octave: OctaveShift, degree: ScaleDegree) -> Self { 28 | Note { duration, pitch: (octave, degree) } 29 | } 30 | } 31 | 32 | impl Default for NoteDuration { 33 | fn default() -> Self { 34 | NoteDuration::Quarter 35 | } 36 | } -------------------------------------------------------------------------------- /src/core/sheet_music/mod.rs: -------------------------------------------------------------------------------- 1 | 2 | pub mod sheet_music; 3 | pub mod playing_music; 4 | -------------------------------------------------------------------------------- /src/core/sheet_music/playing_music.rs: -------------------------------------------------------------------------------- 1 | use std::time::{Duration, Instant}; 2 | use crate::core::control::synth::Command; 3 | use crate::core::sheet_music::sheet_music::*; 4 | 5 | pub struct PlayingMusic { 6 | sections: Vec
, 7 | voices: Vec, 8 | begin: Instant, 9 | #[allow(dead_code)] end: Instant, 10 | current_section_index: usize, 11 | } 12 | impl PlayingMusic { 13 | 14 | pub fn new(sheet_music: SheetMusic) -> Self { 15 | let begin = Instant::now(); 16 | PlayingMusic { 17 | begin, 18 | end: begin + music_duration(&sheet_music), 19 | sections: sheet_music.sections, 20 | voices: sheet_music.voices.into_iter().map(PlayingVoice::new).collect(), 21 | current_section_index: 0, 22 | } 23 | } 24 | 25 | pub fn next(&mut self) -> Reading { 26 | let time_elapsed = Instant::now() - self.begin; 27 | self.update_current_section_index(time_elapsed); 28 | let section = self.sections.get(self.current_section_index) 29 | .unwrap_or_else(|| panic!("No current section")); 30 | let commands =self.voices.iter_mut() 31 | .flat_map(|t| t.next_targeted(time_elapsed, section)) 32 | .collect(); 33 | Reading { commands, time_elapsed, section } 34 | } 35 | 36 | fn update_current_section_index(&mut self, elapsed_time: Duration) { 37 | self.current_section_index = (self.current_section_index..self.sections.len()) 38 | .take_while(|i| self.sections.get(*i).map(|section| section.begin_time <= elapsed_time) == Some(true)) 39 | .last().unwrap_or_else(|| panic!("Failed to find section")); 40 | } 41 | 42 | pub fn tick_duration(&self) -> Duration { 43 | self.sections.get(self.current_section_index) 44 | .unwrap_or_else(|| panic!("No current section")) 45 | .tick_duration 46 | } 47 | } 48 | 49 | pub type TargetedCommand = (Command, ChannelId); 50 | 51 | struct PlayingVoice { 52 | voice: Voice, 53 | current_event_index: usize, 54 | } 55 | impl PlayingVoice { 56 | 57 | fn new(voice: Voice) -> Self { 58 | PlayingVoice { voice, current_event_index: 0 } 59 | } 60 | 61 | fn next_targeted(&mut self, elapsed_time: Duration, section: &Section) -> Vec { 62 | self.next(elapsed_time, section).into_iter() 63 | .map(|c| (c, self.voice.instrument_id)) 64 | .collect() 65 | } 66 | 67 | fn next(&mut self, elapsed_time: Duration, section: &Section) -> Vec { 68 | let begin = self.current_event_index; 69 | let result: Vec = self.voice.events.iter() 70 | .skip(begin) 71 | .take_while(|(_, t)| get_time(*t, section) <= elapsed_time) 72 | .map(|(cmd, _)| cmd.clone()) 73 | .collect(); 74 | self.current_event_index += result.len(); 75 | result 76 | } 77 | 78 | } 79 | 80 | fn music_duration(music: &SheetMusic) -> Duration { 81 | let default_section = Section::default(); 82 | let last_section = music.sections.last().unwrap_or(&default_section); 83 | get_time(music.end, last_section) 84 | } 85 | 86 | fn get_time(tick: Tick, section: &Section) -> Duration { 87 | let ticks = tick - section.begin_tick; 88 | let nanos = ticks as u64 * section.tick_duration.as_nanos() as u64; 89 | section.begin_time + Duration::from_nanos(nanos) 90 | } 91 | 92 | pub struct Reading<'a> { 93 | pub commands: Vec, 94 | pub time_elapsed: Duration, 95 | pub section: &'a Section, 96 | } 97 | impl <'a> Reading<'a> { 98 | pub fn measure(&self) -> MeasurePosition { 99 | self.section.measure_at_time(self.time_elapsed) 100 | } 101 | } -------------------------------------------------------------------------------- /src/core/sheet_music/sheet_music.rs: -------------------------------------------------------------------------------- 1 | use crate::core::control::synth::Command; 2 | use crate::core::music_theory::{pitch_class::PitchClass, Modality}; 3 | use crate::util; 4 | use std::time::Duration; 5 | 6 | pub type Tempo = u32; //microseconds per beat //TODO change to Duration 7 | pub type Tick = u64; //relative time 8 | 9 | pub const DEFAULT_TEMPO: Tempo = 500_000; //microseconds per beat 10 | pub const DEFAULT_TICKS_PER_BEAT: u16 = 480; 11 | 12 | pub struct SheetMusic { 13 | pub title: String, 14 | pub sections: Vec
, 15 | pub voices: Vec, 16 | pub ticks_per_beat: u16, 17 | pub end: Tick, 18 | } 19 | 20 | impl SheetMusic { 21 | 22 | pub fn count_measures(&self) -> usize { 23 | let last_section = self.sections.last() 24 | .unwrap_or_else(|| panic!("Failed to get last section")); 25 | let last_measure: MeasurePosition = last_section.measure_at_tick(self.end); 26 | last_measure.floor() as usize + 1 27 | } 28 | 29 | } 30 | 31 | impl Default for SheetMusic { 32 | fn default() -> Self { 33 | SheetMusic { 34 | title: String::from("Unnamed"), 35 | sections: vec!(Section::default()), 36 | voices: vec![], 37 | ticks_per_beat: 96, 38 | end: 0 39 | } 40 | } 41 | } 42 | 43 | pub type MeasurePosition = f64; 44 | 45 | /// Metadata that: 46 | /// - can change over time 47 | /// - is common to all voices 48 | #[derive(PartialEq, Default, Debug)] 49 | pub struct Section { 50 | pub begin_tick: Tick, 51 | pub begin_time: Duration, 52 | pub begin_measure: MeasurePosition, 53 | pub key: PitchClass, 54 | pub modality: Modality, 55 | pub beat_duration: Tempo, 56 | pub beats_per_measure: u8, 57 | pub tick_duration: Duration, 58 | } 59 | impl Section { 60 | pub fn is_default(&self) -> bool { 61 | *self == Section::default() 62 | } 63 | 64 | pub fn measure_at_time(&self, time_elapsed: Duration) -> MeasurePosition { 65 | let time_in_section = time_elapsed - self.begin_time; //FIXME panic if out of section 66 | self.measure_at(time_in_section) 67 | } 68 | 69 | pub fn measure_at_tick(&self, tick: Tick) -> MeasurePosition { 70 | let tick_in_section = tick - self.begin_tick; //FIXME panic if out of section 71 | let time_in_section = self.tick_duration.mul_f64(tick_in_section as f64); 72 | self.measure_at(time_in_section) 73 | } 74 | 75 | fn measure_at(&self, time_in_section: Duration) -> MeasurePosition { 76 | let beat_duration = Duration::from_micros(u64::from(self.beat_duration)); 77 | let measure_duration = beat_duration * u32::from(self.beats_per_measure); 78 | let measures_in_section = util::duration::div_duration(time_in_section, measure_duration); 79 | self.begin_measure + measures_in_section 80 | } 81 | } 82 | 83 | pub type ScheduledCommand = (Command, Tick); //TODO event with measure position and ref to Section 84 | pub type ChannelId = u8; 85 | 86 | pub struct Voice { 87 | pub events: Vec, 88 | pub instrument_id: ChannelId, 89 | } 90 | impl Voice { 91 | pub fn new(events: Vec, instrument_id: ChannelId) -> Self { 92 | Voice { events, instrument_id } 93 | } 94 | } -------------------------------------------------------------------------------- /src/core/synth/adsr.rs: -------------------------------------------------------------------------------- 1 | use super::{Sample, Seconds, Proportion}; 2 | 3 | #[derive(Clone, Copy, PartialEq, Debug)] 4 | pub struct Adsr { 5 | pub attack: Seconds, 6 | pub decay: Seconds, 7 | pub sustain: Proportion, 8 | pub release: Seconds, 9 | } 10 | impl Adsr { 11 | pub fn new(attack: Seconds, decay: Seconds, sustain: Proportion, release: Seconds) -> Adsr { 12 | assert!(attack >= 0., "attack was: {}", attack); 13 | assert!(decay >= 0., "decay was: {}", decay); 14 | assert!((0. ..=1.).contains(&sustain), "sustain was: {}", sustain); 15 | assert!(release >= 0., "release was: {}", release); 16 | Adsr { attack, decay, sustain, release } 17 | } 18 | 19 | pub fn apply(&self, elapsed: Seconds, elapsed_since_release: Seconds, sample: Sample) -> Sample { 20 | sample * self.scale_ratio(elapsed, elapsed_since_release) 21 | } 22 | 23 | fn scale_ratio(&self, elapsed: Seconds, elapsed_since_release: Seconds) -> Proportion { 24 | if elapsed_since_release > 0. { 25 | let release_progress = elapsed_since_release / self.release; 26 | let release_scale = (1. - release_progress).max(0.); 27 | self.sustain * release_scale 28 | } else if elapsed < self.attack { 29 | elapsed / self.attack 30 | } else if elapsed < self.attack + self.decay { 31 | let decay_progress = (elapsed - self.attack) / self.decay; 32 | let sustain_head_room = 1. - self.sustain; 33 | self.sustain + sustain_head_room * (1. - decay_progress) 34 | } else { 35 | self.sustain 36 | } 37 | } 38 | } 39 | 40 | impl Default for Adsr { 41 | fn default() -> Self { 42 | Adsr { 43 | attack: 0., 44 | decay: 0., 45 | sustain: 1., 46 | release: 0. 47 | } 48 | } 49 | } 50 | 51 | #[cfg(test)] 52 | mod tests { 53 | use super::Adsr; 54 | 55 | fn sut() -> Adsr { 56 | Adsr::new(1., 1., 0.5, 1.) 57 | } 58 | 59 | #[test] 60 | fn attack() { 61 | assert_approx(sut().apply(0., 0., 0.1), 0.); 62 | assert_approx(sut().apply(0.25, 0., 0.1), 0.025); 63 | assert_approx(sut().apply(0.5, 0., 0.1), 0.05); 64 | assert_approx(sut().apply(0.75, 0., 0.1), 0.075); 65 | } 66 | #[test] 67 | fn decay() { 68 | assert_approx(sut().apply(1., 0., 0.1), 0.1); 69 | assert_approx(sut().apply(1.25, 0., 0.1), 0.0875); 70 | assert_approx(sut().apply(1.5, 0., 0.1), 0.075); 71 | assert_approx(sut().apply(1.75, 0., 0.1), 0.0625); 72 | } 73 | #[test] 74 | fn sustain() { 75 | assert_approx(sut().apply(2., 0., 0.1), 0.05); 76 | assert_approx(sut().apply(3., 0., 0.1), 0.05); 77 | assert_approx(sut().apply(10., 0., 0.1), 0.05); 78 | } 79 | #[test] 80 | fn release() { 81 | assert_approx(sut().apply(3., 0.25, 0.1), 0.0375); 82 | assert_approx(sut().apply(3., 0.5, 0.1), 0.025); 83 | assert_approx(sut().apply(3., 0.75, 0.1), 0.0125); 84 | assert_approx(sut().apply(3.5, 1., 0.1), 0.); 85 | assert_approx(sut().apply(4., 1.5, 0.1), 0.); 86 | assert_approx(sut().apply(10., 10., 0.1), 0.); 87 | } 88 | 89 | #[test] 90 | fn release_before_decay() { 91 | assert_approx(sut().apply(0., 10., 0.1), 0.); 92 | assert_approx(sut().apply(1., 10., 0.1), 0.); 93 | assert_approx(sut().apply(1., 0.5, 0.1), 0.025); 94 | } 95 | 96 | fn assert_approx(left: f64, right: f64) { 97 | assert!((right - left).abs() < 0.0000000000000001) 98 | } 99 | } -------------------------------------------------------------------------------- /src/core/synth/builder.rs: -------------------------------------------------------------------------------- 1 | use super::{Seconds, Proportion, instrument::{self, ModTarget, ModSpecs}, oscillator, filter, adsr::Adsr, lfo}; 2 | 3 | pub struct Builder { 4 | max_voices: u8, 5 | oscillator: oscillator::Specs, 6 | filter: filter::Specs, 7 | lfo: Option, 8 | adsr: Adsr, 9 | volume: Proportion, 10 | modulation_x: ModTarget, 11 | modulation_y: ModTarget, 12 | pub modulation_lfo: ModSpecs, 13 | } 14 | impl Builder { 15 | 16 | pub fn osc(oscillator: oscillator::Specs) -> Builder { 17 | Builder { 18 | oscillator, 19 | max_voices: 8, 20 | filter: filter::Specs::default(), 21 | lfo: None, 22 | adsr: Adsr::new(0., 0.05, 0.8, 0.2), 23 | volume: 0.2, 24 | modulation_x: ModTarget::Filter(filter::ModTarget::Cutoff), 25 | modulation_y: ModTarget::Filter(filter::ModTarget::QFactor), 26 | modulation_lfo: ModSpecs{ target: ModTarget::Noop, amount: 1.}, 27 | } 28 | } 29 | 30 | pub fn build(self) -> instrument::Specs { 31 | instrument::Specs { 32 | max_voices: self.max_voices, 33 | oscillator: self.oscillator, 34 | filter: self.filter, 35 | lfo: self.lfo, 36 | adsr: self.adsr, 37 | volume: self.volume, 38 | modulation_x: self.modulation_x, 39 | modulation_y: self.modulation_y, 40 | modulation_lfo: self.modulation_lfo, 41 | } 42 | } 43 | 44 | pub fn filter(mut self, value: filter::Specs) -> Self { 45 | self.filter = value; 46 | self 47 | } 48 | pub fn attack(mut self, value: Seconds) -> Self { 49 | self.adsr.attack = value; 50 | self 51 | } 52 | pub fn decay(mut self, value: Seconds) -> Self { 53 | self.adsr.decay = value; 54 | self 55 | } 56 | pub fn sustain(mut self, value: Proportion) -> Self { 57 | self.adsr.sustain = value; 58 | self 59 | } 60 | pub fn release(mut self, value: Seconds) -> Self { 61 | self.adsr.release = value; 62 | self 63 | } 64 | pub fn lfo(mut self, value: lfo::Specs, target: ModTarget, amount: Proportion) -> Self { 65 | self.lfo = Some(value); 66 | self.modulation_lfo = ModSpecs{ target, amount }; 67 | self 68 | } 69 | pub fn adsr(mut self, a: Seconds, d: Seconds, s: Proportion, r: Seconds) -> Self { 70 | self.adsr = Adsr::new(a, d, s, r); 71 | self 72 | } 73 | pub fn volume(mut self, value: Proportion) -> Self { 74 | self.volume = value; 75 | self 76 | } 77 | pub fn mod_x(mut self, target: ModTarget) -> Self { 78 | self.modulation_x = target; 79 | self 80 | } 81 | pub fn mod_y(mut self, target: ModTarget) -> Self { 82 | self.modulation_y = target; 83 | self 84 | } 85 | } -------------------------------------------------------------------------------- /src/core/synth/filter/biquad.rs: -------------------------------------------------------------------------------- 1 | 2 | use super::*; 3 | use crate::core::{synth::Sample, music_theory::Hz}; 4 | use std::f64::consts::PI; 5 | 6 | /// http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt 7 | pub(super) struct BiquadFilter{ 8 | sample_rate: Hz, 9 | cutoff: ModParam, 10 | qfactor: ModParam, 11 | filter_type: Box, 12 | input_history: [Sample;2], 13 | output_history: [Sample;2], 14 | } 15 | 16 | impl BiquadFilter { 17 | pub(super) fn new(sample_rate: Hz, specs: Specs) -> BiquadFilter { 18 | assert!(sample_rate > 0., "sample_rate was: {}", sample_rate); 19 | let filter_type: Box = match specs.filter_type { 20 | TypeSpec::LPF => Box::new(Lpf), 21 | TypeSpec::HPF => Box::new(Hpf), 22 | TypeSpec::BPF => Box::new(Bpf), 23 | TypeSpec::Notch => Box::new(Notch), 24 | }; 25 | BiquadFilter { 26 | sample_rate, filter_type, 27 | cutoff: ModParam::with_base(specs.cutoff, MIN_CUTOFF, MAX_CUTOFF), 28 | qfactor: ModParam::with_base(specs.resonance, MIN_QFACTOR, MAX_QFACTOR), 29 | input_history: [0., 0.], 30 | output_history: [0., 0.], 31 | } 32 | } 33 | 34 | fn calculate_coefficients(&mut self) -> Coefficients { 35 | let cutoff = self.cutoff.calculate(); 36 | let qfactor = self.qfactor.calculate(); 37 | let w0 = 2. * PI * cutoff / self.sample_rate; 38 | let alpha = w0.sin() / (2. * qfactor); 39 | self.filter_type.coefficients(w0, alpha) 40 | } 41 | } 42 | 43 | impl Filter for BiquadFilter { 44 | fn filter(&mut self, input: Sample) -> Sample { 45 | let coef = &self.calculate_coefficients(); 46 | let a0 = coef.a0; 47 | let output = (coef.b0/a0) * input 48 | + (coef.b1/a0) * self.input_history[1] + (coef.b2/a0) * self.input_history[0] 49 | - (coef.a1/a0) * self.output_history[1] - (coef.a2/a0) * self.output_history[0]; 50 | 51 | self.input_history = [self.input_history[1], input]; 52 | self.output_history = [self.output_history[1], output]; 53 | 54 | output 55 | } 56 | 57 | fn view(&self) -> View { 58 | View { 59 | cutoff: self.cutoff.normalized(), 60 | resonance: self.qfactor.normalized(), 61 | filter_type: self.filter_type.spec(), 62 | } 63 | } 64 | } 65 | 66 | impl Modulated for BiquadFilter { 67 | fn mod_param(&mut self, target: ModTarget) -> Option<&mut ModParam> { 68 | match target { 69 | ModTarget::Cutoff => Some(&mut self.cutoff), 70 | ModTarget::QFactor => Some(&mut self.qfactor), 71 | } 72 | } 73 | } 74 | 75 | struct Coefficients { 76 | b0: f64, b1: f64, b2: f64, a0: f64, a1: f64, a2: f64, 77 | } 78 | 79 | trait FilterType { 80 | fn coefficients(&self, w0: f64, alpha: f64) -> Coefficients; 81 | fn spec(&self) -> TypeSpec; 82 | } 83 | 84 | struct Lpf; 85 | impl FilterType for Lpf { 86 | fn coefficients(&self, w0: f64, alpha: f64) -> Coefficients { 87 | let cos_w0 = w0.cos(); 88 | Coefficients { 89 | b0: (1. - cos_w0) / 2., 90 | b1: 1. - cos_w0, 91 | b2: (1. - cos_w0) / 2., 92 | a0: 1. + alpha, 93 | a1: -2. * cos_w0, 94 | a2: 1. - alpha, 95 | } 96 | } 97 | 98 | fn spec(&self) -> TypeSpec { 99 | TypeSpec::LPF 100 | } 101 | } 102 | 103 | struct Hpf; 104 | impl FilterType for Hpf { 105 | fn coefficients(&self, w0: f64, alpha: f64) -> Coefficients { 106 | let cos_w0 = w0.cos(); 107 | Coefficients{ 108 | b0: (1. + cos_w0)/2., 109 | b1: -(1. + cos_w0), 110 | b2: (1. + cos_w0)/2., 111 | a0: 1. + alpha, 112 | a1: -2. * cos_w0, 113 | a2: 1. - alpha, 114 | } 115 | } 116 | 117 | fn spec(&self) -> TypeSpec { 118 | TypeSpec::HPF 119 | } 120 | } 121 | 122 | struct Bpf; 123 | impl FilterType for Bpf { 124 | fn coefficients(&self, w0: f64, alpha: f64) -> Coefficients { 125 | let sin_w0 = w0.sin(); 126 | let cos_w0 = w0.cos(); 127 | Coefficients{ 128 | b0: sin_w0/2., 129 | b1: 0., 130 | b2: -sin_w0/2., 131 | a0: 1. + alpha, 132 | a1: -2. * cos_w0, 133 | a2: 1. - alpha, 134 | } 135 | } 136 | 137 | fn spec(&self) -> TypeSpec { 138 | TypeSpec::BPF 139 | } 140 | } 141 | 142 | struct Notch; 143 | impl FilterType for Notch { 144 | fn coefficients(&self, w0: f64, alpha: f64) -> Coefficients { 145 | let cos_w0 = w0.cos(); 146 | Coefficients{ 147 | b0: 1., 148 | b1: -2. * cos_w0, 149 | b2: 1., 150 | a0: 1. + alpha, 151 | a1: -2. * cos_w0, 152 | a2: 1. - alpha, 153 | } 154 | } 155 | 156 | fn spec(&self) -> TypeSpec { 157 | TypeSpec::Notch 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/core/synth/filter/mod.rs: -------------------------------------------------------------------------------- 1 | mod biquad; 2 | 3 | use super::{Sample, modulated::*}; 4 | use crate::core::music_theory::Hz; 5 | 6 | const MAX_CUTOFF: Hz = 440. * 32.; 7 | const MIN_CUTOFF: Hz = 0.; 8 | const MAX_QFACTOR: f64 = 50.; 9 | const MIN_QFACTOR: f64 = 1.; 10 | 11 | pub trait Filter: Modulated { 12 | fn filter(&mut self, input: Sample) -> Sample; 13 | fn view(&self) -> View; 14 | } 15 | 16 | #[derive(Clone, Copy, PartialEq, Debug)] 17 | pub struct Specs { 18 | pub filter_type: TypeSpec, 19 | pub cutoff: f64, 20 | pub resonance: f64, 21 | } 22 | 23 | #[derive(Clone, Copy, PartialEq, Debug)] 24 | pub enum TypeSpec { LPF, HPF, BPF, Notch } 25 | 26 | #[derive(Copy, Clone, PartialEq, Debug)] 27 | pub enum ModTarget { Cutoff, QFactor } 28 | 29 | impl dyn Filter { 30 | pub fn new(specs: Specs, sample_rate: Hz) -> Box { 31 | let filter = biquad::BiquadFilter::new(sample_rate, specs); 32 | Box::new(filter) 33 | } 34 | } 35 | 36 | impl Default for Specs { 37 | fn default() -> Self { 38 | Specs { 39 | filter_type: TypeSpec::LPF, 40 | cutoff: 1., 41 | resonance: 0.05, 42 | } 43 | } 44 | } 45 | 46 | #[derive(Copy, Clone, PartialEq, Debug)] 47 | pub struct View { //TODO remove if identical to Specs? 48 | pub cutoff: f64, 49 | pub resonance: f64, 50 | pub filter_type: TypeSpec, 51 | } 52 | 53 | impl Default for View { 54 | fn default() -> Self { 55 | View { 56 | cutoff: 1., 57 | resonance: 0., 58 | filter_type: TypeSpec::LPF 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /src/core/synth/instrument.rs: -------------------------------------------------------------------------------- 1 | use super::{Sample, Seconds, Proportion, Velocity, oscillator::{self, Oscillator}, 2 | filter::{self, Filter}, adsr::Adsr, lfo::{self, LFO}, modulated::*}; 3 | use crate::core::music_theory::{Hz, pitch::Pitch}; 4 | 5 | /// 6 | /// Connects modules of the synthesizer together to produce a stream of sound samples. 7 | /// 8 | 9 | #[derive(Clone, PartialEq, Debug)] 10 | pub struct Specs { 11 | pub max_voices: u8, 12 | pub oscillator: oscillator::Specs, 13 | pub filter: filter::Specs, 14 | pub lfo: Option, 15 | pub adsr: Adsr, 16 | pub volume: Proportion, 17 | pub modulation_x: ModTarget, 18 | pub modulation_y: ModTarget, 19 | pub modulation_lfo: ModSpecs, 20 | } 21 | 22 | #[derive(Copy, Clone, PartialEq, Debug)] 23 | pub enum ModTarget { 24 | Noop, Volume, 25 | Filter(filter::ModTarget), 26 | Oscillator(oscillator::ModTarget), 27 | } 28 | 29 | #[derive(Copy, Clone, PartialEq, Debug)] 30 | pub struct ModSpecs { 31 | pub target: ModTarget, 32 | pub amount: Proportion, 33 | } 34 | 35 | #[derive(Clone, PartialEq, Default, Debug)] 36 | pub struct View { 37 | pub oscillator: oscillator::View, 38 | pub filter: filter::View, 39 | pub lfo: Option, 40 | pub adsr: Adsr, 41 | pub volume: Proportion, 42 | } 43 | 44 | #[derive(Clone)] 45 | pub struct State { 46 | voices: Voices, 47 | clock: Clock, 48 | } 49 | 50 | pub struct Instrument { 51 | oscillator: Box, 52 | filter: Box, 53 | lfo: Option, 54 | adsr: Adsr, 55 | volume: ModParam, 56 | modulation_x: ModTarget, 57 | modulation_y: ModTarget, 58 | modulation_lfo: ModSpecs, 59 | voices: Voices, 60 | clock: Clock, 61 | } 62 | 63 | impl Instrument { 64 | 65 | pub fn new(specs: Specs, sample_rate: Hz) -> Instrument { 66 | Instrument { 67 | oscillator: ::new(&specs.oscillator), 68 | filter: ::new(specs.filter, sample_rate), 69 | lfo: specs.lfo.map(LFO::new), 70 | adsr: specs.adsr, 71 | volume: ModParam::with_base(specs.volume, 0., 1.), 72 | modulation_x: specs.modulation_x, 73 | modulation_y: specs.modulation_y, 74 | modulation_lfo: specs.modulation_lfo, 75 | clock: Clock::new(sample_rate), 76 | voices: Voices::new(specs.max_voices, sample_rate, specs.adsr.release), 77 | } 78 | } 79 | 80 | pub fn hold(&mut self, pitch: Pitch, velocity: Velocity) { 81 | self.voices.hold(pitch, velocity) 82 | } 83 | 84 | pub fn release(&mut self, pitch: Pitch) { 85 | self.voices.release(pitch) 86 | } 87 | 88 | pub fn release_all(&mut self) { 89 | self.voices.release_all() 90 | } 91 | 92 | pub fn next_sample(&mut self) -> Sample { 93 | self.run_next_lfo_modulation(); 94 | let oscillator = &self.oscillator; 95 | let adsr = &self.adsr; 96 | self.voices.drop_finished_voices(); 97 | let sample_mix: Sample = self.voices.voices.iter_mut() 98 | .map(|voice| Instrument::next_sample_for_voice(voice, oscillator, adsr)) 99 | .sum(); 100 | let sample_filtered = self.filter.filter(sample_mix); 101 | sample_filtered * self.volume.calculate() 102 | } 103 | 104 | fn next_sample_for_voice(voice: &mut Voice, oscillator: &Box, adsr: &Adsr) -> Sample { 105 | let clock = voice.clock.tick(); 106 | let sample = oscillator.next_sample(clock, voice.pitch.freq(), 0.) * voice.velocity; 107 | adsr.apply(voice.clock(), voice.released_clock().unwrap_or(0.), sample) 108 | } 109 | 110 | pub fn set_xy_params(&mut self, x: f64, y: f64) { 111 | let x_target = self.modulation_x; 112 | let y_target = self.modulation_y; 113 | if let Some(param) = self.mod_param(x_target){ 114 | param.set_base(x); 115 | } 116 | if let Some(param) = self.mod_param(y_target){ 117 | param.set_base(y); 118 | } 119 | } 120 | 121 | fn run_next_lfo_modulation(&mut self) { 122 | let maybe_lfo_sample = { 123 | let clock_ref = &mut self.clock; 124 | self.lfo.as_ref().map(|lfo| { 125 | let clock = clock_ref.tick(); 126 | lfo.next(clock) 127 | }) 128 | }; 129 | let specs = self.modulation_lfo; 130 | if let Some(lfo_sample) = maybe_lfo_sample { 131 | let normalized = (lfo_sample + 1.) / 2.; 132 | if let Some(param) = self.mod_param(specs.target) { 133 | param.set_signal(normalized * specs.amount); 134 | } 135 | } 136 | } 137 | 138 | pub fn get_state(&self) -> State{ 139 | State { 140 | voices: self.voices.clone(), 141 | clock: self.clock.clone(), 142 | } 143 | } 144 | 145 | pub fn set_state(&mut self, state: State) { 146 | self.clock = state.clock; 147 | self.voices = state.voices; 148 | } 149 | 150 | pub fn view(&self) -> View { 151 | View { 152 | filter: self.filter.view(), 153 | oscillator: self.oscillator.view(), 154 | lfo: self.lfo.as_ref().map(|l| l.view()), 155 | adsr: self.adsr.clone(), 156 | volume: self.volume.normalized(), 157 | } 158 | } 159 | } 160 | 161 | impl Modulated for Instrument { 162 | fn mod_param(&mut self, target: ModTarget) -> Option<&mut ModParam> { 163 | match target { 164 | ModTarget::Noop => None, 165 | ModTarget::Volume => Some(&mut self.volume), 166 | ModTarget::Filter(m) => self.filter.mod_param(m), 167 | ModTarget::Oscillator(m) => self.oscillator.mod_param(m), 168 | } 169 | } 170 | } 171 | 172 | #[derive(Clone)] 173 | struct Voices { 174 | max_voices: u8, 175 | voices: Vec, 176 | sample_rate: Hz, 177 | release: Seconds, 178 | } 179 | impl Voices { 180 | fn new(max_voices: u8, sample_rate: Hz, release: Seconds,) -> Voices { 181 | Voices{ max_voices, voices: vec![], sample_rate, release } 182 | } 183 | 184 | fn hold(&mut self, pitch: Pitch, velocity: Velocity) { 185 | if self.has_free_voice() { 186 | self.voices.push(Voice::new(self.sample_rate, pitch, velocity)) 187 | } 188 | } 189 | 190 | fn release(&mut self, pitch: Pitch) { 191 | if let Some(voice) = self.find_holding_voice(pitch) { 192 | voice.release(); 193 | } 194 | } 195 | 196 | fn release_all(&mut self) { 197 | self.voices.iter_mut().for_each(|v| v.release()); 198 | } 199 | 200 | fn find_holding_voice(&mut self, pitch: Pitch) -> Option<&mut Voice> { 201 | self.voices.iter_mut() 202 | .find(|v| v.pitch == pitch && v.is_holding()) 203 | } 204 | 205 | fn drop_finished_voices(&mut self) { 206 | let release = self.release; 207 | self.voices.retain(|voice| !voice.is_finished(release)) 208 | } 209 | 210 | fn has_free_voice(&self) -> bool { 211 | self.voices.len() < self.max_voices as usize 212 | } 213 | 214 | } 215 | 216 | #[derive(Clone)] 217 | struct Voice { 218 | pitch: Pitch, 219 | velocity: Velocity, 220 | released_at: Option, 221 | clock: Clock, 222 | } 223 | impl Voice { 224 | 225 | fn new(sample_rate: Hz, pitch: Pitch, velocity: Velocity) -> Voice { 226 | Voice { 227 | pitch, velocity, 228 | released_at: None, 229 | clock: Clock::new(sample_rate) 230 | } 231 | } 232 | 233 | fn clock(&self) -> Seconds { 234 | self.clock.get() 235 | } 236 | 237 | fn release(&mut self) { 238 | if self.is_holding() { 239 | self.released_at = Some(self.clock.get()) 240 | } 241 | } 242 | 243 | fn released_clock(&self) -> Option { 244 | self.released_at.as_ref().map(|begin| self.clock() - begin) 245 | } 246 | 247 | fn is_holding(&self) -> bool { 248 | self.released_at.is_none() 249 | } 250 | 251 | fn is_finished(&self, decay: Seconds) -> bool { 252 | let now = self.clock.get(); 253 | self.released_at.map(|released| now - released > decay).unwrap_or(false) 254 | } 255 | 256 | } 257 | 258 | #[derive(Clone)] 259 | struct Clock { 260 | sample_rate: Hz, 261 | clock: f64, 262 | } 263 | impl Clock { 264 | 265 | fn new(sample_rate: Hz) -> Clock { 266 | Clock{ sample_rate, clock: 0. } 267 | } 268 | 269 | fn tick(&mut self) -> Seconds { 270 | self.clock += 1.0; 271 | self.get() 272 | } 273 | 274 | fn get(&self) -> Seconds { 275 | self.clock / self.sample_rate 276 | } 277 | 278 | } 279 | 280 | impl Default for Specs { 281 | fn default() -> Self { 282 | Specs { 283 | max_voices: 8, 284 | oscillator: oscillator::Specs::default(), 285 | filter: filter::Specs::default(), 286 | lfo: None, 287 | adsr: Adsr::default(), 288 | volume: 1., 289 | modulation_x: ModTarget::default(), 290 | modulation_y: ModTarget::default(), 291 | modulation_lfo: ModSpecs::default(), 292 | } 293 | } 294 | } 295 | 296 | impl Default for ModTarget { 297 | fn default() -> Self { 298 | ModTarget::Noop 299 | } 300 | } 301 | 302 | impl Default for ModSpecs { 303 | fn default() -> Self { 304 | ModSpecs { target: ModTarget::Noop, amount: 1. } 305 | } 306 | } 307 | -------------------------------------------------------------------------------- /src/core/synth/lfo.rs: -------------------------------------------------------------------------------- 1 | use super::oscillator::{self, Oscillator, Basic::Sine}; 2 | use crate::core::synth::Seconds; 3 | use crate::core::music_theory::Hz; 4 | 5 | #[derive(Clone, PartialEq, Debug)] 6 | pub struct Specs { 7 | pub oscillator: oscillator::Specs, 8 | pub freq: Hz, 9 | pub phase: Seconds, 10 | } 11 | impl Specs { 12 | pub fn simple(freq: Hz) -> Specs { 13 | Specs { freq, oscillator: oscillator::Specs::Basic(Sine), phase: 0. } 14 | } 15 | } 16 | 17 | pub struct LFO { 18 | oscillator: Box, 19 | freq: Hz, 20 | phase: Seconds, 21 | } 22 | 23 | impl LFO { 24 | pub fn new(specs: Specs) -> LFO { 25 | LFO { 26 | oscillator: ::new(&specs.oscillator), 27 | freq: specs.freq, 28 | phase: specs.phase, 29 | } 30 | } 31 | 32 | pub fn next(&self, clock: Seconds) -> f64 { 33 | self.oscillator.next_sample(clock, self.freq, self.phase) 34 | } 35 | 36 | pub fn view(&self) -> View { 37 | View { 38 | oscillator: self.oscillator.view(), 39 | freq: self.freq, 40 | phase: self.phase, 41 | } 42 | } 43 | } 44 | 45 | #[derive(Clone, PartialEq, Default, Debug)] 46 | pub struct View { 47 | oscillator: oscillator::View, 48 | freq: Hz, 49 | phase: Seconds, 50 | } 51 | -------------------------------------------------------------------------------- /src/core/synth/mod.rs: -------------------------------------------------------------------------------- 1 | 2 | pub mod instrument; 3 | pub mod oscillator; 4 | pub mod filter; 5 | pub mod adsr; 6 | pub mod builder; 7 | pub mod lfo; 8 | pub mod modulated; 9 | 10 | pub type Sample = f64; 11 | pub type Seconds = f64; 12 | pub type Proportion = f64; 13 | pub type Velocity = f64; -------------------------------------------------------------------------------- /src/core/synth/modulated.rs: -------------------------------------------------------------------------------- 1 | 2 | pub trait Modulated { 3 | fn mod_param(&mut self, target: T) -> Option<&mut ModParam>; 4 | } 5 | 6 | #[derive(Debug)] 7 | pub struct ModParam { 8 | pub base: f64, 9 | mod_signal: f64, 10 | min: f64, 11 | range: f64, 12 | } 13 | impl ModParam { 14 | pub fn with_bounds(min: f64, max: f64) -> ModParam { 15 | let range = max - min; 16 | ModParam { base: 1., mod_signal: 0., min, range } 17 | } 18 | pub fn with_base(base: f64, min: f64, max: f64) -> ModParam { 19 | let range = max - min; 20 | let bounded_base = base.max(0.).min(1.); 21 | ModParam { base: bounded_base, mod_signal: 0., min, range } 22 | } 23 | pub fn set_base(&mut self, value: f64) { 24 | self.base = value.max(0.).min(1.); 25 | } 26 | pub fn set_signal(&mut self, value: f64) { 27 | self.mod_signal = value.max(0.).min(1.); 28 | } 29 | pub fn normalized(&self) -> f64 { 30 | (1. - self.mod_signal) * self.base 31 | } 32 | pub fn calculate(&self) -> f64 { 33 | self.normalized() * self.range + self.min 34 | } 35 | } 36 | impl Default for ModParam { 37 | fn default() -> Self { 38 | ModParam { base: 1., mod_signal: 0., min: 0., range: 1. } 39 | } 40 | } 41 | 42 | 43 | #[cfg(test)] 44 | mod tests { 45 | use super::*; 46 | 47 | #[test] 48 | fn up_and_down() { 49 | let mut sut = ModParam::with_bounds(0., 100.); 50 | sut.set_signal(0.); 51 | assert_approx(sut.calculate(), 100.); 52 | sut.set_signal(0.01); 53 | assert_approx(sut.calculate(), 99.); 54 | sut.set_signal(0.5); 55 | assert_approx(sut.calculate(), 50.); 56 | sut.set_signal(0.99); 57 | assert_approx(sut.calculate(), 1.); 58 | sut.set_signal(1.); 59 | assert_approx(sut.calculate(), 0.); 60 | } 61 | 62 | #[test] 63 | fn out_of_bounds() { 64 | let mut sut = ModParam::with_bounds(0., 1.); 65 | sut.set_signal(-1.); 66 | assert_approx(sut.calculate(), 1.); 67 | sut.set_signal(2.); 68 | assert_approx(sut.calculate(), 0.); 69 | } 70 | 71 | #[test] 72 | fn negative_min() { 73 | let mut sut = ModParam::with_bounds(-10., 10.); 74 | sut.set_signal(0.); 75 | assert_approx(sut.calculate(), 10.); 76 | sut.set_signal(0.5); 77 | assert_approx(sut.calculate(), 0.); 78 | sut.set_signal(1.); 79 | assert_approx(sut.calculate(), -10.); 80 | } 81 | 82 | #[test] 83 | fn base_at_half() { 84 | let mut sut = ModParam::with_bounds(0., 10.); 85 | sut.set_base(0.5); 86 | sut.set_signal(0.); 87 | assert_approx(sut.calculate(), 5.); 88 | sut.set_signal(0.5); 89 | assert_approx(sut.calculate(), 2.5); 90 | sut.set_signal(1.); 91 | assert_approx(sut.calculate(), 0.); 92 | } 93 | 94 | fn assert_approx(left: f64, right: f64) { 95 | assert!((right - left).abs() < 0.00000000000001) 96 | } 97 | } -------------------------------------------------------------------------------- /src/core/synth/oscillator/basic.rs: -------------------------------------------------------------------------------- 1 | 2 | use std::f64::consts::PI; 3 | use super::*; 4 | 5 | pub struct Sine; 6 | impl Oscillator for Sine { 7 | fn next_sample(&self, clock: Seconds, freq: Hz, phase: Seconds) -> Sample { 8 | ((clock + phase) * freq * 2. * PI).sin() 9 | } 10 | 11 | fn view(&self) -> View { 12 | View::Sine 13 | } 14 | } 15 | impl Modulated for Sine { 16 | fn mod_param(&mut self, _target: ModTarget) -> Option<&mut ModParam> { None } 17 | } 18 | 19 | pub struct Square; 20 | impl Oscillator for Square { 21 | fn next_sample(&self, clock: Seconds, freq: Hz, phase: Seconds) -> Sample { 22 | (((clock + phase) * freq ) % 1.).round() * 2. - 1. 23 | } 24 | 25 | fn view(&self) -> View { 26 | View::Square 27 | } 28 | } 29 | impl Modulated for Square { 30 | fn mod_param(&mut self, _target: ModTarget) -> Option<&mut ModParam> { None } 31 | } 32 | 33 | pub struct Saw; 34 | impl Oscillator for Saw { 35 | fn next_sample(&self, clock: Seconds, freq: Hz, phase: Seconds) -> Sample { 36 | ((clock + phase) * freq) % 1. 37 | } 38 | 39 | fn view(&self) -> View { 40 | View::Saw 41 | } 42 | } 43 | impl Modulated for Saw { 44 | fn mod_param(&mut self, _target: ModTarget) -> Option<&mut ModParam> { None } 45 | } 46 | -------------------------------------------------------------------------------- /src/core/synth/oscillator/mix.rs: -------------------------------------------------------------------------------- 1 | 2 | use crate::core::synth::{Sample, Seconds, modulated::*}; 3 | use crate::core::music_theory::Hz; 4 | use super::*; 5 | use rand::{self, Rng, StdRng, SeedableRng}; 6 | 7 | pub struct Mix { 8 | voices: Vec, 9 | } 10 | 11 | impl Mix { 12 | pub fn detuned(n_voices: usize, detune_amount: Hz, specs: Basic, random_seed: u64) -> Mix { 13 | Mix { voices: create_voices(n_voices, detune_amount, specs, random_seed) } 14 | } 15 | } 16 | 17 | fn create_voices(n_voices: usize, detune_amount: Hz, specs: Basic, random_seed: u64) -> Vec { 18 | let mut rng = StdRng::seed_from_u64(random_seed); 19 | fn random_around_zero(rng: &mut StdRng, amount: Hz) -> Hz { 20 | rng.gen_range(-amount, amount) 21 | } 22 | 23 | vec![0; n_voices].iter() 24 | .map(|_| Voice::new(random_around_zero(&mut rng, detune_amount), specs)) 25 | .collect() 26 | } 27 | 28 | impl Oscillator for Mix { 29 | fn next_sample(&self, clock: Seconds, freq: Hz, phase: Seconds) -> Sample { 30 | self.voices.iter() 31 | .map(|v| v.next_sample(clock, freq, phase)) 32 | .sum() 33 | } 34 | 35 | fn view(&self) -> View { 36 | View::Mix { 37 | voices: self.voices.iter().map(|v| v.view()).collect() 38 | } 39 | } 40 | } 41 | 42 | impl Modulated for Mix { 43 | fn mod_param(&mut self, _target: ModTarget) -> Option<&mut ModParam> { None } 44 | } 45 | 46 | 47 | struct Voice { 48 | tuning: Hz, 49 | oscillator: Box, 50 | } 51 | 52 | impl Voice { 53 | pub fn new(tuning: f64, specs: Basic) -> Self { 54 | Self { 55 | tuning, 56 | oscillator: ::new(&Specs::Basic(specs)), 57 | } 58 | } 59 | 60 | fn next_sample(&self, clock: Seconds, freq: Hz, phase: Seconds) -> Sample { 61 | let final_freq = freq + self.tuning; 62 | self.oscillator.next_sample(clock, final_freq, phase) 63 | } 64 | 65 | fn view(&self) -> MixVoiceView { 66 | MixVoiceView { 67 | tuning: self.tuning, 68 | oscillator: Box::new(self.oscillator.view()) 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /src/core/synth/oscillator/mod.rs: -------------------------------------------------------------------------------- 1 | mod mix; 2 | mod basic; 3 | mod pulse; 4 | 5 | use super::{Sample, Seconds, Proportion, modulated::*}; 6 | use crate::core::music_theory::Hz; 7 | use crate::core::synth::oscillator::basic::{Sine, Square, Saw}; 8 | use crate::core::synth::oscillator::pulse::Pulse; 9 | 10 | #[derive(Copy, Clone, PartialEq, Debug)] 11 | pub enum Basic { 12 | Sine, Saw, Square 13 | } 14 | 15 | #[derive(Clone, PartialEq, Debug)] 16 | pub enum Specs { 17 | Basic(Basic), 18 | Pulse(Proportion), 19 | Mix { 20 | n_voices: usize, 21 | detune_amount: Hz, 22 | specs: Basic, 23 | random_seed: u64, 24 | } 25 | } 26 | 27 | #[derive(Copy, Clone, PartialEq, Debug)] 28 | pub enum ModTarget { PulseDuty, MixThickness } 29 | 30 | pub trait Oscillator: Modulated { 31 | fn next_sample(&self, clock: Seconds, freq: Hz, phase: Seconds) -> Sample; 32 | fn view(&self) -> View; 33 | } 34 | 35 | impl dyn Oscillator { 36 | pub fn new(spec: &Specs) -> Box { 37 | match spec { 38 | Specs::Basic(Basic::Sine) => Box::new(Sine), 39 | Specs::Basic(Basic::Square) => Box::new(Square), 40 | Specs::Basic(Basic::Saw) => Box::new(Saw), 41 | Specs::Pulse(duty_cycle) => Box::new(Pulse::new(*duty_cycle)), 42 | Specs::Mix { n_voices, detune_amount, specs, random_seed } => 43 | Box::new(mix::Mix::detuned(*n_voices, *detune_amount, *specs, *random_seed)), 44 | } 45 | } 46 | } 47 | 48 | impl Default for Specs { 49 | fn default() -> Self { 50 | Specs::Basic(Basic::Sine) 51 | } 52 | } 53 | 54 | #[derive(Clone, PartialEq, Debug)] 55 | pub struct MixVoiceView { 56 | pub tuning: Hz, 57 | pub oscillator: Box, 58 | } 59 | 60 | #[derive(Clone, PartialEq, Debug)] 61 | pub enum View { 62 | Sine, Saw, Square, Pulse(Proportion), 63 | Mix { 64 | voices: Vec 65 | } 66 | } 67 | 68 | impl Default for View { 69 | fn default() -> Self { 70 | View::Sine 71 | } 72 | } -------------------------------------------------------------------------------- /src/core/synth/oscillator/pulse.rs: -------------------------------------------------------------------------------- 1 | 2 | use super::*; 3 | 4 | pub struct Pulse { 5 | duty_cycle: ModParam, 6 | } 7 | impl Pulse { 8 | pub fn new(duty_cycle: Proportion) -> Pulse { 9 | Pulse { duty_cycle: ModParam::with_base(duty_cycle, 0., 1.) } 10 | } 11 | } 12 | impl Oscillator for Pulse { 13 | fn next_sample(&self, clock: Seconds, freq: Hz, phase: Seconds) -> Sample { 14 | let duty_cycle = self.duty_cycle.calculate(); 15 | if ((clock + phase) * freq) % 1. < duty_cycle {1.} else {-1.} 16 | } 17 | 18 | fn view(&self) -> View { 19 | View::Pulse(self.duty_cycle.normalized()) 20 | } 21 | } 22 | impl Modulated for Pulse { 23 | fn mod_param(&mut self, target: ModTarget) -> Option<&mut ModParam> { 24 | match target { 25 | ModTarget::PulseDuty => Some(&mut self.duty_cycle), 26 | _ => None 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/core/tools/arpeggiator/builder.rs: -------------------------------------------------------------------------------- 1 | use crate::core::music_theory::rhythm::{NoteDuration, Note}; 2 | use crate::core::music_theory::diatonic_scale::ScaleDegree::{self, *}; 3 | use crate::core::music_theory::diatonic_scale::{OctaveShift, RelativePitch}; 4 | use num_traits::FromPrimitive; 5 | 6 | #[derive(Clone, PartialEq, Debug)] 7 | pub enum Chord { 8 | Octaves, Triad, Fantasy, Tetra, Penta 9 | } 10 | 11 | #[derive(Clone, PartialEq, Debug)] 12 | pub enum Direction { 13 | Up, Down, UpDown 14 | } 15 | 16 | #[derive(Clone, PartialEq, Debug)] 17 | pub struct Specs { 18 | pub chord: Chord, 19 | pub direction: Direction, 20 | pub octave_min: OctaveShift, 21 | pub octave_max: OctaveShift, 22 | pub duration: NoteDuration, 23 | } 24 | 25 | pub fn notes(specs: Specs) -> Vec { 26 | let rising = notes_rising(&specs); 27 | let towards_direction_once = match specs.direction { 28 | Direction::Up => rising, 29 | Direction::Down => rising.into_iter().rev().collect(), 30 | Direction::UpDown => rising.clone().into_iter() 31 | .chain(rising.into_iter().skip(1).rev().skip(1)).collect(), 32 | }; 33 | towards_direction_once.into_iter().collect() 34 | } 35 | 36 | fn notes_rising(specs: &Specs) -> Vec { 37 | let chord_degrees = specs.chord.notes(); 38 | let pitches: Vec = octaves(specs.octave_min, specs.octave_max).into_iter() 39 | .flat_map(|octave| chord_degrees.iter().map(move |degree| (octave, *degree))) 40 | .collect(); 41 | pitches.into_iter() 42 | .map(|pitch| Note { duration: specs.duration, pitch }) 43 | .collect() 44 | } 45 | 46 | impl Chord { 47 | fn notes(&self) -> Vec { 48 | match self { 49 | Chord::Octaves => vec![I1], 50 | Chord::Triad => vec![I1, I3, I5], 51 | Chord::Fantasy => vec![I1, I2, I3, I5], 52 | Chord::Tetra => vec![I1, I3, I5, I7], 53 | Chord::Penta => vec![I1, I2, I3, I5, I6], 54 | } 55 | } 56 | } 57 | 58 | fn octaves(min: OctaveShift, max: OctaveShift) -> Vec { 59 | (min as isize ..= max as isize) 60 | .map(|num| FromPrimitive::from_isize(num)).flatten().collect() 61 | } 62 | 63 | impl Default for Chord { 64 | fn default() -> Self { 65 | Chord::Octaves 66 | } 67 | } 68 | impl Default for Direction { 69 | fn default() -> Self { 70 | Direction::Up 71 | } 72 | } 73 | 74 | impl Default for Specs { 75 | fn default() -> Self { 76 | Specs { 77 | chord: Default::default(), 78 | direction: Default::default(), 79 | octave_min: OctaveShift::Down1, 80 | octave_max: OctaveShift::Up1, 81 | duration: Default::default() 82 | } 83 | } 84 | } 85 | 86 | #[cfg(test)] 87 | mod tests { 88 | use super::*; 89 | use NoteDuration::*; 90 | use OctaveShift::*; 91 | 92 | #[test] 93 | fn octaves_up() { 94 | let specs = Specs { 95 | chord: Chord::Octaves, 96 | direction: Direction::Up, 97 | octave_min: Same, 98 | octave_max: Up2, 99 | duration: Whole 100 | }; 101 | 102 | assert_eq!(notes(specs), vec!( 103 | Note::new(Whole, Same, I1), 104 | Note::new(Whole, Up1, I1), 105 | Note::new(Whole, Up2, I1), 106 | )) 107 | } 108 | 109 | #[test] 110 | fn triad() { 111 | let specs = Specs { 112 | chord: Chord::Triad, 113 | direction: Direction::Up, 114 | octave_min: Same, 115 | octave_max: Up2, 116 | duration: Whole 117 | }; 118 | 119 | assert_eq!(notes(specs), vec!( 120 | Note::new(Whole, Same, I1), 121 | Note::new(Whole, Same, I3), 122 | Note::new(Whole, Same, I5), 123 | Note::new(Whole, Up1, I1), 124 | Note::new(Whole, Up1, I3), 125 | Note::new(Whole, Up1, I5), 126 | Note::new(Whole, Up2, I1), 127 | Note::new(Whole, Up2, I3), 128 | Note::new(Whole, Up2, I5), 129 | )) 130 | } 131 | 132 | #[test] 133 | fn octave_down() { 134 | let specs = Specs { 135 | chord: Chord::Octaves, 136 | direction: Direction::Up, 137 | octave_min: Down1, 138 | octave_max: Up1, 139 | duration: Whole 140 | }; 141 | 142 | assert_eq!(notes(specs), vec!( 143 | Note::new(Whole, Down1, I1), 144 | Note::new(Whole, Same, I1), 145 | Note::new(Whole, Up1, I1), 146 | )) 147 | } 148 | 149 | #[test] 150 | fn direction_down() { 151 | let specs = Specs { 152 | chord: Chord::Octaves, 153 | direction: Direction::Down, 154 | octave_min: Same, 155 | octave_max: Up2, 156 | duration: Whole 157 | }; 158 | 159 | assert_eq!(notes(specs), vec!( 160 | Note::new(Whole, Up2, I1), 161 | Note::new(Whole, Up1, I1), 162 | Note::new(Whole, Same, I1), 163 | )) 164 | } 165 | 166 | #[test] 167 | fn direction_up_down() { 168 | let specs = Specs { 169 | chord: Chord::Octaves, 170 | direction: Direction::UpDown, 171 | octave_min: Same, 172 | octave_max: Up2, 173 | duration: Whole 174 | }; 175 | 176 | assert_eq!(notes(specs), vec!( 177 | Note::new(Whole, Same, I1), 178 | Note::new(Whole, Up1, I1), 179 | Note::new(Whole, Up2, I1), 180 | Note::new(Whole, Up1, I1), 181 | )) 182 | } 183 | 184 | #[test] 185 | fn duration_half_more_octaves() { 186 | let specs = Specs { 187 | chord: Chord::Octaves, 188 | direction: Direction::UpDown, 189 | octave_min: Down1, 190 | octave_max: Up2, 191 | duration: Half 192 | }; 193 | 194 | assert_eq!(notes(specs), vec!( 195 | Note::new(Half, Down1, I1), 196 | Note::new(Half, Same, I1), 197 | Note::new(Half, Up1, I1), 198 | Note::new(Half, Up2, I1), 199 | Note::new(Half, Up1, I1), 200 | Note::new(Half, Same, I1), 201 | )) 202 | } 203 | 204 | #[test] 205 | fn mixed() { 206 | let specs = Specs { 207 | chord: Chord::Triad, 208 | direction: Direction::UpDown, 209 | octave_min: Down1, 210 | octave_max: Same, 211 | duration: Half 212 | }; 213 | 214 | assert_eq!(notes(specs), vec!( 215 | Note::new(Half, Down1, I1), 216 | Note::new(Half, Down1, I3), 217 | Note::new(Half, Down1, I5), 218 | Note::new(Half, Same, I1), 219 | Note::new(Half, Same, I3), 220 | Note::new(Half, Same, I5), 221 | Note::new(Half, Same, I3), 222 | Note::new(Half, Same, I1), 223 | Note::new(Half, Down1, I5), 224 | Note::new(Half, Down1, I3), 225 | )) 226 | } 227 | 228 | 229 | } -------------------------------------------------------------------------------- /src/core/tools/arpeggiator/mod.rs: -------------------------------------------------------------------------------- 1 | use std::mem; 2 | 3 | use crate::core::control::{synth::{Command, Id, id}}; 4 | use crate::core::sheet_music::sheet_music::MeasurePosition; 5 | use crate::core::music_theory::{diatonic_scale::*, pitch::Pitch, rhythm::Note}; 6 | 7 | pub mod builder; 8 | pub mod phrase; 9 | 10 | pub struct Arpeggiator { 11 | phrase: Phrase, 12 | pub key: Key, 13 | holding_pitch: Option, 14 | playing_pitch: Option, 15 | pending_command: Option, 16 | } 17 | 18 | #[derive(Clone, PartialEq, Default, Debug)] 19 | pub struct Specs { 20 | pub key: Key, 21 | pub phrase: builder::Specs, 22 | } 23 | 24 | #[derive(Clone, PartialEq, Default, Debug)] 25 | pub struct View { 26 | pub phrase: phrase::View, 27 | pub key: Key, 28 | pub holding_pitch: Option, 29 | pub playing_pitch: Option, 30 | } 31 | 32 | #[derive(Clone, PartialEq, Default, Debug)] 33 | pub struct State { 34 | holding_pitch: Option, 35 | playing_pitch: Option, 36 | pending_command: Option, 37 | } 38 | 39 | impl Arpeggiator { 40 | 41 | pub fn from_specs(specs: Specs) -> Arpeggiator { 42 | Arpeggiator::from_phrase(specs.key, Phrase::from_specs(specs.phrase)) 43 | } 44 | 45 | pub fn from_phrase(key: Key, phrase: Phrase) -> Arpeggiator { 46 | Arpeggiator { 47 | phrase, key, 48 | holding_pitch: None, 49 | playing_pitch: None, 50 | pending_command: None, 51 | } 52 | } 53 | 54 | pub fn interpret(&mut self, command: Command) { 55 | match command { 56 | Command::NoteOn(pitch, _, _) => self.start(pitch), 57 | Command::NoteOff(id) => self.stop(id), 58 | other => panic!("Can't interpret command: {:?}", other) 59 | } 60 | } 61 | 62 | fn start(&mut self, pitch: Pitch) { 63 | self.holding_pitch = Some(pitch); 64 | } 65 | 66 | fn stop(&mut self, id: Id) { 67 | if self.is_holding(id) { 68 | self.pending_command = self.playing_pitch.map(note_off); 69 | self.holding_pitch = None; 70 | self.playing_pitch = None; 71 | } 72 | } 73 | 74 | fn is_holding(&self, id: Id) -> bool { 75 | self.holding_pitch.map(|p| p == id.pitch).unwrap_or(false) 76 | } 77 | 78 | pub fn next(&mut self, from_measure: MeasurePosition, to_measure: MeasurePosition) -> Vec { 79 | match mem::replace(&mut self.pending_command, None) { 80 | Some(pending) => vec![pending], 81 | None => self.next_notes(from_measure, to_measure).iter() 82 | .flat_map(|e| self.update_and_command(e)).collect() 83 | } 84 | } 85 | 86 | fn next_notes(&mut self, from_measure: MeasurePosition, to_measure: MeasurePosition) -> Vec { 87 | self.phrase.range(from_measure, to_measure) 88 | } 89 | 90 | fn update_and_command(&mut self, note: &Note) -> Vec { 91 | match (self.holding_pitch, self.playing_pitch) { 92 | (Some(holding), None) => 93 | self.update_note_on(note.pitch, holding).into_iter().collect(), 94 | (Some(holding), Some(playing)) => 95 | vec![ Some(note_off(playing)), 96 | self.update_note_on(note.pitch, holding), 97 | ].into_iter().flatten().collect(), 98 | (None, Some(playing)) => { 99 | self.playing_pitch = None; 100 | vec![note_off(playing)] 101 | } 102 | _ => vec![], 103 | } 104 | } 105 | 106 | fn update_note_on(&mut self, relative_pitch: RelativePitch, holding: Pitch) -> Option { 107 | let next_pitch = self.key.pitch_at(holding, relative_pitch); 108 | self.playing_pitch = next_pitch; 109 | next_pitch.map(note_on) 110 | } 111 | 112 | pub fn view(&self) -> View { 113 | View { 114 | phrase: self.phrase.view(), 115 | key: self.key, 116 | holding_pitch: self.holding_pitch, 117 | playing_pitch: self.playing_pitch, 118 | } 119 | } 120 | 121 | pub fn state(&self) -> State { 122 | State { 123 | holding_pitch: self.holding_pitch, 124 | playing_pitch: self.playing_pitch, 125 | pending_command: self.pending_command.clone(), 126 | } 127 | } 128 | 129 | pub fn set_state(&mut self, state: State) { 130 | self.holding_pitch = state.holding_pitch; 131 | self.playing_pitch = state.playing_pitch; 132 | self.pending_command = state.pending_command; 133 | } 134 | 135 | } 136 | 137 | fn note_on(pitch: Pitch) -> Command { 138 | Command::NoteOn(pitch, 1., id(pitch)) 139 | } 140 | 141 | fn note_off(pitch: Pitch) -> Command { 142 | Command::NoteOff(id(pitch)) 143 | } 144 | 145 | use std::fmt::{Debug, Formatter}; 146 | use crate::core::tools::arpeggiator::phrase::Phrase; 147 | 148 | impl Debug for Arpeggiator { 149 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 150 | write!(f, "key: {:?}, holding: {:?}, playing: {:?}, pending: {:?}", 151 | self.key, self.holding_pitch, self.playing_pitch, self.pending_command) 152 | } 153 | } -------------------------------------------------------------------------------- /src/core/tools/arpeggiator/phrase.rs: -------------------------------------------------------------------------------- 1 | use crate::util::range_map::CyclicRangeMap; 2 | use crate::core::music_theory::rhythm::{Note, NoteDuration}; 3 | use crate::core::tools::arpeggiator::builder; 4 | use crate::core::sheet_music::sheet_music::MeasurePosition; 5 | 6 | /// Range is valid between 0 and 1. TODO restrictive newtype 7 | /// Values outside this range will result in an empty `Vec`. 8 | #[derive(Clone, PartialEq, Default, Debug)] 9 | pub struct Phrase { 10 | map: CyclicRangeMap, 11 | } 12 | 13 | const MEASURE_DURATION: f64 = NoteDuration::Whole as u8 as f64; 14 | 15 | impl Phrase { 16 | 17 | pub fn from_specs(specs: builder::Specs) -> Phrase { 18 | Phrase::new(&builder::notes(specs)) 19 | } 20 | 21 | pub fn new(notes: &[Note]) -> Self { 22 | let pairs: Vec<(f64, Note)> = notes.iter() 23 | .scan(0., |progress, note| { 24 | let index = *progress; 25 | *progress += note.duration as u8 as f64 / MEASURE_DURATION; 26 | Some((index, *note)) 27 | }).collect(); 28 | let total_duration: f64 = notes.iter().map(|n| n.duration as u8 as f64).sum(); 29 | let total_measures: f64 = total_duration / MEASURE_DURATION; 30 | Phrase { map: CyclicRangeMap::new(pairs, total_measures) } 31 | } 32 | 33 | pub fn range(&self, from: f64, to: f64) -> Vec { 34 | self.map.range(from, to).into_iter().cloned().collect() 35 | } 36 | 37 | pub fn view(&self) -> View { 38 | View { 39 | notes: self.map.full_cycle().into_iter().cloned().collect(), 40 | length: self.map.end(), 41 | } 42 | 43 | } 44 | } 45 | 46 | #[derive(Clone, PartialEq, Default, Debug)] 47 | pub struct View { 48 | pub notes: Vec, 49 | pub length: MeasurePosition, 50 | } 51 | -------------------------------------------------------------------------------- /src/core/tools/loops.rs: -------------------------------------------------------------------------------- 1 | use crate::core::synth::Sample; 2 | use std::{collections::HashMap, mem}; 3 | 4 | #[derive(Clone, Copy)] 5 | pub enum Command { TogglePlayback(usize), ToggleRecording(usize) } 6 | 7 | #[derive(Default)] 8 | pub struct Manager { 9 | loops: HashMap, 10 | playing_loops: HashMap, 11 | recording_loop: Option, 12 | } 13 | 14 | #[derive(Clone, PartialEq, Default, Debug)] 15 | pub struct View { 16 | pub playing_loops: HashMap, 17 | pub recording_loop: Option, 18 | } 19 | 20 | impl Manager { 21 | 22 | pub fn interpret(&mut self, command: Command) { 23 | match command { 24 | Command::TogglePlayback(i) => self.toggle_playback(i), 25 | Command::ToggleRecording(i) => self.toggle_recording(i), 26 | } 27 | } 28 | 29 | fn toggle_recording(&mut self, index: usize) { 30 | if let Some(recorder) = mem::replace(&mut self.recording_loop, None) { 31 | let new_loop = recorder.stop_recording(); 32 | self.loops.insert(index, new_loop); 33 | } else { 34 | self.recording_loop = Some(Recorder::new(index)) 35 | } 36 | } 37 | 38 | fn toggle_playback(&mut self, index: usize) { 39 | if self.playing_loops.remove(&index).is_none() { 40 | if let Some(loop_to_play) = self.loops.get(&index) { 41 | self.playing_loops.insert(index, loop_to_play.start_playback()); 42 | } 43 | } 44 | } 45 | 46 | pub fn write(&mut self, sample: Sample) { 47 | if let Some(rec) = self.recording_loop.as_mut() { 48 | rec.write(sample) 49 | } 50 | } 51 | 52 | pub fn next_sample(&mut self) -> Sample { 53 | self.playing_loops.values_mut() 54 | .filter_map(|l| l.next()) 55 | .sum() 56 | } 57 | 58 | pub fn view(&self) -> View { 59 | View { 60 | playing_loops: self.loops.keys().map(|k| (*k, self.playing_loops.contains_key(k))).collect(), 61 | recording_loop: self.recording_loop.as_ref().map(|l| l.position), 62 | } 63 | } 64 | } 65 | 66 | struct Loop { 67 | samples: Vec 68 | } 69 | impl Loop { 70 | fn start_playback(&self) -> Playback { 71 | Playback::new(self.samples.to_vec()) 72 | } 73 | } 74 | 75 | struct Recorder { 76 | position: usize, 77 | samples: Vec, 78 | } 79 | impl Recorder { 80 | fn new(position: usize) -> Recorder { 81 | Recorder { position, samples: vec![] } 82 | } 83 | fn write(&mut self, sample: Sample) { 84 | self.samples.push(sample) 85 | } 86 | fn stop_recording(self) -> Loop { 87 | Loop { samples: self.samples } 88 | } 89 | } 90 | 91 | struct Playback { 92 | position: usize, 93 | samples: Vec, 94 | } 95 | impl Playback { 96 | fn new(samples: Vec) -> Playback { 97 | Playback { position: 0, samples } 98 | } 99 | } 100 | impl Iterator for Playback { 101 | type Item = Sample; 102 | fn next(&mut self) -> Option { 103 | let position = self.position; 104 | self.position = (self.position + 1) % self.samples.len(); 105 | self.samples.get(position).cloned() 106 | } 107 | } -------------------------------------------------------------------------------- /src/core/tools/mod.rs: -------------------------------------------------------------------------------- 1 | 2 | pub mod transposer; 3 | pub mod arpeggiator; 4 | pub mod tap_tempo; 5 | pub mod pulse; 6 | pub mod loops; 7 | 8 | pub type Millis = u64; 9 | -------------------------------------------------------------------------------- /src/core/tools/pulse.rs: -------------------------------------------------------------------------------- 1 | use std::time::{Duration, Instant}; 2 | use std::ops::Mul; 3 | use super::Millis; 4 | use crate::util; 5 | 6 | pub struct Pulse { 7 | pub period: Duration, 8 | latest: Instant, 9 | } 10 | 11 | #[derive(Clone, PartialEq, Default, Debug)] 12 | pub struct View { 13 | pub period: Duration, 14 | } 15 | 16 | impl Pulse { 17 | 18 | pub fn new_with_millis(period: Millis) -> Self { 19 | Pulse::new(Duration::from_millis(period)) 20 | } 21 | 22 | pub fn new(period: Duration) -> Self { 23 | Pulse{ period, latest: Instant::now() } 24 | } 25 | 26 | pub fn read(&mut self) -> Option { 27 | let elapsed = self.latest.elapsed(); 28 | let periods_passed = util::duration::div_duration(elapsed, self.period).floor() as u32; 29 | if periods_passed > 0 { 30 | let latest = self.latest + self.period.mul(periods_passed); 31 | let missed = (periods_passed - 1).max(0); 32 | let reading = PulseReading{ latest, missed }; 33 | self.latest = latest; 34 | Some(reading) 35 | } else { None } 36 | } 37 | 38 | pub fn with_period(&self, period: Duration) -> Self { 39 | Pulse { period, latest: self.latest } 40 | } 41 | 42 | pub fn view(&self) -> View { 43 | View { 44 | period: self.period 45 | } 46 | } 47 | 48 | } 49 | 50 | #[derive(PartialEq, Eq, Debug)] 51 | pub struct PulseReading { 52 | pub latest: Instant, 53 | pub missed: u32, 54 | } 55 | 56 | #[cfg(test)] 57 | mod tests { 58 | use super::*; 59 | use std::ops::Sub; 60 | 61 | #[test] 62 | fn read_too_early() { 63 | let mut pulse = Pulse { period: Duration::from_millis(1000), latest: Instant::now() }; 64 | assert_eq!(pulse.read(), None); 65 | } 66 | 67 | #[test] 68 | fn read_in_time() { 69 | let past_instant = Instant::now().sub(Duration::from_millis(1500)); 70 | let mut pulse = Pulse { period: Duration::from_millis(1000), latest: past_instant }; 71 | match pulse.read() { 72 | Some(PulseReading { latest, missed }) => { 73 | let elapsed = latest.elapsed().as_secs_f64(); 74 | assert!(elapsed > 0. && elapsed < 2000.); 75 | assert_eq!(missed, 0); 76 | }, 77 | None => panic!() 78 | } 79 | } 80 | 81 | #[test] 82 | fn read_too_late() { 83 | let past_instant = Instant::now().sub(Duration::from_millis(2500)); 84 | let mut pulse = Pulse { period: Duration::from_millis(1000), latest: past_instant }; 85 | match pulse.read() { 86 | Some(PulseReading { latest, missed }) => { 87 | let elapsed = latest.elapsed().as_secs_f64(); 88 | assert!(elapsed > 0. && elapsed < 2000.); 89 | assert_eq!(missed, 1); 90 | }, 91 | 92 | None => panic!() 93 | } 94 | } 95 | } -------------------------------------------------------------------------------- /src/core/tools/tap_tempo.rs: -------------------------------------------------------------------------------- 1 | use std::time::Instant; 2 | use super::Millis; 3 | 4 | #[derive(Clone, PartialEq, Default, Debug)] 5 | pub struct TapTempo { 6 | pub begin: Option, 7 | pub end: Option, 8 | } 9 | 10 | impl TapTempo { 11 | 12 | pub fn tap(&mut self) { 13 | let now = Instant::now(); 14 | match (self.begin, self.end) { 15 | (Some(begin), Some(end)) if begin < end && end < now => { 16 | self.begin = Some(end); 17 | self.end = Some(now); 18 | }, 19 | (Some(begin), _) if begin < now => 20 | self.end = Some(now), 21 | _ => 22 | self.begin = Some(now), 23 | } 24 | } 25 | 26 | pub fn read(&self) -> Option { 27 | match (self.begin, self.end) { 28 | (Some(begin), Some(end)) if begin < end => 29 | Some((end - begin).as_millis() as u64), 30 | _ => None, 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/core/tools/transposer.rs: -------------------------------------------------------------------------------- 1 | use crate::core::music_theory::{Semitones, pitch::Pitch, diatonic_scale::Key}; 2 | 3 | #[derive(Clone, Copy, Debug)] 4 | pub enum Command { 5 | TransposeKey(Semitones), 6 | ShiftPitch(Semitones), 7 | ShiftKeyboard(Semitones), 8 | } 9 | 10 | #[derive(Clone, PartialEq, Default, Debug)] 11 | pub struct State { 12 | pub input_key: Key, 13 | pub transposed_key: Key, 14 | pub pitch_shift: Semitones, 15 | } 16 | 17 | impl State { 18 | 19 | pub fn new(key: Key) -> State { 20 | State { 21 | input_key: key, 22 | transposed_key: key, 23 | pitch_shift: 0, 24 | } 25 | } 26 | 27 | pub fn interpret(&mut self, command: Command) { 28 | match command { 29 | Command::TransposeKey(n) => self.transposed_key = self.transposed_key.shift_fifths(n), 30 | Command::ShiftPitch(n) => self.pitch_shift += n, 31 | Command::ShiftKeyboard(n) => { 32 | self.transposed_key += n; 33 | self.pitch_shift -= n; 34 | } 35 | } 36 | } 37 | 38 | pub fn transpose(&self, pitch: Pitch) -> Pitch { 39 | let transposed = self.input_key.transpose_to(self.transposed_key, pitch) 40 | .unwrap_or_else(|| panic!("Failed to transpose {:?} from {} to {}", pitch, self.input_key, self.transposed_key)); 41 | transposed + self.pitch_shift 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /src/io/audio.rs: -------------------------------------------------------------------------------- 1 | use cpal; 2 | use self::cpal::{ 3 | UnknownTypeOutputBuffer::{F32, I16, U16}, 4 | StreamData::Output, 5 | OutputBuffer, Device, Format, EventLoop 6 | }; 7 | use std::sync::mpsc::Receiver; 8 | use crate::core::{music_theory::Hz, synth::Sample, tools::Millis}; 9 | 10 | const LATENCY: Millis = 250; 11 | 12 | pub struct Out { 13 | device: Device, 14 | format: Format, 15 | } 16 | 17 | impl Out { 18 | pub fn initialize() -> Result { 19 | match cpal::default_output_device() { 20 | Some(device) => 21 | device.default_output_format() 22 | .map_err(|e| format!("Failed to get default output format. {:?}", e)) 23 | .map(|format| Out { device, format }), 24 | None => Err("Failed to get default output device".to_string()), 25 | } 26 | } 27 | 28 | pub fn sample_rate(&self) -> Hz { 29 | Hz::from(self.format.sample_rate.0) 30 | } 31 | 32 | pub fn buffer_size(&self) -> usize { 33 | self.sample_rate() as usize / LATENCY as usize 34 | } 35 | 36 | pub fn start(&self, sound_in: Receiver) { 37 | start(&self.device, &self.format, sound_in) 38 | } 39 | } 40 | 41 | fn start(device: &Device, format: &Format, sound_in: Receiver) { 42 | let channels = format.channels as usize; 43 | let event_loop = EventLoop::new(); 44 | let stream_id = event_loop.build_output_stream(device, format).unwrap(); 45 | event_loop.play_stream(stream_id); 46 | 47 | event_loop.run(move |_, data| { 48 | match data { 49 | Output { buffer: F32(buffer) } => feed_buffer(buffer, &sound_in, channels), 50 | Output { buffer: I16(buffer) } => feed_buffer(buffer, &sound_in, channels), 51 | Output { buffer: U16(buffer) } => feed_buffer(buffer, &sound_in, channels), 52 | _ => panic!("Unexpected buffer type."), 53 | } 54 | }); 55 | } 56 | 57 | fn feed_buffer(mut buffer: OutputBuffer<'_, T>, sig_in: &Receiver, channels: usize) { 58 | for buff_chunks in buffer.chunks_mut(channels) { 59 | match sig_in.recv() { 60 | Ok(sample) => 61 | for out in buff_chunks.iter_mut() { 62 | *out = T::from_f64(sample); 63 | }, 64 | _ => { 65 | panic!("Sample channel hang up?"); 66 | } 67 | } 68 | } 69 | } 70 | 71 | trait SampleFromF64: cpal::Sample { 72 | fn from_f64(value: f64) -> Self; 73 | } 74 | impl SampleFromF64 for f32 { 75 | fn from_f64(value: f64) -> Self { 76 | value as f32 77 | } 78 | } 79 | impl SampleFromF64 for i16 { 80 | fn from_f64(value: f64) -> i16 { 81 | (value * f64::from(f64::MAX)) as i16 82 | } 83 | } 84 | impl SampleFromF64 for u16 { 85 | fn from_f64(value: f64) -> u16 { 86 | ((value * 0.5 + 0.5) * f64::from(u16::MAX)) as u16 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/io/midi/meta_events.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, mem, time::Duration}; 2 | use crate::core::{music_theory::{Modality, pitch_class::*}, sheet_music::sheet_music::*}; 3 | use crate::util; 4 | use super::rimd::{MetaCommand, MetaEvent}; 5 | 6 | 7 | #[derive(Debug)] 8 | pub enum Meta { 9 | TrackName(String), 10 | InstrumentName(String), 11 | TimeSignature { 12 | numerator: u8, //beats per measure 13 | denominator: u8, //quarter notes per beat 14 | metronome_period: u8, //MIDI ticks per metronome quarter. Default: 24 15 | rate_32ths: u8 //32th notes per MIDI quarter note. Default: 8 16 | }, 17 | KeySignature { sharps: i8, minor: bool }, 18 | TempoSetting(Tempo), // microseconds per quarter note 19 | EndOfTrack, 20 | } 21 | 22 | pub type ScheduledMeta = (Meta, Tick); 23 | 24 | pub fn decode_meta_event(msg: &MetaEvent) -> Option { 25 | match msg.command { 26 | MetaCommand::SequenceOrTrackName => 27 | String::from_utf8(msg.data.clone()).map(Meta::TrackName).ok(), 28 | MetaCommand::InstrumentName => 29 | String::from_utf8(msg.data.clone()).map(Meta::InstrumentName).ok(), 30 | MetaCommand::EndOfTrack => Some(Meta::EndOfTrack), 31 | MetaCommand::TempoSetting => decode_tempo_setting(msg.data.as_slice()), 32 | MetaCommand::TimeSignature => decode_time_signature(msg.data.as_slice()), 33 | MetaCommand::KeySignature => decode_key_signature(msg.data.as_slice()), 34 | other => { 35 | println!("MIDI: Ignored meta event: {:?}", other); 36 | None 37 | } 38 | } 39 | } 40 | 41 | fn decode_tempo_setting(data: &[u8]) -> Option { 42 | match data { 43 | [byte1, byte2, byte3] => { 44 | let array: [u8; 4] = [*byte3, *byte2, *byte1, 0]; 45 | let microsecs_per_quarternote: u32 = unsafe { mem::transmute(array) }; 46 | Some(Meta::TempoSetting(microsecs_per_quarternote)) 47 | } 48 | _ => { 49 | eprintln!("MIDI: Invalid meta event: TempoSignature, data={:?}", data); 50 | None 51 | } 52 | } 53 | } 54 | 55 | fn decode_key_signature(data: &[u8]) -> Option { 56 | match data { 57 | [byte1, byte2] => { 58 | let meta = Meta::KeySignature { 59 | sharps: unsafe { mem::transmute(*byte1) }, 60 | minor: *byte2 != 0, 61 | }; 62 | Some(meta) 63 | } 64 | _ => { 65 | eprintln!("MIDI: Invalid meta event: KeySignature, data={:?}", data); 66 | None 67 | } 68 | } 69 | } 70 | 71 | /// http://www.deluge.co/?q=midi-tempo-bpm 72 | fn decode_time_signature(data: &[u8]) -> Option { 73 | match data { 74 | [byte1, byte2, byte3, byte4] => { 75 | let denom_power: u8 = unsafe { mem::transmute(*byte2) }; 76 | let meta = Meta::TimeSignature { 77 | numerator: unsafe { mem::transmute(*byte1) }, 78 | denominator: 2_u8.pow(denom_power as u32), 79 | metronome_period: unsafe { mem::transmute(*byte3) }, 80 | rate_32ths: unsafe { mem::transmute(*byte4) }, 81 | }; 82 | Some(meta) 83 | } 84 | _ => { 85 | eprintln!("MIDI: Invalid meta event: TimeSignature, data={:?}", data); 86 | None 87 | } 88 | } 89 | } 90 | 91 | pub fn collect_meta_events(events: Vec, ticks_per_beat: u16) -> SheetMusic { 92 | let mut music = SheetMusic::default(); 93 | let mut changes_per_section: HashMap = HashMap::default(); 94 | for (meta, tick) in events.into_iter() { 95 | match meta { 96 | Meta::TrackName(name) => music.title = name, 97 | Meta::EndOfTrack => music.end = tick, 98 | other => { 99 | let changes = changes_per_section.entry(tick).or_insert_with(SectionChanges::default); 100 | changes.begin_tick = Some(tick); 101 | add_section_change(changes, other) 102 | }, 103 | } 104 | } 105 | 106 | music.sections = create_sections(changes_per_section, ticks_per_beat); 107 | music 108 | } 109 | 110 | fn add_section_change(section: &mut SectionChanges, event: Meta) { 111 | match event { 112 | Meta::KeySignature { sharps, minor } => { 113 | section.key = Some(PitchClass::C.shift_fifths(sharps)); 114 | section.modality = Some(if minor {Modality::MINOR} else {Modality::MAJOR}); 115 | }, 116 | Meta::TempoSetting(t) => 117 | section.beat_duration = Some(t), 118 | Meta::TimeSignature { numerator: n, .. } => 119 | section.beats_per_measure = Some(n), 120 | _ => (), 121 | } 122 | } 123 | 124 | fn create_sections(changes_per_section: HashMap, ticks_per_beat: u16) -> Vec
{ 125 | let mut result = vec!(); 126 | let mut begin_ticks: Vec = changes_per_section.keys().cloned().collect::>(); 127 | begin_ticks.sort_unstable(); 128 | for i in 0..changes_per_section.len() { 129 | let previous = result.last(); 130 | let current = begin_ticks.get(i) 131 | .and_then(|tick| changes_per_section.get(tick)) 132 | .and_then(|changes| changes.to_section(previous, ticks_per_beat)); 133 | if let Some(section) = current { 134 | result.push(section) 135 | } 136 | } 137 | result 138 | } 139 | 140 | /// Incremental changes on top of the previous Section 141 | #[derive(PartialEq, Eq)] 142 | struct SectionChanges { 143 | begin_tick: Option, 144 | begin_time: Option, 145 | key: Option, 146 | modality: Option, 147 | beat_duration: Option, 148 | beats_per_measure: Option, 149 | } 150 | impl SectionChanges { 151 | fn to_section(&self, previous: Option<&Section>, ticks_per_beat: u16) -> Option
{ 152 | let default = Section::default(); 153 | self.begin_tick.map(|begin_tick| { 154 | let time_since_previous = previous.map(|p| { 155 | let ticks_since_previous = begin_tick - p.begin_tick; 156 | p.tick_duration.mul_f64(ticks_since_previous as f64) 157 | }); 158 | let begin_time: Duration = time_since_previous 159 | .and_then(|time| previous.map(|p| p.begin_time + time)) 160 | .unwrap_or_else(Duration::default); 161 | let beat_duration = self.beat_duration.or_else(|| previous.map(|p| p.beat_duration)) 162 | .unwrap_or(default.beat_duration); 163 | let tick_duration = Duration::from_micros(u64::from(beat_duration) / u64::from(ticks_per_beat)); 164 | let beats_per_measure = self.beats_per_measure.or_else(|| previous.map(|p| p.beats_per_measure)) 165 | .unwrap_or(default.beats_per_measure); 166 | let begin_measure = time_since_previous 167 | .and_then(|time| previous.map(|p| calculate_section_begin_measure(time, p))) 168 | .unwrap_or( 0.); 169 | Section { 170 | begin_tick, begin_time, begin_measure, beat_duration, tick_duration, beats_per_measure, 171 | key: self.key.or_else(|| previous.map(|p| p.key)).unwrap_or(default.key), 172 | modality: self.modality.or_else(|| previous.map(|p| p.modality)).unwrap_or(default.modality), 173 | } 174 | }) 175 | } 176 | } 177 | impl Default for SectionChanges { 178 | fn default() -> Self { 179 | SectionChanges { 180 | begin_tick: None, 181 | begin_time: None, 182 | key: None, 183 | modality: None, 184 | beat_duration: None, 185 | beats_per_measure: None, 186 | } 187 | } 188 | } 189 | 190 | fn calculate_section_begin_measure(time_since_previous_section: Duration, previous_section: &Section) -> f64 { 191 | let beat_duration = Duration::from_micros(u64::from(previous_section.beat_duration)); 192 | let beats_since_previous = util::duration::div_duration(time_since_previous_section, beat_duration); 193 | let measures_since_previous = beats_since_previous / f64::from(previous_section.beats_per_measure); 194 | previous_section.begin_measure + measures_since_previous 195 | } 196 | -------------------------------------------------------------------------------- /src/io/midi/mod.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, path::Path}; 2 | 3 | use rimd; 4 | 5 | use crate::core::{ 6 | control::{synth::{Command, Command::*, id}}, 7 | music_theory::pitch::*, 8 | }; 9 | use crate::core::sheet_music::sheet_music::*; 10 | 11 | use self::meta_events::{collect_meta_events, decode_meta_event, ScheduledMeta}; 12 | use self::rimd::{Event as RimdEvent, MidiMessage, SMF, SMFError, 13 | Status, Track as RimdTrack, TrackEvent as RimdTrackEvent}; 14 | 15 | mod patch; 16 | mod meta_events; 17 | 18 | pub fn read_file(file_path: &str) -> Option { 19 | println!("MIDI: Reading file: {}", file_path); 20 | match SMF::from_file(Path::new(file_path)) { 21 | Ok(smf) => 22 | Some(decode_midi_file(&smf)) 23 | , 24 | Err(e) => { 25 | match e { 26 | SMFError::InvalidSMFFile(s) => println!("Invalid Midi file: {}", s), 27 | SMFError::Error(e) => println!("IO Error: {}", e), 28 | SMFError::MidiError(e) => println!("Midi Error: {}", e), 29 | SMFError::MetaError(_) => println!("Meta Error"), 30 | }; 31 | None 32 | } 33 | } 34 | } 35 | 36 | fn decode_midi_file(midi_file: &SMF) -> SheetMusic { 37 | assert!(midi_file.division > 0, "MIDI: Unsupported format. Header has negative division."); 38 | let ticks_per_beat: u16 = midi_file.division as u16; 39 | let music = SheetMusic { ticks_per_beat, ..Default::default() }; 40 | midi_file.tracks.iter() 41 | .map(|track| decode_track(track, ticks_per_beat)) 42 | .fold(music, merge_tracks) 43 | } 44 | 45 | fn merge_tracks(mut left: SheetMusic, mut right: SheetMusic) -> SheetMusic { 46 | let mut left_voices = std::mem::take(&mut left.voices); 47 | let mut right_voices = std::mem::take(&mut right.voices); 48 | left_voices.append(&mut right_voices); 49 | let default = SheetMusic::default(); 50 | SheetMusic { 51 | title: if left.title != default.title {left.title} else {right.title}, 52 | sections: if !is_default_sections(&left.sections) {left.sections} else {right.sections}, 53 | ticks_per_beat: if left.ticks_per_beat != default.ticks_per_beat {left.ticks_per_beat} else {right.ticks_per_beat}, 54 | end: if left.end > right.end {left.end} else {right.end}, 55 | voices: left_voices, 56 | } 57 | } 58 | fn is_default_sections(sections: &[Section]) -> bool { 59 | match sections { 60 | [section] => section.is_default(), 61 | _ => false 62 | } 63 | } 64 | 65 | fn decode_track(track: &RimdTrack, ticks_per_beat: u16) -> SheetMusic { 66 | let mixed_events: Vec = decode_events(track); 67 | let (commands_by_channel, meta_events) = organize_events(mixed_events); 68 | 69 | let mut music = collect_meta_events(meta_events, ticks_per_beat); 70 | music.voices = collect_note_events(commands_by_channel); 71 | music 72 | } 73 | 74 | fn collect_note_events(commands_by_channel: HashMap>) -> Vec { 75 | commands_by_channel.into_iter() 76 | .map(|(channel, events)| Voice::new(events, channel)) 77 | .collect() 78 | } 79 | 80 | fn organize_events(events: Vec) -> (HashMap>, Vec) { 81 | let mut commands_by_channel: HashMap> = HashMap::default(); 82 | let mut meta_events: Vec = Vec::default(); 83 | for event in events.into_iter() { 84 | match event { 85 | Event::Midi(cmd, channel) => 86 | commands_by_channel.entry(channel).or_insert_with(Vec::default).push(cmd), 87 | Event::Meta(meta) => 88 | meta_events.push(meta), 89 | } 90 | } 91 | (commands_by_channel, meta_events) 92 | } 93 | 94 | fn decode_events(track: &RimdTrack) -> Vec { 95 | let events = track.events.iter() 96 | .filter_map(decode_event) 97 | .collect(); 98 | accumulate_time(events) 99 | } 100 | 101 | fn accumulate_time(events: Vec) -> Vec { 102 | events.into_iter() 103 | .scan(0, |accumulated_time, event| match event { 104 | Event::Midi((cmd, time), channel) => { 105 | *accumulated_time += time; 106 | Some(Event::Midi((cmd, *accumulated_time), channel)) 107 | }, 108 | Event::Meta((cmd, time)) => { 109 | *accumulated_time += time; 110 | Some(Event::Meta((cmd, *accumulated_time))) 111 | } 112 | }).collect() 113 | } 114 | 115 | enum Event { 116 | Midi(ScheduledCommand, ChannelId), 117 | Meta(ScheduledMeta) 118 | } 119 | 120 | fn decode_event(event: &RimdTrackEvent) -> Option { 121 | match event.event { 122 | RimdEvent::Midi(ref message) => 123 | message.channel().and_then(|channel| 124 | decode_note_event(message) 125 | .map(|cmd| ((cmd, event.vtime), channel)) 126 | ).map(|(cmd, channel)|Event::Midi(cmd, channel)), 127 | RimdEvent::Meta(ref meta) => { 128 | decode_meta_event(meta).map(|meta| Event::Meta((meta, event.vtime))) 129 | }, 130 | 131 | } 132 | } 133 | 134 | fn decode_note_event(msg: &MidiMessage) -> Option { 135 | match msg.data.as_slice() { 136 | [_, pitch_byte, velocity_byte] => { 137 | let pitch = Pitch::from_index(*pitch_byte as usize); 138 | let velocity: f64 = *velocity_byte as f64 / u8::MAX as f64; 139 | let note_on = NoteOn(pitch, velocity, id(pitch)); 140 | let note_off = NoteOff(id(pitch)); 141 | match (msg.status(), *velocity_byte) { 142 | (Status::NoteOn, 0) => Some(note_off), 143 | (Status::NoteOn, _) => Some(note_on), 144 | (Status::NoteOff, _) => Some(note_off), 145 | _ => None, 146 | } 147 | } 148 | [_, byte] => { 149 | match msg.status() { 150 | Status::ProgramChange => patch::decode(*byte).map(SetPatch), 151 | _ => None, 152 | } 153 | } 154 | _ => None, 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/io/midi/patch.rs: -------------------------------------------------------------------------------- 1 | use num_traits::FromPrimitive; 2 | use crate::core::synth::instrument::Specs; 3 | use crate::preset; 4 | use self::PatchCategory::*; 5 | 6 | pub fn decode(program_byte: u8) -> Option { 7 | decode_category(program_byte) 8 | .map(patch_to_specs) 9 | } 10 | 11 | fn patch_to_specs(patch: Patch) -> Specs { 12 | match patch.category { 13 | SynthLead | Piano | Guitar | Bass | SynthEffects | Ensemble => preset::supersaw(), 14 | ChromaticPercussion => preset::pulse(), 15 | Organ | Reed | Pipe => preset::sine(), 16 | Strings | SynthPad => preset::saw_pad(), 17 | _ => preset::sine(), 18 | } 19 | } 20 | 21 | fn decode_category(byte: u8) -> Option { 22 | let group_index = byte / 8; 23 | FromPrimitive::from_u8(group_index) 24 | .map(|category| Patch { category, specific: byte % 8 }) 25 | } 26 | 27 | #[derive(Clone, Copy, Debug)] 28 | struct Patch { 29 | category: PatchCategory, 30 | specific: u8, 31 | } 32 | 33 | #[derive(Clone, Copy, FromPrimitive, Debug)] 34 | enum PatchCategory { 35 | Piano, 36 | ChromaticPercussion, 37 | Organ, 38 | Guitar, 39 | Bass, 40 | Strings, 41 | Ensemble, 42 | Reed, 43 | Pipe, 44 | SynthLead, 45 | SynthPad, 46 | SynthEffects, 47 | Ethnic, 48 | Percussive, 49 | SoundEffects, 50 | } 51 | -------------------------------------------------------------------------------- /src/io/mod.rs: -------------------------------------------------------------------------------- 1 | use std::sync::mpsc::{Sender, SyncSender, Receiver}; 2 | use std::sync::mpsc; 3 | use std::thread; 4 | 5 | use crate::core::{control::{tools, sheet_music}}; 6 | use crate::core::synth::Sample; 7 | use crate::io::audio::Out; 8 | 9 | pub mod midi; 10 | pub mod audio; 11 | 12 | pub fn start_audio() -> (SyncSender, f64){ 13 | let out = Out::initialize().unwrap_or_else(|e| panic!("Failed to initialize audio: {}", e)); 14 | let sample_rate = out.sample_rate(); 15 | let (sound_out, sound_in) = mpsc::sync_channel::(out.buffer_size()); 16 | thread::spawn(move || out.start(sound_in)); 17 | (sound_out, sample_rate) 18 | } 19 | 20 | pub fn start_manual() -> (Sender, Receiver) { 21 | let (sound_out, sample_rate) = start_audio(); 22 | let (command_out, command_in) = mpsc::channel::(); 23 | let (view_out, view_in) = mpsc::sync_channel::(1); 24 | thread::spawn(move || tools::start(sample_rate, command_in, sound_out, view_out)); 25 | (command_out, view_in) 26 | } 27 | 28 | pub fn start_midi(file_path: &str) { 29 | let (sound_out, sample_rate) = start_audio(); 30 | let music = midi::read_file(file_path) 31 | .unwrap_or_else(|| panic!("Failed to load MIDI file: [{}]", file_path)); 32 | thread::spawn(move || sheet_music::start(sample_rate, music, sound_out)); 33 | } 34 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] extern crate num_derive; 2 | 3 | pub mod io; 4 | pub mod core; 5 | pub mod preset; 6 | pub mod util; 7 | -------------------------------------------------------------------------------- /src/preset.rs: -------------------------------------------------------------------------------- 1 | use crate::core::{ 2 | music_theory::{ 3 | rhythm::{Note, NoteDuration::*}, 4 | diatonic_scale::{ScaleDegree::*, OctaveShift::*} 5 | }, 6 | synth::{builder::*, lfo, 7 | instrument::{self, ModTarget::*}, 8 | oscillator::{Basic::*, Specs::*, ModTarget::*}, 9 | filter::ModTarget::* 10 | }, 11 | control::tools::Patch, 12 | tools::arpeggiator::phrase::Phrase, 13 | }; 14 | 15 | 16 | pub fn instruments() -> Vec { 17 | vec!( 18 | supersaw(), 19 | pulse(), 20 | sine(), 21 | saw_pad(), 22 | ) 23 | } 24 | 25 | pub fn sequences() -> Vec { 26 | vec!( 27 | cyborg_chase(), 28 | topgear(), 29 | octaves(), 30 | ) 31 | } 32 | 33 | pub fn patches() -> Vec { 34 | vec!( 35 | Patch::Instrument(supersaw()), 36 | Patch::Instrument(pulse()), 37 | Patch::Instrument(sine()), 38 | Patch::Instrument(saw_pad()), 39 | Patch::Noop, 40 | Patch::Noop, 41 | Patch::ArpeggiatorPhrase(Some(cyborg_chase())), 42 | Patch::ArpeggiatorPhrase(Some(topgear())), 43 | Patch::ArpeggiatorPhrase(Some(octaves())), 44 | Patch::ArpeggiatorPhrase(None), 45 | ) 46 | } 47 | 48 | pub fn sine() -> instrument::Specs { 49 | Builder::osc(Basic(Sine)).mod_y(Volume).build() 50 | } 51 | 52 | pub fn pulse() -> instrument::Specs { 53 | Builder::osc(Pulse(0.5)).mod_y(Oscillator(PulseDuty)).build() 54 | } 55 | 56 | pub fn saw_pad() -> instrument::Specs { 57 | Builder::osc(Basic(Saw)).adsr(0.25, 0., 1., 0.25).build() 58 | } 59 | 60 | pub fn supersaw() -> instrument::Specs { 61 | Builder::osc(Mix { n_voices: 8, detune_amount: 3., specs: Saw, random_seed: 0 }) 62 | .lfo(lfo::Specs::simple(0.1), Filter(Cutoff), 0.8).build() 63 | } 64 | 65 | fn octaves() -> Phrase { 66 | Phrase::new(&[ 67 | Note::new(Eight, Down1, I1), 68 | Note::new(Eight, Same, I1), 69 | Note::new(Eight, Down1, I1), 70 | Note::new(Eight, Same, I1), 71 | Note::new(Eight, Down1, I1), 72 | Note::new(Eight, Same, I1), 73 | Note::new(Eight, Down1, I1), 74 | Note::new(Eight, Same, I1), 75 | ]) 76 | } 77 | 78 | 79 | fn topgear() -> Phrase { 80 | Phrase::new(&[ 81 | Note::new(Sixteenth, Down1, I1), 82 | Note::new(Sixteenth, Down1, I3), 83 | Note::new(Sixteenth, Down1, I5), 84 | Note::new(Sixteenth, Same, I1), 85 | Note::new(Sixteenth, Same, I3), 86 | Note::new(Sixteenth, Same, I5), 87 | Note::new(Sixteenth, Up1, I1), 88 | Note::new(Sixteenth, Up1, I3), 89 | Note::new(Sixteenth, Up1, I5), 90 | Note::new(Sixteenth, Up1, I3), 91 | Note::new(Sixteenth, Up1, I1), 92 | Note::new(Sixteenth, Same, I5), 93 | Note::new(Sixteenth, Same, I3), 94 | Note::new(Sixteenth, Same, I1), 95 | Note::new(Sixteenth, Down1, I5), 96 | Note::new(Sixteenth, Down1, I3), 97 | ]) 98 | } 99 | 100 | fn cyborg_chase() -> Phrase { 101 | Phrase::new(&[ 102 | Note::new(Sixteenth, Same, I6), 103 | Note::new(Sixteenth, Same, I6), 104 | Note::new(Sixteenth, Up1, I4), 105 | Note::new(Sixteenth, Up1, I3), 106 | Note::new(Sixteenth, Up1, I1), 107 | Note::new(Sixteenth, Same, I6), 108 | Note::new(Sixteenth, Same, I6), 109 | Note::new(Sixteenth, Same, I6), 110 | Note::new(Sixteenth, Up1, I1), 111 | Note::new(Sixteenth, Same, I6), 112 | Note::new(Sixteenth, Same, I6), 113 | Note::new(Sixteenth, Up1, I2), 114 | Note::new(Sixteenth, Same, I6), 115 | Note::new(Sixteenth, Same, I6), 116 | Note::new(Sixteenth, Up1, I2), 117 | Note::new(Sixteenth, Up1, I3), 118 | ]) 119 | } 120 | 121 | -------------------------------------------------------------------------------- /src/util/duration.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | //TODO replace with std Duration when stable 4 | 5 | pub fn div_duration(a: Duration, b: Duration) -> f64 { 6 | a.as_secs_f64() / b.as_secs_f64() 7 | } 8 | -------------------------------------------------------------------------------- /src/util/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod duration; 2 | pub mod reckless_float; 3 | pub mod range_map; -------------------------------------------------------------------------------- /src/util/range_map.rs: -------------------------------------------------------------------------------- 1 | use crate::util::reckless_float::RecklessFloat; 2 | use std::collections::BTreeMap; 3 | use std::collections::HashMap; 4 | 5 | /// Maps ranges of f64 to values. 6 | /// f64 keys in the map are cycled around when queried with range(from, to) 7 | /// e.g. With state [0.0, 0.5, end:1.0], range(2.0, 3.0) returns [0.0, 0.5] from a 2nd cycle. 8 | /// Same key can have multiple values. 9 | /// Keys can't be negative 10 | #[derive(Clone, PartialEq, Debug)] 11 | pub struct CyclicRangeMap { 12 | tree_map: BTreeMap>, 13 | end: f64 14 | } 15 | 16 | impl CyclicRangeMap { 17 | pub fn new(source: Vec<(f64, T)>, end: f64) -> Self { 18 | let map: BTreeMap> = source.into_iter() 19 | .map(|(k, v)| (RecklessFloat::from(k), v)) 20 | .fold(HashMap::>::default(), |mut map, (k, v)| { 21 | map.entry(k).or_insert_with(Vec::default).push(v); 22 | map 23 | }).into_iter().collect(); 24 | CyclicRangeMap { tree_map: map, end } 25 | } 26 | 27 | pub fn full_cycle(&self) -> Vec<&T> { 28 | self.range(0., self.end) 29 | } 30 | 31 | pub fn end(&self) -> f64 { 32 | self.end 33 | } 34 | 35 | pub fn range(&self, from: f64, to: f64) -> Vec<&T> { 36 | let full_cycles = ((to - from) / self.end).floor() as usize; 37 | let cycled_from = from % self.end; 38 | let cycled_to = to % self.end; 39 | 40 | if to < from { 41 | vec![] 42 | } else if full_cycles > 0 { 43 | let middle = self.tree_map.iter() 44 | .flat_map(|(_, v)| v).cycle().take(self.tree_map.len() * full_cycles); 45 | let end = self.tree_map 46 | .range(..RecklessFloat(cycled_to)) 47 | .flat_map(|(_, v)| v); 48 | let tail = middle.chain(end); 49 | if cycled_from > 0. { 50 | let begin = self.tree_map 51 | .range(RecklessFloat(cycled_from)..) 52 | .flat_map(|(_, v)| v); 53 | begin.chain(tail).collect() 54 | } else { 55 | tail.collect() 56 | } 57 | } else if cycled_to < cycled_from { 58 | let begin = self.tree_map 59 | .range(RecklessFloat(cycled_from)..) 60 | .flat_map(|(_, v)| v); 61 | let end = self.tree_map 62 | .range(..RecklessFloat(cycled_to)) 63 | .flat_map(|(_, v)| v); 64 | begin.chain(end).collect() 65 | } else { 66 | self.tree_map 67 | .range(RecklessFloat(cycled_from)..RecklessFloat(cycled_to)) 68 | .flat_map(|(_, v)| v).collect() 69 | } 70 | 71 | } 72 | 73 | pub fn into_tuples(self) -> Vec<(f64, T)> { 74 | self.tree_map.into_iter() 75 | .flat_map(|(k,list)| list.into_iter().map(move |item| (f64::from(k), item))) 76 | .collect() 77 | } 78 | } 79 | 80 | impl Default for CyclicRangeMap { 81 | fn default() -> Self { 82 | CyclicRangeMap { 83 | tree_map: BTreeMap::default(), 84 | end: 0. 85 | } 86 | } 87 | } 88 | 89 | 90 | #[cfg(test)] 91 | mod tests { 92 | use super::*; 93 | 94 | fn seq_basic() -> CyclicRangeMap { 95 | CyclicRangeMap::new(vec!( 96 | (0., "A".to_string()), 97 | (0.5, "B".to_string()), 98 | (0.75, "C".to_string()), 99 | ), 1.) 100 | } 101 | 102 | #[test] 103 | fn whole() { 104 | assert_eq!(seq_basic().range(0., 1.), vec!("A", "B", "C")) 105 | } 106 | 107 | #[test] 108 | fn beginning() { 109 | assert_eq!(seq_basic().range(0., 0.5), vec!("A")) 110 | } 111 | 112 | #[test] 113 | fn end() { 114 | assert_eq!(seq_basic().range(0.4, 1.), vec!("B", "C")) 115 | } 116 | 117 | #[test] 118 | fn middle() { 119 | assert_eq!(seq_basic().range(0.5, 0.6), vec!("B")) 120 | } 121 | 122 | #[test] 123 | fn crossing_cycle() { 124 | assert_eq!(seq_basic().range(0.9, 1.1), vec!("A")) 125 | } 126 | 127 | #[test] 128 | fn from_gt_to() { 129 | assert_eq!(seq_basic().range(0.9, 0.1), Vec::<&String>::default()) 130 | } 131 | 132 | #[test] 133 | fn second_cycle() { 134 | assert_eq!(seq_basic().range(1., 2.), vec!("A", "B", "C")) 135 | } 136 | 137 | #[test] 138 | fn multiple_cycles() { 139 | assert_eq!(seq_basic().range(-1., 2.), vec!("A", "B", "C", "A", "B", "C", "A", "B", "C")) 140 | } 141 | 142 | 143 | fn seq_overlapping() -> CyclicRangeMap { 144 | CyclicRangeMap::new(vec!( 145 | (0., "A".to_string()), 146 | (0., "B".to_string()), 147 | ), 1.) 148 | } 149 | 150 | #[test] 151 | fn overlapping() { 152 | assert_eq!(seq_overlapping().range(-1., 1.), vec!("A", "B")) 153 | } 154 | 155 | } -------------------------------------------------------------------------------- /src/util/reckless_float.rs: -------------------------------------------------------------------------------- 1 | use std::{hash, cmp::Ordering}; 2 | 3 | /// Compares f64's for equality by binary value, ignoring that there are 16 million different NaN values. 4 | /// Orders f64's considering NaN to be greater than everything, with undefined order between NaN's. 5 | /// 6 | /// https://stackoverflow.com/questions/39638363/how-can-i-use-a-hashmap-with-f64-as-key-in-rust 7 | /// https://stackoverflow.com/questions/28247990/how-to-do-a-binary-search-on-a-vec-of-floats 8 | #[derive(Copy, Clone, PartialOrd, Default, Debug)] 9 | pub struct RecklessFloat(pub f64); 10 | 11 | impl RecklessFloat { 12 | fn key(&self) -> u64 { 13 | self.0.to_bits() 14 | } 15 | } 16 | 17 | impl hash::Hash for RecklessFloat { 18 | fn hash(&self, state: &mut H) where H: hash::Hasher { 19 | self.key().hash(state) 20 | } 21 | } 22 | 23 | impl PartialEq for RecklessFloat { 24 | fn eq(&self, other: &RecklessFloat) -> bool { 25 | self.key() == other.key() 26 | } 27 | } 28 | 29 | impl Eq for RecklessFloat {} 30 | 31 | impl Ord for RecklessFloat { 32 | fn cmp(&self, other: &Self) -> Ordering { 33 | self.0.partial_cmp(&other.0) 34 | .unwrap_or_else(|| 35 | if self.0.is_nan() { 36 | Ordering::Greater 37 | } else { 38 | Ordering::Less 39 | } 40 | ) 41 | } 42 | } 43 | 44 | impl From for f64 { 45 | fn from(wrapped_value: RecklessFloat) -> Self { 46 | wrapped_value.0 47 | } 48 | } 49 | 50 | impl From<&RecklessFloat> for f64 { 51 | fn from(wrapped_value: &RecklessFloat) -> Self { 52 | wrapped_value.0 53 | } 54 | } 55 | 56 | impl From for RecklessFloat { 57 | fn from(value: f64) -> Self { 58 | RecklessFloat(value) 59 | } 60 | } 61 | 62 | impl From<&f64> for RecklessFloat { 63 | fn from(value: &f64) -> Self { 64 | RecklessFloat(*value) 65 | } 66 | } --------------------------------------------------------------------------------