├── .gitignore ├── WebVersionScreenshot.png ├── src ├── transient.rs ├── lib.rs ├── turbulence.rs ├── math.rs ├── noise.rs ├── rng.rs ├── tract_shaper.rs ├── noise_gen.rs ├── trombone.rs ├── glottis.rs └── tract.rs ├── Cargo.toml ├── LICENSE ├── README.md └── examples └── pink-trombone.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /WebVersionScreenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lostmsu/pink-trombone/HEAD/WebVersionScreenshot.png -------------------------------------------------------------------------------- /src/transient.rs: -------------------------------------------------------------------------------- 1 | pub struct Transient { 2 | pub position: usize, 3 | pub start_time: f32, 4 | pub life_time: f32, 5 | pub strength: f64, 6 | pub exponent: f64, 7 | } -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | mod glottis; 2 | mod math; 3 | mod noise; 4 | mod noise_gen; 5 | mod rng; 6 | mod tract; 7 | mod tract_shaper; 8 | mod transient; 9 | mod trombone; 10 | mod turbulence; 11 | 12 | pub use noise::NoiseSource; 13 | pub use trombone::PinkTrombone; 14 | pub use turbulence::TurbulencePoint; 15 | -------------------------------------------------------------------------------- /src/turbulence.rs: -------------------------------------------------------------------------------- 1 | pub struct TurbulencePoint { 2 | pub diameter: f32, 3 | pub position: f32, 4 | pub start_time: f32, 5 | pub end_time: f32, 6 | } 7 | 8 | impl Default for TurbulencePoint { 9 | fn default() -> TurbulencePoint { 10 | TurbulencePoint { 11 | diameter: 0.0, 12 | position: 0.0, 13 | start_time: 0.0, 14 | end_time: f32::NAN, 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pink-trombone" 3 | version = "0.2.1" 4 | edition = "2018" 5 | readme = "README.md" 6 | authors = ["Neil Thapen", "Victor Nova "] 7 | description = "Vocal cords simulator" 8 | keywords = ["voice"] 9 | categories = ["multimedia::audio", "simulation"] 10 | license = "MIT" 11 | repository = "https://github.com/lostmsu/pink-trombone" 12 | 13 | [dependencies] 14 | rand = "0.8.4" 15 | 16 | [dev-dependencies] 17 | rodio = "0.14.0" 18 | -------------------------------------------------------------------------------- /src/math.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Add, Mul, Sub}; 2 | 3 | pub fn interpolate(i0: T, i1: T, v: T) -> T 4 | where 5 | T: Sub + Mul + Add + Copy, 6 | { 7 | i0 + v * (i1 - i0) 8 | } 9 | 10 | pub fn move_towards(current: f64, target: f64, amount_up: f64, amount_down: f64) -> f64 { 11 | if current < target { 12 | target.min(current + amount_up) 13 | } else { 14 | target.max(current - amount_down) 15 | } 16 | } 17 | 18 | pub fn sqr(x: T) -> T 19 | where 20 | T: Mul + Copy, 21 | { 22 | x * x 23 | } 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | Copyright 2017 Neil Thapen 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a 5 | copy of this software and associated documentation files (the "Software"), 6 | to deal in the Software without restriction, including without limitation 7 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | and/or sell copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pink Trombone 2 | 3 | [![Pink Trombone on crates.io](https://img.shields.io/crates/v/pink-trombone)](https://crates.io/crates/pink-trombone/) 4 | 5 | This is a revised version of the Pink Trombone speech 6 | synthesizer originally developed by Neil Thapen in 2017. 7 | The original source code was modularized and converted to TypeScript. 8 | Then the TypeScript code was converted to Rust. 9 | 10 | Pink Trombone uses two-dimensional 11 | [digital waveguide synthesis](https://en.wikipedia.org/wiki/Digital_waveguide_synthesis) 12 | to synthesize human speech sounds. 13 | 14 | **Online demo**: [chdh.github.io/pink-trombone-mod](https://chdh.github.io/pink-trombone-mod) 15 | 16 | Screenshot:
17 | ![Pink Trombone screenshot](WebVersionScreenshot.png) 18 | 19 | # Sample code 20 | 21 | You can easily connect Pink Trombone to any audio framework, that accepts 22 | `f32` inputs. [An example](examples/pink-trombone.rs) for 23 | [rodio](https://github.com/RustAudio/rodio). 24 | 25 | ## Bibliographic references cited by Neil Thapen 26 | 27 | - Julius O. Smith III, "Physical audio signal processing for virtual musical instruments and audio effects."
28 | https://ccrma.stanford.edu/~jos/pasp/ 29 | 30 | - Story, Brad H. "A parametric model of the vocal tract area function for vowel and consonant simulation."
31 | The Journal of the Acoustical Society of America 117.5 (2005): 3231-3254.
32 | http://sal.arizona.edu/sites/default/files/story_jasa2005.pdf 33 | 34 | - Lu, Hui-Ling, and J. O. Smith. "Glottal source modeling for singing voice synthesis."
35 | Proceedings of the 2000 International Computer Music Conference, 2000. 36 | 37 | - Mullen, Jack. Physical modelling of the vocal tract with the 2D digital waveguide mesh.
38 | PhD thesis, University of York, 2006.
39 | http://www-users.york.ac.uk/~dtm3/Download/JackThesis.pdf 40 | -------------------------------------------------------------------------------- /examples/pink-trombone.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Arc, Mutex}; 2 | 3 | use pink_trombone::{NoiseSource, PinkTrombone}; 4 | use rand::Rng; 5 | use rodio::{OutputStream, Source}; 6 | 7 | struct ThreadRng {} 8 | 9 | impl NoiseSource for ThreadRng { 10 | fn noise(&mut self) -> f64 { 11 | let mut rng = rand::thread_rng(); 12 | rng.gen() 13 | } 14 | } 15 | 16 | #[derive(Clone)] 17 | struct PinkTromboneSource { 18 | trombone: Arc>, 19 | buffer_pos: usize, 20 | buffer: [f32; 512], 21 | } 22 | 23 | impl PinkTromboneSource { 24 | pub fn new(trombone: PinkTrombone) -> PinkTromboneSource { 25 | let buffer = [0_f32; 512]; 26 | PinkTromboneSource { 27 | trombone: Arc::new(Mutex::new(trombone)), 28 | buffer_pos: buffer.len(), 29 | buffer, 30 | } 31 | } 32 | } 33 | 34 | impl Iterator for PinkTromboneSource { 35 | type Item = f32; 36 | fn next(&mut self) -> Option { 37 | if self.buffer_pos == self.buffer.len() { 38 | self.trombone.lock().unwrap().synthesize(&mut self.buffer); 39 | self.buffer_pos = 0; 40 | } 41 | let result = self.buffer[self.buffer_pos]; 42 | assert!(result.abs() <= 1.0); 43 | self.buffer_pos += 1; 44 | Some(result) 45 | } 46 | } 47 | 48 | impl Source for PinkTromboneSource { 49 | fn current_frame_len(&self) -> Option { 50 | Some(512) 51 | } 52 | 53 | fn channels(&self) -> u16 { 54 | 1 55 | } 56 | 57 | fn sample_rate(&self) -> u32 { 58 | self.trombone.lock().unwrap().sample_rate() 59 | } 60 | 61 | fn total_duration(&self) -> Option { 62 | None 63 | } 64 | } 65 | 66 | const SAMPLE_RATE: u32 = 48000; 67 | 68 | fn main() { 69 | let mut random = ThreadRng {}; 70 | let seed = rand::thread_rng().gen(); 71 | let trombone = PinkTrombone::new(SAMPLE_RATE, &mut random, seed); 72 | let source = PinkTromboneSource::new(trombone); 73 | 74 | let (_stream, stream_handle) = OutputStream::try_default().unwrap(); 75 | stream_handle.play_raw(source.clone()).unwrap(); 76 | 77 | for tone in 0..24 { 78 | { 79 | let mut src = source.trombone.lock().unwrap(); 80 | src.set_musical_note(tone as f32); 81 | } 82 | std::thread::sleep(std::time::Duration::from_millis(300)); 83 | } 84 | 85 | for tone in (0..23).rev() { 86 | { 87 | let mut src = source.trombone.lock().unwrap(); 88 | src.set_musical_note(tone as f32); 89 | } 90 | std::thread::sleep(std::time::Duration::from_millis(300)); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/noise.rs: -------------------------------------------------------------------------------- 1 | use std::f64::consts::PI; 2 | 3 | pub trait NoiseSource { 4 | fn noise(&mut self) -> T; 5 | } 6 | 7 | pub fn new_filtered_noise_source( 8 | f0: f64, 9 | q: f64, 10 | sample_rate: u32, 11 | loop_size: usize, 12 | rng: &mut dyn NoiseSource, 13 | ) -> Box f64 + Send + 'static> { 14 | let mut white_noise = new_looped_white_noise(loop_size, rng); 15 | let mut filter = new_bandpass_filter(f0, q, sample_rate); 16 | Box::new(move || filter.filter(white_noise.noise())) 17 | } 18 | 19 | fn new_looped_white_noise( 20 | loop_size: usize, 21 | rng: &mut dyn NoiseSource, 22 | ) -> impl NoiseSource { 23 | let noise = (0..loop_size).map(|_| 2.0 * rng.noise() - 1.0).collect(); 24 | LoopedNoiseBuffer { 25 | noise, 26 | current_index: 0, 27 | } 28 | } 29 | 30 | struct LoopedNoiseBuffer { 31 | noise: Vec, 32 | current_index: usize, 33 | } 34 | 35 | impl NoiseSource for LoopedNoiseBuffer { 36 | fn noise(&mut self) -> f64 { 37 | if self.current_index >= self.noise.len() { 38 | self.current_index = 0 39 | } 40 | let val = self.noise[self.current_index]; 41 | self.current_index += 1; 42 | val 43 | } 44 | } 45 | 46 | pub trait Filter { 47 | fn filter(&mut self, x: f64) -> f64; 48 | } 49 | 50 | pub fn new_bandpass_filter(f0: f64, q: f64, sample_rate: u32) -> impl Filter { 51 | let w0 = 2.0 * PI * f0 / sample_rate as f64; 52 | let alpha = w0.sin() / (2.0 * q); 53 | let b0 = alpha; 54 | let b1 = 0.0; 55 | let b2 = -alpha; 56 | let a0 = 1.0 + alpha; 57 | let a1 = -2.0 * w0.cos(); 58 | let a2 = 1.0 - alpha; 59 | BiquadIirFilter::new(b0, b1, b2, a0, a1, a2) 60 | } 61 | 62 | struct BiquadIirFilter { 63 | nb0: f64, 64 | nb1: f64, 65 | nb2: f64, 66 | na1: f64, 67 | na2: f64, 68 | x1: f64, 69 | x2: f64, 70 | y1: f64, 71 | y2: f64, 72 | } 73 | 74 | impl BiquadIirFilter { 75 | pub fn new(b0: f64, b1: f64, b2: f64, a0: f64, a1: f64, a2: f64) -> BiquadIirFilter { 76 | BiquadIirFilter { 77 | nb0: b0 / a0, 78 | nb1: b1 / a0, 79 | nb2: b2 / a0, 80 | na1: a1 / a0, 81 | na2: a2 / a0, 82 | x1: 0.0, 83 | x2: 0.0, 84 | y1: 0.0, 85 | y2: 0.0, 86 | } 87 | } 88 | } 89 | 90 | impl Filter for BiquadIirFilter { 91 | fn filter(&mut self, x: f64) -> f64 { 92 | let y = self.nb0 * x + self.nb1 * self.x1 + self.nb2 * self.x2 93 | - self.na1 * self.y1 94 | - self.na2 * self.y2; 95 | self.x2 = self.x1; 96 | self.x1 = x; 97 | self.y2 = self.y1; 98 | self.y1 = y; 99 | y 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/rng.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | pub mod xorshift { 3 | use crate::NoiseSource; 4 | 5 | // from https://gitlab.com/pomma89/troschuetz-random/-/blob/main/src/Troschuetz.Random/Generators/XorShift128Generator.cs 6 | struct BaseGenerator { 7 | bit_buffer: u32, 8 | bit_count: i32, 9 | seed: u32, 10 | } 11 | 12 | impl BaseGenerator { 13 | pub fn reset(&mut self, seed: u32) { 14 | self.bit_buffer = 0; 15 | self.bit_count = 0; 16 | self.seed = seed; 17 | } 18 | } 19 | 20 | pub struct XorShift128 { 21 | x: u64, 22 | y: u64, 23 | bytes_available: bool, 24 | base: BaseGenerator, 25 | } 26 | 27 | const SEED_X: u64 = 521288629 << 32; 28 | const SEED_Y: u64 = 362436069; 29 | 30 | impl XorShift128 { 31 | pub fn new(seed: u32) -> XorShift128 { 32 | let mut gen = XorShift128 { 33 | base: BaseGenerator { 34 | bit_buffer: 0, 35 | bit_count: 0, 36 | seed: seed, 37 | }, 38 | x: 0, 39 | y: 0, 40 | bytes_available: false, 41 | }; 42 | 43 | // base code 44 | gen.reset(seed); 45 | 46 | // this code 47 | gen 48 | } 49 | 50 | pub fn next_f64(&mut self) -> f64 { 51 | let mut tx = self.x; 52 | let ty = self.y; 53 | self.x = ty; 54 | tx ^= tx << 23; 55 | tx ^= tx >> 17; 56 | tx ^= ty ^ (ty >> 26); 57 | self.y = tx; 58 | self.bytes_available = false; 59 | 60 | let result = to_f64(tx.overflowing_add(ty).0); 61 | assert!(result >= 0.0 && result <= 1.0); 62 | result 63 | } 64 | 65 | pub fn next_u64(&mut self) -> u64 { 66 | let mut tx = self.x; 67 | let ty = self.y; 68 | self.x = ty; 69 | tx ^= tx << 23; 70 | tx ^= tx >> 17; 71 | tx ^= ty ^ (ty >> 26); 72 | self.y = tx; 73 | self.bytes_available = false; 74 | tx.overflowing_add(ty).0 75 | } 76 | 77 | pub fn reset(&mut self, seed: u32) { 78 | // base 79 | self.base.reset(seed); 80 | 81 | // this 82 | self.x = SEED_X + seed as u64; 83 | self.y = SEED_Y.overflowing_mul((seed as u64) << 32).0; 84 | self.bytes_available = false; 85 | 86 | self.next_u64(); 87 | } 88 | } 89 | 90 | impl NoiseSource for XorShift128 { 91 | fn noise(&mut self) -> f64 { 92 | self.next_f64() 93 | } 94 | } 95 | 96 | fn to_f64(mut value: u64) -> f64 { 97 | value = (value >> 12) | 0x3FF0000000000000; 98 | let res = unsafe { std::mem::transmute::(value) }; 99 | res - 1.0 100 | } 101 | 102 | mod tests { 103 | // Note this useful idiom: importing names from outer (for mod tests) scope. 104 | use super::*; 105 | 106 | #[test] 107 | fn reproducible() { 108 | let mut generator = XorShift128::new(9452); 109 | let vals: Vec = (0..461456).map(|_| generator.next_f64()).collect(); 110 | assert_eq!(format!("{:.10}", vals.last().unwrap()), "0.5612585810"); 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/tract_shaper.rs: -------------------------------------------------------------------------------- 1 | use std::f64::consts::PI; 2 | 3 | use crate::{math, tract::Tract, transient::Transient}; 4 | 5 | pub struct TractShaper { 6 | pub tract: Tract, 7 | velum_open_target: f32, 8 | velum_closed_target: f32, 9 | target_diameter: [f64; Tract::N], 10 | velum_target: f32, 11 | pub tongue_index: f64, 12 | pub tongue_diameter: f64, 13 | last_obstruction: i32, 14 | } 15 | 16 | const GRID_OFFSET: f64 = 1.7; 17 | const MOVEMENT_SPEED: f64 = 15.0; 18 | 19 | impl TractShaper { 20 | pub fn new(tract: Tract) -> TractShaper { 21 | let mut res = TractShaper { 22 | tract, 23 | velum_open_target: 0.4, 24 | velum_closed_target: 0.01, 25 | velum_target: 0.0, 26 | tongue_index: 12.9, 27 | tongue_diameter: 2.43, 28 | last_obstruction: -1, 29 | target_diameter: [0.0; Tract::N], 30 | }; 31 | res.shape_noise(true); 32 | res.tract.calculate_nose_reflections(); // (nose reflections are calculated only once, but with open velum) 33 | res.shape_noise(false); 34 | res.shape_main_tract(); 35 | res 36 | } 37 | 38 | fn shape_main_tract(&mut self) { 39 | for i in 0..Tract::N { 40 | let d = self.get_rest_diameter(i); 41 | self.tract.diameter[i] = d; 42 | self.target_diameter[i] = d; 43 | } 44 | } 45 | 46 | pub fn get_rest_diameter(&self, i: usize) -> f64 { 47 | if i < 7 { 48 | return 0.6; 49 | } 50 | if i < Tract::BLADE_START { 51 | return 1.1; 52 | } 53 | if i >= Tract::LIP_START { 54 | return 1.5; 55 | } 56 | 57 | let t = 1.1 * PI * (self.tongue_index - i as f64) 58 | / (Tract::TIP_START - Tract::BLADE_START) as f64; 59 | let fixed_tongue_diameter = 2.0 + (self.tongue_diameter - 2.0) / 1.5; 60 | let mut curve = (1.5 - fixed_tongue_diameter + GRID_OFFSET) * t.cos(); 61 | 62 | if i == Tract::BLADE_START - 2 || i == Tract::LIP_START - 1 { 63 | curve *= 0.8; 64 | } 65 | if i == Tract::BLADE_START || i == Tract::LIP_START - 2 { 66 | curve *= 0.94; 67 | } 68 | 69 | 1.5 - curve 70 | } 71 | 72 | pub fn adjust_tract_shape(&mut self, delta_time: f64) { 73 | let amount = delta_time * MOVEMENT_SPEED; 74 | let mut new_last_obstruction: i32 = -1; 75 | for i in 0..Tract::N { 76 | let diameter = self.tract.diameter[i]; 77 | let target_diameter = self.target_diameter[i]; 78 | if diameter <= 0.0 { 79 | new_last_obstruction = i as i32; 80 | } 81 | let slow_return = if i < Tract::NOSE_START { 82 | 0.6 83 | } else if i >= Tract::TIP_START { 84 | 1.0 85 | } else { 86 | 0.6 + 0.4 * (i - Tract::NOSE_START) as f64 87 | / (Tract::TIP_START - Tract::NOSE_START) as f64 88 | }; 89 | 90 | self.tract.diameter[i] = math::move_towards( 91 | diameter, 92 | target_diameter, 93 | slow_return * amount, 94 | 2.0 * amount, 95 | ); 96 | } 97 | 98 | if self.last_obstruction >= 0 99 | && new_last_obstruction < 0 100 | && self.tract.nose_diameter[0] < 0.223 101 | { 102 | self.add_transient(self.last_obstruction as usize); 103 | } 104 | self.last_obstruction = new_last_obstruction; 105 | self.tract.nose_diameter[0] = math::move_towards( 106 | self.tract.nose_diameter[0], 107 | self.velum_target as f64, 108 | amount * 0.25, 109 | amount * 0.1, 110 | ); 111 | } 112 | 113 | fn add_transient(&mut self, position: usize) { 114 | self.tract.transients.push(Transient { 115 | position, 116 | start_time: self.tract.time, 117 | life_time: 0.2, 118 | strength: 0.3, 119 | exponent: 200.0, 120 | }); 121 | } 122 | 123 | fn shape_noise(&mut self, velum_open: bool) { 124 | self.set_velum_open(velum_open); 125 | for i in 0..Tract::NOSE_LEN { 126 | let d = i as f64 * 2.0 / Tract::NOSE_LEN as f64; 127 | let mut diameter = if i == 0 { 128 | self.velum_target as f64 129 | } else if d < 1.0 { 130 | 0.4 + 1.6 * d 131 | } else { 132 | 0.5 + 1.5 * (2.0 - d) 133 | }; 134 | 135 | diameter = diameter.min(1.9); 136 | self.tract.nose_diameter[i] = diameter; 137 | } 138 | } 139 | 140 | pub fn set_velum_open(&mut self, velum_open: bool) { 141 | self.velum_target = if velum_open { 142 | self.velum_open_target 143 | } else { 144 | self.velum_closed_target 145 | }; 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/noise_gen.rs: -------------------------------------------------------------------------------- 1 | #[derive(Copy, Clone)] 2 | struct Grad { 3 | pub x: f32, pub y: f32, pub z: f32 4 | } 5 | 6 | impl Grad { 7 | pub fn dot2(&self, x: f32, y: f32) -> f32 { self.x * x + self.y * y } 8 | } 9 | 10 | const GRAD3: [Grad; 12] = [ 11 | Grad{ x: 1.0, y: 1.0, z: 0.0}, Grad{x: -1.0, y: 1.0, z: 0.0}, Grad{x: 1.0, y: -1.0, z: 0.0}, Grad{x: -1.0, y: -1.0, z: 0.0 }, 12 | Grad{ x: 1.0, y: 0.0, z: 1.0}, Grad{x: -1.0, y: 0.0, z: 1.0}, Grad{x: 1.0, y: 0.0, z: -1.0}, Grad{x:-1.0, y:0.0, z:-1.0}, 13 | Grad{ x: 0.0, y: 1.0, z: 1.0 }, Grad{ x: 0.0, y: -1.0, z: 1.0 }, Grad{ x: 0.0, y: 1.0, z: -1.0}, Grad{x:0.0, y:-1.0, z:-1.0}, 14 | ]; 15 | 16 | const P: [u8; 256] = [ 17 | 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 18 | 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 19 | 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 20 | 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 21 | 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 22 | 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 23 | 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 24 | 52, 217, 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 25 | 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, 26 | 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 27 | 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193, 28 | 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239, 107, 49, 192, 29 | 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 30 | 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180, 31 | ]; 32 | 33 | pub struct NoiseGenerator { 34 | grad_p: [Grad; 512], 35 | perm: [u8; 512], 36 | } 37 | 38 | impl NoiseGenerator { 39 | pub fn set_seed(&mut self, mut seed: u16) { 40 | if seed < 256 { 41 | seed |= seed << 8; 42 | } 43 | 44 | for i in 0..256 { 45 | let v = if (i & 1) == 1 { 46 | P[i] ^ (seed & 255) as u8 47 | } else { 48 | P[i] ^ ((seed >> 8) & 255) as u8 49 | }; 50 | self.perm[i + 256] = v; 51 | self.perm[i] = v; 52 | let new_grad = GRAD3[v as usize % 12]; 53 | self.grad_p[i + 256] = new_grad; 54 | self.grad_p[i] = new_grad; 55 | } 56 | } 57 | 58 | pub fn new(seed: u16) -> NoiseGenerator { 59 | let mut gen = NoiseGenerator { 60 | grad_p: [Grad{x:0.0, y: 0.0, z:0.0}; 512], 61 | perm: [0; 512], 62 | }; 63 | gen.set_seed(seed); 64 | gen 65 | } 66 | 67 | pub fn simplex(&mut self, x: f32) -> f32 { self.simplex2(x * 1.2, -x * 0.7) } 68 | 69 | fn simplex2(&mut self, xin: f32, yin: f32) -> f32 { 70 | // Skewing and unskewing factors for 2, 3, and 4 dimensions 71 | let f2: f32 = 0.5 * (3.0_f32.sqrt() - 1.0); 72 | let g2: f32 = (3.0 - 3.0_f32.sqrt()) / 6.0; 73 | 74 | // Skew the input space to determine which simplex cell we're in 75 | let s = (xin + yin) * f2; // Hairy factor for 2D 76 | let i = (xin + s).floor() as isize; 77 | let j = (yin + s).floor() as isize; 78 | let t = (i as f32 + j as f32) * g2; 79 | let x0 = xin - i as f32 + t; // The x,y distances from the cell origin, unskewed. 80 | let y0 = yin - j as f32 + t; 81 | // For the 2D case, the simplex shape is an equilateral triangle. 82 | // Determine which simplex we are in. 83 | let (i1, j1) = // Offsets for second (middle) corner of simplex in (i,j) coords 84 | if x0 > y0 { // lower triangle, XY order: (0,0)->(1,0)->(1,1) 85 | (1, 0) 86 | } else { // upper triangle, YX order: (0,0)->(0,1)->(1,1) 87 | (0, 1) 88 | }; 89 | // A step of (1,0) in (i,j) means a step of (1-c,-c) in (x,y), and 90 | // a step of (0,1) in (i,j) means a step of (-c,1-c) in (x,y), where 91 | // c = (3 - sqrt(3)) / 6 92 | let x1 = x0 - i1 as f32 + g2; // Offsets for middle corner in (x,y) unskewed coords 93 | let y1 = y0 - j1 as f32 + g2; 94 | let x2 = x0 - 1.0 + 2.0 * g2; // Offsets for last corner in (x,y) unskewed coords 95 | let y2 = y0 - 1.0 + 2.0 * g2; 96 | // Work out the hashed gradient indices of the three simplex corners 97 | let i_wrapped = i as u8 as usize; 98 | let j_wrapped = j as u8 as usize; 99 | let gi0 = self.grad_p[i_wrapped + self.perm[j_wrapped] as usize]; 100 | let gi1 = self.grad_p[i_wrapped + i1 + self.perm[j_wrapped + j1] as usize]; 101 | let gi2 = self.grad_p[i_wrapped + 1 + self.perm[j_wrapped + 1] as usize]; 102 | // Noise contributions from the three corners 103 | let mut t0 = 0.5 - x0 * x0 - y0 * y0; 104 | let n0 = if t0 < 0.0 { 105 | 0.0 106 | } else { 107 | t0 *= t0; 108 | t0 * t0 * gi0.dot2(x0, y0) // (x,y) of grad3 used for 2D gradient 109 | }; 110 | let mut t1 = 0.5 - x1 * x1 - y1 * y1; 111 | let n1 = if t1 < 0.0 { 112 | 0.0 113 | } else { 114 | t1 *= t1; 115 | t1 * t1 * gi1.dot2(x1, y1) 116 | }; 117 | let mut t2 = 0.5 - x2 * x2 - y2 * y2; 118 | let n2 = if t2 < 0.0 { 119 | 0.0 120 | } else { 121 | t2 *= t2; 122 | t2 * t2 * gi2.dot2(x2, y2) 123 | }; 124 | // Add contributions from each corner to get the final noise value. 125 | // The result is scaled to return values in the leterval [-1,1]. 126 | 70.0 * (n0 + n1 + n2) 127 | } 128 | } 129 | 130 | #[cfg(test)] 131 | mod tests { 132 | // Note this useful idiom: importing names from outer (for mod tests) scope. 133 | use super::*; 134 | 135 | #[test] 136 | fn reproducible() { 137 | let mut generator = NoiseGenerator::new(15122); 138 | let vals: Vec = (0..112341).map(|i| generator.simplex(i as f32)).collect(); 139 | assert_eq!(format!("{:.10}", vals.last().unwrap()), "0.8608906865"); 140 | } 141 | } -------------------------------------------------------------------------------- /src/trombone.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use crate::{ 4 | glottis::Glottis, noise::NoiseSource, tract::Tract, tract_shaper::TractShaper, 5 | turbulence::TurbulencePoint, 6 | }; 7 | 8 | pub struct PinkTrombone { 9 | shaper: TractShaper, 10 | sample_rate: u32, 11 | } 12 | 13 | impl PinkTrombone { 14 | pub fn new(sample_rate: u32, rng: &mut dyn NoiseSource, seed: u16) -> PinkTrombone { 15 | if sample_rate >= u32::MAX / 2 { 16 | panic!("sample_rate too large"); 17 | }; 18 | if sample_rate == 0 { 19 | panic!("sample_rate must not be 0"); 20 | } 21 | let glottis = Glottis::new(sample_rate, rng, seed); 22 | // tract runs at twice the sample rate 23 | let tract = Tract::new(glottis, 2 * sample_rate, rng); 24 | PinkTrombone { 25 | sample_rate, 26 | shaper: TractShaper::new(tract), 27 | } 28 | } 29 | 30 | const MAX_BLOCK_LEN: usize = 512; 31 | 32 | pub fn sample_rate(&self) -> u32 { 33 | self.sample_rate 34 | } 35 | 36 | /// How much time has already been generated. 37 | pub fn time(&self) -> Duration { 38 | Duration::from_secs_f32(self.tract().time) 39 | } 40 | 41 | // /// -1..+1 42 | //pub fn noise(self) -> f32 {} 43 | 44 | /// 0..1 45 | pub fn intensity(&self) -> f32 { 46 | self.glottis().intensity 47 | } 48 | pub fn set_intensity(&mut self, intensity: f32) { 49 | self.glottis_mut().intensity = intensity 50 | } 51 | 52 | /// 0..1 53 | pub fn loudness(&self) -> f32 { 54 | self.glottis().loudness 55 | } 56 | pub fn set_loudness(&mut self, loudness: f32) { 57 | self.glottis_mut().loudness = loudness 58 | } 59 | 60 | /// 0.. 61 | pub fn target_frequency(&self) -> f32 { 62 | self.glottis().target_frequency 63 | } 64 | pub fn set_target_frequency(&mut self, target_frequency: f32) { 65 | self.glottis_mut().target_frequency = target_frequency 66 | } 67 | 68 | /// 0..1 69 | pub fn target_tenseness(&self) -> f32 { 70 | self.glottis().target_tenseness 71 | } 72 | pub fn set_target_tenseness(&mut self, target_tenseness: f32) { 73 | self.glottis_mut().target_tenseness = target_tenseness 74 | } 75 | 76 | /// 0..44 (see also Tract::n) 77 | pub fn tongue_index(&self) -> f64 { 78 | self.shaper.tongue_index 79 | } 80 | pub fn set_tongue_index(&mut self, tongue_index: f64) { 81 | self.shaper.tongue_index = tongue_index 82 | } 83 | 84 | /// 0..3(?) 85 | pub fn tongue_diameter(&self) -> f64 { 86 | self.shaper.tongue_diameter 87 | } 88 | pub fn set_tongue_diameter(&mut self, tongue_diameter: f64) { 89 | self.shaper.tongue_diameter = tongue_diameter 90 | } 91 | 92 | /// 0.. 93 | pub fn vibrato_gain(&self) -> f32 { 94 | self.glottis().vibrato_amount 95 | } 96 | pub fn set_vibrato_gain(&mut self, vibrato_gain: f32) { 97 | self.glottis_mut().vibrato_amount = vibrato_gain 98 | } 99 | 100 | /// 0.. 101 | pub fn vibrato_frequency(&self) -> f32 { 102 | self.glottis().vibrato_frequency 103 | } 104 | pub fn set_vibrato_frequency(&mut self, vibrato_frequency: f32) { 105 | self.glottis_mut().vibrato_frequency = vibrato_frequency 106 | } 107 | 108 | pub fn vibrato_wobble(&self) -> bool { 109 | self.glottis().auto_wobble 110 | } 111 | pub fn set_vibrato_wobble(&mut self, vibrato_wobble: bool) { 112 | self.glottis_mut().auto_wobble = vibrato_wobble 113 | } 114 | 115 | pub fn set_velum_open(&mut self, velum_open: bool) { 116 | self.shaper.set_velum_open(velum_open); 117 | } 118 | 119 | pub fn turbulence_points(&mut self) -> &mut Vec { 120 | &mut self.tract_mut().turbulence_points 121 | } 122 | 123 | /// Set `target_frequency` to the specified musical note. 124 | /// # Arguments 125 | /// * `semitone` - semitone, based at A4. 126 | pub fn set_musical_note(&mut self, semitone: f32) { 127 | self.glottis_mut().set_musical_note(semitone) 128 | } 129 | 130 | pub fn synthesize(&mut self, buf: &mut [f32]) { 131 | let mut p = 0; 132 | while p < buf.len() { 133 | let block_len = (buf.len() - p).min(PinkTrombone::MAX_BLOCK_LEN); 134 | let block_buf = &mut buf[p..p + block_len]; 135 | self.synthesize_block(block_buf); 136 | p += block_len; 137 | } 138 | } 139 | 140 | pub fn reset(&mut self) { 141 | self.calculate_new_block_parameters(0.0); 142 | } 143 | 144 | fn synthesize_block(&mut self, buf: &mut [f32]) { 145 | let delta_time = buf.len() as f32 / self.sample_rate as f32; 146 | self.calculate_new_block_parameters(delta_time); 147 | for i in 0..buf.len() { 148 | let lambda1 = i as f64 / buf.len() as f64; 149 | let lambda2 = (i as f64 + 0.5) / buf.len() as f64; 150 | let glottal_output = self.glottis_mut().step(lambda1 as f32) as f64; 151 | let vocal1 = self.tract_mut().step(glottal_output, lambda1); 152 | let vocal2 = self.tract_mut().step(glottal_output, lambda2); 153 | buf[i] = (vocal1 + vocal2) * 0.125; 154 | } 155 | } 156 | 157 | fn calculate_new_block_parameters(&mut self, delta_time: f32) { 158 | self.glottis_mut().adjust_parameters(delta_time); 159 | self.shaper.adjust_tract_shape(delta_time as f64); 160 | self.tract_mut().calculate_new_block_parameters(); 161 | } 162 | 163 | fn tract(&self) -> &Tract { 164 | &self.shaper.tract 165 | } 166 | 167 | fn glottis(&self) -> &Glottis { 168 | &self.tract().glottis 169 | } 170 | 171 | fn glottis_mut(&mut self) -> &mut Glottis { 172 | &mut self.tract_mut().glottis 173 | } 174 | 175 | fn tract_mut(&mut self) -> &mut Tract { 176 | &mut self.shaper.tract 177 | } 178 | } 179 | 180 | #[cfg(test)] 181 | mod tests { 182 | // Note this useful idiom: importing names from outer (for mod tests) scope. 183 | use super::*; 184 | use crate::rng::xorshift; 185 | 186 | const SAMPLE_RATE: u32 = 48000; 187 | const SEED: u16 = 9452; 188 | 189 | #[test] 190 | fn reproducible() { 191 | let mut random = xorshift::XorShift128::new(SEED.into()); 192 | let mut trombone = PinkTrombone::new(SAMPLE_RATE, &mut random, SEED); 193 | let mut buffer = vec![0.0; SAMPLE_RATE as usize * 15]; 194 | trombone.synthesize(&mut buffer); 195 | assert_eq!(format!("{:.10}", buffer.last().unwrap()), "0.0385491103"); 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /src/glottis.rs: -------------------------------------------------------------------------------- 1 | use std::f32::consts::PI; 2 | 3 | use crate::{ 4 | math::interpolate, 5 | noise::{self, NoiseSource}, 6 | noise_gen::NoiseGenerator, 7 | }; 8 | 9 | pub struct Glottis { 10 | pub always_voice: bool, 11 | pub auto_wobble: bool, 12 | is_touched: bool, 13 | pub target_tenseness: f32, 14 | pub target_frequency: f32, 15 | pub vibrato_amount: f32, 16 | pub vibrato_frequency: f32, 17 | 18 | noise_generator: NoiseGenerator, 19 | 20 | sample_rate: u32, 21 | sample_count: u64, 22 | pub intensity: f32, 23 | pub loudness: f32, 24 | smooth_frequency: f32, 25 | time_in_waveform: f32, 26 | old_tenseness: f32, 27 | new_tenseness: f32, 28 | old_frequency: f32, 29 | new_frequency: f32, 30 | aspiration_noise_source: Box f64 + Send + 'static>, 31 | waveform_length: f32, 32 | 33 | // waveform state 34 | alpha: f32, 35 | e0: f32, 36 | epsilon: f32, 37 | shift: f32, 38 | delta: f32, 39 | te: f32, 40 | omega: f32, 41 | } 42 | 43 | impl Glottis { 44 | pub fn new(sample_rate: u32, rng: &mut dyn NoiseSource, seed: u16) -> Glottis { 45 | let mut glottis = Glottis { 46 | always_voice: true, 47 | auto_wobble: true, 48 | is_touched: false, 49 | target_tenseness: 0.6, 50 | target_frequency: 140.0, 51 | vibrato_amount: 0.005, 52 | vibrato_frequency: 6.0, 53 | 54 | noise_generator: NoiseGenerator::new(seed), 55 | 56 | sample_rate, 57 | 58 | sample_count: 0, 59 | intensity: 0.0, 60 | loudness: 1.0, 61 | smooth_frequency: 140.0, 62 | time_in_waveform: 0.0, 63 | old_tenseness: 0.6, 64 | new_tenseness: 0.6, 65 | old_frequency: 140.0, 66 | new_frequency: 140.0, 67 | 68 | aspiration_noise_source: noise::new_filtered_noise_source( 69 | 500.0, 70 | 0.5, 71 | sample_rate, 72 | 0x8000, 73 | rng, 74 | ), 75 | 76 | waveform_length: 0.0, 77 | 78 | // waveform state 79 | alpha: 0.0, 80 | e0: 0.0, 81 | epsilon: 0.0, 82 | shift: 0.0, 83 | delta: 0.0, 84 | te: 0.0, 85 | omega: 0.0, 86 | }; 87 | 88 | glottis.setup_waveform(0.0); 89 | 90 | glottis 91 | } 92 | 93 | pub fn set_musical_note(&mut self, semitone: f32) { 94 | const A4: f32 = 440.0; 95 | self.target_frequency = A4 * 2.0_f32.powf(semitone * (1.0 / 12.0)); 96 | } 97 | 98 | pub fn step(&mut self, lambda: f32) -> f32 { 99 | let time = self.sample_count as f32 / self.sample_rate as f32; 100 | 101 | if self.time_in_waveform > self.waveform_length { 102 | self.time_in_waveform -= self.waveform_length; 103 | self.setup_waveform(lambda); 104 | } 105 | 106 | let out1 = self.normalized_lf_waveform(self.time_in_waveform / self.waveform_length); 107 | let asp_noise = (self.aspiration_noise_source)() as f32; 108 | let aspiration1 = self.intensity 109 | * (1.0 - self.target_tenseness.sqrt()) 110 | * self.get_noise_modulator() 111 | * asp_noise; 112 | let aspiration2 = aspiration1 * (0.2 + 0.02 * self.noise_generator.simplex(time * 1.99)); 113 | let result = out1 + aspiration2; 114 | self.sample_count += 1; 115 | self.time_in_waveform += 1.0 / self.sample_rate as f32; 116 | result 117 | } 118 | 119 | pub fn get_noise_modulator(&self) -> f32 { 120 | let voiced = 121 | 0.1 + 0.2 * 0_f32.max((PI * 2.0 * self.time_in_waveform / self.waveform_length).sin()); 122 | self.target_tenseness * self.intensity * voiced 123 | + (1.0 - self.target_tenseness * self.intensity) * 0.3 124 | } 125 | 126 | pub fn adjust_parameters(&mut self, delta_time: f32) { 127 | let delta = delta_time * self.sample_rate as f32 / 512.0; 128 | let old_time = self.sample_count as f32 / self.sample_rate as f32; 129 | let new_time = old_time + delta_time; 130 | self.adjust_intensity(delta); 131 | self.calculate_new_frequency(new_time, delta); 132 | self.calculate_new_tenseness(new_time); 133 | } 134 | 135 | fn calculate_new_frequency(&mut self, time: f32, delta_time: f32) { 136 | if self.intensity == 0.0 { 137 | self.smooth_frequency = self.target_frequency; 138 | } else if self.target_frequency > self.smooth_frequency { 139 | self.smooth_frequency = self 140 | .target_frequency 141 | .min(self.smooth_frequency * (1.0 + 0.1 * delta_time)); 142 | } else if self.target_frequency < self.smooth_frequency { 143 | self.smooth_frequency = self 144 | .target_frequency 145 | .max(self.smooth_frequency / (1.0 + 0.1 * delta_time)); 146 | } 147 | 148 | self.old_frequency = self.new_frequency; 149 | self.new_frequency = 150 | (self.smooth_frequency * (1.0 + self.calculate_vibrato(time))).max(10.0); 151 | } 152 | 153 | fn calculate_new_tenseness(&mut self, time: f32) { 154 | self.old_tenseness = self.new_tenseness; 155 | self.new_tenseness = self.target_tenseness 156 | + 0.1 * self.noise_generator.simplex(time * 0.46) 157 | + 0.05 * self.noise_generator.simplex(time * 0.36); 158 | self.new_tenseness = self.new_tenseness.max(0.0); 159 | 160 | if !self.is_touched && self.always_voice { 161 | // attack 162 | self.new_tenseness += (3.0 - self.target_tenseness) * (1.0 - self.intensity); 163 | } 164 | } 165 | 166 | fn adjust_intensity(&mut self, delta: f32) { 167 | if self.is_touched || self.always_voice { 168 | self.intensity += 0.13 * delta; 169 | } else { 170 | self.intensity -= 0.05 * delta; 171 | } 172 | 173 | self.intensity = self.intensity.clamp(0.0, 1.0); 174 | } 175 | 176 | fn calculate_vibrato(&mut self, time: f32) -> f32 { 177 | let mut vibrato = self.vibrato_amount * (PI * 2.0 * time * self.vibrato_frequency).sin(); 178 | vibrato += 0.02 * self.noise_generator.simplex(time * 4.07); 179 | vibrato += 0.04 * self.noise_generator.simplex(time * 2.15); 180 | if self.auto_wobble { 181 | vibrato += 0.2 * self.noise_generator.simplex(time * 0.96); 182 | vibrato += 0.4 * self.noise_generator.simplex(time * 0.5); 183 | } 184 | vibrato 185 | } 186 | 187 | fn setup_waveform(&mut self, lambda: f32) { 188 | let frequency = interpolate(self.old_frequency, self.new_frequency, lambda); 189 | let tenseness = interpolate(self.old_tenseness, self.new_tenseness, lambda); 190 | self.waveform_length = 1.0 / frequency; 191 | self.loudness = tenseness.max(0.0).powf(0.25); 192 | 193 | let rd = (3.0 * (1.0 - tenseness)).clamp(0.5, 2.7); 194 | 195 | // normalized to time = 1, Ee = 1 196 | let ra = -0.01 + 0.048 * rd; 197 | 198 | let rk = 0.224 + 0.118 * rd; 199 | let rg = (rk / 4.0) * (0.5 + 1.2 * rk) / (0.11 * rd - ra * (0.5 + 1.2 * rk)); 200 | 201 | let ta = ra; 202 | let tp = 1.0 / (2.0 * rg); 203 | let te = tp + tp * rk; 204 | 205 | let epsilon = 1.0 / ta; 206 | let shift = (-epsilon * (1.0 - te)).exp(); 207 | let delta = 1.0 - shift; // divide by self to scale RHS 208 | 209 | let rhs_integral = ((1.0 / epsilon) * (shift - 1.0) + (1.0 - te) * shift) / delta; 210 | let total_lower_integral = rhs_integral - (te - tp) / 2.0; 211 | let total_upper_integral = -total_lower_integral; 212 | 213 | let omega = PI / tp; 214 | let s = (omega * te).sin(); 215 | 216 | // need E0*e^(alpha*Te)*s = -1 (to meet the return at -1) 217 | // and E0*e^(alpha*Tp/2) * Tp*2/pi = totalUpperIntegral 218 | // (our approximation of the integral up to Tp) 219 | // writing x for e^alpha, 220 | // have E0*x^Te*s = -1 and E0 * x^(Tp/2) * Tp*2/pi = totalUpperIntegral 221 | // dividing the second by the first, 222 | // letting y = x^(Tp/2 - Te), 223 | // y * Tp*2 / (pi*s) = -totalUpperIntegral; 224 | 225 | let y = -PI * s * total_upper_integral / (tp * 2.0); 226 | let z = y.ln(); 227 | let alpha = z / (tp / 2.0 - te); 228 | let e0 = -1.0 / (s * (alpha * te).exp()); 229 | 230 | self.alpha = alpha; 231 | self.e0 = e0; 232 | self.epsilon = epsilon; 233 | self.shift = shift; 234 | self.delta = delta; 235 | self.te = te; 236 | self.omega = omega; 237 | } 238 | 239 | fn normalized_lf_waveform(&self, t: f32) -> f32 { 240 | let output = if t > self.te { 241 | (-(-self.epsilon * (t - self.te)).exp() + self.shift) / self.delta 242 | } else { 243 | self.e0 * (self.alpha * t).exp() * (self.omega * t).sin() 244 | }; 245 | output * self.intensity * self.loudness 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /src/tract.rs: -------------------------------------------------------------------------------- 1 | use crate::glottis::Glottis; 2 | use crate::math::{interpolate, sqr}; 3 | use crate::noise::{self, NoiseSource}; 4 | use crate::transient::Transient; 5 | use crate::turbulence::TurbulencePoint; 6 | 7 | pub struct Tract { 8 | pub glottis: Glottis, 9 | sample_rate: u32, 10 | frication_noise_source: Box f64 + Send + 'static>, 11 | 12 | sample_count: usize, 13 | pub time: f32, 14 | 15 | left: [f64; Tract::N], 16 | right: [f64; Tract::N], 17 | reflection: [f64; Tract::N], 18 | new_reflection: [f64; Tract::N], 19 | junction_output_right: [f64; Tract::N], 20 | justion_output_left: [f64; Tract::N + 1], 21 | max_amplitude: [f64; Tract::N], 22 | /// vocal tract cell diameters 23 | pub diameter: [f64; Tract::N], 24 | 25 | pub transients: Vec, 26 | pub turbulence_points: Vec, 27 | 28 | nose_right: [f64; NOSE_LEN], 29 | nose_left: [f64; NOSE_LEN], 30 | nose_junction_output_right: [f64; NOSE_LEN], 31 | nose_junction_output_left: [f64; NOSE_LEN + 1], 32 | nose_reflection: [f64; NOSE_LEN], 33 | pub nose_diameter: [f64; NOSE_LEN], 34 | nose_max_amplitude: [f64; NOSE_LEN], 35 | 36 | reflection_left: f64, 37 | reflection_right: f64, 38 | new_reflection_left: f64, 39 | new_reflection_right: f64, 40 | reflection_nose: f64, 41 | new_reflection_nose: f64, 42 | } 43 | 44 | const N: usize = Tract::N; 45 | 46 | const GLOTTAL_REFLECTION: f64 = 0.75; 47 | const LIP_REFLECTION: f64 = -0.85; 48 | 49 | const NOSE_LEN: usize = 28; 50 | const NOSE_START: usize = N - NOSE_LEN + 1; 51 | 52 | impl Tract { 53 | pub const N: usize = 44; 54 | pub const BLADE_START: usize = 10; 55 | pub const TIP_START: usize = 32; 56 | pub const LIP_START: usize = 39; 57 | pub const NOSE_START: usize = NOSE_START; 58 | pub const NOSE_LEN: usize = NOSE_LEN; 59 | 60 | pub fn new(glottis: Glottis, sample_rate: u32, rng: &mut dyn NoiseSource) -> Tract { 61 | if sample_rate == 0 { 62 | panic!("sample_rate must be > 0") 63 | }; 64 | 65 | Tract { 66 | glottis, 67 | sample_rate, 68 | frication_noise_source: noise::new_filtered_noise_source( 69 | 1000.0, 70 | 0.5, 71 | sample_rate, 72 | 0x8000, 73 | rng, 74 | ), 75 | 76 | transients: Vec::new(), 77 | turbulence_points: Vec::new(), 78 | 79 | sample_count: 0, 80 | time: 0.0, 81 | left: [0.0; Tract::N], 82 | right: [0.0; Tract::N], 83 | reflection: [0.0; Tract::N], 84 | new_reflection: [0.0; Tract::N], 85 | junction_output_right: [0.0; Tract::N], 86 | justion_output_left: [0.0; Tract::N + 1], 87 | max_amplitude: [0.0; Tract::N], 88 | diameter: [0.0; Tract::N], 89 | 90 | nose_right: [0.0; NOSE_LEN], 91 | nose_left: [0.0; NOSE_LEN], 92 | nose_junction_output_right: [0.0; NOSE_LEN], 93 | nose_junction_output_left: [0.0; NOSE_LEN + 1], 94 | nose_reflection: [0.0; NOSE_LEN], 95 | nose_diameter: [0.0; NOSE_LEN], 96 | nose_max_amplitude: [0.0; NOSE_LEN], 97 | reflection_left: 0.0, 98 | reflection_right: 0.0, 99 | new_reflection_left: 0.0, 100 | new_reflection_right: 0.0, 101 | reflection_nose: 0.0, 102 | new_reflection_nose: 0.0, 103 | } 104 | } 105 | 106 | pub fn calculate_nose_reflections(&mut self) { 107 | let mut a = [0.0; NOSE_LEN]; 108 | for i in 0..NOSE_LEN { 109 | a[i] = 1e-6_f64.max(sqr(self.nose_diameter[i])); 110 | } 111 | for i in 1..NOSE_LEN { 112 | self.nose_reflection[i] = assert_volume((a[i - 1] - a[i]) / (a[i - 1] + a[i])); 113 | } 114 | } 115 | 116 | pub fn calculate_new_block_parameters(&mut self) { 117 | self.calculate_main_tract_reflections(); 118 | self.calculate_nose_junction_reflections(); 119 | } 120 | 121 | fn calculate_main_tract_reflections(&mut self) { 122 | let mut a = [0.0; Tract::N]; 123 | for i in 0..Tract::N { 124 | a[i] = sqr(self.diameter[i]); 125 | } 126 | for i in 1..Tract::N { 127 | self.reflection[i] = self.new_reflection[i]; 128 | let sum = a[i - 1] + a[i]; 129 | self.new_reflection[i] = if sum.abs() > 1e-6 { 130 | (a[i - 1] - a[i]) / sum 131 | } else { 132 | 1.0 133 | }; 134 | } 135 | } 136 | 137 | fn calculate_nose_junction_reflections(&mut self) { 138 | self.reflection_left = self.new_reflection_left; 139 | self.reflection_right = self.new_reflection_right; 140 | self.reflection_nose = self.new_reflection_nose; 141 | 142 | let velum_a = sqr(self.nose_diameter[0]); 143 | let an0 = sqr(self.diameter[NOSE_START]); 144 | let an1 = sqr(self.diameter[NOSE_START + 1]); 145 | let sum = an0 + an1 + velum_a; 146 | 147 | if sum.abs() > 1e-6 { 148 | self.new_reflection_left = (2.0 * an0 - sum) / sum; 149 | self.new_reflection_right = (2.0 * an1 - sum) / sum; 150 | self.new_reflection_nose = (2.0 * velum_a - sum) / sum; 151 | } else { 152 | self.new_reflection_left = 1.0; 153 | self.new_reflection_right = 1.0; 154 | self.new_reflection_nose = 1.0; 155 | } 156 | } 157 | 158 | pub fn step(&mut self, glottal_output: f64, lambda: f64) -> f32 { 159 | // mouth 160 | self.process_transients(); 161 | self.add_turbulence_noise(); 162 | 163 | // self.glottalReflection = -0.8 + 1.6 * self.glottis.newTenseness; 164 | self.junction_output_right[0] = self.left[0] * GLOTTAL_REFLECTION + glottal_output; 165 | self.justion_output_left[N] = self.right[N - 1] * LIP_REFLECTION; 166 | 167 | for i in 1..N { 168 | let r = interpolate(self.reflection[i], self.new_reflection[i], lambda); 169 | let w = r * (self.right[i - 1] + self.left[i]); 170 | self.junction_output_right[i] = assert_volume(self.right[i - 1] - w); 171 | self.justion_output_left[i] = assert_volume(self.left[i] + w); 172 | } 173 | 174 | // now at junction with nose 175 | let i = NOSE_START; 176 | let r = interpolate(self.reflection_left, self.new_reflection_left, lambda); 177 | self.justion_output_left[i] = 178 | assert_volume(r * self.right[i - 1] + (1.0 + r) * (self.nose_left[0] + self.left[i])); 179 | let r = interpolate(self.reflection_right, self.new_reflection_right, lambda); 180 | self.junction_output_right[i] = 181 | assert_volume(r * self.left[i] + (1.0 + r) * (self.right[i - 1] + self.nose_left[0])); 182 | let r = interpolate(self.reflection_nose, self.new_reflection_nose, lambda); 183 | self.nose_junction_output_right[0] = 184 | assert_volume(r * self.nose_left[0] + (1.0 + r) * (self.left[i] + self.right[i - 1])); 185 | 186 | for i in 0..N { 187 | let right = self.junction_output_right[i] * 0.999; 188 | let left = self.justion_output_left[i + 1] * 0.999; 189 | 190 | self.right[i] = right; 191 | self.left[i] = left; 192 | 193 | let amplitude = (right + left).abs(); 194 | 195 | self.max_amplitude[i] *= 0.9999; 196 | self.max_amplitude[i] = self.max_amplitude[i].max(amplitude); 197 | } 198 | 199 | let lip_output = self.right[N - 1]; 200 | 201 | // nose 202 | self.nose_junction_output_left[NOSE_LEN] = self.nose_right[NOSE_LEN - 1] * LIP_REFLECTION; 203 | 204 | for i in 1..NOSE_LEN { 205 | let w = self.nose_reflection[i] * (self.nose_right[i - 1] + self.nose_left[i]); 206 | self.nose_junction_output_right[i] = assert_volume(self.nose_right[i - 1] - w); 207 | self.nose_junction_output_left[i] = assert_volume(self.nose_left[i] + w); 208 | } 209 | 210 | for i in 0..NOSE_LEN { 211 | let right = self.nose_junction_output_right[i]; 212 | let left = self.nose_junction_output_left[i + 1]; 213 | self.nose_right[i] = right; 214 | self.nose_left[i] = left; 215 | let amplitude = (right + left).abs(); 216 | self.nose_max_amplitude[i] *= 0.9999; 217 | self.nose_max_amplitude[i] = self.nose_max_amplitude[i].max(amplitude); 218 | } 219 | 220 | let nose_output = self.nose_right[NOSE_LEN - 1]; 221 | 222 | self.sample_count += 1; 223 | self.time = self.sample_count as f32 / self.sample_rate as f32; 224 | 225 | (lip_output + nose_output) as f32 226 | } 227 | 228 | fn process_transients(&mut self) { 229 | for i in (0..self.transients.len()).rev() { 230 | let trans = &self.transients[i]; 231 | 232 | let time_alive = self.time - trans.start_time; 233 | if time_alive > trans.life_time { 234 | self.transients.remove(i); 235 | continue; 236 | } 237 | let amplitude = trans.strength * 2.0_f64.powf(-trans.exponent * time_alive as f64); 238 | 239 | self.right[trans.position] += amplitude * 0.5; 240 | self.left[trans.position] += amplitude * 0.5; 241 | } 242 | } 243 | 244 | fn add_turbulence_noise(&mut self) { 245 | const FRICATIVE_ATTACK_TIME: f32 = 0.1; // seconds 246 | 247 | let mut turbulence_noises = Vec::<(f64, f64, f64)>::new(); 248 | 249 | for p in &self.turbulence_points { 250 | if p.position < 2.0 || p.position > N as f32 { 251 | continue; 252 | } 253 | if p.diameter <= 0.0 { 254 | continue; 255 | } 256 | 257 | let intensity = if f32::is_nan(p.end_time) { 258 | (self.time - p.start_time) / FRICATIVE_ATTACK_TIME 259 | } else { 260 | 1.0 - (self.time - p.end_time) / FRICATIVE_ATTACK_TIME 261 | } 262 | .clamp(0.0, 1.0); 263 | 264 | if intensity <= 0.0 { 265 | continue; 266 | } 267 | 268 | let turbulence_noise = 0.66 269 | * (self.frication_noise_source)() 270 | * intensity as f64 271 | * self.glottis.get_noise_modulator() as f64; 272 | 273 | turbulence_noises.push((turbulence_noise, p.position as f64, p.diameter as f64)); 274 | } 275 | 276 | for (noise, pos, diameter) in turbulence_noises.into_iter() { 277 | self.add_turbulence_noise_at_position(noise, pos, diameter); 278 | } 279 | } 280 | 281 | fn add_turbulence_noise_at_position( 282 | &mut self, 283 | turbulence_noise: f64, 284 | position: f64, 285 | diameter: f64, 286 | ) { 287 | let i = position.floor() as i64; 288 | let delta = position - i as f64; 289 | let thinnes0 = (8.0 * (0.7 - diameter)).clamp(0.0, 1.0); 290 | let openness = (30.0 * (diameter - 0.3)).clamp(0.0, 1.0); 291 | let noise0 = turbulence_noise * (1.0 - delta) * thinnes0 * openness; 292 | let noise1 = turbulence_noise * delta * thinnes0 * openness; 293 | if i + 1 < N as i64 { 294 | let idx = (i + 1) as usize; 295 | self.right[idx] += noise0 * 0.5; 296 | self.left[idx] += noise0 * 0.5; 297 | } 298 | if i + 2 < N as i64 { 299 | let idx = (i + 2) as usize; 300 | self.right[idx] += noise1 * 0.5; 301 | self.left[idx] += noise1 * 0.5; 302 | } 303 | } 304 | } 305 | 306 | fn assert_volume(val: f64) -> f64 { 307 | //assert!(val.abs() <= 1.0); 308 | val 309 | } 310 | --------------------------------------------------------------------------------