synth.rs 8.54 KB
Newer Older
Aske Simon Christensen's avatar
Aske Simon Christensen committed
1
2
3

use std::collections::{HashMap, VecDeque};
use std::marker::PhantomData;
4
use std::mem::transmute;
5
use std::sync::RwLock;
Aske Simon Christensen's avatar
Aske Simon Christensen committed
6
7
8
9

use vst2::api::Supported;
use vst2::buffer::AudioBuffer;
use vst2::event::Event;
10
11
use vst2::host::Host;
use vst2::plugin::{CanDo, Category, HostCallback, Info, Plugin};
Aske Simon Christensen's avatar
Aske Simon Christensen committed
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70

use cache::SoundCache;
use generate::{Sample, SoundGenerator, SoundParameters};


#[allow(dead_code)]
pub enum MidiCommand {
	NoteOn      { channel: u8, key: u8, velocity: u8 },
	NoteOff     { channel: u8, key: u8, velocity: u8 },
	AllNotesOff { channel: u8,          velocity: u8 },
	AllSoundOff { channel: u8,          velocity: u8 },
	Unknown
}

impl MidiCommand {
	fn fromData(data: &[u8; 3]) -> MidiCommand {
		match data[0] & 0xF0 {
			0x80 => MidiCommand::NoteOff { channel: data[0] & 0x0F, key: data[1], velocity: data[2] },
			0x90 => MidiCommand::NoteOn  { channel: data[0] & 0x0F, key: data[1], velocity: data[2] },
			0xB0 => match data[1] {
				120 => MidiCommand::AllSoundOff { channel: data[0] & 0x0F, velocity: data[2] },
				123 => MidiCommand::AllNotesOff { channel: data[0] & 0x0F, velocity: data[2] },
				_   => MidiCommand::Unknown
			},
			_    => MidiCommand::Unknown
		}
	}
}

pub struct MidiEvent {
	time: usize,
	command: MidiCommand,
}

struct Note {
	time: usize,
	tone: u8,
	velocity: u8,
	attack: f32,
	release: f32,

	release_time: Option<usize>
}

impl Note {
	fn new(tone: u8, velocity: u8, attack: f32, release: f32) -> Note {
		Note {
			time: 0,
			tone: tone,
			velocity: velocity,
			attack: attack,
			release: release,

			release_time: None
		}
	}

	fn produce_sample<G: SoundGenerator>(&mut self, cache: &mut Vec<SoundCache<G>>, param: &G::Parameters, global: &G::Global) -> Sample {
		let sample = cache[self.tone as usize].get_sample(self.time, param, global);
71
		let amp = self.attack_amp().min(self.release_amp()) * (self.velocity as f32 / 127.0);
Aske Simon Christensen's avatar
Aske Simon Christensen committed
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
		self.time += 1;

		sample * amp
	}

	fn attack_amp(&self) -> f32 {
		(self.time as f32 * self.attack).min(1.0)
	}

	fn release_amp(&self) -> f32 {
		match self.release_time {
			None => 1.0,
			Some(t) => (1.0 - (self.time - t) as f32 * self.release).max(0.0)
		}
	}

	fn release(&mut self, _velocity: u8) {
		self.release_time = Some(self.time);
	}

92
	fn is_released(&self) -> bool {
Aske Simon Christensen's avatar
Aske Simon Christensen committed
93
94
95
		self.release_time.is_some()
	}

96
	fn is_alive(&self) -> bool {
Aske Simon Christensen's avatar
Aske Simon Christensen committed
97
98
99
100
101
102
103
104
105
106
		self.release_amp() > 0.0
	}
}


pub trait SynthInfo {
	fn get_info() -> Info;
}

pub struct SynthPlugin<G: SoundGenerator, S: SynthInfo> {
107
108
	host: Option<HostCallback>,

Aske Simon Christensen's avatar
Aske Simon Christensen committed
109
110
111
112
	sample_rate: f32,
	time: usize,
	notes: Vec<Note>,
	events: VecDeque<MidiEvent>,
113
	cache: RwLock<Vec<SoundCache<G>>>,
Aske Simon Christensen's avatar
Aske Simon Christensen committed
114
115
116
117

