use std::collections::{BTreeMap, BTreeSet};
use sim_lib_music_core::{LaneId, LaneKind, PlayEvent, TracePolicy};
use thiserror::Error;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum FilterCapability {
Rule,
Callable,
ReadEval,
Clone,
Rewrite,
Route,
Annotate,
Quantize,
Thin,
Expand,
Sidechain,
}
impl FilterCapability {
pub fn wire_label(self) -> &'static str {
match self {
Self::Rule => "rule",
Self::Callable => "callable",
Self::ReadEval => "read-eval",
Self::Clone => "clone",
Self::Rewrite => "rewrite",
Self::Route => "route",
Self::Annotate => "annotate",
Self::Quantize => "quantize",
Self::Thin => "thin",
Self::Expand => "expand",
Self::Sidechain => "sidechain",
}
}
pub fn from_wire_label(value: &str) -> Option<Self> {
Some(match value {
"rule" => Self::Rule,
"callable" => Self::Callable,
"read-eval" => Self::ReadEval,
"clone" => Self::Clone,
"rewrite" => Self::Rewrite,
"route" => Self::Route,
"annotate" => Self::Annotate,
"quantize" => Self::Quantize,
"thin" => Self::Thin,
"expand" => Self::Expand,
"sidechain" => Self::Sidechain,
_ => return None,
})
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct FilterCapabilitySet {
items: BTreeSet<FilterCapability>,
}
impl FilterCapabilitySet {
pub fn new(items: impl IntoIterator<Item = FilterCapability>) -> Self {
Self {
items: items.into_iter().collect(),
}
}
pub fn all() -> Self {
Self::new([
FilterCapability::Rule,
FilterCapability::Callable,
FilterCapability::ReadEval,
FilterCapability::Clone,
FilterCapability::Rewrite,
FilterCapability::Route,
FilterCapability::Annotate,
FilterCapability::Quantize,
FilterCapability::Thin,
FilterCapability::Expand,
FilterCapability::Sidechain,
])
}
pub fn rule_ops(items: impl IntoIterator<Item = FilterCapability>) -> Self {
let mut set = Self::new([FilterCapability::Rule]);
set.items.extend(items);
set
}
pub fn contains(&self, capability: FilterCapability) -> bool {
self.items.contains(&capability)
}
pub fn insert(&mut self, capability: FilterCapability) {
self.items.insert(capability);
}
pub fn iter(&self) -> impl Iterator<Item = FilterCapability> + '_ {
self.items.iter().copied()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum DeterminismPolicy {
Deterministic,
RequiresSeed,
AllowNondeterministic,
}
impl DeterminismPolicy {
pub fn wire_label(self) -> &'static str {
match self {
Self::Deterministic => "deterministic",
Self::RequiresSeed => "requires-seed",
Self::AllowNondeterministic => "allow-nondeterministic",
}
}
pub fn from_wire_label(value: &str) -> Option<Self> {
Some(match value {
"deterministic" => Self::Deterministic,
"requires-seed" => Self::RequiresSeed,
"allow-nondeterministic" => Self::AllowNondeterministic,
_ => return None,
})
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct FilterShape {
kinds: BTreeSet<LaneKind>,
}
impl FilterShape {
pub fn new(kinds: impl IntoIterator<Item = LaneKind>) -> Result<Self, CustomFilterError> {
let shape = Self {
kinds: kinds.into_iter().collect(),
};
if shape.kinds.is_empty() {
return Err(CustomFilterError::EmptyShape);
}
Ok(shape)
}
pub fn any_event() -> Self {
Self::new([
LaneKind::Note,
LaneKind::Midi,
LaneKind::Pitch,
LaneKind::Control,
LaneKind::Audio,
LaneKind::Playable,
LaneKind::Performance,
LaneKind::Diagnostic,
LaneKind::Trace,
])
.expect("non-empty shape")
}
pub fn notes() -> Self {
Self::new([LaneKind::Note]).expect("non-empty shape")
}
pub fn accepts(&self, event: &PlayEvent) -> bool {
self.kinds.contains(&event.kind())
}
pub fn kinds(&self) -> impl Iterator<Item = LaneKind> + '_ {
self.kinds.iter().copied()
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CustomFilter {
pub id: String,
pub input: FilterShape,
pub output: FilterShape,
pub capabilities: FilterCapabilitySet,
pub determinism: DeterminismPolicy,
pub trace: TracePolicy,
pub body: FilterBody,
}
impl CustomFilter {
pub fn new(
id: impl Into<String>,
input: FilterShape,
output: FilterShape,
capabilities: FilterCapabilitySet,
determinism: DeterminismPolicy,
trace: TracePolicy,
body: FilterBody,
) -> Result<Self, CustomFilterError> {
let filter = Self {
id: id.into(),
input,
output,
capabilities,
determinism,
trace,
body,
};
filter.validate_declaration()?;
Ok(filter)
}
fn validate_declaration(&self) -> Result<(), CustomFilterError> {
for capability in self.body.declared_capabilities().iter() {
if !self.capabilities.contains(capability) {
return Err(CustomFilterError::UndeclaredCapability(
capability.wire_label().to_owned(),
));
}
}
Ok(())
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum FilterBody {
Rule(Vec<FilterRule>),
Callable(CallableFilterRef),
}
impl FilterBody {
fn declared_capabilities(&self) -> FilterCapabilitySet {
match self {
Self::Rule(rules) => {
let mut set = FilterCapabilitySet::new([FilterCapability::Rule]);
for rule in rules {
set.insert(rule.op.capability());
}
set
}
Self::Callable(_) => FilterCapabilitySet::new([FilterCapability::Callable]),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct FilterRule {
pub when: FilterPredicate,
pub op: FilterOp,
}
impl FilterRule {
pub fn new(when: FilterPredicate, op: FilterOp) -> Self {
Self { when, op }
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum FilterPredicate {
Any,
Kind(LaneKind),
Lane(LaneId),
}
impl FilterPredicate {
pub(crate) fn matches(&self, event: &PlayEvent) -> bool {
match self {
Self::Any => true,
Self::Kind(kind) => event.kind() == *kind,
Self::Lane(lane) => event.lane_id() == lane,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum FilterOp {
Accept,
Reject,
Clone {
copies: u8,
},
Rewrite {
lane: Option<LaneId>,
pitch_delta: i16,
velocity_delta: i16,
},
Route {
lane: LaneId,
},
Annotate {
message: String,
},
Quantize {
grid_ticks: i64,
},
Thin {
keep_every: u32,
},
Expand {
copies: u8,
step_ticks: i64,
},
Sidechain {
lane: LaneId,
control: String,
},
}
impl FilterOp {
pub fn capability(&self) -> FilterCapability {
match self {
Self::Accept | Self::Reject => FilterCapability::Rule,
Self::Clone { .. } => FilterCapability::Clone,
Self::Rewrite { .. } => FilterCapability::Rewrite,
Self::Route { .. } => FilterCapability::Route,
Self::Annotate { .. } => FilterCapability::Annotate,
Self::Quantize { .. } => FilterCapability::Quantize,
Self::Thin { .. } => FilterCapability::Thin,
Self::Expand { .. } => FilterCapability::Expand,
Self::Sidechain { .. } => FilterCapability::Sidechain,
}
}
pub fn wire_label(&self) -> &'static str {
match self {
Self::Accept => "accept",
Self::Reject => "reject",
Self::Clone { .. } => "clone",
Self::Rewrite { .. } => "rewrite",
Self::Route { .. } => "route",
Self::Annotate { .. } => "annotate",
Self::Quantize { .. } => "quantize",
Self::Thin { .. } => "thin",
Self::Expand { .. } => "expand",
Self::Sidechain { .. } => "sidechain",
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CallableFilterRef {
pub name: String,
}
impl CallableFilterRef {
pub fn new(name: impl Into<String>) -> Self {
Self { name: name.into() }
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CallableFilterDefinition {
pub name: String,
pub deterministic: bool,
pub uses_read_eval: bool,
pub capabilities: FilterCapabilitySet,
pub rules: Vec<FilterRule>,
}
impl CallableFilterDefinition {
pub fn new(name: impl Into<String>, rules: Vec<FilterRule>) -> Self {
let mut capabilities = FilterCapabilitySet::new([FilterCapability::Callable]);
for rule in &rules {
capabilities.insert(rule.op.capability());
}
Self {
name: name.into(),
deterministic: true,
uses_read_eval: false,
capabilities,
rules,
}
}
pub fn nondeterministic(mut self) -> Self {
self.deterministic = false;
self
}
pub fn with_read_eval(mut self) -> Self {
self.uses_read_eval = true;
self.capabilities.insert(FilterCapability::ReadEval);
self
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct CallableFilterRegistry {
callables: BTreeMap<String, CallableFilterDefinition>,
}
impl CallableFilterRegistry {
pub fn register(&mut self, definition: CallableFilterDefinition) {
self.callables.insert(definition.name.clone(), definition);
}
pub fn get(&self, name: &str) -> Option<&CallableFilterDefinition> {
self.callables.get(name)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct FilterContext {
pub capabilities: FilterCapabilitySet,
pub seed: Option<u64>,
}
impl FilterContext {
pub fn new(capabilities: FilterCapabilitySet) -> Self {
Self {
capabilities,
seed: None,
}
}
pub fn all_capabilities() -> Self {
Self::new(FilterCapabilitySet::all())
}
pub fn with_seed(mut self, seed: u64) -> Self {
self.seed = Some(seed);
self
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CustomFilterRun {
pub events: Vec<PlayEvent>,
pub traces: Vec<CustomFilterTrace>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CustomFilterTrace {
pub sequence: u64,
pub filter_id: String,
pub operation: &'static str,
pub action: FilterTraceAction,
pub event: PlayEvent,
pub message: String,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum FilterTraceAction {
Accepted,
Rejected,
Cloned,
Rewritten,
Routed,
Annotated,
Quantized,
Thinned,
Expanded,
Sidechained,
}
#[derive(Debug, Error, Clone, PartialEq, Eq)]
pub enum CustomFilterError {
#[error("custom filter shape cannot be empty")]
EmptyShape,
#[error("custom filter capability is not declared: {0}")]
UndeclaredCapability(String),
#[error("custom filter missing capability: {0}")]
MissingCapability(String),
#[error("custom filter {phase} shape does not accept {kind} events")]
ShapeMismatch {
phase: &'static str,
kind: &'static str,
},
#[error("custom filter callable is not registered: {0}")]
MissingCallable(String),
#[error("custom filter callable is nondeterministic: {0}")]
NondeterministicCallable(String),
#[error("custom filter seed is required")]
MissingSeed,
#[error("custom filter operation is invalid: {0}")]
InvalidOperation(String),
#[error("custom filter codec is unsupported: {0}")]
UnsupportedCodec(String),
}
pub fn lane_kind_from_wire(value: &str) -> Option<LaneKind> {
Some(match value {
"note" => LaneKind::Note,
"drum" => LaneKind::Drum,
"scale-degree" => LaneKind::ScaleDegree,
"midi" => LaneKind::Midi,
"pitch" => LaneKind::Pitch,
"control" => LaneKind::Control,
"automation" => LaneKind::Automation,
"audio" => LaneKind::Audio,
"object" => LaneKind::Object,
"playable" => LaneKind::Playable,
"performance" => LaneKind::Performance,
"diagnostic" => LaneKind::Diagnostic,
"trace" => LaneKind::Trace,
_ => return None,
})
}
pub fn ensure_custom_filter_codec(codec: &str) -> Result<(), CustomFilterError> {
match codec {
"lisp" | "json" => Ok(()),
_ => Err(CustomFilterError::UnsupportedCodec(codec.to_owned())),
}
}