devalang_core/core/audio/engine/
export.rs

1use 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}