use sim_kernel::{Expr, Symbol};
use sim_lib_scene::{data_map, node, sym};
use sim_value::build::{int, list, text, uint};
pub const PIANO_ROLL_VIEW_ID: &str = "view:piano-roll";
pub const PIANO_ROLL_DEMO_FIXTURE: &str = "keyboard-rack-roll";
pub const PIANO_ROLL_EDIT_ACTIONS: &[&str] = &[
"draw",
"move",
"trim",
"split",
"delete",
"duplicate",
"quantize",
"set-velocity",
"set-pitch",
"set-lane",
"set-curve",
"freeze",
];
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PianoRollLaneKind {
Note,
Drum,
ScaleDegree,
Object,
Automation,
}
impl PianoRollLaneKind {
fn as_str(self) -> &'static str {
match self {
Self::Note => "note",
Self::Drum => "drum",
Self::ScaleDegree => "scale-degree",
Self::Object => "object",
Self::Automation => "automation",
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PianoRollEvent {
pub id: Symbol,
pub lane: Symbol,
pub at: u64,
pub len: u64,
pub pitch: i32,
pub velocity: u8,
pub event_kind: PianoRollLaneKind,
pub generated: bool,
pub live: bool,
pub curve: Option<String>,
}
impl PianoRollEvent {
pub fn new(
id: Symbol,
lane: Symbol,
at: u64,
len: u64,
pitch: i32,
velocity: u8,
event_kind: PianoRollLaneKind,
) -> Self {
Self {
id,
lane,
at,
len,
pitch,
velocity,
event_kind,
generated: false,
live: false,
curve: None,
}
}
pub fn generated(mut self) -> Self {
self.generated = true;
self
}
pub fn live(mut self) -> Self {
self.live = true;
self
}
pub fn with_curve(mut self, curve: impl Into<String>) -> Self {
self.curve = Some(curve.into());
self
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PianoRollLane {
pub id: Symbol,
pub label: String,
pub lane_kind: PianoRollLaneKind,
pub events: Vec<PianoRollEvent>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PianoRollView {
pub target: Symbol,
pub source: Symbol,
pub player_chain: Symbol,
pub lanes: Vec<PianoRollLane>,
pub live_notes: Vec<PianoRollEvent>,
pub generated_notes: Vec<PianoRollEvent>,
}
pub fn piano_roll_view(view: &PianoRollView) -> Expr {
node(
"piano-roll",
vec![
("lens", sym(PIANO_ROLL_VIEW_ID)),
("role", sym("piano-roll")),
("target", Expr::Symbol(view.target.clone())),
("source", Expr::Symbol(view.source.clone())),
("player-chain", Expr::Symbol(view.player_chain.clone())),
(
"edit-actions",
list(
PIANO_ROLL_EDIT_ACTIONS
.iter()
.map(|action| text(*action))
.collect(),
),
),
(
"lanes",
list(view.lanes.iter().map(piano_roll_lane_expr).collect()),
),
(
"live-notes",
list(view.live_notes.iter().map(piano_roll_event_expr).collect()),
),
(
"generated-notes",
list(
view.generated_notes
.iter()
.map(piano_roll_event_expr)
.collect(),
),
),
],
)
}
pub fn piano_roll_demo_view() -> PianoRollView {
let note_lane = Symbol::qualified("music/piano-roll-lane", "lead-notes");
let drum_lane = Symbol::qualified("music/piano-roll-lane", "drums");
let degree_lane = Symbol::qualified("music/piano-roll-lane", "degrees");
let object_lane = Symbol::qualified("music/piano-roll-lane", "objects");
let automation_lane = Symbol::qualified("music/piano-roll-lane", "automation");
let live = PianoRollEvent::new(
Symbol::qualified("music/piano-roll-event", "live-c4"),
note_lane.clone(),
96,
96,
60,
108,
PianoRollLaneKind::Note,
)
.live();
let generated = PianoRollEvent::new(
Symbol::qualified("music/piano-roll-event", "generated-g4"),
note_lane.clone(),
192,
96,
67,
92,
PianoRollLaneKind::Note,
)
.generated();
PianoRollView {
target: Symbol::qualified("music/piano-roll", "lead"),
source: Symbol::qualified("music/performance-source", "keyboard"),
player_chain: Symbol::qualified("music/player-chain", "onscreen-keyboard"),
lanes: vec![
PianoRollLane {
id: note_lane.clone(),
label: "Notes".to_owned(),
lane_kind: PianoRollLaneKind::Note,
events: vec![live.clone(), generated.clone()],
},
PianoRollLane {
id: drum_lane.clone(),
label: "Drums".to_owned(),
lane_kind: PianoRollLaneKind::Drum,
events: vec![PianoRollEvent::new(
Symbol::qualified("music/piano-roll-event", "kick"),
drum_lane,
0,
48,
36,
120,
PianoRollLaneKind::Drum,
)],
},
PianoRollLane {
id: degree_lane.clone(),
label: "Scale degrees".to_owned(),
lane_kind: PianoRollLaneKind::ScaleDegree,
events: vec![PianoRollEvent::new(
Symbol::qualified("music/piano-roll-event", "degree-five"),
degree_lane,
288,
96,
5,
96,
PianoRollLaneKind::ScaleDegree,
)],
},
PianoRollLane {
id: object_lane.clone(),
label: "Objects".to_owned(),
lane_kind: PianoRollLaneKind::Object,
events: vec![PianoRollEvent::new(
Symbol::qualified("music/piano-roll-event", "phrase-a"),
object_lane,
384,
192,
0,
100,
PianoRollLaneKind::Object,
)],
},
PianoRollLane {
id: automation_lane.clone(),
label: "Automation".to_owned(),
lane_kind: PianoRollLaneKind::Automation,
events: vec![
PianoRollEvent::new(
Symbol::qualified("music/piano-roll-event", "cutoff-rise"),
automation_lane,
0,
384,
0,
80,
PianoRollLaneKind::Automation,
)
.with_curve("rise"),
],
},
],
live_notes: vec![live],
generated_notes: vec![generated],
}
}
pub fn piano_roll_demo_scene() -> Expr {
piano_roll_view(&piano_roll_demo_view())
}
fn piano_roll_lane_expr(lane: &PianoRollLane) -> Expr {
data_map(vec![
("id", Expr::Symbol(lane.id.clone())),
("label", text(lane.label.clone())),
("lane-kind", text(lane.lane_kind.as_str())),
(
"events",
list(lane.events.iter().map(piano_roll_event_expr).collect()),
),
])
}
fn piano_roll_event_expr(event: &PianoRollEvent) -> Expr {
let mut fields = vec![
("id", Expr::Symbol(event.id.clone())),
("lane", Expr::Symbol(event.lane.clone())),
("event-kind", text(event.event_kind.as_str())),
("at", uint(event.at)),
("len", uint(event.len)),
("pitch", int(i64::from(event.pitch))),
("velocity", uint(u64::from(event.velocity))),
("generated", Expr::Bool(event.generated)),
("live", Expr::Bool(event.live)),
];
if let Some(curve) = &event.curve {
fields.push(("curve", text(curve.clone())));
}
data_map(fields)
}