Skip to content

Commit 64b4cc5

Browse files
committed
Persistent loops
1 parent 94bfcb7 commit 64b4cc5

File tree

12 files changed

+369
-122
lines changed

12 files changed

+369
-122
lines changed

builder-proc-macros/src/lib.rs

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,8 +124,6 @@ pub fn builder(input: TokenStream) -> TokenStream {
124124
let mut build_fn = Vec::new();
125125
let mut build_ty = Vec::new();
126126
let mut constructor = None;
127-
let mut constructor_generics: Punctuated<GenericParam, Comma> =
128-
Punctuated::new();
129127
let mut generic_setter_type_name = None;
130128
let mut constructor_doc = None;
131129
let mut constructor_where_predicates = Vec::new();
@@ -166,6 +164,35 @@ pub fn builder(input: TokenStream) -> TokenStream {
166164
}
167165
continue;
168166
}
167+
if attr.path().is_ident("extra_generic") {
168+
let list = attr.meta.require_list().expect("Expected list");
169+
let args = list
170+
.parse_args_with(
171+
Punctuated::<LitStr, Token![,]>::parse_terminated,
172+
)
173+
.expect("Failed to parse argument list");
174+
assert!(
175+
args.len() == 2,
176+
"#[extra_generics(type, constraint)] expected 2 arguments, got {}.",
177+
args.len()
178+
);
179+
let type_ident = args.get(0).unwrap();
180+
let constraint = args.get(1).unwrap();
181+
let type_ident: Ident =
182+
type_ident.parse().expect("Expected identifier");
183+
let constraint = constraint
184+
.parse_with(
185+
Punctuated::<TypeParamBound, Token![+]>::parse_terminated,
186+
)
187+
.expect("Expected constraint");
188+
let generic_type_param =
189+
generic_type_param(type_ident, &constraint);
190+
input
191+
.generics
192+
.params
193+
.push(GenericParam::Type(generic_type_param));
194+
continue;
195+
}
169196
if attr.path().is_ident("generic_setter_type_name") {
170197
if let Some(s) = attr_lit_str(&attr) {
171198
let ident: Ident = s.parse().expect("Expected identifier");
@@ -202,6 +229,7 @@ pub fn builder(input: TokenStream) -> TokenStream {
202229
exactly once, or not set at all."
203230
);
204231
}
232+
let mut constructor_generics = input.generics.params.clone();
205233
for field in input.fields.iter_mut() {
206234
if let Some(ident) = field.ident.as_ref() {
207235
all_field_idents_in_order.push(ident.clone());

caw/examples/live_example.rs

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,19 +25,16 @@ fn main() {
2525
let length = 16;
2626
out.set_channel(|channel| {
2727
let voice = key_events.clone().mono_voice();
28-
let (note, gate) = key_looper(
29-
voice.gated_note(),
30-
clock.clone(),
31-
space_button.clone(),
32-
length,
33-
)
34-
.ungated();
35-
let cutoff_hz = value_looper(
36-
cutoff_hz.clone(),
37-
clock.clone(),
38-
lpf_space.clone(),
39-
length,
40-
);
28+
let (note, gate) = key_looper(voice.gated_note(), clock.clone())
29+
.clearing(space_button.clone())
30+
.length(length)
31+
.name("keys")
32+
.build()
33+
.ungated();
34+
let cutoff_hz =
35+
value_looper(cutoff_hz.clone(), clock.clone(), lpf_space.clone())
36+
.length(length)
37+
.build();
4138
let env = adsr(gate)
4239
.key_press_trig(clock.clone())
4340
.a(tempo_s.clone() * knob("attack").build())

keyboard/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ edition = "2024"
1313
web = ["getrandom/wasm_js"]
1414

1515
[dependencies]
16+
serde = { version = "1.0", features = ["serde_derive"] }
1617
caw_core = { version = "0.5", path = "../core" }
1718
smallvec = ">=1.6.1,<2"
1819
log = "0.4"

keyboard/src/a440_12tet.rs

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@
55
//! note is G_9. Thus the 9th octave does not contain all notes. C is considered the first note in
66
//! each octave.
77
use caw_core::{Buf, ConstBuf, Sig, SigCtx, SigT};
8+
use serde::{Deserialize, Serialize};
89
use std::{fmt::Display, str::FromStr};
910

1011
/// Octaves go from -1 to 8. Some notes in the 9th octave can be constructed, however the regular
1112
/// `Note::new` function can't be passed the 9th octave since that would permit construction of
1213
/// non-MIDI notes (those in the 9th octave above G_9).
13-
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
14+
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)]
1415
pub struct Octave {
1516
/// To make the math easier octaves are represented by the index of the first note of the
1617
/// octave (c) divided by 12. Unfortunately this means that the first octave, named octave "-1"
@@ -85,7 +86,7 @@ pub const OCTAVE_7: Octave = Octave::_7;
8586
pub const OCTAVE_8: Octave = Octave::_8;
8687

8788
/// A note without an octave
88-
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
89+
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)]
8990
pub struct NoteName {
9091
relative_midi_index: u8,
9192
}
@@ -215,7 +216,18 @@ pub fn semitone_ratio(num_semitones: f32) -> f32 {
215216
pub const TONE_RATIO: f32 = 1.122_462;
216217

217218
/// Definition of notes based on MIDI tuned to A_440
218-
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
219+
#[derive(
220+
Debug,
221+
Clone,
222+
Copy,
223+
PartialEq,
224+
Eq,
225+
PartialOrd,
226+
Ord,
227+
Hash,
228+
Serialize,
229+
Deserialize,
230+
)]
219231
pub struct Note {
220232
midi_index: u8,
221233
}
@@ -384,8 +396,10 @@ impl Default for Note {
384396
pub mod chord {
385397
use super::{Note, NoteName, Octave, note_name};
386398
use caw_core::{Buf, ConstBuf, SigCtx, SigT};
399+
use serde::{Deserialize, Serialize};
387400
use smallvec::{SmallVec, smallvec};
388401

402+
#[derive(Clone, Debug)]
389403
pub struct Notes(SmallVec<[Note; 4]>);
390404

391405
impl Notes {
@@ -400,27 +414,27 @@ pub mod chord {
400414
}
401415
}
402416

403-
#[derive(Clone, Copy, Debug)]
417+
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
404418
pub enum Third {
405419
Major,
406420
Minor,
407421
Sus2,
408422
Sus4,
409423
}
410424

411-
#[derive(Clone, Copy, Debug)]
425+
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
412426
pub enum Fifth {
413427
Perfect,
414428
Diminished,
415429
}
416430

417-
#[derive(Clone, Copy, Debug)]
431+
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
418432
pub enum Seventh {
419433
Major,
420434
Minor,
421435
}
422436

423-
#[derive(Clone, Copy, Debug)]
437+
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
424438
pub struct ChordType {
425439
pub third: Option<Third>,
426440
pub fifth: Option<Fifth>,

persistent/src/lib.rs

Lines changed: 90 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -9,88 +9,117 @@ fn caw_tmp_dir() -> PathBuf {
99
tmp_dir.join("caw")
1010
}
1111

12-
pub trait PersistentData: Serialize + for<'a> Deserialize<'a> {
13-
const NAME: &'static str;
12+
fn containing_dir(name: &str) -> PathBuf {
13+
caw_tmp_dir().join(name)
14+
}
15+
16+
fn file_path(name: &str, title: impl AsRef<str>) -> PathBuf {
17+
let title = title.as_ref();
18+
// Hex the title so we can safely use it as part of a file path (ie. so it contains no
19+
// slashes or other characters that have meaning within a file path).
20+
let hexxed_title = hex::encode(title);
21+
let prefix = title
22+
.chars()
23+
.take_while(|&c| {
24+
c.is_ascii_alphanumeric() || c == '_' || c == '-' || c == ' '
25+
})
26+
.map(|c| if c == ' ' { '-' } else { c })
27+
.collect::<String>();
28+
containing_dir(name).join(format!("{}-{}.json", prefix, hexxed_title))
29+
}
30+
31+
fn save(
32+
name: &str,
33+
data: &(impl Serialize + for<'a> Deserialize<'a>),
34+
title: impl AsRef<str>,
35+
) -> anyhow::Result<()> {
36+
use std::io::Write;
37+
fs::create_dir_all(&containing_dir(name))?;
38+
let json_string = serde_json::to_string(data)?;
39+
let mut file = File::create(file_path(name, title))?;
40+
write!(file, "{}", json_string)?;
41+
Ok(())
42+
}
1443

15-
fn containing_dir() -> PathBuf {
16-
caw_tmp_dir().join(Self::NAME)
44+
/// Like `save` but prints a warning on failure rather than returning an error value.
45+
fn save_(
46+
name: &str,
47+
data: &(impl Serialize + for<'a> Deserialize<'a>),
48+
title: impl AsRef<str>,
49+
) {
50+
if let Err(e) = save(name, data, &title) {
51+
log::warn!("Failed to save {} for {}: {}", name, title.as_ref(), e);
1752
}
53+
}
54+
55+
fn load<T>(name: &str, title: impl AsRef<str>) -> anyhow::Result<T>
56+
where
57+
T: Serialize + for<'a> Deserialize<'a>,
58+
{
59+
let json_string = fs::read_to_string(file_path(name, title))?;
60+
let t = serde_json::from_str(json_string.as_str())?;
61+
Ok(t)
62+
}
1863

19-
fn file_path(title: impl AsRef<str>) -> PathBuf {
20-
let title = title.as_ref();
21-
// Hex the title so we can safely use it as part of a file path (ie. so it contains no
22-
// slashes or other characters that have meaning within a file path).
23-
let hexxed_title = hex::encode(title);
24-
let prefix = title
25-
.chars()
26-
.take_while(|&c| {
27-
c.is_ascii_alphanumeric() || c == '_' || c == '-' || c == ' '
28-
})
29-
.map(|c| if c == ' ' { '-' } else { c })
30-
.collect::<String>();
31-
Self::containing_dir().join(format!("{}-{}.json", prefix, hexxed_title))
64+
/// Like `load` but prints a warning on failure rather than returning an error value.
65+
fn load_<T>(name: &str, title: impl AsRef<str>) -> Option<T>
66+
where
67+
T: Serialize + for<'a> Deserialize<'a>,
68+
{
69+
match load(name, &title) {
70+
Ok(t) => Some(t),
71+
Err(e) => {
72+
log::warn!("Failed to load {} for {}: {}", name, title.as_ref(), e);
73+
None
74+
}
3275
}
76+
}
77+
78+
/// The type itself knows how to save and load itself
79+
pub trait PersistentData: Serialize + for<'a> Deserialize<'a> {
80+
const NAME: &'static str;
3381

3482
fn save(&self, title: impl AsRef<str>) -> anyhow::Result<()> {
35-
use std::io::Write;
36-
fs::create_dir_all(&Self::containing_dir())?;
37-
let json_string = serde_json::to_string(self)?;
38-
let mut file = File::create(Self::file_path(title))?;
39-
write!(file, "{}", json_string)?;
40-
Ok(())
83+
save(Self::NAME, self, title)
4184
}
4285

4386
/// Like `save` but prints a warning on failure rather than returning an error value.
4487
fn save_(&self, title: impl AsRef<str>) {
45-
if let Err(e) = self.save(&title) {
46-
log::warn!(
47-
"Failed to save {} for {}: {}",
48-
Self::NAME,
49-
title.as_ref(),
50-
e
51-
);
52-
}
88+
save_(Self::NAME, self, title)
5389
}
5490

5591
fn load(title: impl AsRef<str>) -> anyhow::Result<Self> {
56-
let json_string = fs::read_to_string(Self::file_path(title))?;
57-
let t = serde_json::from_str(json_string.as_str())?;
58-
Ok(t)
92+
load(Self::NAME, title)
5993
}
6094

6195
/// Like `load` but prints a warning on failure rather than returning an error value.
6296
fn load_(title: impl AsRef<str>) -> Option<Self> {
63-
match Self::load(&title) {
64-
Ok(t) => Some(t),
65-
Err(e) => {
66-
log::warn!(
67-
"Failed to load {} for {}: {}",
68-
Self::NAME,
69-
title.as_ref(),
70-
e
71-
);
72-
None
73-
}
74-
}
97+
load_(Self::NAME, title)
7598
}
7699
}
77100

78-
#[derive(Serialize, Deserialize, Clone, Copy, Debug)]
79-
pub struct WindowPosition {
80-
pub x: i32,
81-
pub y: i32,
82-
}
101+
/// Knows how to save and load some type `T`
102+
pub trait PersistentWitness<T>
103+
where
104+
T: Serialize + for<'a> Deserialize<'a>,
105+
{
106+
const NAME: &'static str;
83107

84-
impl PersistentData for WindowPosition {
85-
const NAME: &'static str = "window_position";
86-
}
108+
fn save(&self, data: &T, title: impl AsRef<str>) -> anyhow::Result<()> {
109+
save(Self::NAME, data, title)
110+
}
87111

88-
#[derive(Serialize, Deserialize, Clone, Copy, Debug)]
89-
pub struct WindowSize {
90-
pub width: u32,
91-
pub height: u32,
92-
}
112+
/// Like `save` but prints a warning on failure rather than returning an error value.
113+
fn save_(&self, data: &T, title: impl AsRef<str>) {
114+
save_(Self::NAME, data, title)
115+
}
93116

94-
impl PersistentData for WindowSize {
95-
const NAME: &'static str = "window_size";
117+
fn load(&self, title: impl AsRef<str>) -> anyhow::Result<T> {
118+
load(Self::NAME, title)
119+
}
120+
121+
/// Like `load` but prints a warning on failure rather than returning an error value.
122+
fn load_(&self, title: impl AsRef<str>) -> Option<T> {
123+
load_(Self::NAME, title)
124+
}
96125
}

utils/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ web = ["getrandom/wasm_js"]
1414

1515
[dependencies]
1616
caw_core = { version = "0.5", path = "../core" }
17+
caw_builder_proc_macros = { version = "0.1", path = "../builder-proc-macros" }
18+
serde = { version = "1.0", features = ["serde_derive"] }
19+
caw_persistent = { version = "0.1", path = "../persistent" }
1720
rand = "0.9"
1821
getrandom = "0.3"
1922
itertools = "0.14"

0 commit comments

Comments
 (0)