use super::{Pattern, Snapshot, SnapshotSequencer};
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone)]
pub struct SequencerDocument {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub snapshots: Vec<Snapshot>,
pub patterns: Vec<Pattern>,
#[serde(default)]
pub active_pattern: String,
#[serde(default)]
pub auto_start: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
impl SequencerDocument {
pub fn new(patterns: Vec<Pattern>) -> Self {
Self {
snapshots: Vec::new(),
patterns,
active_pattern: String::new(),
auto_start: false,
description: None,
}
}
pub fn into_sequencer(self) -> SnapshotSequencer {
let mut seq = SnapshotSequencer::with_lib(self.snapshots, self.patterns);
if !self.active_pattern.is_empty() {
seq.set_active_pattern(&self.active_pattern);
}
if self.auto_start {
seq.start();
}
seq
}
#[cfg(feature = "json")]
pub fn to_json(&self) -> Result<String, String> {
serde_json::to_string_pretty(self).map_err(|e| e.to_string())
}
#[cfg(feature = "json")]
pub fn from_json(json: &str) -> Result<Self, String> {
serde_json::from_str(json).map_err(|e| e.to_string())
}
#[cfg(feature = "cbor")]
pub fn to_cbor(&self) -> Result<Vec<u8>, String> {
serde_cbor::to_vec(self).map_err(|e| e.to_string())
}
#[cfg(feature = "cbor")]
pub fn from_cbor(bytes: &[u8]) -> Result<Self, String> {
serde_cbor::from_slice(bytes).map_err(|e| e.to_string())
}
}
#[cfg(test)]
#[cfg(feature = "serde")]
mod tests {
use super::*;
use crate::sequencer::{ParameterTarget, SequenceStep, StepPlayMode};
use rill_core::NodeId;
fn sample_doc() -> SequencerDocument {
SequencerDocument {
snapshots: vec![Snapshot::new(
"verse",
vec![ParameterTarget::new(NodeId(1), "gain", 0.8)],
)],
patterns: vec![Pattern::new(
"main",
vec![
SequenceStep::single(NodeId(1), "cutoff", 0.3, 1.0),
SequenceStep::single(NodeId(1), "cutoff", 0.7, 1.0),
],
)],
active_pattern: "main".into(),
auto_start: true,
description: Some("test preset".into()),
}
}
#[test]
fn test_json_roundtrip() {
let doc = sample_doc();
let json = doc.to_json().unwrap();
let restored = SequencerDocument::from_json(&json).unwrap();
assert_eq!(restored.snapshots.len(), 1);
assert_eq!(restored.patterns.len(), 1);
assert_eq!(restored.active_pattern, "main");
assert!(restored.auto_start);
assert_eq!(restored.description.as_deref(), Some("test preset"));
assert_eq!(restored.patterns[0].steps.len(), 2);
}
#[test]
fn test_into_sequencer() {
let doc = sample_doc();
let seq = doc.into_sequencer();
assert!(seq.is_running());
assert_eq!(seq.active_pattern(), "main");
}
#[test]
fn test_empty_document() {
let doc = SequencerDocument::new(vec![]);
let json = doc.to_json().unwrap();
let restored = SequencerDocument::from_json(&json).unwrap();
assert!(restored.patterns.is_empty());
assert!(restored.snapshots.is_empty());
let seq = restored.into_sequencer();
assert!(!seq.is_running());
}
#[cfg(feature = "cbor")]
#[test]
fn test_cbor_roundtrip() {
let doc = sample_doc();
let cbor = doc.to_cbor().unwrap();
let restored = SequencerDocument::from_cbor(&cbor).unwrap();
assert_eq!(restored.active_pattern, "main");
}
}