devalang_wasm/services/build/outputs/audio/
builder.rs1#![cfg(feature = "cli")]
2
3use crate::engine::audio::settings::{AudioBitDepth, AudioChannels, AudioFormat, ResampleQuality};
4use crate::language::syntax::ast::Statement;
5use crate::tools::logger::Logger;
6use anyhow::{Context, Result};
7use std::path::{Path, PathBuf};
8use std::sync::Arc;
9use std::time::{Duration, Instant};
10
11use crate::services::build::outputs::audio::helpers::calculate_rms;
12use crate::services::build::outputs::audio::writer::write_wav;
13
14#[derive(Debug, Clone)]
15pub struct AudioRenderSummary {
16 pub path: PathBuf,
17 pub format: AudioFormat,
18 pub bit_depth: AudioBitDepth,
19 pub rms: f32,
20 pub render_time: Duration,
21 pub audio_length: Duration,
22}
23
24#[derive(Debug, Clone)]
25pub struct MultiFormatRenderSummary {
26 pub primary_path: PathBuf,
27 pub primary_format: AudioFormat,
28 pub exported_formats: Vec<(AudioFormat, PathBuf)>,
29 pub bit_depth: AudioBitDepth,
30 pub rms: f32,
31 pub render_time: Duration,
32 pub audio_length: Duration,
33}
34
35#[derive(Clone)]
36pub struct AudioBuilder {
37 _logger: Arc<Logger>,
38}
39
40impl AudioBuilder {
41 pub fn new(
42 _log_writer: crate::services::build::outputs::logs::LogWriter,
43 logger: Arc<Logger>,
44 ) -> Self {
45 Self { _logger: logger }
46 }
47
48 #[allow(clippy::too_many_arguments)]
49 pub fn render_all_formats(
50 &self,
51 statements: &[Statement],
52 _entry_path: &Path,
53 output_root: &Path,
54 module_name: &str,
55 requested_formats: &[AudioFormat],
56 requested_bit_depth: AudioBitDepth,
57 channels: AudioChannels,
58 sample_rate: u32,
59 _resample: ResampleQuality,
60 _bpm: f32,
61 ) -> Result<MultiFormatRenderSummary> {
62 let start = Instant::now();
63
64 let primary_fmt = if requested_formats.is_empty() {
66 AudioFormat::Wav
67 } else {
68 requested_formats[0]
69 };
70
71 let audio_summary = self.render(
72 statements,
73 _entry_path,
74 output_root,
75 module_name,
76 primary_fmt,
77 requested_bit_depth,
78 channels,
79 sample_rate,
80 ResampleQuality::Sinc24,
81 )?;
82
83 let exported = vec![(audio_summary.format, audio_summary.path.clone())];
84
85 let total_time = start.elapsed();
86
87 Ok(MultiFormatRenderSummary {
88 primary_path: audio_summary.path,
89 primary_format: audio_summary.format,
90 exported_formats: exported,
91 bit_depth: audio_summary.bit_depth,
92 rms: audio_summary.rms,
93 render_time: total_time,
94 audio_length: audio_summary.audio_length,
95 })
96 }
97
98 pub fn render(
99 &self,
100 statements: &[Statement],
101 _entry_path: &Path,
102 output_root: impl AsRef<Path>,
103 module_name: &str,
104 requested_format: AudioFormat,
105 requested_bit_depth: AudioBitDepth,
106 channels: AudioChannels,
107 sample_rate: u32,
108 _resample: ResampleQuality,
109 ) -> Result<AudioRenderSummary> {
110 use crate::engine::audio::interpreter::driver::AudioInterpreter;
111
112 let mut interpreter = AudioInterpreter::new(sample_rate);
113 interpreter.suppress_print = true;
118
119 let buffer = interpreter.interpret(statements)?;
126
127 let output_root = output_root.as_ref();
128 let audio_dir = output_root.join("audio");
129 std::fs::create_dir_all(&audio_dir).with_context(|| {
130 format!(
131 "failed to create audio output directory: {}",
132 audio_dir.display()
133 )
134 })?;
135
136 let output_path = audio_dir.join(format!("{}.wav", module_name));
137
138 let mut rms = 0.0f32;
139 let audio_length = if buffer.is_empty() {
140 Duration::from_secs(0)
141 } else {
142 rms = calculate_rms(&buffer);
143 let channel_count = channels.count() as usize;
144 let frames = if channel_count == 0 {
145 0
146 } else {
147 buffer.len() / channel_count
148 };
149 if sample_rate == 0 {
150 Duration::from_secs(0)
151 } else {
152 Duration::from_secs_f64(frames as f64 / sample_rate as f64)
153 }
154 };
155
156 if !buffer.is_empty() {
157 let applied = write_wav(
158 &output_path,
159 &buffer,
160 sample_rate,
161 requested_bit_depth,
162 channels,
163 )?;
164
165 let log_path = output_path.with_file_name(format!("{}.printlog", module_name));
167 if !interpreter.events.logs.is_empty() {
168 if let Ok(mut f) = std::fs::File::create(&log_path) {
169 use std::io::Write;
170 for (t, msg) in &interpreter.events.logs {
171 let _ = writeln!(f, "{:.6}\t{}", t, msg);
173 }
174 }
175 }
176
177 Ok(AudioRenderSummary {
178 path: output_path,
179 format: requested_format,
180 bit_depth: applied,
181 rms,
182 render_time: Duration::from_secs(0),
183 audio_length,
184 })
185 } else {
186 Ok(AudioRenderSummary {
187 path: output_path,
188 format: requested_format,
189 bit_depth: requested_bit_depth,
190 rms: 0.0,
191 render_time: Duration::from_secs(0),
192 audio_length,
193 })
194 }
195 }
196}