Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ can't, so install this one the way you installed the last.
Sample packs installed by 0.2 can hold damaged FLAC files: two notes of VCSL's harmonica take hundreds of MB and
play as noise. `claudius-packs install --defaults` converts such files again and downloads nothing else.

Eight more default packs, and 21 more instruments in VCSL and FreePats: Sonic Pi's samples, a solo cello, an erhu, a
steel pan, synth drums, body percussion, glitch kits and sound effects, among them a hang, wine glasses and a TX81Z.
`claudius setup` downloads them, about 0.9 GB more ([Sample packs](https://github.com/devlato/claudius/blob/main/docs/src/guide/packs.md)).

## [0.2.0] - 2026-10-19

### Added
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ terminal instead:
```sh
curl -fsSL https://github.com/devlato/claudius/releases/latest/download/install.sh | sh # macOS (the app too), Linux
irm https://github.com/devlato/claudius/releases/latest/download/install.ps1 | iex # Windows (PowerShell)
claudius setup # sample packs and listening models, about 3 GB to download
claudius setup # sample packs and listening models, about 4 GB to download
```

You can also build it from source; see [install](docs/src/getting-started/install.md).
Expand Down
2 changes: 1 addition & 1 deletion crates/claudius-cli/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ pub(crate) enum Cmd {
},
/// Print the script language reference
Reference,
/// Download the default sample packs (about 1.1 GB) and earshot's listening models (about 0.8 GB)
/// Download the default sample packs (about 3.1 GB) and earshot's listening models (about 0.8 GB)
Setup {
/// do not ask before downloading
#[arg(short, long)]
Expand Down
8 changes: 6 additions & 2 deletions crates/claudius-packs/src/catalog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ pub struct InstSpec {
// --- SFZ source
#[serde(default)]
pub sfz: Option<String>,
/// the folder the SFZ's sample and include paths start from, when it is not the SFZ's own: a mapping file that
/// a program in another folder includes (the erhu's maps, used without the program's unison voices)
#[serde(default)]
pub base: Option<String>,
/// keep this many velocity layers (evenly spread) and widen them to cover the full range
#[serde(default)]
pub keep_layers: Option<usize>,
Expand All @@ -103,7 +107,7 @@ pub struct InstSpec {
/// one variant per distinct key range of the SFZ (unpitched percussion with several hits)
#[serde(default)]
pub kit_auto: bool,
/// regex on sample paths to leave out
/// regex on sample paths to leave out (SFZ regions and file-name sources)
#[serde(default)]
pub exclude: Option<String>,
/// shift roots and key ranges by octaves (mappings at written pitch, e.g. bass guitars one octave up)
Expand Down Expand Up @@ -227,7 +231,7 @@ mod tests {
}
}
}
assert!(default_mb < 3000.0, "default packs are {default_mb} MB");
assert!(default_mb < 3300.0, "default packs are {default_mb} MB");
}

#[test]
Expand Down
41 changes: 41 additions & 0 deletions crates/claudius-packs/src/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -652,6 +652,47 @@ mod tests {
assert!(glob_regex("**/*.wav").unwrap().is_match("a/b/c.wav"));
}

#[test]
fn file_globs_leave_out_what_is_excluded() {
let listing: Vec<(String, u64)> = [
"etc/samples/loop_amen.flac",
"etc/samples/loop_amen_full.flac",
"etc/samples/loop_breakbeat.flac",
"etc/samples/loop_garzul.flac",
"etc/samples/loop_tabla.flac",
"etc/samples/README.md",
"etc/samples/ambi_drone.flac",
]
.iter()
.map(|p| (p.to_string(), 1))
.collect();
let got = matching(&listing, "etc/samples/loop_*.flac", Some("loop_(amen|breakbeat)")).unwrap();
assert_eq!(got, ["etc/samples/loop_garzul.flac", "etc/samples/loop_tabla.flac"]);
assert_eq!(matching(&listing, "etc/samples/loop_*.flac", None).unwrap().len(), 5);
}

#[test]
fn sfz_envelope_and_pan_follow_the_controller_defaults() {
// Karoryfer's style: knobs with defaults open the sustain and centre the pan
let src = r#"
<control> set_cc10=64 set_cc103=127
<global> pan=-100 pan_oncc10=200 ampeg_sustain=0 ampeg_sustain_oncc103=100 ampeg_hold=0.5
ampeg_decay=0.001 ampeg_decay_oncc102=6
<region> sample=a.wav key=60
"#;
let file = sfz::parse(src, &mut |_| Ok(String::new())).unwrap();
let z = sfz_zone(&file.regions[0], &file, 0, false);
assert!(z.pan.abs() < 1.0, "pan {}", z.pan);
assert_eq!(z.sustain, 1.0);
assert!((z.hold - 0.5).abs() < 1e-6);
assert!((z.decay - 0.001).abs() < 1e-6, "cc102 has no default, so it adds nothing: {}", z.decay);
// sfzinstruments' style: a bipolar curve centres the same knob
let file =
sfz::parse("<global> pan_oncc10=100 pan_curvecc10=1 <region> sample=a.wav", &mut |_| Ok(String::new()))
.unwrap();
assert_eq!(sfz_zone(&file.regions[0], &file, 0, false).pan, 0.0);
}

#[test]
fn velocity_layer_reduction() {
// 16 Salamander-style layers on one key
Expand Down
19 changes: 15 additions & 4 deletions crates/claudius-packs/src/install/from_names.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,20 @@ pub(super) fn zones_from_names(paths: &[(u32, String)], vel_re: Option<&Regex>,
zones
}

/// the audio files of a listing that the glob matches and `exclude` (a regex) does not, sorted
pub(super) fn matching(listing: &[(String, u64)], glob: &str, exclude: Option<&str>) -> Result<Vec<String>> {
let re = glob_regex(glob)?;
let exclude = exclude.map(Regex::new).transpose()?;
let mut matched: Vec<String> = listing
.iter()
.map(|(p, _)| p.clone())
.filter(|p| re.is_match(p) && !junk(p) && is_audio(p))
.filter(|p| !exclude.as_ref().is_some_and(|x| x.is_match(p)))
.collect();
matched.sort();
Ok(matched)
}

pub(super) fn build_files(ctx: &mut Ctx, src_key: &str, spec: &InstSpec) -> Result<InstrumentDef> {
let kind = spec_kind(spec);
let listing = ctx.sources.get_mut(src_key).expect("source").list()?;
Expand All @@ -130,10 +144,7 @@ pub(super) fn build_files(ctx: &mut Ctx, src_key: &str, spec: &InstSpec) -> Resu
let mut variants: Vec<Variant> = Vec::new();
for part in parts {
let glob = part.files.as_deref().ok_or_else(|| anyhow!("{}: variant without files", spec.name))?;
let re = glob_regex(glob)?;
let mut matched: Vec<String> =
listing.iter().map(|(p, _)| p.clone()).filter(|p| re.is_match(p) && !junk(p) && is_audio(p)).collect();
matched.sort();
let mut matched = matching(&listing, glob, part.exclude.as_deref().or(spec.exclude.as_deref()))?;
if matched.is_empty() {
bail!("{}: no files match {glob}", spec.name);
}
Expand Down
15 changes: 8 additions & 7 deletions crates/claudius-packs/src/install/from_sfz.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ pub(super) fn sfz_zone(r: &SfzRegion, file: &SfzFile, file_id: u32, keep_release
lorand: r.f32("lorand").unwrap_or(0.0),
hirand: r.f32("hirand").unwrap_or(1.0),
gain_db,
pan: r.f32("pan").unwrap_or(0.0).clamp(-100.0, 100.0),
pan: modded(r, file, "pan", 0.0).clamp(-100.0, 100.0),
offset: modded(r, file, "offset", 0.0).max(0.0) as u32,
end: r.f32("end").unwrap_or(0.0).max(0.0) as u32,
loop_mode,
Expand All @@ -133,9 +133,9 @@ pub(super) fn sfz_zone(r: &SfzRegion, file: &SfzFile, file_id: u32, keep_release
keytrack: r.f32("pitch_keytrack").unwrap_or(100.0) / 100.0,
release,
attack,
hold: r.f32("ampeg_hold").unwrap_or(0.0).max(0.0),
decay: r.f32("ampeg_decay").unwrap_or(0.0).max(0.0),
sustain: (r.f32("ampeg_sustain").unwrap_or(100.0) / 100.0).clamp(0.0, 1.0),
hold: modded(r, file, "ampeg_hold", 0.0).max(0.0),
decay: modded(r, file, "ampeg_decay", 0.0).max(0.0),
sustain: (modded(r, file, "ampeg_sustain", 100.0) / 100.0).clamp(0.0, 1.0),
}
}

Expand Down Expand Up @@ -177,9 +177,10 @@ pub(super) fn drumify(z: &mut Zone, center: f32) {

pub(super) fn build_sfz(ctx: &mut Ctx, src_key: &str, spec: &InstSpec) -> Result<Vec<InstrumentDef>> {
let sfz_path = spec.sfz.as_deref().expect("sfz");
let base_dir = match sfz_path.rfind('/') {
Some(i) => sfz_path[..i].to_string(),
None => String::new(),
let base_dir = match (&spec.base, sfz_path.rfind('/')) {
(Some(b), _) => b.trim_end_matches('/').to_string(),
(None, Some(i)) => sfz_path[..i].to_string(),
(None, None) => String::new(),
};
let text = ctx.sources[src_key].read_text(sfz_path).with_context(|| format!("reading {sfz_path}"))?;
let file = {
Expand Down
Loading
Loading