	sound_params: G::Parameters,
	param_names: Vec<&'static str>,
	param_values: Vec<f32>,
118
	param_map: RwLock<HashMap<&'static str, f32>>,
Aske Simon Christensen's avatar
Aske Simon Christensen committed
119
120
121
122
123
124

	global: G::Global,

	phantom: PhantomData<S>
}

125
126
127
128
129
130
131
132
fn make_param_map(param_names: &[&'static str], param_values: &[f32]) -> HashMap<&'static str, f32> {
	let mut param_map = HashMap::new();
	for (s, v) in param_names.iter().zip(param_values) {
		param_map.insert(*s, *v);
	}
	param_map
}

Aske Simon Christensen's avatar
Aske Simon Christensen committed
133
134
135
impl<G: SoundGenerator, S: SynthInfo> Default for SynthPlugin<G, S> {
	fn default() -> Self {
		let param_names = G::Parameters::names().to_vec();
136
137
138
139
		let param_values: Vec<f32> = param_names.iter().map(|s| G::Parameters::default_value(s)).collect();
		let param_map = make_param_map(&param_names, &param_values);

		let cache = (0..128).map(|tone| SoundCache::new(tone)).collect();
Aske Simon Christensen's avatar
Aske Simon Christensen committed
140
141
142
143

		let sample_rate = 44100.0;

		SynthPlugin {
144
145
			host: None,

Aske Simon Christensen's avatar
Aske Simon Christensen committed
146
147
148
149
			sample_rate: sample_rate,
			time: 0,
			notes: Vec::new(),
			events: VecDeque::new(),
150
			cache: RwLock::new(cache),
Aske Simon Christensen's avatar
Aske Simon Christensen committed
151
152
153
154

			sound_params: G::Parameters::build(&param_map, sample_rate),
			param_names: param_names,
			param_values: param_values,
155
			param_map: RwLock::new(param_map),
Aske Simon Christensen's avatar
Aske Simon Christensen committed
156
157
158
159
160
161
162
163
164

			global: G::Global::default(),

			phantom: PhantomData
		}
	}
}

impl<G: SoundGenerator, S: SynthInfo> Plugin for SynthPlugin<G, S> {
165
166
167
168
169
170
171
172
	fn new(host: HostCallback) -> SynthPlugin<G, S> {
		SynthPlugin {
			host: Some(host),

			.. SynthPlugin::default()
		}
	}

Aske Simon Christensen's avatar
Aske Simon Christensen committed
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
	fn get_info(&self) -> Info {
		Info {
			presets: 0,
			parameters: self.param_names.len() as i32,
			inputs: 0,
			outputs: 2,
			category: Category::Synth,
			f64_precision: false,

			.. S::get_info()
		}
	}

	fn can_do(&self, can_do: CanDo) -> Supported {
		match can_do {
			CanDo::ReceiveMidiEvent => Supported::Yes,
			CanDo::Offline          => Supported::Yes,
			_                       => Supported::No
		}
	}

	fn process_events(&mut self, events: Vec<Event>) {
		for e in events.iter() {
			match *e {
				Event::Midi { delta_frames, ref data, .. } => {
					self.events.push_back(MidiEvent {
						time: self.time + (delta_frames as usize),
						command: MidiCommand::fromData(data)
					});
				}
				_ => {}
			}
		}
	}

	fn process(&mut self, buffer: AudioBuffer<f32>) {
		let mut outputs = buffer.split().1;
		for i in 0..outputs[0].len() {
			while !self.events.is_empty() && self.events.front().unwrap().time == self.time {
				let event = self.events.pop_front().unwrap();
				self.handle_event(event);
			}
			let sample = self.produce_sample();
			outputs[0][i] = sample.left;
			outputs[1][i] = sample.right;
			self.time += 1;
		}
	}

	fn set_sample_rate(&mut self, rate: f32) {
		self.sample_rate = rate;
		self.build_sound_params();
	}

	fn get_parameter_name(&self, index: i32) -> String {
		self.param_names[index as usize].to_string()
	}

	fn get_parameter_text(&self, index: i32) -> String {
232
		let param_map: &HashMap<&'static str, f32> = &self.param_map.read().unwrap();
233
		self.sound_params.display(self.param_names[index as usize], param_map, self.sample_rate).0
Aske Simon Christensen's avatar
Aske Simon Christensen committed
234
235
236
	}

	fn get_parameter_label(&self, index: i32) -> String {
237
		let param_map: &HashMap<&'static str, f32> = &self.param_map.read().unwrap();
238
		self.sound_params.display(self.param_names[index as usize], param_map, self.sample_rate).1
Aske Simon Christensen's avatar
Aske Simon Christensen committed
239
240
241
242
243
244
245
246
	}

	fn get_parameter(&self, index: i32) -> f32 {
		self.param_values[index as usize]
	}

	fn set_parameter(&mut self, index: i32, value: f32) {
		self.param_values[index as usize] = value;
247
248
249
250
251
252
253
254
255
256

		if let Some(ref mut host) = self.host {
			for name in G::Parameters::influence(self.param_names[index as usize]) {
				if let Some(p) = self.param_names.iter().position(|n| *n == name) {
					self.param_values[p] = infinitesimal_change(self.param_values[p]).min(1.0);
					host.automate(p as i32, self.param_values[p]);
				}
			}
		}

257
		self.build_sound_params();
Aske Simon Christensen's avatar
Aske Simon Christensen committed
258
259
260
261
262
	}
}

impl<G: SoundGenerator, S: SynthInfo> SynthPlugin<G, S> {
	fn handle_event(&mut self, event: MidiEvent) {
263
		let param_map: &HashMap<&'static str, f32> = &self.param_map.read().unwrap();
Aske Simon Christensen's avatar
Aske Simon Christensen committed
264
265
		match event.command {
			MidiCommand::NoteOn { key, velocity, .. } => {
266
267
				let attack = G::Parameters::attack(param_map, self.sample_rate);
				let release = G::Parameters::release(param_map, self.sample_rate);
Aske Simon Christensen's avatar
Aske Simon Christensen committed
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
				let note = Note::new(key, velocity, attack, release);
				self.notes.push(note);
			},
			MidiCommand::NoteOff { key, velocity, .. } => {
				for note in &mut self.notes {
					if note.tone == key && !note.is_released() {
						note.release(velocity);
						break;
					}
				}
			},
			MidiCommand::AllNotesOff { velocity, .. } => {
				for note in &mut self.notes {
					if !note.is_released() {
						note.release(velocity);
					}
				}
			},
			MidiCommand::AllSoundOff { .. } => {
				self.notes.clear();
			},
			MidiCommand::Unknown => {}
		}
	}

	fn produce_sample(&mut self) -> Sample {
294
		let cache: &mut Vec<SoundCache<G>> = &mut self.cache.write().unwrap();
Aske Simon Christensen's avatar
Aske Simon Christensen committed
295
296
297
		let mut sample: Sample = Sample::from(0.0);
		for i in (0..self.notes.len()).rev() {
			if self.notes[i].is_alive() {
298
				sample += self.notes[i].produce_sample(cache, &self.sound_params, &self.global);
Aske Simon Christensen's avatar
Aske Simon Christensen committed
299
300
301
302
303
304
305
306
			} else {
				self.notes.remove(i);
			}
		}
		sample
	}

	fn build_sound_params(&mut self) {
307
308
309
		let param_map: &mut HashMap<&'static str, f32> = &mut self.param_map.write().unwrap();
		*param_map = make_param_map(&self.param_names, &self.param_values);
		let new_sound_params = G::Parameters::build(param_map, self.sample_rate);
Aske Simon Christensen's avatar
Aske Simon Christensen committed
310
		if new_sound_params != self.sound_params {
311
			let cache: &mut Vec<SoundCache<G>> = &mut self.cache.write().unwrap();
Aske Simon Christensen's avatar
Aske Simon Christensen committed
312
			self.sound_params = new_sound_params;
313
			for mut c in cache {
Aske Simon Christensen's avatar
Aske Simon Christensen committed
314
315
316
317
318
				c.invalidate();
			}
		}
	}
}
319
320
321
322
323
324

fn infinitesimal_change(value: f32) -> f32 {
	let mut bits = unsafe { transmute::<f32, u32>(value) };
	bits += 1;
	unsafe { transmute::<u32, f32>(bits) }
}