devalang_core/core/audio/engine/
driver.rs1use devalang_types::Value;
2use std::collections::HashMap;
3
4#[derive(Debug, Clone, PartialEq)]
6pub struct MidiNoteEvent {
7 pub key: u8,
9 pub vel: u8,
11 pub start_ms: u32,
13 pub duration_ms: u32,
15 pub channel: u8,
17}
18
19pub const SAMPLE_RATE: u32 = 44100;
21pub const CHANNELS: u16 = 2;
22
23#[derive(Debug, Clone, PartialEq)]
31pub struct AudioEngine {
32 pub volume: f32,
34 pub buffer: Vec<i16>,
36 pub midi_events: Vec<MidiNoteEvent>,
38 pub last_notes: std::collections::HashMap<String, Vec<(usize, usize)>>,
40 pub module_name: String,
42 pub note_count: usize,
44 pub sample_rate: u32,
46 pub channels: u16,
48}
49
50impl AudioEngine {
51 pub fn new(module_name: String) -> Self {
52 AudioEngine {
53 volume: 1.0,
54 buffer: vec![],
55 midi_events: Vec::new(),
56 module_name,
57 note_count: 0,
58 sample_rate: SAMPLE_RATE,
59 channels: CHANNELS,
60 last_notes: std::collections::HashMap::new(),
61 }
62 }
63
64 pub fn get_buffer(&self) -> &[i16] {
65 &self.buffer
66 }
67
68 pub fn get_normalized_buffer(&self) -> Vec<f32> {
69 self.buffer.iter().map(|&s| (s as f32) / 32768.0).collect()
70 }
71
72 pub fn mix(&mut self, other: &AudioEngine) {
73 let max_len = self.buffer.len().max(other.buffer.len());
74 self.buffer.resize(max_len, 0);
75
76 for (i, &sample) in other.buffer.iter().enumerate() {
77 self.buffer[i] = self.buffer[i].saturating_add(sample);
78 }
79 }
80
81 pub fn merge_with(&mut self, other: AudioEngine) {
82 if other.buffer.is_empty() {
84 return;
85 }
86
87 if other.buffer.iter().all(|&s| s == 0) {
89 eprintln!("⚠️ Skipping merge: other buffer is silent");
90 return;
91 }
92
93 if self.buffer.iter().all(|&s| s == 0) {
94 self.buffer = other.buffer;
95 return;
96 }
97
98 self.mix(&other);
99 }
100
101 pub fn set_duration(&mut self, duration_secs: f32) {
102 let total_samples =
103 (duration_secs * (self.sample_rate as f32) * (self.channels as f32)) as usize;
104
105 if self.buffer.len() < total_samples {
106 self.buffer.resize(total_samples, 0);
107 }
108 }
109
110 pub fn generate_midi_file(
111 &mut self,
112 output_path: &String,
113 bpm: Option<f32>,
114 tpqn: Option<u16>,
115 ) -> Result<(), String> {
116 crate::core::audio::engine::export::generate_midi_file_impl(
117 &self.midi_events,
118 output_path,
119 bpm,
120 tpqn,
121 )
122 }
123
124 pub fn generate_wav_file(
125 &mut self,
126 output_dir: &String,
127 audio_format: Option<String>,
128 sample_rate: Option<u32>,
129 ) -> Result<(), String> {
130 crate::core::audio::engine::export::generate_wav_file_impl(
131 &mut self.buffer,
132 output_dir,
133 audio_format,
134 sample_rate,
135 )
136 }
137
138 pub fn insert_note(
139 &mut self,
140 owner: Option<String>,
141 waveform: String,
142 freq: f32,
143 amp: f32,
144 start_time_ms: f32,
145 duration_ms: f32,
146 synth_params: HashMap<String, Value>,
147 note_params: HashMap<String, Value>,
148 automation: Option<HashMap<String, Value>>,
149 ) -> Vec<(usize, usize)> {
150 crate::core::audio::engine::notes::insert_note_impl(
152 self,
153 owner,
154 waveform,
155 freq,
156 amp,
157 start_time_ms,
158 duration_ms,
159 synth_params,
160 note_params,
161 automation,
162 )
163 }
164
165 pub fn record_last_note_range(
166 &mut self,
167 owner: &str,
168 start_sample: usize,
169 total_samples: usize,
170 ) {
171 self.last_notes
172 .entry(owner.to_string())
173 .or_default()
174 .push((start_sample, total_samples));
175 }
176
177 pub(crate) fn extract_f32(&self, map: &HashMap<String, Value>, key: &str) -> Option<f32> {
179 match map.get(key) {
180 Some(Value::Number(n)) => Some(*n),
181 Some(Value::String(s)) => s.parse::<f32>().ok(),
182 Some(Value::Boolean(b)) => Some(if *b { 1.0 } else { 0.0 }),
183 _ => None,
184 }
185 }
186
187 pub(crate) fn extract_boolean(&self, map: &HashMap<String, Value>, key: &str) -> Option<bool> {
188 match map.get(key) {
189 Some(Value::Boolean(b)) => Some(*b),
190 Some(Value::Number(n)) => Some(*n != 0.0),
191 Some(Value::Identifier(s)) => {
192 if s == "true" {
193 Some(true)
194 } else if s == "false" {
195 Some(false)
196 } else {
197 None
198 }
199 }
200 Some(Value::String(s)) => {
201 if s == "true" {
202 Some(true)
203 } else if s == "false" {
204 Some(false)
205 } else {
206 None
207 }
208 }
209 _ => None,
210 }
211 }
212}
213
214pub fn parse_fraction_to_seconds(s: &str, bpm: f32) -> Option<f32> {
216 let trimmed = s.trim();
217 if let Some((num, den)) = trimmed.split_once('/') {
218 if let (Ok(n), Ok(d)) = (num.parse::<f32>(), den.parse::<f32>()) {
219 if d != 0.0 {
220 let beats = n / d; let secs_per_beat = 60.0 / bpm.max(1.0);
222 return Some(beats * secs_per_beat);
223 }
224 }
225 }
226 None
227}
228
229pub fn duration_to_seconds(d: &devalang_types::Duration, bpm: f32) -> Option<f32> {
231 use devalang_types::Duration as D;
232 match d {
233 D::Number(s) => Some(*s),
234 D::Beat(frac) | D::Identifier(frac) => parse_fraction_to_seconds(frac, bpm),
235 _ => None,
236 }
237}