use crate::params::ChannelNo;
use crate::pattern::{ParseError, Song, Tempo};
use crate::presets::sid::Preset;
const REST: &str = ". . . . . . . . . . . . . . . .";
const LEAD: [&str; 4] = [
". . B4 . D5 . E5 . F#5 - . . . . . .",
". . B4 . D5 . E5 . F#5 . G5 . F#5 . D5 .",
"E5 . E4 - . . E4 . D5 . F#4 . F#5 . B4 -",
REST,
];
const BASS: [&str; 4] = [
"B2 - . . . . . . . . B3 . B2 . B3 .",
"B2 - . . . . . . . . . . . . . .",
". . . . E3 - . . . . . . . . . .",
"B2 . B3 . B2 . B3 . B2 . B3 . B2 . B3 .",
];
const BASS_END: &str = "B2 . B3 . B2 . B3 . B2 . B3 . B2 . B3 -";
const SUB: [&str; 4] = [
". . B1 - . . B1 - . . B1 - . . B1 -",
". . B1 - . . B1 - . . B1 - . . B1 -",
". . E2 - . . E2 - . . F#2 - . . F#2 -",
". . B1 - . . B1 - . . B1 - . . B1 -",
];
const BEAT: &str = "k h o h k h o h k h o h k h o h";
const FILL: &str = "k h o h k h o h k h o h k h k k+h";
#[must_use]
pub fn lazy_jones() -> Song {
try_lazy_jones().expect("the built-in Lazy Jones song is valid")
}
fn try_lazy_jones() -> Result<Song, ParseError> {
let lead = [LEAD, LEAD].concat();
let bass = [&BASS[..], &BASS[..3], &[BASS_END]].concat();
let sub = [[REST; 4], SUB].concat();
let free = [REST; 8];
let drums = [BEAT, BEAT, BEAT, FILL].repeat(2);
Song::new(Tempo::bpm(140.0, 4))
.patch(Preset::ArpLead.params())
.patch(Preset::C64Bass.params())
.patch(Preset::PwmLead.params())
.lane(ChannelNo(0), &with_patch(&lead, 0))?
.lane(ChannelNo(1), &with_patch(&bass, 1))?
.lane(ChannelNo(2), &with_patch(&sub, 1))?
.lane(ChannelNo(3), &with_patch(&free, 2))?
.lane(ChannelNo(3), &drums.join(" | "))
}
fn with_patch(bars: &[&str], index: u16) -> String {
let lane = bars.join(" | ");
let (first, rest) = lane.split_once(' ').unwrap_or((&lane, ""));
let first = if first == "." { "" } else { first };
format!("{first}@{index} {rest}")
}
#[cfg(test)]
mod tests {
use super::*;
use crate::pattern::{Action, Cell};
#[test]
fn lazy_jones_is_eight_bars_of_sixteenths_at_140_bpm() {
let song = lazy_jones();
assert!(song.looped);
assert!((song.tempo.as_rows_per_minute() - 560.0).abs() < f32::EPSILON);
assert_eq!(song.lanes.len(), 5);
for lane in &song.lanes {
assert_eq!(lane.rows.len(), 128, "lane on {:?}", lane.channel);
}
}
#[test]
fn every_melodic_lane_starts_on_its_preset_without_an_extra_row() {
let song = lazy_jones();
let first = |lane: usize| song.lanes[lane].rows[0];
assert_eq!(
first(0),
Cell {
patch: Some(0),
action: Action::Hold
}
);
assert_eq!(first(1).patch, Some(1));
assert!(matches!(first(1).action, Action::Note(_)));
assert_eq!(first(2).patch, Some(1));
assert_eq!(first(3).patch, Some(2));
assert_eq!(first(4).patch, None);
assert_eq!(song.patches[0], Preset::ArpLead.params());
}
}