xsynth-render 0.3.4

A command line utility for rendering MIDIs to audio using XSynth.
mod config;
use config::*;

mod rendered;
use rendered::*;

mod utils;
use utils::get_midi_length;

mod writer;

use xsynth_core::{
    channel::{ChannelAudioEvent, ChannelConfigEvent, ChannelEvent, ControlEvent},
    channel_group::SynthEvent,
    soundfont::{SampleSoundfont, SoundfontBase},
};

use midi_toolkit::{
    events::{Event, MIDIEventEnum},
    io::MIDIFile,
    pipe,
    sequence::{
        event::{cancel_tempo_events, scale_event_time},
        unwrap_items, TimeCaster,
    },
};

use std::{
    sync::{
        atomic::{AtomicU64, Ordering},
        Arc,
    },
    thread,
    time::{Duration, Instant},
};

use atomic_float::AtomicF64;

fn main() {
    let state = State::from_args();

    let mut synth = XSynthRender::new(state.config.clone(), state.output.clone());

    print!("Loading soundfonts...");
    synth.send_event(SynthEvent::AllChannels(ChannelEvent::Config(
        ChannelConfigEvent::SetSoundfonts(
            state
                .soundfonts
                .iter()
                .map(|s| {
                    let sf: Arc<dyn SoundfontBase> = Arc::new(
                        SampleSoundfont::new(s, synth.get_params(), state.config.sf_options)
                            .unwrap(),
                    );
                    sf
                })
                .collect::<Vec<Arc<dyn SoundfontBase>>>(),
        ),
    )));

    synth.send_event(SynthEvent::AllChannels(ChannelEvent::Config(
        ChannelConfigEvent::SetLayerCount(state.layers),
    )));

    let length = get_midi_length(state.midi.to_str().unwrap());

    let midi = MIDIFile::open(state.midi, None).unwrap();

    let ppq = midi.ppq();
    let merged = pipe!(
        midi.iter_all_track_events_merged_batches()
        |>TimeCaster::<f64>::cast_event_delta()
        |>cancel_tempo_events(250000)
        |>scale_event_time(1.0 / ppq as f64)
        |>unwrap_items()
    );

    let (snd, rcv) = crossbeam_channel::bounded(100);

    thread::spawn(move || {
        for batch in merged {
            snd.send(batch).unwrap();
        }
    });

    let position = Arc::new(AtomicF64::new(0.0));
    let voices = Arc::new(AtomicU64::new(0));

    {
        let position = position.clone();
        let voices = voices.clone();

        thread::spawn(move || loop {
            let pos = position.load(Ordering::Relaxed);
            let progress = (pos / length) * 100.0 + 0.0004;
            print!("\rProgress: [");
            let bars = progress as u8 / 5;
            for _ in 0..bars {
                print!("=");
            }
            for _ in 0..(20 - bars) {
                print!(" ");
            }
            print!("] {progress:.3}% | ");
            print!("Voice Count: {}", voices.load(Ordering::Relaxed));
            for _ in 0..10 {
                print!(" ");
            }
            if progress >= 100.0 {
                println!();
                break;
            }
        });
    }

    let now = Instant::now();

    for batch in rcv {
        if batch.delta > 0.0 {
            synth.render_batch(batch.delta);
            position.fetch_add(batch.delta, Ordering::Relaxed);
            voices.store(synth.voice_count(), Ordering::Relaxed);
        }
        for e in batch.iter_events() {
            match e.as_event() {
                Event::NoteOn(e) => {
                    synth.send_event(SynthEvent::Channel(
                        e.channel as u32,
                        ChannelEvent::Audio(ChannelAudioEvent::NoteOn {
                            key: e.key,
                            vel: e.velocity,
                        }),
                    ));
                }
                Event::NoteOff(e) => {
                    synth.send_event(SynthEvent::Channel(
                        e.channel as u32,
                        ChannelEvent::Audio(ChannelAudioEvent::NoteOff { key: e.key }),
                    ));
                }
                Event::ControlChange(e) => {
                    synth.send_event(SynthEvent::Channel(
                        e.channel as u32,
                        ChannelEvent::Audio(ChannelAudioEvent::Control(ControlEvent::Raw(
                            e.controller,
                            e.value,
                        ))),
                    ));
                }
                Event::PitchWheelChange(e) => {
                    synth.send_event(SynthEvent::Channel(
                        e.channel as u32,
                        ChannelEvent::Audio(ChannelAudioEvent::Control(
                            ControlEvent::PitchBendValue(e.pitch as f32 / 8192.0),
                        )),
                    ));
                }
                Event::ProgramChange(e) => {
                    synth.send_event(SynthEvent::Channel(
                        e.channel as u32,
                        ChannelEvent::Audio(ChannelAudioEvent::ProgramChange(e.program)),
                    ));
                }
                _ => {}
            }
        }
    }
    synth.send_event(SynthEvent::AllChannels(ChannelEvent::Audio(
        ChannelAudioEvent::AllNotesOff,
    )));
    synth.send_event(SynthEvent::AllChannels(ChannelEvent::Audio(
        ChannelAudioEvent::ResetControl,
    )));
    synth.finalize();

    let elapsed = now.elapsed();
    thread::sleep(Duration::from_millis(200));
    println!("Render time: {:?}", elapsed);
}