extern crate wasm_bindgen;
use wasm_bindgen::prelude::*;
use sakuramml;
use wav_io;
use rustysynth::{SynthesizerSettings, Synthesizer, SoundFont, MidiFile, MidiFileSequencer};
const SAMPLE_RATE: usize = 44_100;
#[wasm_bindgen]
pub fn get_pico_version() -> String {
format!("v{}", env!("CARGO_PKG_VERSION"))
}
#[wasm_bindgen]
pub fn get_sakura_version() -> String {
let ver = sakuramml::get_version();
ver.to_string()
}
#[wasm_bindgen]
pub struct PicoResult {
pub result: bool,
bin: Vec<u8>,
log: String,
}
#[wasm_bindgen]
impl PicoResult {
pub fn get_bin(&self) -> Vec<u8> {
self.bin.clone()
}
pub fn get_log(&self) -> String {
self.log.clone()
}
}
#[wasm_bindgen]
pub fn pico_compile_to_midi(mml_source: &str) -> PicoResult {
let mut result = PicoResult {
result: false,
bin: Vec::new(),
log: String::new(),
};
if mml_source == "" {
result.log = "[ERROR] MML source is empty".to_string();
return result;
}
let mut sakura = sakuramml::SakuraCompiler::new();
let midi = sakura.compile(&mml_source);
let log = sakura.get_log();
result.log = log.clone();
if log.contains("ERROR") {
return result
}
result.result = true;
result.bin = midi;
return result
}
#[wasm_bindgen]
pub fn make_wav_custom(mml_source: &str, soundfont: Vec<u8>, sample_rate: usize, sample_bit: usize, out_format: &str) -> PicoResult {
let mut result = PicoResult {
result: false,
log: String::new(),
bin: Vec::new(),
};
let midi_result = pico_compile_to_midi(mml_source);
if !midi_result.result { return midi_result; }
result.log.push_str("compiled to midi\n");
let mut reader = std::io::Cursor::new(soundfont);
let sound_font = match SoundFont::new(&mut reader) {
Ok(s) => s,
Err(e) => {
result.log = format!("[ERROR] Failded to load SoundFont, {}", e);
return result;
}
};
let sound_font = std::sync::Arc::new(sound_font);
result.log.push_str("loaded soundfont\n");
let settings = SynthesizerSettings::new(SAMPLE_RATE as i32);
let synthesizer = Synthesizer::new(&sound_font, &settings).unwrap();
let mut sequencer = MidiFileSequencer::new(synthesizer);
let mut midi_reader = std::io::Cursor::new(midi_result.get_bin());
let midi_file = std::sync::Arc::new(MidiFile::new(&mut midi_reader).unwrap());
let midi_time_len = midi_file.get_length();
result.log.push_str("loaded midi file\n");
let sample_count = (SAMPLE_RATE as f64 * midi_time_len) as usize;
result.log.push_str(format!("samples.length={}\n", sample_count).as_str());
let mut samples = vec![0.0f32; sample_count * 2];
let mut left_buf = vec![0.0f32; sample_count];
let mut right_buf = vec![0.0f32; sample_count];
sequencer.play(&midi_file, false);
sequencer.render(&mut left_buf[..], &mut right_buf[..]);
for i in 0..left_buf.len() {
samples[i*2+0] = left_buf[i];
samples[i*2+1] = right_buf[i];
}
let mut wav_head = wav_io::new_stereo_header();
wav_head.sample_rate = sample_rate as u32;
if sample_bit == 8 {
wav_head.bits_per_sample = 8;
wav_head.set_int_format();
} else if sample_bit == 16 {
wav_head.bits_per_sample = 16;
wav_head.set_int_format();
} else if sample_bit == 24 {
wav_head.bits_per_sample = 24;
wav_head.set_int_format();
} else {
wav_head.bits_per_sample = 32;
}
if out_format == "ogg" {
let samples:Vec<f32> = wav_io::resample::linear(samples, 2, sample_rate as u32, 16000).try_into().unwrap();
if samples.len() == 0 {
result.log = format!("[ERROR] {}", "Failed to resample");
return result;
}
let samples:Vec<i16> = convert_samples_f32_to_i16(&samples);
if samples.len() == 0 {
result.log = format!("[ERROR] {}", "Failed to convert samples f32 to i16");
return result;
}
result.log = format!("[ERROR] ogg-orpus: Not supported");
result.result = false;
return result;
} else {
match wav_io::write_to_bytes(&wav_head, &samples) {
Ok(bytes) => {
result.result = true;
result.bin = bytes;
},
Err(e) => {
result.log = format!("[ERROR] write wav: {}", e);
result.result = false;
return result;
}
}
}
result
}
#[wasm_bindgen]
pub fn make_wav(mml_source: &str, soundfont: Vec<u8>) -> PicoResult {
make_wav_custom(mml_source, soundfont, SAMPLE_RATE, 32, "wav")
}
pub fn convert_samples_f32_to_i16(samples: &Vec<f32>) -> Vec<i16> {
let mut samples_i16 = vec![];
for v in samples {
samples_i16.push((*v * std::i16::MAX as f32) as i16);
}
samples_i16
}