From df9102e1b01876c89fa1ff11db15190b3e8795e1 Mon Sep 17 00:00:00 2001 From: raeaw Date: Sun, 4 Feb 2024 10:32:58 +1030 Subject: [PATCH 1/4] feat: backup and restore of samples using yaml file --- Cargo.lock | 110 ++++++++++++++++++++--- Cargo.toml | 2 + rustfmt.toml | 77 ++++++++++++++++ src/domain.rs | 5 ++ src/domain/backup.rs | 16 ++++ src/domain/sample_slots.rs | 134 ++++++++++++++++++++++++++++ src/main.rs | 175 ++++++++++++++++++++++++++++++++++--- src/opt.rs | 21 +++++ src/util.rs | 4 +- 9 files changed, 517 insertions(+), 27 deletions(-) create mode 100644 rustfmt.toml create mode 100644 src/domain.rs create mode 100644 src/domain/backup.rs create mode 100644 src/domain/sample_slots.rs diff --git a/Cargo.lock b/Cargo.lock index 582a9e4..3cb8b8e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -45,7 +45,7 @@ dependencies = [ "derive_utils", "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -92,7 +92,7 @@ checksum = "1aca418a974d83d40a0c1f0c5cba6ff4bc28d8df099109ca459a2118d40b6322" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -138,7 +138,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -166,7 +166,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn", + "syn 1.0.107", ] [[package]] @@ -177,9 +177,15 @@ checksum = "7590f99468735a318c254ca9158d0c065aa9b5312896b5a043b5e39bc96f5fa2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "errno" version = "0.2.8" @@ -227,6 +233,12 @@ dependencies = [ "wasi", ] +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + [[package]] name = "heck" version = "0.4.1" @@ -257,6 +269,16 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +[[package]] +name = "indexmap" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "433de089bd45971eecf4668ee0ee8f4cec17db4f8bd8f7bc3197a6ce37aa7d9b" +dependencies = [ + "equivalent", + "hashbrown", +] + [[package]] name = "instant" version = "0.1.12" @@ -288,6 +310,12 @@ dependencies = [ "windows-sys 0.45.0", ] +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + [[package]] name = "lazy_static" version = "1.4.0" @@ -425,7 +453,7 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", - "syn", + "syn 1.0.107", "version_check", ] @@ -442,9 +470,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.51" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" dependencies = [ "unicode-ident", ] @@ -484,9 +512,9 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quote" -version = "1.0.23" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] @@ -617,12 +645,51 @@ dependencies = [ "wait-timeout", ] +[[package]] +name = "ryu" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" + [[package]] name = "semver" version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a" +[[package]] +name = "serde" +version = "1.0.196" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.196" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "serde_yaml" +version = "0.9.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adf8a49373e98a4c5f0ceb5d05aa7c648d75f63774981ed95b7c7443bbd50c6e" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + [[package]] name = "sharded-slab" version = "0.1.4" @@ -661,6 +728,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "2.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "tempfile" version = "3.4.0" @@ -700,7 +778,7 @@ checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -733,7 +811,7 @@ checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -793,6 +871,12 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" +[[package]] +name = "unsafe-libyaml" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b" + [[package]] name = "valuable" version = "0.1.0" @@ -821,6 +905,8 @@ dependencies = [ "humantime", "proptest", "rubato", + "serde", + "serde_yaml", "smallvec", "thiserror", "tracing", diff --git a/Cargo.toml b/Cargo.toml index e497aa0..0eb573a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,8 @@ clap = { version = "4.1", features = ["derive"] } humantime = "2.1.0" tracing = "0.1" tracing-subscriber = "0.3" +serde = { version = "1.0.196", features = ["derive"] } +serde_yaml = "0.9.31" [dev-dependencies] proptest = "1.1.0" diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..56cba64 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,77 @@ +max_width = 100 +hard_tabs = false +tab_spaces = 4 +newline_style = "Auto" +indent_style = "Block" +use_small_heuristics = "Default" +fn_call_width = 60 +attr_fn_like_width = 70 +struct_lit_width = 18 +struct_variant_width = 35 +array_width = 60 +chain_width = 60 +single_line_if_else_max_width = 50 +wrap_comments = false +format_code_in_doc_comments = false +doc_comment_code_block_width = 100 +comment_width = 80 +normalize_comments = false +normalize_doc_attributes = false +format_strings = false +format_macro_matchers = false +format_macro_bodies = true +skip_macro_invocations = [] +hex_literal_case = "Preserve" +empty_item_single_line = true +struct_lit_single_line = true +fn_single_line = false +where_single_line = false +imports_indent = "Block" +imports_layout = "Mixed" +imports_granularity = "Preserve" +group_imports = "Preserve" +reorder_imports = true +reorder_modules = true +reorder_impl_items = false +type_punctuation_density = "Wide" +space_before_colon = false +space_after_colon = true +spaces_around_ranges = false +binop_separator = "Front" +remove_nested_parens = true +combine_control_expr = true +short_array_element_width_threshold = 10 +overflow_delimited_expr = false +struct_field_align_threshold = 0 +enum_discrim_align_threshold = 0 +match_arm_blocks = true +match_arm_leading_pipes = "Never" +force_multiline_blocks = false +fn_params_layout = "Tall" +brace_style = "SameLineWhere" +control_brace_style = "AlwaysSameLine" +trailing_semicolon = true +trailing_comma = "Vertical" +match_block_trailing_comma = false +blank_lines_upper_bound = 1 +blank_lines_lower_bound = 0 +edition = "2015" +version = "One" +inline_attribute_width = 0 +format_generated_files = true +merge_derives = true +use_try_shorthand = false +use_field_init_shorthand = false +force_explicit_abi = true +condense_wildcard_suffixes = false +color = "Auto" +required_version = "1.5.2" +unstable_features = false +disable_all_formatting = false +skip_children = false +hide_parse_errors = false +error_on_line_overflow = false +error_on_unformatted = false +ignore = [] +emit_mode = "Files" +make_backup = false diff --git a/src/domain.rs b/src/domain.rs new file mode 100644 index 0000000..b9e2e4d --- /dev/null +++ b/src/domain.rs @@ -0,0 +1,5 @@ +mod backup; +mod sample_slots; + +pub use backup::BackupData; +pub use sample_slots::SampleSlots; diff --git a/src/domain/backup.rs b/src/domain/backup.rs new file mode 100644 index 0000000..15a79c5 --- /dev/null +++ b/src/domain/backup.rs @@ -0,0 +1,16 @@ +//! Handles serialization and deserialization of backup structs +use super::sample_slots::SampleSlots; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize)] +pub struct BackupData { + pub sample_slots: SampleSlots, +} + +impl BackupData { + pub fn new() -> Self { + Self { + sample_slots: SampleSlots::new(), + } + } +} diff --git a/src/domain/sample_slots.rs b/src/domain/sample_slots.rs new file mode 100644 index 0000000..a0cd106 --- /dev/null +++ b/src/domain/sample_slots.rs @@ -0,0 +1,134 @@ +//! Handles backups of the volca's sample memory slot layout +use serde::de::{Deserialize, Deserializer, MapAccess, Visitor}; +use serde::ser::{Serialize, SerializeMap, Serializer}; +use std::fmt; +use std::ops::{Index, IndexMut}; + +extern crate serde_yaml; + +// This is used instead of a HashMap to enforce a fixed set of keys and +// customize serialization behaviour for readability +pub struct SampleSlots { + slots: [Option; 200], +} + +impl SampleSlots { + const EMPTY_SAMPLE: Option = Option::None; + + pub fn new() -> Self { + Self { + slots: [Self::EMPTY_SAMPLE; 200], + } + } + pub fn len(&self) -> usize { + self.slots.len() + } +} + +impl Index for SampleSlots { + type Output = Option; + + fn index(&self, index: usize) -> &Self::Output { + return &self.slots[index as usize]; + } +} + +impl IndexMut for SampleSlots { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + return &mut self.slots[index as usize]; + } +} + +impl Serialize for SampleSlots { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut map = serializer.serialize_map(Some(self.slots.len()))?; + for i in 0..self.slots.len() { + if self.slots[i].is_some() { + map.serialize_entry(&i, &self.slots[i])?; + } + } + map.end() + } +} + +struct SampleSlotsVisitor; + +impl SampleSlotsVisitor { + fn new() -> Self { + SampleSlotsVisitor {} + } +} + +impl<'de> Visitor<'de> for SampleSlotsVisitor { + // The type that our Visitor is going to produce. + type Value = SampleSlots; + + // Format a message stating what data this Visitor expects to receive. + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a map of sample memory locations to filenames without file extensions") + } + + // Deserialize MyMap from an abstract "map" provided by the + // Deserializer. The MapAccess input is a callback provided by + // the Deserializer to let us see each entry in the map. + fn visit_map(self, mut access: M) -> Result + where + M: MapAccess<'de>, + { + let mut backup = SampleSlots::new(); + + // While there are entries remaining in the input, add them + // into our map. + while let Some((key, value)) = access.next_entry::()? { + backup.slots[key] = Some(value); + } + + Ok(backup) + } +} + +// This is the trait that informs Serde how to deserialize MyMap. +impl<'de> Deserialize<'de> for SampleSlots { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + // Instantiate our Visitor and ask the Deserializer to drive + // it over the input data, resulting in an instance of MyMap. + deserializer.deserialize_map(SampleSlotsVisitor::new()) + } +} + +// tests use yaml at the moment but could be made generic to support other +// (de)serializers +#[cfg(test)] +mod tests { + use super::*; + + const EXPECTED_YAML: &str = "0: Hello1\n2: Hello3\n"; + + #[test] + fn serialize_samples_backup() { + let mut samples_backup = SampleSlots::new(); + samples_backup.slots[0] = Some(String::from("Hello1")); + samples_backup.slots[2] = Some(String::from("Hello3")); + + let s = serde_yaml::to_string(&samples_backup).unwrap(); + assert_eq!(EXPECTED_YAML, s); + } + + #[test] + fn deserialize_samples_backup() { + let samples_backup: SampleSlots = serde_yaml::from_str(EXPECTED_YAML).unwrap(); + + assert_eq!("Hello1", samples_backup.slots[0].as_ref().unwrap()); + assert_eq!(None, samples_backup.slots[1]); + assert_eq!("Hello3", samples_backup.slots[2].as_ref().unwrap()); + for i in 3..200 { + assert_eq!(None, samples_backup.slots[i]); + } + } +} diff --git a/src/main.rs b/src/main.rs index 8cc4550..f0ab518 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,12 @@ mod audio; mod device; +mod domain; mod opt; mod proto; mod seven_bit; mod util; +use std::fs; use std::path::{Path, PathBuf}; use std::time::Duration; @@ -13,6 +15,7 @@ use clap::Parser; use crate::audio::{write_sample_to_file, AudioReader, MonoMode}; use crate::device::Device; +use crate::domain::BackupData; use crate::util::{ask, extract_file_name, normalize_path}; struct App { @@ -76,7 +79,13 @@ impl App { Self::save_sample(&sample_data.data, &output, &header.name, sample_type) } - fn upload_sample(&mut self, sample_no: Option, name: &str, data: Vec) -> Result<()> { + fn upload_sample( + &mut self, + sample_no: Option, + name: &str, + data: Vec, + check_overwrite: bool, + ) -> Result<()> { let volca = self.volca()?; let sample_no = sample_no .map(Ok) @@ -90,7 +99,7 @@ impl App { .ok_or_else(|| anyhow!("could not find empty slot"))??; let current_header = volca.get_sample_header(sample_no)?; - if !current_header.is_empty() { + if !current_header.is_empty() && check_overwrite { // TODO: format_args? let question = format!( "Sample slot is not empty (current - {}). Do you want to overwrite?", @@ -115,12 +124,36 @@ impl App { Ok(()) } + fn upload_sample_from_file( + &mut self, + input: PathBuf, + sample_no: Option, + mono_mode: MonoMode, + output: Option, + dry_run: bool, + name: Option<&str>, + check_overwrite: bool, + ) -> Result<()> { + let file_name = extract_file_name(&input)?; + let name = name.unwrap_or(&file_name); + let sample = Self::load_audio_file(&input, mono_mode)?; + output + .map(|path| Self::save_sample(&sample, &path, &name, "processed")) + .transpose()?; + + if !dry_run { + self.upload_sample(sample_no, &name, sample, check_overwrite)?; + } + + Ok(()) + } + fn delete_sample(&mut self, sample_no: u8, print_name: bool) -> Result<()> { let volca = self.volca()?; let name = if print_name { let mut header = volca.get_sample_header(sample_no)?; if header.is_empty() { - println!("Sample is already empty"); + println!("Sample {sample_no} is already empty"); return Ok(()); } @@ -147,13 +180,134 @@ impl App { } fn save_sample(data: &[i16], path: &Path, name: &str, sample_type: &str) -> Result<()> { - let output = normalize_path(path, name)?; + let output = normalize_path(path, name, "wav")?; write_sample_to_file(data, &output)?; let space = if sample_type.is_empty() { "" } else { " " }; println!("Wrote {sample_type}{space}sample to {output:?}"); Ok(()) } + + fn get_sample_memory_backup(&mut self) -> Result { + let volca = self.volca()?; + + let mut backup = BackupData::new(); + + for header in volca + .iter_sample_headers() + .filter(|res| res.as_ref().map_or(true, |header| !header.is_empty())) + { + let header = header?; + backup.sample_slots[header.sample_no as usize] = Some(header.name); + } + + Ok(backup) + } + + fn download_backup_data(&mut self, output: PathBuf) -> Result<()> { + let backup = self.get_sample_memory_backup()?; + Self::save_backup_data(backup, output) + } + + fn save_backup_data(backup: BackupData, output: PathBuf) -> Result<()> { + let f = fs::OpenOptions::new() + .write(true) + .create(true) + .open(&output)?; + serde_yaml::to_writer(f, &backup)?; + + Ok(()) + } + + fn load_backup_data(input: &PathBuf) -> Result { + // check extension to enforce yaml format + let ext = match input.extension() { + Some(ffi_str) => ffi_str.to_str().unwrap_or(""), + None => "", + }; + + if ext != "yaml" { + return Err(anyhow!( + "Volsa2 currently only supports volca backups in Yaml format, \ + input path was {ext}" + )); + } + + let f = fs::OpenOptions::new().read(true).open(&input)?; + let backup: BackupData = serde_yaml::from_reader(f)?; + + Ok(backup) + } + + fn backup(&mut self, output: PathBuf, sample_type: &str) -> Result<()> { + let backup = self.get_sample_memory_backup()?; + fs::create_dir_all(&output)?; + + let volca = self.volca()?; + + for i in 0..backup.sample_slots.len() { + match &backup.sample_slots[i] { + Some(slot) => { + println!(r#"Downloading sample "{}" from Volca"#, slot); + let sample_data = volca.get_sample(i as u8)?; + Self::save_sample( + &sample_data.data, + &output, + &format!("{slot}.wav"), + &sample_type, + )?; + } + None => {} + } + } + + let layout_filename = normalize_path(&output, "layout", "yaml")?; + Self::save_backup_data(backup, layout_filename) + } + + fn restore(&mut self, backup_data_path: PathBuf, dry_run: bool) -> Result<()> { + if !dry_run { + let question = "This will replace all samples on the device. Are you sure?"; + + if !ask(&question)? { + bail!("Restore cancelled"); + } + } + + let backup = Self::load_backup_data(&backup_data_path)?; + + let parent_folder = backup_data_path.parent().unwrap(); + + for i in 0..backup.sample_slots.len() { + match &backup.sample_slots[i] { + Some(sample_name) => { + if dry_run { + println!("{i:03} - {sample_name}"); + } + + let file_name = normalize_path(parent_folder, sample_name.as_str(), "wav")?; + self.upload_sample_from_file( + file_name, + Some(i as u8), + MonoMode::Mid, + None, + dry_run, + Some(sample_name.as_str()), + false, // already checked this for the restore operation + )?; + } + None => { + if dry_run { + println!("{i:03} - EMPTY"); + } else { + self.delete_sample(i as u8, true)?; + } + } + } + } + + return Ok(()); + } } fn main() -> Result<()> { @@ -174,20 +328,15 @@ fn main() -> Result<()> { output, dry_run, } => { - let name = extract_file_name(&file)?; - let sample = App::load_audio_file(&file, mono_mode)?; - output - .map(|path| App::save_sample(&sample, &path, &name, "processed")) - .transpose()?; - - if !dry_run { - app.upload_sample(sample_no, &name, sample)?; - } + app.upload_sample_from_file(file, sample_no, mono_mode, output, dry_run, None, true)? } opt::Operation::Remove { sample_no, print_name, } => app.delete_sample(sample_no, print_name)?, + opt::Operation::Layout { output } => app.download_backup_data(output)?, + opt::Operation::Backup { output } => app.backup(output, "")?, + opt::Operation::Restore { input, dry_run } => app.restore(input, dry_run)?, } Ok(()) diff --git a/src/opt.rs b/src/opt.rs index 6df8e9a..e09127e 100644 --- a/src/opt.rs +++ b/src/opt.rs @@ -61,4 +61,25 @@ pub enum Operation { #[arg(short, long, default_value = "false")] print_name: bool, }, + /// Save the sample memory layout to a YAML file + #[command(alias = "lay")] + Layout { + /// Output path + output: PathBuf, + }, + /// Backup entire sample memory to a given folder + #[command(alias = "bk")] + Backup { + /// Output folder path + output: PathBuf, + }, + /// Restore entire sample memory from a given yaml + #[command(alias = "rs")] + Restore { + /// Input yaml path + input: PathBuf, + /// Do no upload the sample, just test + #[arg(short = 'd', long, default_value = "false")] + dry_run: bool, + }, } diff --git a/src/util.rs b/src/util.rs index 259b61a..65cd2df 100644 --- a/src/util.rs +++ b/src/util.rs @@ -76,11 +76,11 @@ pub fn ask(question: &str) -> io::Result { } } -pub fn normalize_path(path: &Path, filename: &str) -> Result { +pub fn normalize_path(path: &Path, filename: &str, extension: &str) -> Result { let mut path = path.canonicalize()?; if path.is_dir() { path.push(filename); - path.set_extension("wav"); + path.set_extension(extension); } Ok(path) } From 81f97152100ce9d171447493c49f0b313ff63a8f Mon Sep 17 00:00:00 2001 From: raeaw Date: Sun, 4 Feb 2024 10:50:51 +1030 Subject: [PATCH 2/4] Update README.md with backup/restore functionality --- README.md | 14 ++++++++++++++ src/main.rs | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7d2198c..168fcf7 100644 --- a/README.md +++ b/README.md @@ -60,3 +60,17 @@ Volsa2 will offer you to backup the sample if the desired slot is occupied. volsa2-cli remove ``` Erases sample at slot `` from the device memory. Use `-p`/`--print-name` if you want to print the name of the sample. + +### Backup (`bk`) +```sh +volsa2-cli backup +``` +Creates a folder at `` if one doesn't already exist, and dumps all samples from the Volca Sample 2 into it. Creates a file called layout.yaml in the folder that specifies which samples are to be inserted into which sample slots. + +### Restore (`rs`) +```sh +volsa2-cli restore +``` +Reads the backup data in the yaml file at ``, and attempts to restore the Volca Sample 2 to the state specified in this yaml file. This means it will clear slots that are not specified in the yaml, and upload the samples that are specified. This expects the samples to be placed in the same directory as the yaml file. +##### Options: +- `--dry-run` - Check the behaviour without actually modifying anything on the device diff --git a/src/main.rs b/src/main.rs index f0ab518..5cc8b75 100644 --- a/src/main.rs +++ b/src/main.rs @@ -164,7 +164,7 @@ impl App { }; volca.delete_sample(sample_no)?; - println!("Removed sample {name}at slot {sample_no}"); + println!("Removed sample {name} at slot {sample_no}"); Ok(()) } From 43d3d16d5ef2144cb944a864980fb7630ca84849 Mon Sep 17 00:00:00 2001 From: raeaw Date: Sun, 4 Feb 2024 11:02:04 +1030 Subject: [PATCH 3/4] Flesh out README.md --- README.md | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 168fcf7..5c1a9a3 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,23 @@ Creates a folder at `` if one doesn't already exist, and ```sh volsa2-cli restore ``` -Reads the backup data in the yaml file at ``, and attempts to restore the Volca Sample 2 to the state specified in this yaml file. This means it will clear slots that are not specified in the yaml, and upload the samples that are specified. This expects the samples to be placed in the same directory as the yaml file. +Reads the backup data in the yaml file at ``, and attempts to restore the Volca Sample 2 to the state specified in this yaml file. This means it will clear slots that are not specified in the yaml, and upload the samples that are specified. This expects the samples to be placed in the same directory as the yaml file, and to be named the same as specified in the yaml file but with a `.wav` extension. + +For example, your yaml file might look like this: +```yaml +sample_slots: + 0: bd909 + 1: bd808 + 2: bd707 +``` +and your directory may look like this: +``` +sample_backup/ +|-bd909.wav +|-bd808.wav +|-bd707.wav +``` +When restored, all sample slots on the Volca Sample 2 will be cleared except for the first three which will contain the three `bdx0x.wav` samples. + ##### Options: - `--dry-run` - Check the behaviour without actually modifying anything on the device From 92d7aa8e0d0892f29acfb250b939c799396af530 Mon Sep 17 00:00:00 2001 From: raeaw Date: Sun, 4 Feb 2024 11:03:05 +1030 Subject: [PATCH 4/4] Flesh out README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 5c1a9a3..af8363e 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,7 @@ Reads the backup data in the yaml file at ``, and attempts to r For example, your yaml file might look like this: ```yaml +# layout.yaml sample_slots: 0: bd909 1: bd808 @@ -86,6 +87,7 @@ sample_backup/ |-bd909.wav |-bd808.wav |-bd707.wav +|-layout.yaml ``` When restored, all sample slots on the Volca Sample 2 will be cleared except for the first three which will contain the three `bdx0x.wav` samples.