Feat: adapt cagire to doux v0.0.12
Some checks failed
Deploy Website / deploy (push) Failing after 20s

This commit is contained in:
2026-03-14 12:43:18 +01:00
parent 9cc17d14de
commit 82e5f47933
20 changed files with 175 additions and 552 deletions

117
Cargo.lock generated
View File

@@ -859,7 +859,7 @@ dependencies = [
"cpal 0.17.1",
"crossbeam-channel",
"crossterm",
"doux",
"doux 0.0.12",
"eframe",
"egui",
"egui_ratatui",
@@ -911,7 +911,7 @@ dependencies = [
"cagire-ratatui",
"crossbeam-channel",
"crossterm",
"doux",
"doux 0.0.10",
"egui_ratatui",
"nih_plug",
"nih_plug_egui",
@@ -1473,7 +1473,7 @@ dependencies = [
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"windows 0.62.2",
"windows 0.61.3",
]
[[package]]
@@ -1809,14 +1809,31 @@ dependencies = [
[[package]]
name = "doux"
version = "0.0.8"
source = "git+https://github.com/sova-org/doux#a916717fa54b1cc0be093c131e03668d14ee1e3b"
version = "0.0.10"
source = "git+https://github.com/sova-org/doux#7f4e548ae3a917e62cf4c9acb7540496684f0d8f"
dependencies = [
"arc-swap",
"clap",
"cpal 0.17.1",
"crossbeam-channel",
"mi-plaits-dsp",
"ringbuf",
"rosc",
"rustyline",
"soundfont",
"symphonia",
]
[[package]]
name = "doux"
version = "0.0.12"
source = "git+https://github.com/sova-org/doux?tag=v0.0.12#5b62d6634df217a00ced5e711fe98b77c9d3f79c"
dependencies = [
"arc-swap",
"clap",
"cpal 0.17.1",
"crossbeam-channel",
"ringbuf",
"rosc",
"rustyline",
"soundfont",
@@ -6567,23 +6584,11 @@ version = "0.61.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893"
dependencies = [
"windows-collections 0.2.0",
"windows-collections",
"windows-core 0.61.2",
"windows-future 0.2.1",
"windows-future",
"windows-link 0.1.3",
"windows-numerics 0.2.0",
]
[[package]]
name = "windows"
version = "0.62.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580"
dependencies = [
"windows-collections 0.3.2",
"windows-core 0.62.2",
"windows-future 0.3.2",
"windows-numerics 0.3.1",
"windows-numerics",
]
[[package]]
@@ -6595,15 +6600,6 @@ dependencies = [
"windows-core 0.61.2",
]
[[package]]
name = "windows-collections"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610"
dependencies = [
"windows-core 0.62.2",
]
[[package]]
name = "windows-core"
version = "0.54.0"
@@ -6652,19 +6648,6 @@ dependencies = [
"windows-strings 0.4.2",
]
[[package]]
name = "windows-core"
version = "0.62.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
dependencies = [
"windows-implement 0.60.2",
"windows-interface 0.59.3",
"windows-link 0.2.1",
"windows-result 0.4.1",
"windows-strings 0.5.1",
]
[[package]]
name = "windows-future"
version = "0.2.1"
@@ -6673,18 +6656,7 @@ checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e"
dependencies = [
"windows-core 0.61.2",
"windows-link 0.1.3",
"windows-threading 0.1.0",
]
[[package]]
name = "windows-future"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb"
dependencies = [
"windows-core 0.62.2",
"windows-link 0.2.1",
"windows-threading 0.2.1",
"windows-threading",
]
[[package]]
@@ -6775,16 +6747,6 @@ dependencies = [
"windows-link 0.1.3",
]
[[package]]
name = "windows-numerics"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26"
dependencies = [
"windows-core 0.62.2",
"windows-link 0.2.1",
]
[[package]]
name = "windows-result"
version = "0.1.2"
@@ -6812,15 +6774,6 @@ dependencies = [
"windows-link 0.1.3",
]
[[package]]
name = "windows-result"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
dependencies = [
"windows-link 0.2.1",
]
[[package]]
name = "windows-strings"
version = "0.1.0"
@@ -6840,15 +6793,6 @@ dependencies = [
"windows-link 0.1.3",
]
[[package]]
name = "windows-strings"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
dependencies = [
"windows-link 0.2.1",
]
[[package]]
name = "windows-sys"
version = "0.45.0"
@@ -6951,15 +6895,6 @@ dependencies = [
"windows-link 0.1.3",
]
[[package]]
name = "windows-threading"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37"
dependencies = [
"windows-link 0.2.1",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.2"

View File

@@ -51,7 +51,7 @@ cagire-forth = { path = "crates/forth" }
cagire-markdown = { path = "crates/markdown" }
cagire-project = { path = "crates/project" }
cagire-ratatui = { path = "crates/ratatui" }
doux = { git = "https://github.com/sova-org/doux", features = ["native", "soundfont"] }
doux = { git = "https://github.com/sova-org/doux", tag = "v0.0.12", features = ["native", "soundfont"] }
rusty_link = "0.4"
ratatui = "0.30"
crossterm = "0.29"

View File

@@ -45,13 +45,13 @@ sine sound 2 fm 0.5 fmh
- User-defined words: extend (or redefine) the language on the fly with `:name ... ;` definitions.
- Interactive documentation: built-in tutorials with runnable examples.
- **Audio engine** (powered by [Doux](https://doux.livecoding.fr)):
- Synthesis: classic waveforms (saw, pulse, tri, sine), additive, FM (2-op, 3 algorithms), additive synthesis, wavetables, 7-voice spread, Mutable Instruments Plaits models: modal, granular, waveshaping, chord, swarm, etc.
- Synthesis: classic waveforms (saw, pulse, tri, sine), additive (up to 32 partials), FM (2-op, 3 algorithms), wavetables, 7-voice spread.
- Drum models: seven drum models with timbral morphing.
- Sampling: disk-loaded samples with slicing, looping, pitch tracking, wavetable mode, and live recording from engine output or line input.
- Filters: biquad LP/HP/BP and ladder filters, each with independent envelope. Filters can be modulated, stacked, etc.
- Filters: biquad LP/HP/BP and ladder filters. Filters can be modulated, stacked, etc.
- Effects: phaser, flanger, chorus, smear, distortion, wavefolder, wavewrapper, bitcrusher, sample-rate reduction, 3-band EQ, tilt EQ, Haas stereo.
- Bus effects: delay (standard, ping-pong, tape, multitap), two reverb engines (Dattorro plate, Vital Space), comb filter, feedback delay with LFO, sidechain compressor.
- Modulation: vibrato, AM, ring mod, pitch envelope, FM envelope, glide — all with selectable LFO shapes (sine, tri, saw, square, sample & hold).
- Modulation: vibrato, AM, ring mod, audio-rate LFO, transitions, DAHDSR envelope modulation — all applicable to any parameter.
- **Sequencing**: probabilities, patterns, euclidean structures, sub-step timing, pattern chaining and a lot more.
- **MIDI**: receive or send MIDI messages across up to 4 inputs and 4 outputs.
- **Ableton Link**: tempo and phase sync with any Link-enabled software or hardware.
@@ -77,7 +77,6 @@ Cagire includes interactive documentation with runnable code examples. Press **F
Cagire is developed by [BuboBubo](https://raphaelforment.fr) (Raphael Forment).
- **[Doux](https://doux.livecoding.fr)** (audio engine) — Rust port of Dough, originally written in C by Felix Roos
- **mi-plaits-dsp-rs** — Rust port of Mutable Instruments Plaits DSP by Oliver Rockstedt, original code by Emilie Gillet
### License

View File

@@ -133,6 +133,8 @@ pub enum Op {
ModSlide(u8),
ModRnd(u8),
ModEnv,
ModEnvAd,
ModEnvAdr,
// Global params
EmitAll,
ClearGlobal,

View File

@@ -1446,22 +1446,38 @@ impl Forth {
stack.push(Value::Str(s.into(), None));
}
Op::ModEnv => {
ensure(stack, 1)?;
let values = std::mem::take(stack);
let mut floats = Vec::with_capacity(values.len());
for v in &values {
floats.push(v.as_float()?);
}
if floats.len() < 3 || (floats.len() - 1) % 2 != 0 {
return Err("env expects: start target1 dur1 [target2 dur2 ...]".into());
}
let step_dur = ctx.step_duration();
let release = pop_float(stack)? * ctx.step_duration();
let sustain = pop_float(stack)?;
let decay = pop_float(stack)? * ctx.step_duration();
let attack = pop_float(stack)? * ctx.step_duration();
let max = pop_float(stack)?;
let min = pop_float(stack)?;
use std::fmt::Write;
let mut s = String::new();
let _ = write!(&mut s, "{}", floats[0]);
for pair in floats[1..].chunks(2) {
let _ = write!(&mut s, ">{}:{}", pair[0], pair[1] * step_dur);
}
let _ = write!(&mut s, "{min}^{max}:{attack}:{decay}:{sustain}:{release}");
stack.push(Value::Str(s.into(), None));
}
Op::ModEnvAd => {
let decay = pop_float(stack)? * ctx.step_duration();
let attack = pop_float(stack)? * ctx.step_duration();
let max = pop_float(stack)?;
let min = pop_float(stack)?;
use std::fmt::Write;
let mut s = String::new();
let _ = write!(&mut s, "{min}^{max}:{attack}:{decay}:0:0");
stack.push(Value::Str(s.into(), None));
}
Op::ModEnvAdr => {
let release = pop_float(stack)? * ctx.step_duration();
let decay = pop_float(stack)? * ctx.step_duration();
let attack = pop_float(stack)? * ctx.step_duration();
let max = pop_float(stack)?;
let min = pop_float(stack)?;
use std::fmt::Write;
let mut s = String::new();
let _ = write!(&mut s, "{min}^{max}:{attack}:{decay}:0:{release}");
stack.push(Value::Str(s.into(), None));
}
@@ -1713,27 +1729,7 @@ fn extract_dev_param(params: &[(&str, Value)]) -> u8 {
fn is_tempo_scaled_param(name: &str) -> bool {
matches!(
name,
"attack"
| "decay"
| "release"
| "lpa"
| "lpd"
| "lpr"
| "hpa"
| "hpd"
| "hpr"
| "bpa"
| "bpd"
| "bpr"
| "patt"
| "pdec"
| "prel"
| "fma"
| "fmd"
| "fmr"
| "glide"
| "chorusdelay"
| "duration"
"attack" | "decay" | "release" | "envdelay" | "hold" | "chorusdelay"
)
}

View File

@@ -142,7 +142,9 @@ pub(super) fn simple_op(name: &str) -> Option<Op> {
"jit" => Op::ModRnd(0),
"sjit" => Op::ModRnd(1),
"drunk" => Op::ModRnd(2),
"env" => Op::ModEnv,
"ead" => Op::ModEnvAd,
"eadr" => Op::ModEnvAdr,
"eadsr" | "env" => Op::ModEnv,
_ => return None,
})
}

View File

@@ -73,6 +73,26 @@ pub(super) const WORDS: &[Word] = &[
compile: Param,
varargs: true,
},
Word {
name: "envdelay",
aliases: &["envdly"],
category: "Envelope",
stack: "(v.. --)",
desc: "Set envelope delay time",
example: "0.1 envdelay",
compile: Param,
varargs: true,
},
Word {
name: "hold",
aliases: &["hld"],
category: "Envelope",
stack: "(v.. --)",
desc: "Set envelope hold time",
example: "0.05 hold",
compile: Param,
varargs: true,
},
Word {
name: "adsr",
aliases: &[],
@@ -93,56 +113,6 @@ pub(super) const WORDS: &[Word] = &[
compile: Simple,
varargs: false,
},
Word {
name: "penv",
aliases: &[],
category: "Envelope",
stack: "(v.. --)",
desc: "Set pitch envelope",
example: "0.5 penv",
compile: Param,
varargs: true,
},
Word {
name: "patt",
aliases: &[],
category: "Envelope",
stack: "(v.. --)",
desc: "Set pitch attack",
example: "0.01 patt",
compile: Param,
varargs: true,
},
Word {
name: "pdec",
aliases: &[],
category: "Envelope",
stack: "(v.. --)",
desc: "Set pitch decay",
example: "0.1 pdec",
compile: Param,
varargs: true,
},
Word {
name: "psus",
aliases: &[],
category: "Envelope",
stack: "(v.. --)",
desc: "Set pitch sustain",
example: "0 psus",
compile: Param,
varargs: true,
},
Word {
name: "prel",
aliases: &[],
category: "Envelope",
stack: "(v.. --)",
desc: "Set pitch release",
example: "0.1 prel",
compile: Param,
varargs: true,
},
// Filter
Word {
name: "lpf",
@@ -164,56 +134,6 @@ pub(super) const WORDS: &[Word] = &[
compile: Param,
varargs: true,
},
Word {
name: "lpe",
aliases: &[],
category: "Filter",
stack: "(v.. --)",
desc: "Set lowpass envelope",
example: "0.5 lpe",
compile: Param,
varargs: true,
},
Word {
name: "lpa",
aliases: &[],
category: "Filter",
stack: "(v.. --)",
desc: "Set lowpass attack",
example: "0.01 lpa",
compile: Param,
varargs: true,
},
Word {
name: "lpd",
aliases: &[],
category: "Filter",
stack: "(v.. --)",
desc: "Set lowpass decay",
example: "0.1 lpd",
compile: Param,
varargs: true,
},
Word {
name: "lps",
aliases: &[],
category: "Filter",
stack: "(v.. --)",
desc: "Set lowpass sustain",
example: "0.5 lps",
compile: Param,
varargs: true,
},
Word {
name: "lpr",
aliases: &[],
category: "Filter",
stack: "(v.. --)",
desc: "Set lowpass release",
example: "0.3 lpr",
compile: Param,
varargs: true,
},
Word {
name: "hpf",
aliases: &[],
@@ -234,56 +154,6 @@ pub(super) const WORDS: &[Word] = &[
compile: Param,
varargs: true,
},
Word {
name: "hpe",
aliases: &[],
category: "Filter",
stack: "(v.. --)",
desc: "Set highpass envelope",
example: "0.5 hpe",
compile: Param,
varargs: true,
},
Word {
name: "hpa",
aliases: &[],
category: "Filter",
stack: "(v.. --)",
desc: "Set highpass attack",
example: "0.01 hpa",
compile: Param,
varargs: true,
},
Word {
name: "hpd",
aliases: &[],
category: "Filter",
stack: "(v.. --)",
desc: "Set highpass decay",
example: "0.1 hpd",
compile: Param,
varargs: true,
},
Word {
name: "hps",
aliases: &[],
category: "Filter",
stack: "(v.. --)",
desc: "Set highpass sustain",
example: "0.5 hps",
compile: Param,
varargs: true,
},
Word {
name: "hpr",
aliases: &[],
category: "Filter",
stack: "(v.. --)",
desc: "Set highpass release",
example: "0.3 hpr",
compile: Param,
varargs: true,
},
Word {
name: "bpf",
aliases: &[],
@@ -304,56 +174,6 @@ pub(super) const WORDS: &[Word] = &[
compile: Param,
varargs: true,
},
Word {
name: "bpe",
aliases: &[],
category: "Filter",
stack: "(v.. --)",
desc: "Set bandpass envelope",
example: "0.5 bpe",
compile: Param,
varargs: true,
},
Word {
name: "bpa",
aliases: &[],
category: "Filter",
stack: "(v.. --)",
desc: "Set bandpass attack",
example: "0.01 bpa",
compile: Param,
varargs: true,
},
Word {
name: "bpd",
aliases: &[],
category: "Filter",
stack: "(v.. --)",
desc: "Set bandpass decay",
example: "0.1 bpd",
compile: Param,
varargs: true,
},
Word {
name: "bps",
aliases: &[],
category: "Filter",
stack: "(v.. --)",
desc: "Set bandpass sustain",
example: "0.5 bps",
compile: Param,
varargs: true,
},
Word {
name: "bpr",
aliases: &[],
category: "Filter",
stack: "(v.. --)",
desc: "Set bandpass release",
example: "0.3 bpr",
compile: Param,
varargs: true,
},
Word {
name: "llpf",
aliases: &[],

View File

@@ -126,16 +126,6 @@ pub(super) const WORDS: &[Word] = &[
compile: Param,
varargs: true,
},
Word {
name: "repeat",
aliases: &[],
category: "Sample",
stack: "(v.. --)",
desc: "Set repeat count",
example: "4 repeat",
compile: Param,
varargs: true,
},
Word {
name: "dur",
aliases: &[],
@@ -151,7 +141,7 @@ pub(super) const WORDS: &[Word] = &[
aliases: &[],
category: "Sample",
stack: "(v.. --)",
desc: "Set gate time",
desc: "Set gate duration (total note length, 0 = infinite sustain)",
example: "0.8 gate",
compile: Param,
varargs: true,
@@ -297,16 +287,6 @@ pub(super) const WORDS: &[Word] = &[
compile: Param,
varargs: true,
},
Word {
name: "glide",
aliases: &[],
category: "Oscillator",
stack: "(v.. --)",
desc: "Set glide/portamento",
example: "0.1 glide",
compile: Param,
varargs: true,
},
Word {
name: "pw",
aliases: &[],
@@ -372,7 +352,7 @@ pub(super) const WORDS: &[Word] = &[
aliases: &[],
category: "Oscillator",
stack: "(v.. --)",
desc: "Set harmonics (mutable only)",
desc: "Set harmonics (add source)",
example: "4 harmonics",
compile: Param,
varargs: true,
@@ -382,7 +362,7 @@ pub(super) const WORDS: &[Word] = &[
aliases: &[],
category: "Oscillator",
stack: "(v.. --)",
desc: "Set timbre (mutable only)",
desc: "Set timbre (add source)",
example: "0.5 timbre",
compile: Param,
varargs: true,
@@ -392,7 +372,7 @@ pub(super) const WORDS: &[Word] = &[
aliases: &[],
category: "Oscillator",
stack: "(v.. --)",
desc: "Set morph (mutable only)",
desc: "Set morph (add source)",
example: "0.5 morph",
compile: Param,
varargs: true,
@@ -478,36 +458,6 @@ pub(super) const WORDS: &[Word] = &[
compile: Param,
varargs: true,
},
Word {
name: "scanlfo",
aliases: &[],
category: "Wavetable",
stack: "(v.. --)",
desc: "Set scan LFO rate (Hz)",
example: "0.2 scanlfo",
compile: Param,
varargs: true,
},
Word {
name: "scandepth",
aliases: &[],
category: "Wavetable",
stack: "(v.. --)",
desc: "Set scan LFO depth (0-1)",
example: "0.4 scandepth",
compile: Param,
varargs: true,
},
Word {
name: "scanshape",
aliases: &[],
category: "Wavetable",
stack: "(v.. --)",
desc: "Set scan LFO shape (sine/tri/saw/square/sh)",
example: "\"tri\" scanshape",
compile: Param,
varargs: true,
},
// FM
Word {
name: "fm",
@@ -539,56 +489,6 @@ pub(super) const WORDS: &[Word] = &[
compile: Param,
varargs: true,
},
Word {
name: "fme",
aliases: &[],
category: "FM",
stack: "(v.. --)",
desc: "Set FM envelope",
example: "0.5 fme",
compile: Param,
varargs: true,
},
Word {
name: "fma",
aliases: &[],
category: "FM",
stack: "(v.. --)",
desc: "Set FM attack",
example: "0.01 fma",
compile: Param,
varargs: true,
},
Word {
name: "fmd",
aliases: &[],
category: "FM",
stack: "(v.. --)",
desc: "Set FM decay",
example: "0.1 fmd",
compile: Param,
varargs: true,
},
Word {
name: "fms",
aliases: &[],
category: "FM",
stack: "(v.. --)",
desc: "Set FM sustain",
example: "0.5 fms",
compile: Param,
varargs: true,
},
Word {
name: "fmr",
aliases: &[],
category: "FM",
stack: "(v.. --)",
desc: "Set FM release",
example: "0.1 fmr",
compile: Param,
varargs: true,
},
Word {
name: "fm2",
aliases: &[],
@@ -922,13 +822,43 @@ pub(super) const WORDS: &[Word] = &[
compile: Simple,
varargs: false,
},
Word {
name: "ead",
aliases: &[],
category: "Audio Modulation",
stack: "(min max a d -- str)",
desc: "Percussive envelope mod: min^max:a:d:0:0",
example: "200 8000 0.01 0.1 ead lpf",
compile: Simple,
varargs: false,
},
Word {
name: "eadr",
aliases: &[],
category: "Audio Modulation",
stack: "(min max a d r -- str)",
desc: "Percussive envelope mod with release: min^max:a:d:0:r",
example: "200 8000 0.01 0.1 0.3 eadr lpf",
compile: Simple,
varargs: false,
},
Word {
name: "eadsr",
aliases: &[],
category: "Audio Modulation",
stack: "(min max a d s r -- str)",
desc: "ADSR envelope mod: min^max:a:d:s:r",
example: "200 8000 0.01 0.1 0.5 0.3 eadsr lpf",
compile: Simple,
varargs: false,
},
Word {
name: "env",
aliases: &[],
category: "Audio Modulation",
stack: "(start t1 d1 ... -- str)",
desc: "Multi-segment envelope: start>t1:d1>...",
example: "0 1 0.01 0.7 0.1 0 2 env gain",
stack: "(min max a d s r -- str)",
desc: "DAHDSR envelope modulation: min^max:a:d:s:r",
example: "200 8000 0.01 0.1 0.5 0.3 env lpf",
compile: Simple,
varargs: false,
},

View File

@@ -57,29 +57,15 @@ The `ftype` parameter sets the filter slope (rolloff steepness).
saw 800 lpf 3 ftype . ( 48 dB/oct lowpass )
```
## Filter Envelope
## Filter Envelope Modulation
Filters can be modulated by an ADSR envelope. The envelope multiplies the base cutoff:
```
final_cutoff = lpf + (lpe × envelope × lpf)
```
When the envelope is at 1.0 and `lpe` is 1.0, the cutoff doubles. When the envelope is at 0, the cutoff equals `lpf`.
Use the `env` word to apply a DAHDSR envelope to any filter cutoff:
```forth
saw 200 lpf 2 lpe 0.01 lpa 0.3 lpd . ( cutoff sweeps from 600 Hz down to 200 Hz )
saw 200 8000 0.01 0.3 0.5 0.3 env lpf . ( cutoff sweeps from 200 to 8000 Hz )
```
| Parameter | Description |
|-----------|-------------|
| `lpe` | Envelope depth (multiplier, 1.0 = double cutoff at peak) |
| `lpa` | Attack time in seconds |
| `lpd` | Decay time in seconds |
| `lps` | Sustain level (0-1) |
| `lpr` | Release time in seconds |
The same pattern works for highpass (`hpe`, `hpa`, etc.) and bandpass (`bpe`, `bpa`, etc.).
The same works for highpass and bandpass: `env hpf`, `env bpf`.
## Ladder Filters
@@ -100,7 +86,7 @@ saw 1000 lbpf 0.8 lbpq . ( ladder bandpass )
| `lbpf` | Hz | Ladder bandpass cutoff |
| `lbpq` | 0-1 | Ladder bandpass resonance |
Ladder filters share the lowpass envelope parameters (`lpe`, `lpa`, etc.).
Ladder filter cutoffs can also be modulated with `env`, `lfo`, `slide`, etc.
## EQ

View File

@@ -16,34 +16,6 @@ saw 5 vib 0.5 vibmod . ( 5 Hz, 0.5 semitone depth )
| `vibmod` | semitones | Modulation depth |
| `vibshape` | shape | LFO waveform (sine, tri, saw, square) |
## Pitch Envelope
The pitch envelope applies an ADSR to the oscillator frequency.
```forth
sine 100 freq 24 penv 0.001 patt 0.1 pdec .
```
| Parameter | Description |
|-----------|-------------|
| `penv` | Envelope depth in semitones |
| `patt` | Attack time in seconds |
| `pdec` | Decay time in seconds |
| `psus` | Sustain level (0-1) |
| `prel` | Release time in seconds |
## Glide
Glide interpolates between pitch changes over time.
```forth
saw c4 0.1 glide . ( 100ms glide )
```
| Parameter | Range | Description |
|-----------|-------|-------------|
| `glide` | seconds | Glide time |
## FM Synthesis
FM modulates the carrier frequency with a modulator oscillator.
@@ -58,7 +30,7 @@ sine 440 freq 2 fm 2 fmh . ( modulator at 2× carrier frequency )
| `fmh` | ratio | Harmonic ratio (modulator / carrier) |
| `fmshape` | shape | Modulator waveform |
FM has its own envelope (`fme`, `fma`, `fmd`, `fms`, `fmr`).
Use `env` to apply a DAHDSR envelope to FM depth: `0 5 0.01 0.1 0.3 0.5 env fm`.
## Amplitude Modulation

View File

@@ -56,37 +56,29 @@ Noise sources ignore pitch. Use filters to shape the spectrum.
All filter and effect parameters apply to the input signal.
## Plaits Engines
## Additive
The Plaits engines come from Mutable Instruments and provide a range of synthesis methods. Beware, these sources can be quite CPU hungry. All share three control parameters (`0.0`-`1.0`):
| Name | Description |
|------|-------------|
| `add` | Stacks 1-32 sine partials with spectral tilt, even/odd morph, harmonic stretching, phase shaping. |
| Parameter | Controls |
|-----------|----------|
| `harmonics` | Harmonic content, structure, detuning. |
| `timbre` | Brightness, tonal color. |
| `harmonics` | Harmonic content / structure. |
| `timbre` | Brightness / tonal color. |
| `morph` | Smooth transitions between variations. |
| `partials` | Number of active harmonics (1-32). |
### Pitched
## Percussion
Native drum synthesis with timbral morphing. All share `wave` (waveform: 0=sine, 0.5=tri, 1=saw), `morph`, `harmonics`, and `timbre` parameters.
| Name | Description |
|------|-------------|
| `modal` | Struck/plucked resonant bodies (strings, plates, tubes). |
| `va`, `analog` | Virtual analog with waveform sync and crossfading. |
| `ws`, `waveshape` | Waveshaper and wavefolder. |
| `fm2` | Two-operator FM synthesis with feedback. |
| `grain` | Granular formant oscillator (vowel-like). |
| `additive` | Harmonic additive synthesis. |
| `wavetable` | Built-in Plaits wavetables (four 8x8 banks). |
| `chord` | Four-note chord generator. |
| `swarm` | Granular cloud of enveloped sawtooths. |
| `pnoise` | Clocked noise through multimode filter. |
### Percussion
| Name | Description |
|------|-------------|
| `kick`, `bass` | 808-style bass drum. |
| `snare` | Analog snare drum with tone/noise balance. |
| `hihat`, `hat` | Metallic 808-style hi-hat. |
Percussions are super hard to use correctly, because you need to tweak their envelope correctly.
| `kick` | Bass drum. |
| `snare` | Snare drum with tone/noise balance. |
| `hat` | Hi-hat. |
| `tom` | Tom drum. |
| `rim` | Rimshot. |
| `cowbell` | Cowbell. |
| `cymbal` | Cymbal. |

View File

@@ -32,9 +32,6 @@ Without `scan`, the sample plays normally. With `scan`, it becomes a looping wav
|-----------|-------|-------------|
| `scan` | 0-1 | Position in wavetable (0 = first cycle, 1 = last) |
| `wtlen` | samples | Cycle length in samples (0 = entire sample) |
| `scanlfo` | Hz | LFO rate for scan modulation |
| `scandepth` | 0-1 | LFO modulation depth |
| `scanshape` | shape | LFO waveform |
## Cycle Length
@@ -57,24 +54,16 @@ pad 0.5 scan . ( blend between middle cycles )
pad 1 scan . ( last cycle only )
```
## LFO Modulation
## Scan Modulation
Automate the scan position with a built-in LFO:
Use audio-rate modulation words to automate the scan position:
```forth
pad 0 scan 2 scanlfo 0.3 scandepth . ( 2 Hz modulation, 30% depth )
pad 0 1 2 lfo scan . ( sine LFO, full range, 2 Hz )
pad 0 0.5 1 tlfo scan . ( triangle LFO, half range, 1 Hz )
pad 0 1 0.5 jit scan . ( random scan every 0.5 steps )
```
Available LFO shapes:
| Shape | Description |
|-------|-------------|
| `sine` | Smooth oscillation (default) |
| `tri` | Triangle wave |
| `saw` | Sawtooth, ramps up |
| `square` | Alternates between extremes |
| `sh` | Sample and hold, random steps |
## Creating Wavetables
A proper wavetable file:

View File

@@ -9,7 +9,7 @@ Each bank can carry its own prelude script. Press `p` to open the current bank's
Bank preludes make banks self-contained. When you share a bank, its prelude travels with it — recipients get all the definitions they need without merging anything into their own project.
```forth
: bass pulse sound 0.8 gain 400 lpf 1 lpd 8 lpe 0.6 width . ;
: bass pulse sound 0.8 gain 400 8000 0.01 0.3 0.5 0.3 env lpf 0.6 width . ;
: pad sine sound 0.5 gain 2 spread 1.5 attack 0.4 verb . ;
```
@@ -48,13 +48,13 @@ Only non-empty bank preludes are evaluated. Last-evaluated wins for name collisi
The most common use of a bank prelude is to define words for your instruments. Without a prelude, every step that plays a bass has to spell out the full sound design:
```forth
pulse sound c2 note 0.8 gain 400 lpf 1 lpd 8 lpe 0.6 width .
pulse sound c2 note 0.8 gain 400 8000 0.01 0.3 0.5 0.3 env lpf 0.6 width .
```
In the bank prelude, define it once:
```forth
: bass pulse sound 0.8 gain 400 lpf 1 lpd 8 lpe 0.6 width . ;
: bass pulse sound 0.8 gain 400 8000 0.01 0.3 0.5 0.3 env lpf 0.6 width . ;
```
Now every step just writes `c2 note bass`. Change the sound in one place, every step follows.
@@ -63,7 +63,7 @@ Now every step just writes `c2 note bass`. Change the sound in one place, every
```forth
;; instruments
: bass pulse sound 0.8 gain 400 lpf 1 lpd 8 lpe 0.6 width . ;
: bass pulse sound 0.8 gain 400 8000 0.01 0.3 0.5 0.3 env lpf 0.6 width . ;
: pad sine sound 0.5 gain 2 spread 1.5 attack 0.4 verb . ;
: lead tri sound 0.6 gain 5000 lpf 2 decay . ;

View File

@@ -51,7 +51,7 @@ Cagire includes a complete synthesis and sampling engine. No external software i
```forth
;; sawtooth wave + lowpass filter with envelope + chorus + reverb
100 199 freq saw sound 250 lpf 8 lpe 1 lpd 0.2 chorus 0.8 verb 2 dur .
100 199 freq saw sound 250 8000 0.01 0.3 0.5 0.3 env lpf 0.2 chorus 0.8 verb 2 dur .
```
```forth
@@ -61,12 +61,12 @@ Cagire includes a complete synthesis and sampling engine. No external software i
```forth
;; white noise + sine wave + envelope = percussion
white sine sound 100 freq 0.5 decay 24 penv 0.5 pdec 2 dur .
white sine sound 100 freq 0.5 decay 2 dur .
```
```forth
;; random robot noises: sine + randomized freq + ring modulation
10 1000 rand freq sine sound 1 100 rand rm 0.5 1.0 rand rmddepth .
10 1000 rand freq sine sound 1 100 rand rm 0.5 1.0 rand rmdepth .
```
By _creating words_, registering synth definitions and effects, you will form a vocabulary that can be used to create complex sounds and music. The audio engine is quite capable, and you won't ever run out of new things to try!

View File

@@ -35,7 +35,7 @@ c4 M3 P5 note 1.5 decay sine snd .
That builds a C major triad from scratch: C4 (60), then a major third above (64), then a perfect fifth above the root (67). Three notes on the stack, all played together.
```forth
a3 m3 P5 note 1.2 decay va snd .
a3 m3 P5 note 1.2 decay saw snd .
```
A minor triad: A3, C4, E4.
@@ -94,7 +94,7 @@ c4 maj note 1.5 decay sine snd .
That's the same C major triad, but in one word instead of `M3 P5`. A few more:
```forth
d3 min7 note 1.5 decay va snd .
d3 min7 note 1.5 decay saw snd .
```
```forth
@@ -160,13 +160,13 @@ G3 C4 E4. The fifth drops below the root.
`drop2` and `drop3` are jazz voicing techniques for four-note chords. `drop2` takes the second-from-top note and drops it an octave:
```forth
c4 maj7 drop2 note 1.2 decay va snd .
c4 maj7 drop2 note 1.2 decay saw snd .
```
From C4 E4 G4 B4, the G drops to G3: G3 C4 E4 B4. `drop3` drops the third-from-top:
```forth
c4 maj7 drop3 note 1.2 decay va snd .
c4 maj7 drop3 note 1.2 decay saw snd .
```
E drops to E3: E3 C4 G4 B4. These create wider, more open voicings common in jazz guitar and piano.
@@ -182,7 +182,7 @@ c4 maj 3 tp note 1.5 decay sine snd .
C major transposed up 3 semitones becomes Eb major. Works with any number of notes:
```forth
c4 min7 -2 tp note 1.5 decay va snd .
c4 min7 -2 tp note 1.5 decay saw snd .
```
Shifts the whole chord down 2 semitones (Bb minor 7).
@@ -219,7 +219,7 @@ Walk through a scale with `cycle`:
Random notes from a scale:
```forth
0 7 rand pentatonic note 0.8 decay va snd .
0 7 rand pentatonic note 0.8 decay saw snd .
```
### Setting the key
@@ -273,7 +273,7 @@ Degree 0 of the major scale, stacked in thirds: C E G — a major triad. The sca
`seventh` adds a fourth note:
```forth
0 major seventh note 1.2 decay va snd .
0 major seventh note 1.2 decay saw snd .
```
C E G B — Cmaj7. Degree 1 gives Dm7, degree 4 gives G7 (dominant). The diatonic context determines everything.
@@ -291,7 +291,7 @@ A I-vi-IV-V chord progression using `pcycle`:
```forth
( 0 major seventh ) ( 5 major seventh )
( 3 major seventh ) ( 4 major seventh ) 4 pcycle
note 1.2 decay va snd .
note 1.2 decay saw snd .
```
Combine with voicings for smoother voice leading:
@@ -299,7 +299,7 @@ Combine with voicings for smoother voice leading:
```forth
( 0 major seventh ) ( 5 major seventh inv )
( 3 major seventh ) ( 4 major seventh drop2 ) 4 pcycle
note 1.5 decay va snd .
note 1.5 decay saw snd .
```
Arpeggiate diatonic chords using `arp` (see the *Timing with at* tutorial for details on `arp`):

View File

@@ -188,7 +188,7 @@ A melodic step with weighted note selection and random timbre:
c4 0.4 e4 0.3 g4 0.2 b4 0.1 4 wchoose note
0.3 0.7 rand decay
1.0 4.0 exprand harmonics
modal snd .
add snd .
```
The root note plays most often. Higher chord tones are rarer. Decay and harmonics vary continuously.

View File

@@ -22,4 +22,3 @@ Cagire is mainly developed by BuboBubo (Raphaël Maurice Forment, [raphaelformen
### Credits
* **Doux** (audio engine) is a Rust port of Dough, originally written in C by Felix Roos.
* **mi-plaits-dsp-rs** is a Rust port of the code used by the Mutable Instruments Plaits (Emilie Gillet). Rust port by Oliver Rockstedt.

View File

@@ -339,8 +339,9 @@ pub fn build_stream(
let channels = channels as usize;
let max_voices = config.max_voices;
let block_size = if config.buffer_size > 0 { config.buffer_size as usize } else { 512 };
let mut engine =
Engine::new_with_metrics(sample_rate, channels, max_voices, Arc::clone(&metrics));
Engine::new_with_metrics(sample_rate, channels, max_voices, Arc::clone(&metrics), block_size);
engine.sample_index = initial_samples;
for path in sample_paths {

View File

@@ -5,9 +5,9 @@ import fs from 'node:fs';
const EMIT = new Set(['.', '.!']);
const SOUNDS = new Set([
'sound', 's', 'saw', 'sine', 'kick', 'hat', 'snare', 'modal', 'noise',
'square', 'tri', 'pulse', 'clap', 'rim', 'crash', 'fm', 'sample', 'plaits',
'analog', 'waveshaping', 'granular', 'string', 'chord', 'speech', 'sub',
'sound', 's', 'saw', 'sine', 'kick', 'hat', 'snare', 'noise', 'add',
'square', 'tri', 'pulse', 'clap', 'rim', 'crash', 'fm', 'sample',
'tom', 'cowbell', 'cymbal', 'white', 'pink', 'brown', 'live', 'sub',
'super', 'wt', 'input', 'hh',
]);
const PARAMS = new Set([

View File

@@ -103,9 +103,9 @@ const DL = 'https://dlcagire.raphaelforment.fr';
<h2>Features (click to learn more!)</h2>
<div class="features">
<div class="feature-tags">
<button data-desc="Powered by an audio engine crafted specifically for live coding. Classic waveforms (aliased and non-aliased). Noise generators. Mutable Instruments Plaits modes (modal, virtual analog, waveshaping, FM, granular, additive, etc). Small but fun FM synth with 2 operators, multiple algorithms and feedback control. Wavetable scanning with LFO control: bring your own wavetables. Sub-oscillators with independent waveform and octave. Sample playback with slicing, time-stretching and gating. Live microphone input. Configurable polyphony (default 32 voices). 8 independent effect buses (orbits).">Synthesis</button>
<button data-desc="Powered by an audio engine crafted specifically for live coding. Classic waveforms (aliased and non-aliased). Noise generators. Additive synthesis (up to 32 partials). 7 native drum models with timbral morphing. FM synth with 2 operators, multiple algorithms and feedback control. Wavetable scanning: bring your own wavetables. Sub-oscillators with independent waveform and octave. Sample playback with slicing, time-stretching and gating. Live microphone input. Configurable polyphony (default 32 voices). 8 independent effect buses (orbits).">Synthesis</button>
<button data-desc="Two reverb algorithms, Four delay types: standard, ping-pong, tape and multitap. Feedback, chorus, phaser and flanger with configurable depth, sweep and feedback. Distortion, bitcrusher, wave folding and wave wrapping. Comb filter with tunable frequency, feedback and damping. Audio-rate modulation for the final touch. ">Effects</button>
<button data-desc="Multimode filter with lowpass, highpass and bandpass modes, each with its own ADSR envelope, frequency and resonance. Selectable filter slope (12, 24 or 48 dB/oct). Ladder filter variants (lowpass, highpass, bandpass) if you like that! 3-band parametric EQ (lo/mid/hi) and a tilt EQ for broad tonal shaping.">Filters</button>
<button data-desc="Multimode filter with lowpass, highpass and bandpass modes, modulatable via universal envelope modulation, frequency and resonance. Selectable filter slope (12, 24 or 48 dB/oct). Ladder filter variants (lowpass, highpass, bandpass) if you like that! 3-band parametric EQ (lo/mid/hi) and a tilt EQ for broad tonal shaping.">Filters</button>
<button data-desc="4 MIDI inputs and 4 MIDI outputs. Send and receive CC messages, pitch bend, channel aftertouch and program changes. MIDI clock output (clock, start, stop, continue). Read incoming CC values in real-time from any device. Full channel selection per voice.">MIDI</button>
<button data-desc="Write notes by name (c4, d#5), build chords and scales from a rich built-in library. Convert between MIDI and frequency on the fly. Express musical ideas directly in code.">Theory</button>
<button data-desc="Conditional execution and probability branching built into the language. Weighted random choices, coin flips, euclidean rhythms with rotation. Multiple random distributions, Perlin noise, seeded randomness. Cagire is designed to be generative and fun.">Probability</button>