diff --git a/Cargo.lock b/Cargo.lock index cde7385..5ea99c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index 358d761..6bac381 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/README.md b/README.md index 2ca10c5..e4b4097 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/crates/forth/src/ops.rs b/crates/forth/src/ops.rs index 0e5f2a2..77bcb7e 100644 --- a/crates/forth/src/ops.rs +++ b/crates/forth/src/ops.rs @@ -133,6 +133,8 @@ pub enum Op { ModSlide(u8), ModRnd(u8), ModEnv, + ModEnvAd, + ModEnvAdr, // Global params EmitAll, ClearGlobal, diff --git a/crates/forth/src/vm.rs b/crates/forth/src/vm.rs index 9ecf82f..7493afc 100644 --- a/crates/forth/src/vm.rs +++ b/crates/forth/src/vm.rs @@ -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" ) } diff --git a/crates/forth/src/words/compile.rs b/crates/forth/src/words/compile.rs index c1e1249..a71a31c 100644 --- a/crates/forth/src/words/compile.rs +++ b/crates/forth/src/words/compile.rs @@ -142,7 +142,9 @@ pub(super) fn simple_op(name: &str) -> Option { "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, }) } diff --git a/crates/forth/src/words/effects.rs b/crates/forth/src/words/effects.rs index ad41f6d..5e0ce23 100644 --- a/crates/forth/src/words/effects.rs +++ b/crates/forth/src/words/effects.rs @@ -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: &[], diff --git a/crates/forth/src/words/sound.rs b/crates/forth/src/words/sound.rs index ca18758..843195f 100644 --- a/crates/forth/src/words/sound.rs +++ b/crates/forth/src/words/sound.rs @@ -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, }, diff --git a/docs/engine/filters.md b/docs/engine/filters.md index f773578..c49f4d0 100644 --- a/docs/engine/filters.md +++ b/docs/engine/filters.md @@ -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 diff --git a/docs/engine/modulation.md b/docs/engine/modulation.md index 58d5f66..10f626c 100644 --- a/docs/engine/modulation.md +++ b/docs/engine/modulation.md @@ -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 diff --git a/docs/engine/sources.md b/docs/engine/sources.md index fd08644..52b2d62 100644 --- a/docs/engine/sources.md +++ b/docs/engine/sources.md @@ -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. | diff --git a/docs/engine/wavetable.md b/docs/engine/wavetable.md index 3864dd2..2d9b610 100644 --- a/docs/engine/wavetable.md +++ b/docs/engine/wavetable.md @@ -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: diff --git a/docs/forth/prelude.md b/docs/forth/prelude.md index c27e4e2..3346e86 100644 --- a/docs/forth/prelude.md +++ b/docs/forth/prelude.md @@ -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 . ; diff --git a/docs/getting-started/big_picture.md b/docs/getting-started/big_picture.md index d6e3351..aa82ace 100644 --- a/docs/getting-started/big_picture.md +++ b/docs/getting-started/big_picture.md @@ -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! diff --git a/docs/tutorials/harmony.md b/docs/tutorials/harmony.md index 2929211..e381e64 100644 --- a/docs/tutorials/harmony.md +++ b/docs/tutorials/harmony.md @@ -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`): diff --git a/docs/tutorials/randomness.md b/docs/tutorials/randomness.md index c04ea95..ed7a609 100644 --- a/docs/tutorials/randomness.md +++ b/docs/tutorials/randomness.md @@ -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. diff --git a/docs/welcome.md b/docs/welcome.md index 74d0af3..4fbfae3 100644 --- a/docs/welcome.md +++ b/docs/welcome.md @@ -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. diff --git a/src/engine/audio.rs b/src/engine/audio.rs index 539079d..ce86dfb 100644 --- a/src/engine/audio.rs +++ b/src/engine/audio.rs @@ -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 { diff --git a/website/src/pages/docs.astro b/website/src/pages/docs.astro index c6cbf86..b145971 100644 --- a/website/src/pages/docs.astro +++ b/website/src/pages/docs.astro @@ -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([ diff --git a/website/src/pages/index.astro b/website/src/pages/index.astro index 64f09db..0ae5ac2 100644 --- a/website/src/pages/index.astro +++ b/website/src/pages/index.astro @@ -103,9 +103,9 @@ const DL = 'https://dlcagire.raphaelforment.fr';

Features (click to learn more!)

- + - +