Reorganize repository
This commit is contained in:
89
crates/project/src/file.rs
Normal file
89
crates/project/src/file.rs
Normal file
@@ -0,0 +1,89 @@
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::project::{Bank, Project};
|
||||
|
||||
const VERSION: u8 = 1;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct ProjectFile {
|
||||
version: u8,
|
||||
banks: Vec<Bank>,
|
||||
#[serde(default)]
|
||||
sample_paths: Vec<PathBuf>,
|
||||
#[serde(default = "default_tempo")]
|
||||
tempo: f64,
|
||||
}
|
||||
|
||||
fn default_tempo() -> f64 {
|
||||
120.0
|
||||
}
|
||||
|
||||
impl From<&Project> for ProjectFile {
|
||||
fn from(project: &Project) -> Self {
|
||||
Self {
|
||||
version: VERSION,
|
||||
banks: project.banks.clone(),
|
||||
sample_paths: project.sample_paths.clone(),
|
||||
tempo: project.tempo,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ProjectFile> for Project {
|
||||
fn from(file: ProjectFile) -> Self {
|
||||
Self {
|
||||
banks: file.banks,
|
||||
sample_paths: file.sample_paths,
|
||||
tempo: file.tempo,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum FileError {
|
||||
Io(io::Error),
|
||||
Json(serde_json::Error),
|
||||
Version(u8),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for FileError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
FileError::Io(e) => write!(f, "IO error: {e}"),
|
||||
FileError::Json(e) => write!(f, "JSON error: {e}"),
|
||||
FileError::Version(v) => write!(f, "Unsupported version: {v}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for FileError {
|
||||
fn from(e: io::Error) -> Self {
|
||||
FileError::Io(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<serde_json::Error> for FileError {
|
||||
fn from(e: serde_json::Error) -> Self {
|
||||
FileError::Json(e)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn save(project: &Project, path: &Path) -> Result<(), FileError> {
|
||||
let file = ProjectFile::from(project);
|
||||
let json = serde_json::to_string_pretty(&file)?;
|
||||
fs::write(path, json)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn load(path: &Path) -> Result<Project, FileError> {
|
||||
let json = fs::read_to_string(path)?;
|
||||
let file: ProjectFile = serde_json::from_str(&json)?;
|
||||
if file.version > VERSION {
|
||||
return Err(FileError::Version(file.version));
|
||||
}
|
||||
Ok(Project::from(file))
|
||||
}
|
||||
10
crates/project/src/lib.rs
Normal file
10
crates/project/src/lib.rs
Normal file
@@ -0,0 +1,10 @@
|
||||
mod file;
|
||||
mod project;
|
||||
|
||||
pub const MAX_BANKS: usize = 16;
|
||||
pub const MAX_PATTERNS: usize = 16;
|
||||
pub const MAX_STEPS: usize = 128;
|
||||
pub const DEFAULT_LENGTH: usize = 16;
|
||||
|
||||
pub use file::{load, save, FileError};
|
||||
pub use project::{Bank, Pattern, PatternSpeed, Project, Step};
|
||||
210
crates/project/src/project.rs
Normal file
210
crates/project/src/project.rs
Normal file
@@ -0,0 +1,210 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{DEFAULT_LENGTH, MAX_BANKS, MAX_PATTERNS, MAX_STEPS};
|
||||
|
||||
#[derive(Clone, Copy, Serialize, Deserialize, Default, PartialEq)]
|
||||
pub enum PatternSpeed {
|
||||
Eighth, // 1/8x
|
||||
Quarter, // 1/4x
|
||||
Half, // 1/2x
|
||||
#[default]
|
||||
Normal, // 1x
|
||||
Double, // 2x
|
||||
Quad, // 4x
|
||||
Octo, // 8x
|
||||
}
|
||||
|
||||
impl PatternSpeed {
|
||||
pub fn multiplier(&self) -> f64 {
|
||||
match self {
|
||||
Self::Eighth => 0.125,
|
||||
Self::Quarter => 0.25,
|
||||
Self::Half => 0.5,
|
||||
Self::Normal => 1.0,
|
||||
Self::Double => 2.0,
|
||||
Self::Quad => 4.0,
|
||||
Self::Octo => 8.0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn label(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Eighth => "1/8x",
|
||||
Self::Quarter => "1/4x",
|
||||
Self::Half => "1/2x",
|
||||
Self::Normal => "1x",
|
||||
Self::Double => "2x",
|
||||
Self::Quad => "4x",
|
||||
Self::Octo => "8x",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next(&self) -> Self {
|
||||
match self {
|
||||
Self::Eighth => Self::Quarter,
|
||||
Self::Quarter => Self::Half,
|
||||
Self::Half => Self::Normal,
|
||||
Self::Normal => Self::Double,
|
||||
Self::Double => Self::Quad,
|
||||
Self::Quad => Self::Octo,
|
||||
Self::Octo => Self::Octo,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prev(&self) -> Self {
|
||||
match self {
|
||||
Self::Eighth => Self::Eighth,
|
||||
Self::Quarter => Self::Eighth,
|
||||
Self::Half => Self::Quarter,
|
||||
Self::Normal => Self::Half,
|
||||
Self::Double => Self::Normal,
|
||||
Self::Quad => Self::Double,
|
||||
Self::Octo => Self::Quad,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_label(s: &str) -> Option<Self> {
|
||||
match s.trim() {
|
||||
"1/8x" | "1/8" | "0.125x" => Some(Self::Eighth),
|
||||
"1/4x" | "1/4" | "0.25x" => Some(Self::Quarter),
|
||||
"1/2x" | "1/2" | "0.5x" => Some(Self::Half),
|
||||
"1x" | "1" => Some(Self::Normal),
|
||||
"2x" | "2" => Some(Self::Double),
|
||||
"4x" | "4" => Some(Self::Quad),
|
||||
"8x" | "8" => Some(Self::Octo),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct Step {
|
||||
pub active: bool,
|
||||
pub script: String,
|
||||
#[serde(skip)]
|
||||
pub command: Option<String>,
|
||||
#[serde(default)]
|
||||
pub source: Option<usize>,
|
||||
}
|
||||
|
||||
impl Default for Step {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
active: true,
|
||||
script: String::new(),
|
||||
command: None,
|
||||
source: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct Pattern {
|
||||
pub steps: Vec<Step>,
|
||||
pub length: usize,
|
||||
#[serde(default)]
|
||||
pub speed: PatternSpeed,
|
||||
#[serde(default)]
|
||||
pub name: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for Pattern {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
steps: (0..MAX_STEPS).map(|_| Step::default()).collect(),
|
||||
length: DEFAULT_LENGTH,
|
||||
speed: PatternSpeed::default(),
|
||||
name: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Pattern {
|
||||
pub fn step(&self, index: usize) -> Option<&Step> {
|
||||
self.steps.get(index)
|
||||
}
|
||||
|
||||
pub fn step_mut(&mut self, index: usize) -> Option<&mut Step> {
|
||||
self.steps.get_mut(index)
|
||||
}
|
||||
|
||||
pub fn set_length(&mut self, length: usize) {
|
||||
let length = length.clamp(1, MAX_STEPS);
|
||||
while self.steps.len() < length {
|
||||
self.steps.push(Step::default());
|
||||
}
|
||||
self.length = length;
|
||||
}
|
||||
|
||||
pub fn resolve_source(&self, index: usize) -> usize {
|
||||
let mut current = index;
|
||||
for _ in 0..self.steps.len() {
|
||||
if let Some(step) = self.steps.get(current) {
|
||||
if let Some(source) = step.source {
|
||||
current = source;
|
||||
} else {
|
||||
return current;
|
||||
}
|
||||
} else {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
index
|
||||
}
|
||||
|
||||
pub fn resolve_script(&self, index: usize) -> Option<&str> {
|
||||
let source_idx = self.resolve_source(index);
|
||||
self.steps.get(source_idx).map(|s| s.script.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct Bank {
|
||||
pub patterns: Vec<Pattern>,
|
||||
#[serde(default)]
|
||||
pub name: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for Bank {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
patterns: (0..MAX_PATTERNS).map(|_| Pattern::default()).collect(),
|
||||
name: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct Project {
|
||||
pub banks: Vec<Bank>,
|
||||
#[serde(default)]
|
||||
pub sample_paths: Vec<PathBuf>,
|
||||
#[serde(default = "default_tempo")]
|
||||
pub tempo: f64,
|
||||
}
|
||||
|
||||
fn default_tempo() -> f64 {
|
||||
120.0
|
||||
}
|
||||
|
||||
impl Default for Project {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
banks: (0..MAX_BANKS).map(|_| Bank::default()).collect(),
|
||||
sample_paths: Vec::new(),
|
||||
tempo: default_tempo(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Project {
|
||||
pub fn pattern_at(&self, bank: usize, pattern: usize) -> &Pattern {
|
||||
&self.banks[bank].patterns[pattern]
|
||||
}
|
||||
|
||||
pub fn pattern_at_mut(&mut self, bank: usize, pattern: usize) -> &mut Pattern {
|
||||
&mut self.banks[bank].patterns[pattern]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user