use alloc::vec;
use alloc::vec::Vec;
use xmrs::core::fixed::units::Volume;
use xmrs::core::module::Module;
use xmrs::core::pitch::Pitch;
use xmrs::tracker::import::build::{build_timeline_layer, Pattern, Row};
use xmrs::tracker::import::unit::TrackImportUnit;
use crate::xmrsplayer::XmrsPlayer;
struct TimelineBackup {
tracks: Vec<xmrs::core::daw::track::Track>,
clips: xmrs::core::daw::sorted_clips::SortedClips,
automation: Vec<xmrs::core::daw::automation::AutomationLane>,
timeline_map: xmrs::core::daw::timeline::TimelineMap,
song_loop_to: Option<u32>,
quirks: xmrs::core::compatibility::PlaybackQuirks,
}
impl TimelineBackup {
fn take(module: &mut Module) -> Self {
Self {
tracks: core::mem::take(&mut module.tracks),
clips: core::mem::take(&mut module.clips),
automation: core::mem::take(&mut module.automation),
timeline_map: core::mem::take(&mut module.timeline_map),
song_loop_to: module.song_loop_to, quirks: module.quirks, }
}
fn restore(self, module: &mut Module) {
module.tracks = self.tracks;
module.clips = self.clips;
module.automation = self.automation;
module.timeline_map = self.timeline_map;
module.song_loop_to = self.song_loop_to;
module.quirks = self.quirks;
}
}
fn audition_pattern(instrument: usize, pitch: Pitch, rows: usize) -> Pattern {
let rows = rows.max(2);
let mut trigger: Row = vec![TrackImportUnit::default()];
trigger[0].note = xmrs::core::cell_note::CellNote::Play(pitch);
trigger[0].instrument = Some(instrument);
trigger[0].velocity = Volume::FULL;
let mut pattern: Pattern = Vec::with_capacity(rows);
pattern.push(trigger);
for _ in 1..rows {
pattern.push(vec![TrackImportUnit::default()]);
}
pattern
}
fn rows_for_samples(module: &Module, sample_rate: u32, max_samples: usize) -> usize {
let bpm = (module.default_bpm as u64).max(1);
let tempo = (module.default_tempo as u64).max(1);
let samples_per_row = ((sample_rate as u64 * 5 / 2) / bpm).max(1) * tempo;
((max_samples as u64 / samples_per_row) + 2).max(2) as usize
}
pub fn install_instrument_audition(
module: &mut Module,
instrument: usize,
pitch: Pitch,
rows: usize,
) -> bool {
if instrument >= module.instrument.len() {
return false;
}
let pattern = audition_pattern(instrument, pitch, rows);
build_timeline_layer(module, &[vec![0]], &[pattern]);
true
}
pub fn render_instrument(
module: &mut Module,
instrument: usize,
pitch: Pitch,
sample_rate: u32,
max_samples: usize,
) -> Vec<(i16, i16)> {
if instrument >= module.instrument.len() || max_samples == 0 {
return Vec::new();
}
let rows = rows_for_samples(module, sample_rate, max_samples);
let backup = TimelineBackup::take(module);
build_timeline_layer(module, &[vec![0]], &[audition_pattern(instrument, pitch, rows)]);
let pcm = {
let mut player = XmrsPlayer::new(module, sample_rate, 0);
player.set_max_loop_count(1);
let mut out = Vec::new();
while out.len() < max_samples {
match player.sample(true) {
Some(s) => out.push(s),
None => break,
}
}
out
};
backup.restore(module);
pcm
}
#[cfg(all(test, feature = "std"))]
mod tests {
use super::*;
use xmrs::prelude::*;
fn instrument_only_bank() -> Module {
let data: Vec<i8> = (0..64).map(|i| if i < 32 { 100 } else { -100 }).collect();
let sample = Sample {
name: "sfx".into(),
relative_pitch: 0,
finetune: Finetune::default(),
volume: ChannelVolume::FULL,
default_note_volume: Volume::FULL,
panning: Panning::CENTER,
loop_flag: LoopType::Forward,
loop_start: 0,
loop_length: 64,
sustain_loop_flag: LoopType::No,
sustain_loop_start: 0,
sustain_loop_length: 0,
data: Some(SampleDataType::Mono8(data)),
};
let mut instr = InstrDefault::default();
instr.sample.push(Some(sample));
instr.keyboard.map_all_to(0);
let mut bank = Module::default();
bank.frequency_type = FrequencyType::AmigaFrequencies;
bank.instrument.push(Instrument {
name: "sfx".into(),
instr_type: InstrumentType::Default(instr),
muted: false,
});
bank
}
#[test]
fn renders_audible_pcm_and_restores_module() {
let mut bank = instrument_only_bank();
assert!(bank.clips.is_empty() && bank.tracks.is_empty());
let pcm = render_instrument(&mut bank, 0, Pitch::C4, 48_000, 48_000);
assert!(!pcm.is_empty(), "render produced no samples");
let peak = pcm
.iter()
.map(|(l, r)| (*l as i32).abs().max((*r as i32).abs()))
.max()
.unwrap_or(0);
assert!(peak > 0, "render produced silence");
assert!(bank.clips.is_empty() && bank.tracks.is_empty());
assert!(bank.timeline_map.entries.is_empty());
}
#[test]
fn out_of_range_instrument_is_empty_and_harmless() {
let mut bank = instrument_only_bank();
let pcm = render_instrument(&mut bank, 9, Pitch::C4, 48_000, 1_000);
assert!(pcm.is_empty());
assert!(bank.clips.is_empty() && bank.tracks.is_empty());
}
}