use super::{Song, SongError, SongTrack};
use crate::dsl::{ENGINE_VERSION, Node, SeqNote, Track};
const SEND_ROOM: f32 = 0.6;
const SEND_MIX_MAX: f32 = 0.5;
fn note_end(n: &SeqNote) -> u32 {
n.step.saturating_add(n.len.max(1))
}
impl Song {
pub fn length_bars(&self) -> u32 {
let steps_per_bar = self.steps_per_bar();
let from_patterns = self
.arrangement
.iter()
.map(|pl| {
let bars = self
.patterns
.iter()
.find(|p| p.name == pl.pattern)
.map(|p| p.bars)
.unwrap_or(0);
pl.bar.saturating_add(bars)
})
.max()
.unwrap_or(0);
let from_notes = self
.tracks
.iter()
.flat_map(|t| t.notes.iter())
.map(|n| note_end(n).div_ceil(steps_per_bar))
.max()
.unwrap_or(0);
from_patterns.max(from_notes)
}
fn steps_per_bar(&self) -> u32 {
self.beats_per_bar.max(1) * self.steps_per_beat.max(1)
}
pub fn to_doc(&self) -> Result<crate::dsl::SoundDoc, SongError> {
if self.tracks.is_empty() {
return Err(SongError::Empty);
}
for pl in &self.arrangement {
if !self.tracks.iter().any(|t| t.name == pl.track) {
return Err(SongError::UnknownTrack(pl.track.clone()));
}
if !self.patterns.iter().any(|p| p.name == pl.pattern) {
return Err(SongError::UnknownPattern(pl.pattern.clone()));
}
}
let sec_per_step = 60.0 / (self.bpm.max(1.0) * self.steps_per_beat.max(1) as f32);
let mut end_step = 0u32;
let mut doc_tracks = Vec::with_capacity(self.tracks.len());
for t in &self.tracks {
doc_tracks.push(self.compile_track(t, &mut end_step)?);
}
let duration = end_step as f32 * sec_per_step + 2.0; let root = Node::Tracks {
tracks: doc_tracks,
master: self.master.clone(),
};
let mut json = serde_json::json!({
"name": self.name,
"duration": duration,
"engine": self.engine.unwrap_or(ENGINE_VERSION),
"root": serde_json::to_value(&root).map_err(|e| SongError::Compile(e.to_string()))?,
});
if let Some(v) = self.version {
json["version"] = serde_json::json!(v);
}
let doc: crate::dsl::SoundDoc = serde_json::from_value(json)
.map_err(|e| SongError::Compile(format!("song doc build: {e}")))?;
Ok(doc)
}
fn compile_track(&self, t: &SongTrack, end_step: &mut u32) -> Result<Track, SongError> {
let steps_per_bar = self.steps_per_bar();
let mut notes: Vec<SeqNote> = t.notes.clone();
for n in ¬es {
*end_step = (*end_step).max(note_end(n));
}
for pl in self.arrangement.iter().filter(|p| p.track == t.name) {
let pat = self
.patterns
.iter()
.find(|p| p.name == pl.pattern)
.expect("pattern existence checked above");
let offset = pl.bar.saturating_mul(steps_per_bar);
for n in &pat.notes {
let placed = SeqNote {
step: n.step.saturating_add(offset),
len: n.len,
pitch: n.pitch.clone(),
gain: n.gain,
};
*end_step = (*end_step).max(note_end(&placed));
notes.push(placed);
}
}
notes.sort_by_key(|n| n.step);
let mut seq_json = serde_json::json!({
"type": "seq",
"bpm": self.bpm.max(1.0),
"steps_per_beat": self.steps_per_beat.max(1),
"wave": serde_json::to_value(t.wave).map_err(|e| SongError::Compile(e.to_string()))?,
"env": serde_json::to_value(t.env).map_err(|e| SongError::Compile(e.to_string()))?,
"swing": t.swing.unwrap_or(self.swing),
"humanize": t.humanize.unwrap_or(self.humanize),
"sf2": t.sf2,
"sf2_preset": t.sf2_preset,
"sf2_bank": t.sf2_bank,
"notes": serde_json::to_value(¬es).map_err(|e| SongError::Compile(e.to_string()))?,
});
if let serde_json::Value::Object(voice) =
serde_json::to_value(t.voice).map_err(|e| SongError::Compile(e.to_string()))?
{
for (key, val) in voice {
if !val.is_null() {
seq_json[key] = val;
}
}
}
let seq: Node = serde_json::from_value(seq_json)
.map_err(|e| SongError::Compile(format!("track '{}' seq build: {e}", t.name)))?;
let node = if t.reverb > 0.0 {
let rv = t.reverb.clamp(0.0, 1.0);
Node::Chain {
stages: vec![
seq,
Node::Reverb {
room: SEND_ROOM,
mix: SEND_MIX_MAX * rv,
},
],
}
} else {
seq
};
Ok(Track {
id: Some(t.name.clone()),
node,
pan: t.pan,
gain: t.gain,
at: 0.0,
mute: false,
automation: Vec::new(),
})
}
}