devalang_core/core/audio/engine/
export.rs1use crate::core::audio::engine::driver::MidiNoteEvent;
2use std::fs::File;
3
4pub fn generate_midi_file_impl(
5 midi_events: &Vec<MidiNoteEvent>,
6 output_path: &String,
7 bpm: Option<f32>,
8 tpqn: Option<u16>,
9) -> Result<(), String> {
10 use midly::num::{u7, u15, u24};
11 use midly::{
12 Format, Header, MetaMessage, MidiMessage, Smf, Timing, TrackEvent, TrackEventKind,
13 };
14
15 if midi_events.is_empty() {
16 return Ok(());
17 }
18
19 let bpm = bpm.unwrap_or(120.0_f32);
20 let tpqn: u16 = tpqn.unwrap_or(480u16);
21 let header = Header {
22 format: Format::SingleTrack,
23 timing: Timing::Metrical(u15::from(tpqn)),
24 };
25
26 #[derive(Clone)]
27 struct AbsEvent {
28 tick: u64,
29 kind: TrackEventKind<'static>,
30 }
31
32 let mut abs_events: Vec<AbsEvent> = Vec::new();
33 let microsecs_per_quarter = (60_000_000.0 / bpm) as u32;
34 abs_events.push(AbsEvent {
35 tick: 0,
36 kind: TrackEventKind::Meta(MetaMessage::Tempo(u24::from(microsecs_per_quarter))),
37 });
38
39 for ev in midi_events {
40 let start_secs = (ev.start_ms as f32) / 1000.0;
41 let dur_secs = (ev.duration_ms as f32) / 1000.0;
42 let start_ticks_f = start_secs * (bpm / 60.0) * (tpqn as f32);
43 let dur_ticks_f = dur_secs * (bpm / 60.0) * (tpqn as f32);
44 let start_tick = start_ticks_f.max(0.0).round() as u64;
45 let off_tick = (start_ticks_f + dur_ticks_f).max(start_tick as f32).round() as u64;
46
47 let key = u7::from(ev.key.min(127));
48 let vel = u7::from(ev.vel.min(127));
49
50 abs_events.push(AbsEvent {
51 tick: start_tick,
52 kind: TrackEventKind::Midi {
53 channel: (ev.channel as u8).into(),
54 message: MidiMessage::NoteOn { key, vel },
55 },
56 });
57
58 abs_events.push(AbsEvent {
59 tick: off_tick,
60 kind: TrackEventKind::Midi {
61 channel: (ev.channel as u8).into(),
62 message: MidiMessage::NoteOff {
63 key,
64 vel: u7::from(0),
65 },
66 },
67 });
68 }
69
70 let max_tick = abs_events.iter().map(|e| e.tick).max().unwrap_or(0);
71 abs_events.push(AbsEvent {
72 tick: max_tick + 0,
73 kind: TrackEventKind::Meta(MetaMessage::EndOfTrack),
74 });
75 abs_events.sort_by_key(|e| e.tick);
76
77 let mut track: Vec<TrackEvent> = Vec::new();
78 let mut last_tick: u64 = 0;
79 for e in abs_events {
80 let delta = (e.tick - last_tick) as u32;
81 track.push(TrackEvent {
82 delta: delta.into(),
83 kind: e.kind,
84 });
85 last_tick = e.tick;
86 }
87
88 let smf = Smf {
89 header,
90 tracks: vec![track],
91 };
92
93 if let Ok(mut file) = File::create(output_path) {
94 if let Err(e) = smf.write_std(&mut file) {
95 return Err(format!("Error writing MIDI file: {}", e));
96 }
97 } else {
98 return Err(format!("Cannot create MIDI file at {}", output_path));
99 }
100
101 Ok(())
102}
103
104pub fn generate_wav_file_impl(
105 buffer: &mut Vec<i16>,
106 output_dir: &String,
107 audio_format: Option<String>,
108 sample_rate: Option<u32>,
109) -> Result<(), String> {
110 if buffer.len() % (crate::core::audio::engine::CHANNELS as usize) != 0 {
111 buffer.push(0);
112 }
113
114 let sr = sample_rate.unwrap_or(crate::core::audio::engine::SAMPLE_RATE);
115 let format_str = audio_format.unwrap_or_else(|| "Wav16".to_string());
116 let fmt_low = format_str.to_lowercase();
117
118 match fmt_low.as_str() {
119 "wav16" => {
120 let spec = hound::WavSpec {
121 channels: crate::core::audio::engine::CHANNELS,
122 sample_rate: sr,
123 bits_per_sample: 16,
124 sample_format: hound::SampleFormat::Int,
125 };
126
127 let mut writer = hound::WavWriter::create(output_dir, spec)
128 .map_err(|e| format!("Error creating WAV file: {}", e))?;
129
130 for sample in buffer.iter() {
131 writer
132 .write_sample(*sample)
133 .map_err(|e| format!("Error writing sample: {:?}", e))?;
134 }
135
136 writer
137 .finalize()
138 .map_err(|e| format!("Error finalizing WAV: {:?}", e))?;
139 }
140 "wav24" | "wav32" => {
141 let bits = if fmt_low.contains("24") { 24 } else { 32 };
142 let spec = hound::WavSpec {
143 channels: crate::core::audio::engine::CHANNELS,
144 sample_rate: sr,
145 bits_per_sample: bits,
146 sample_format: hound::SampleFormat::Int,
147 };
148
149 let mut writer = hound::WavWriter::create(output_dir, spec)
150 .map_err(|e| format!("Error creating WAV file: {}", e))?;
151
152 for &s in buffer.iter() {
153 let v32 = (s as i32) << (bits - 16);
154 writer
155 .write_sample(v32)
156 .map_err(|e| format!("Error writing sample: {:?}", e))?;
157 }
158
159 writer
160 .finalize()
161 .map_err(|e| format!("Error finalizing WAV: {:?}", e))?;
162 }
163 _ => {
164 return Err(format!("Unsupported audio format: {}", format_str));
165 }
166 }
167
168 Ok(())
169}